pax_global_header00006660000000000000000000000064143006630240014510gustar00rootroot0000000000000052 comment=de56d748c896a98860cd9682961bdad07e410179 kore-4.2.3/000077500000000000000000000000001430066302400124565ustar00rootroot00000000000000kore-4.2.3/.gitignore000066400000000000000000000001551430066302400144470ustar00rootroot00000000000000kore *.o *.swp *.swo *.module *.DSYM cert obj .lvimrc kodev/kodev kore.features src/version.c src/platform.h kore-4.2.3/BEERS000066400000000000000000000015521430066302400132440ustar00rootroot00000000000000THE BEER LIST. Any contributer to Kore that submitted patches will be put on this list. If you are on this list, I owe you a beer if we ever run into each other at any time. It is however your responsibility to remind me ;) I will note down the beer of your choice between the brackets. [] Andreas Pfohl [] Ansen Dong [] Carl Ekerot [] Cleve Lendon [] Corbin Hughes [] Daniel Fahlgren x 6 [] Dmitrii Golub [] Elliot Schlegelmilch [] Erik Karlsson x 2 [] Frederic Cambus [] Guy Nankivell [] James Turner [] Joel Arbring x 2 [] Manuel Kniep [] Marcin Szczepaniak [] Matt Thompson [] Matthew Norström [] Nandor Kracser [] Pascal Borreli [] Quentin Perez [] Raphaël Monrouzeau [] Raymond Pasco [] Remy Noulin [] Rickard Lind x 2 [] Shih-Yuan Lee [] Stanislav Yudin [] Stig Telfer [] Thordur Bjornsson [] Tobias Kortkamp [] Yorick de Wid [] Yvan Sraka [] Ángel González kore-4.2.3/LICENSE000066400000000000000000000014301430066302400134610ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ kore-4.2.3/Makefile000066400000000000000000000166101430066302400141220ustar00rootroot00000000000000# Kore Makefile CC?=cc DESTDIR?= PREFIX?=/usr/local OBJDIR?=obj KORE=kore KODEV=kodev/kodev KOREPATH?=$(shell pwd) KORE_CRYPTO?=crypto INSTALL_DIR=$(PREFIX)/bin MAN_DIR?=$(PREFIX)/share/man SHARE_DIR=$(PREFIX)/share/kore INCLUDE_DIR=$(PREFIX)/include/kore TLS_BACKEND?=openssl KORE_TMPDIR?=/tmp TOOLS= kore-serve GENERATED= PLATFORM=platform.h VERSION=$(OBJDIR)/version.c PYTHON_CURLOPT=misc/curl/python_curlopt.h S_SRC= src/kore.c src/buf.c src/config.c src/connection.c \ src/domain.c src/filemap.c src/fileref.c src/json.c src/log.c \ src/mem.c src/msg.c src/module.c src/net.c src/pool.c src/runtime.c \ src/sha1.c src/sha2.c src/timer.c src/utils.c src/worker.c S_SRC+= src/tls_$(TLS_BACKEND).c FEATURES= FEATURES_INC= CFLAGS+=-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes CFLAGS+=-Wmissing-declarations -Wshadow -Wpointer-arith -Wcast-qual CFLAGS+=-Wsign-compare -Iinclude/kore -I$(OBJDIR) -std=c99 -pedantic CFLAGS+=-Wtype-limits -fno-common CFLAGS+=-DPREFIX='"$(PREFIX)"' -fstack-protector-all LDFLAGS+=-rdynamic ifeq ("$(TLS_BACKEND)", "openssl") S_SRC+=src/keymgr_openssl.c CFLAGS+=-DTLS_BACKEND_OPENSSL FEATURES+=-DTLS_BACKEND_OPENSSL ifneq ("$(OPENSSL_PATH)", "") CFLAGS+=-I$(OPENSSL_PATH)/include LDFLAGS+=-L$(OPENSSL_PATH)/lib -lssl -l$(KORE_CRYPTO) else LDFLAGS+=-lssl -l$(KORE_CRYPTO) endif else ifneq ("$(ACME)", "") $(error ACME not supported under TLS backend $(TLS_BACKEND)) endif endif ifneq ("$(KORE_SINGLE_BINARY)", "") CFLAGS+=-DKORE_SINGLE_BINARY -DKORE_TMPDIR='"$(KORE_TMPDIR)"' FEATURES+=-DKORE_SINGLE_BINARY endif ifneq ("$(DEBUG)", "") CFLAGS+=-g FEATURES+=-DKORE_DEBUG endif ifneq ("$(NOOPT)", "") CFLAGS+=-O0 else CFLAGS+=-O2 endif ifneq ("$(NOSENDFILE)", "") CFLAGS+=-DKORE_NO_SENDFILE endif ifneq ("$(NOHTTP)", "") CFLAGS+=-DKORE_NO_HTTP FEATURES+=-DKORE_NO_HTTP else S_SRC+= src/auth.c src/accesslog.c src/http.c \ src/route.c src/validator.c src/websocket.c endif ifneq ("$(PGSQL)", "") S_SRC+=src/pgsql.c LDFLAGS+=-L$(shell pg_config --libdir) -lpq CFLAGS+=-I$(shell pg_config --includedir) -DKORE_USE_PGSQL \ -DPGSQL_INCLUDE_PATH="\"$(shell pg_config --includedir)\"" FEATURES+=-DKORE_USE_PGSQL FEATURES_INC+=-I$(shell pg_config --includedir) endif ifneq ("$(TASKS)", "") S_SRC+=src/tasks.c LDFLAGS+=-lpthread CFLAGS+=-DKORE_USE_TASKS FEATURES+=-DKORE_USE_TASKS endif ifneq ("$(JSONRPC)", "") S_SRC+=src/jsonrpc.c LDFLAGS+=-lyajl CFLAGS+=-DKORE_USE_JSONRPC FEATURES+=-DKORE_USE_JSONRPC endif ifneq ("$(PYTHON)", "") S_SRC+=src/python.c GENERATED+=$(PYTHON_CURLOPT) KORE_PYTHON_LIB?=$(shell ./misc/python3-config.sh --ldflags) KORE_PYTHON_INC?=$(shell ./misc/python3-config.sh --includes) LDFLAGS+=$(KORE_PYTHON_LIB) CFLAGS+=$(KORE_PYTHON_INC) -DKORE_USE_PYTHON FEATURES+=-DKORE_USE_PYTHON FEATURES_INC+=$(KORE_PYTHON_INC) endif OSNAME=$(shell uname -s | sed -e 's/[-_].*//g' | tr A-Z a-z) ifeq ("$(OSNAME)", "freebsd") KORE_CURL_LIB=-L/usr/local/lib -lcurl KORE_CURL_INC=-I/usr/local/include endif ifneq ("$(ACME)", "") S_SRC+=src/acme.c CURL=1 CFLAGS+=-DKORE_USE_ACME FEATURES+=-DKORE_USE_ACME endif ifneq ("$(CURL)", "") S_SRC+=src/curl.c KORE_CURL_LIB?=$(shell curl-config --libs) KORE_CURL_INC?=$(shell curl-config --cflags) LDFLAGS+=$(KORE_CURL_LIB) CFLAGS+=$(KORE_CURL_INC) -DKORE_USE_CURL FEATURES+=-DKORE_USE_CURL FEATURES_INC+=$(KORE_CURL_INC) endif ifneq ("$(SANITIZE)", "") CFLAGS+=-fsanitize=$(SANITIZE) LDFLAGS+=-fsanitize=$(SANITIZE) endif ifeq ("$(OSNAME)", "darwin") ifeq ("$(TLS_BACKEND)", "openssl") OSSL_INCL=$(shell pkg-config openssl --cflags) CFLAGS+=$(OSSL_INCL) LDFLAGS+=$(shell pkg-config openssl --libs) FEATURES_INC+=$(OSSL_INCL) endif S_SRC+=src/bsd.c else ifeq ("$(OSNAME)", "linux") CFLAGS+=-D_GNU_SOURCE=1 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 LDFLAGS+=-ldl S_SRC+=src/linux.c src/seccomp.c else S_SRC+=src/bsd.c ifneq ("$(JSONRPC)", "") CFLAGS+=-I/usr/local/include LDFLAGS+=-L/usr/local/lib endif endif S_OBJS= $(S_SRC:src/%.c=$(OBJDIR)/%.o) S_OBJS+=$(OBJDIR)/version.o all: $(PLATFORM) $(GENERATED) $(VERSION) $(KORE) $(KODEV) $(PLATFORM): $(OBJDIR) force @if [ -f misc/$(OSNAME)-platform.sh ]; then \ misc/$(OSNAME)-platform.sh > $(OBJDIR)/$(PLATFORM) ; \ fi $(PYTHON_CURLOPT): $(OBJDIR) force @cp $(PYTHON_CURLOPT) $(OBJDIR) $(VERSION): $(OBJDIR) force @if [ -d .git ]; then \ GIT_REVISION=`git rev-parse --short=8 HEAD`; \ GIT_BRANCH=`git rev-parse --abbrev-ref HEAD`; \ rm -f $(VERSION); \ printf "const char *kore_version = \"%s-%s\";\n" \ $$GIT_BRANCH $$GIT_REVISION > $(VERSION); \ elif [ -f RELEASE ]; then \ printf "const char *kore_version = \"%s\";\n" \ `cat RELEASE` > $(VERSION); \ else \ echo "No version information found (no .git or RELEASE)"; \ exit 1; \ fi @printf "const char *kore_build_date = \"%s\";\n" \ `date +"%Y-%m-%d"` >> $(VERSION); $(KODEV): src/cli.c $(MAKE) -C kodev $(KORE): $(OBJDIR) $(S_OBJS) $(CC) $(S_OBJS) $(LDFLAGS) -o $(KORE) @echo $(LDFLAGS) > kore.linker @echo $(FEATURES) $(FEATURES_INC) > kore.features objects: $(OBJDIR) $(PLATFORM) $(GENERATED) $(S_OBJS) @echo $(LDFLAGS) > $(OBJDIR)/ldflags @echo "$(FEATURES) $(FEATURES_INC)" > $(OBJDIR)/features $(OBJDIR): @mkdir -p $(OBJDIR) install: mkdir -p $(DESTDIR)$(SHARE_DIR) mkdir -p $(DESTDIR)$(INCLUDE_DIR) mkdir -p $(DESTDIR)$(INSTALL_DIR) mkdir -p $(DESTDIR)$(MAN_DIR)/man1 install -m 644 share/man/kodev.1 $(DESTDIR)$(MAN_DIR)/man1/kodev.1 install -m 555 $(KORE) $(DESTDIR)$(INSTALL_DIR)/$(KORE) install -m 644 kore.features $(DESTDIR)$(SHARE_DIR)/features install -m 644 kore.linker $(DESTDIR)$(SHARE_DIR)/linker install -m 644 include/kore/*.h $(DESTDIR)$(INCLUDE_DIR) install -m 644 misc/ffdhe4096.pem $(DESTDIR)$(SHARE_DIR)/ffdhe4096.pem $(MAKE) -C kodev install $(MAKE) install-sources install-sources: @mkdir -p $(DESTDIR)$(SHARE_DIR) @cp Makefile $(DESTDIR)$(SHARE_DIR) @cp -R src $(DESTDIR)$(SHARE_DIR) @cp -R include $(DESTDIR)$(SHARE_DIR) @cp -R misc $(DESTDIR)$(SHARE_DIR) @if [ -d .git ]; then \ GIT_REVISION=`git rev-parse --short=8 HEAD`; \ GIT_BRANCH=`git rev-parse --abbrev-ref HEAD`; \ rm -f $(VERSION); \ echo "$$GIT_BRANCH-$$GIT_REVISION" > \ $(DESTDIR)$(SHARE_DIR)/RELEASE; \ elif [ -f RELEASE ]; then \ cp RELEASE $(DESTDIR)$(SHARE_DIR); \ else \ echo "No version information found (no .git or RELEASE)"; \ exit 1; \ fi uninstall: rm -f $(DESTDIR)$(INSTALL_DIR)/$(KORE) rm -rf $(DESTDIR)$(INCLUDE_DIR) rm -rf $(DESTDIR)$(SHARE_DIR) $(MAKE) -C kodev uninstall tools-build: $(KODEV) for t in $(TOOLS); do \ cd tools/$$t; \ env \ KODEV_OUTPUT=$(KOREPATH) \ KORE_SOURCE=$(KOREPATH) \ KORE_BUILD_FLAVOR=$(OSNAME) \ $(KOREPATH)/$(KODEV) build; \ cd $(KOREPATH); \ done tools-clean: $(KODEV) for t in $(TOOLS); do \ cd tools/$$t; \ $(KOREPATH)/$(KODEV) clean; \ cd $(KOREPATH); \ done tools-install: mkdir -p $(DESTDIR)$(INSTALL_DIR) for t in $(TOOLS); do \ install -m 555 $$t $(DESTDIR)$(INSTALL_DIR)/$$t; \ done $(OBJDIR)/%.o: src/%.c $(CC) $(CFLAGS) -c $< -o $@ src/kore.c: $(VERSION) src/python.c: $(PYTHON_CURLOPT) src/seccomp.c: $(PLATFORM) clean: rm -f $(VERSION) find . -type f -name \*.o -exec rm {} \; rm -rf $(KORE) $(OBJDIR) kore.features kore.linker $(MAKE) -C kodev clean releng-build-examples: rm -rf /tmp/kore_releng $(MAKE) clean $(MAKE) PYTHON=1 PGSQL=1 TASKS=1 PREFIX=/tmp/kore_releng $(MAKE) install PREFIX=/tmp/kore_releng $(MAKE) -C examples .PHONY: all clean force kore-4.2.3/README.md000066400000000000000000000071221430066302400137370ustar00rootroot00000000000000About ----- Kore 4.2.3 (https://kore.io) is a web application platform for writing scalable, concurrent web based processes in C or Python. It is built with a "secure by default" approach. It is fully privilege separated while using strong security features at the operating system level such as seccomp, pledge, unveil and more. Today Kore is used in a variety of applications ranging from high assurance cryptographic devices, machine-learning stacks and even in the aerospace industry. From embedded platforms all the way to high performance servers. *Kore scales.* Key Features ------------ * Supports SNI * Supports HTTP/1.1 * Websocket support * Privseps by default * TLS enabled by default * Optional background tasks * Built-in parameter validation * Optional asynchronous PostgreSQL support * Optional support for page handlers in Python * Reload private keys and certificates on-the-fly * Automatic X509 certificates via ACME (with privsep) * Private keys isolated in separate process (RSA and ECDSA) * Default sane TLS ciphersuites (PFS in all major browsers) * Modules can be reloaded on-the-fly, even while serving content * Worker processes sandboxed on OpenBSD (pledge) and Linux (seccomp) * Event driven (epoll/kqueue) architecture with per CPU worker processes * Build your web application as a precompiled dynamic library or single binary And lots more. License ------- * Kore is licensed under the ISC license Documentation -------------- [Read the documentation](https://docs.kore.io/4.2.0/) Performance ----------- Read the [benchmarks](https://blog.kore.io/posts/benchmarks) blog post. Platforms supported ------------------- * Linux * OpenBSD * FreeBSD * MacOS Kore only supports x64, arm and aarch64 architectures. Building Kore ------------- Clone this repository or get the latest release at [https://kore.io/releases/4.2.2](https://kore.io/releases/4.2.2). Requirements * openssl 1.1.1 or libressl 3.x (note: openssl 3.0.0 is currently *not* supported) Requirement for asynchronous curl (optional) * libcurl (7.64.0 or higher) Requirements for background tasks (optional) * pthreads Requirements for pgsql (optional) * libpq Requirements for python (optional) * Python 3.6+ Normal compilation and installation: ``` $ cd kore $ make # make install ``` If you would like to build a specific flavor, you can enable those by setting a shell environment variable before running **_make_**. * ACME=1 (compiles in ACME support) * CURL=1 (compiles in asynchronous curl support) * TASKS=1 (compiles in task support) * PGSQL=1 (compiles in pgsql support) * DEBUG=1 (enables use of -d for debug) * NOHTTP=1 (compiles Kore without HTTP support) * NOOPT=1 (disable compiler optimizations) * JSONRPC=1 (compiles in JSONRPC support) * PYTHON=1 (compiles in the Python support) * TLS_BACKEND=none (compiles Kore without any TLS backend) Note that certain build flavors cannot be mixed together and you will just be met with compilation errors. Example applications ----------------- You can find example applications under **_examples/_**. The examples contain a README file with instructions on how to build or use them. Mailing lists ------------- **patches@kore.io** - Send patches here, preferably inline. **users@kore.io** - Questions regarding kore. If you want to signup to those mailing lists send an empty email to listname+subscribe@kore.io Other mailboxes (these are **not** mailing lists): **security@kore.io** - Mail this email if you think you found a security problem. **sponsor@kore.io** - If your company would like to sponsor part of Kore development. More information can be found on https://kore.io/ kore-4.2.3/RELEASE000066400000000000000000000000061430066302400134550ustar00rootroot000000000000004.2.3 kore-4.2.3/RELEASE.md000066400000000000000000000017061430066302400140640ustar00rootroot00000000000000Kore release procedure: $next = next release $prev = previous release kore: $ git checkout 4.x-releng $ git merge master [update RELEASE, README.md] $ git commit -a -m "update for $next" $ git tag -a $next -m "Kore $next" $ git archive --format=tgz --prefix=kore-$next/ -o ~/kore-$next.tgz $next $ minisign -S -c "Kore $next release" -m kore-$next.tar.gz $ shasum -a256 kore-$next.tar.gz > kore-$next.tar.gz.sha256 $ git push --tags origin 4.x-releng $ git push --tags github 4.x-releng kore-site: $ cp ~/kore-$next* webroot/releases $ cp webroot/releases/$prev.html webroot/releases/$next.html [update all relevant links] [write changelog on release page] $ git add webroot && git commit -a -m "update to $next" $ git push origin master [on nightfall] $ cd kore-site && git pull origin master && make install-docs kore-docker: $ cp -R $prev $next $ ./build.sh $next kore-4.2.3/conf/000077500000000000000000000000001430066302400134035ustar00rootroot00000000000000kore-4.2.3/conf/kore.conf.example000066400000000000000000000272371430066302400166570ustar00rootroot00000000000000# Example Kore configuration # Below you will find all available configuration # options for Kore. Options which have a default value # and can be left out of the configuration are commented # out with their default value specified. # Maximum length to queue pending connections (see listen(2)) # MUST be set before any bind directive. #socket_backlog 5000 # Server configuration. server tls { bind 127.0.0.1 443 #unix /var/run/kore.sock } #server notls { # bind 127.0.0.1 80 # tls no #} # Kore can have multiple settings for each processes that run under it. # There are 3 different type of processes: # # 1) A worker process, these handle the HTTP requests and your code # runs inside of these. # 2) A keymgr process, this handles your domain private keys # and signing during the TLS handshakes. It also holds your # ACME account-key and will sign ACME requests. # 3) An acme process, this talks to the ACME servers. # # You can individually turn on/off chrooting and dropping user # privileges per process. The -n and -r command-line options # are a global override for skipping chroot or dropping user # permissions on all processes. # # If no root/runas options are set in a process, it will inherit the # default values from the worker processes. # # The worker processes will get the current working directory or # current user if no options where specified for it. # # Configures the worker processes. privsep worker { # The user the workers will run as. runas kore # The root directory for the worker processes, if chroot isn't # skipped, this is the directory it will chroot into. # # If not set, Kore will take the current working directory. root /var/chroot/kore # We could configure this process to not chroot and only # chdir into its root directory. #skip chroot } # Configures the keymgr process. # If TLS is enabled you will need to specify paths to the domain # certificate and key that Kore will load. This loading is done # from the keymgr (separate process) and all paths must be relative # to the keymgr process its root configuration option. privsep keymgr { # The user the keymgr will run as. runas keymgr # The root directory for the keymgr process. In this example # we do not turn off chroot for this process so the keymgr # will chroot into this directory. root /etc/keymgr } # Configure Kore to log all worker output to a certain file. # # This forces all logs from the workers to be written to this file # instead of stdout. Note that this is not the actual access log. # # Any message logged by your application with kore_log() will also # appear under here. #logfile /var/log/kore.log # How many worker processes Kore will spawn. If the directive # worker_set_affinity is set to 1 (the default) Kore will automatically # pin these worker processes to different CPU cores in your system. # NOTE: If you set this to the maximum number of cores you have # in your system (or more) you might consider turning off affinity # if you are running CPU heavy services on the same machine. workers 4 # The number of active connections each worker can handle. # You might have to tweak this number based on your hardware. #worker_max_connections 512 # Limit of maximum open files per worker. #worker_rlimit_nofiles 768 # Limit the number of new connections a worker can accept # in a single event loop. By default Kore will accept as # many new connections it can up to worker_max_connections. # # NOTE: If you are running benchmark tools that throw all # connections at Kore at the same time (when they are less # then worker_max_connections) or you have an actual reason # to not spend too much time in the accept loop this setting # will make a HUGE positive difference. # Number of accept() calls a worker will do at most in one go # before releasing the lock to others. #worker_accept_threshold 16 # What should the Kore parent process do if a worker # process unexpectedly exits. The default policy is that # the worker process is automatically restarted. # # If you want the kore server to exit if a worker dies # you can swap the policy to "terminate". #worker_death_policy restart # Workers bind themselves to a single CPU by default. # Turn this off by setting this option to 0 #worker_set_affinity 1 # Store the pid of the main process in this file. #pidfile kore.pid # If TLS is enabled you can specify a file where Kore will read # initial entropy from and save entropy towards when exiting. # # Note that if you enable this you must provide the first iteration # of this file by generating 1024 cryptographically safe random bytes # and writing them to the file specified. # # Kore will refuse to start if the specified file does not exist, # is of the wrong size or cannot be opened in anyway. # # NOTE: This file location must be inside your chrooted environment. #rand_file random.data # Filemap settings # filemap_index Name of the file to be used as the directory # index for a filemap. #filemap_index index.html # HTTP specific settings. # http_header_max Maximum size of HTTP headers (in bytes). # # http_header_timeout Timeout in seconds for receiving the # HTTP headers before the connection is closed. # # http_body_max Maximum size of an HTTP body (in bytes). # If set to 0 disallows requests with a body # all together. # # http_body_timeout Timeout in seconds for receiving the # HTTP body in full before the connection # is closed with an 408. # # http_body_disk_offload Number of bytes after which Kore will use # a temporary file to hold the HTTP body # instead of holding it in memory. If set to # 0 no disk offloading will be done. This is # turned off by default. # # http_body_disk_path Path where Kore will store any temporary # HTTP body files. # # http_keepalive_time Maximum seconds an HTTP connection can be # kept alive by the browser. # (Set to 0 to disable keepalive completely). # # http_hsts_enable Send Strict Transport Security header in # all responses. Parameter is the age. # (Set to 0 to disable sending this header). # # http_request_limit Limit the number of HTTP requests workers # can queue up. # # http_request_ms The number of milliseconds workers can max # spend inside the HTTP processing loop. # # http_server_version Override the server version string. # #http_header_max 4096 #http_header_timeout 10 #http_body_max 1024000 #http_body_timeout 60 #http_keepalive_time 0 #http_hsts_enable 31536000 #http_request_limit 1000 #http_request_ms 10 #http_body_disk_offload 0 #http_body_disk_path tmp_files #http_server_version kore # Websocket specific settings. # websocket_maxframe Specifies the maximum frame size we can receive # websocket_timeout Specifies the time in seconds before a websocket # connection would be closed due to inactivity. #websocket_maxframe 16384 #websocket_timeout 120 # Configure the number of available threads for background tasks. #task_threads 2 # Load modules (you can load multiple at the same time). # An additional parameter can be specified as the "onload" function # which Kore will call when the module is loaded/reloaded. load contrib/examples/generic/example.module example_load # Load a python file (if built with PYTHON=1) #python_import src/index.py example_load # Validators # validator name type regex|function # validator v_example function v_example_func validator v_regex regex ^/test/[a-z]*$ validator v_number regex ^[0-9]*$ validator v_session function v_session_validate # Specify what TLS version to be used. Default is TLSv1.3 if available. # Otherwise it will use TLS 1.2. # Allowed values: # 1.3 for TLSv1.3 (default, if available) # 1.2 for TLSv1.2 # both for TLSv1.2 and TLSv1.3 #tls_version 1.3 # Specify the TLS ciphers that will be used. #tls_cipher AEAD-AES256-GCM-SHA384:AEAD-CHACHA20-POLY1305-SHA256:AEAD-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256 # Required DH parameters for TLS if DHE ciphersuites are in-use. # Defaults to SHARE_DIR/ffdhe4096.pem, can be overwritten. #tls_dhparam /usr/local/share/kore/ffdhe4096.pem # OpenBSD specific settings. # Add more pledges if your application requires more privileges. # All worker processes call pledge(2) after dropping privileges # (even if -rn was specified). # By default Kore will use the following promises: "stdio rpath inet error" #pledge dns wpath # seccomp specific settings. # If set to "yes", Kore will trace its child processes and properly # log seccomp violations while still allowing the syscalls. #seccomp_tracing yes # Authentication configuration # # Using authentication blocks you can define a standard way for # Kore to validate your users. In the example below we create # a authentication block called auth_example, which requires # a cookie (session_id) to be set. # # If no cookie is present or the cookie is not valid according # to the set validator, Kore will redirect the browser to the # URI set in authentication_uri. # # Page handlers can be bound to authentication by specifying # authentication block at the end of the page directive (see below). authentication auth_example { # The authentication type denotes the way the user should # be authenticated. # # Allow values: # - cookie (checks for the cookie presence + pass to validator) # - header (checks for header presence + pass to validator) # - request (passes the http_request to the validator) # # Use cases for request could for example be IP based ACLs or # any other criteria that can be extracted from a http_request. # # The request type does not need an authentication_validator. # authentication_type cookie # The name of whatever we are looking for. authentication_value session_id # The validator that will be called to verify the cookie. # Note this is YOUR validator, Kore does not have built-in # session support. You must add this manually using your # preferred method (Storing it in postgres, redis, ...) authentication_validator v_session # The URI Kore will redirect to if a authentication fails. # If this is not set, Kore will return a simple 403. authentication_uri /private } # Domain configuration # # Each domain configuration starts with listing what domain # the directives that follow are to be applied upon. # # Additionally you can specify the following in a domain configuration: # # accesslog # - File where all requests are logged. # # NOTE: due to current limitations the client_verify CA path # MUST be in the 'root' of the Kore workers, not the keymgr. # # client_verify [CA] [optional CRL] # - Turns on client verification, requiring the client to # send a certificate that will be verified by the given CA. # client_verify_depth [depth] # - Configure the depth for x509 chain validation. # By default 1. # # Routes # # Routes can be a static path or a POSIX regular expression. # # route /path { # handler myhandler # methods get post # ... # } # # Example domain that responds to localhost. domain localhost { attach tls certfile cert/server.crt certkey cert/server.key accesslog /var/log/kore_access.log route / { handler index_page methods get } route /login { handler login_do methods post validate post username v_username validate post passphrase v_passphrase } route /mypages/ { handler mypages_index methods get authenticate auth_example } # Allow access to files from the directory route_files via # the /files/ URI. # # Note the directory given must be relative to the root configuration # option. filemap /files/ static_files [auth] } # Example redirect 80->443. #domain localhost { # attach notls # # # specific redirect with a capture group and arguments # redirect ^/account/(.*)$ 301 https://localhost/account/$1 # # # redirect the others back to root. # redirect ^/.*$ 301 https://localhost #} kore-4.2.3/examples/000077500000000000000000000000001430066302400142745ustar00rootroot00000000000000kore-4.2.3/examples/Makefile000066400000000000000000000014751430066302400157430ustar00rootroot00000000000000# # Build all relevant examples. # This only exists to quickly test building all examples. # # Kore must be built with PGSQL=1 TASKS=1 PYTHON=1 to get all # of the below examples to build correctly. # # Don't run this directly, run it from the top level as # $ make releng-build-examples # CURDIR= $(shell pwd) KODEV= /tmp/kore_releng/bin/kodev EXAMPLES= async-curl \ cookies \ cpp \ generic \ headers \ integers \ memtag \ messaging \ nohttp \ parameters \ pgsql \ pgsql-sync \ pipe_task \ sse \ tasks \ tls-proxy \ upload \ video_stream \ websocket \ all: @for example in $(EXAMPLES); do \ cd $$example; \ $(KODEV) clean && $(KODEV) build || exit 1; \ cd $(CURDIR); \ done clean: @for example in $(EXAMPLES); do \ cd $$example; \ $(KODEV) clean; \ cd $(CURDIR); \ done kore-4.2.3/examples/async-curl/000077500000000000000000000000001430066302400163545ustar00rootroot00000000000000kore-4.2.3/examples/async-curl/.gitignore000066400000000000000000000000461430066302400203440ustar00rootroot00000000000000*.o .flavor .objs ht.so assets.h cert kore-4.2.3/examples/async-curl/README.md000066400000000000000000000004771430066302400176430ustar00rootroot00000000000000Kore asynchronous libcurl integration example. This example demonstrates how you can use the asynchronous libcurl api from Kore to perform HTTP client requests, or FTP requests, or send emails all in an asynchronous fashion. Run: ``` $ kodev run $ curl https://127.0.0.1:8888 $ curl https://127.0.0.1:8888/ftp ``` kore-4.2.3/examples/async-curl/conf/000077500000000000000000000000001430066302400173015ustar00rootroot00000000000000kore-4.2.3/examples/async-curl/conf/async-curl.conf000066400000000000000000000003331430066302400222270ustar00rootroot00000000000000# ht configuration server tls { bind 127.0.0.1 8888 } workers 1 pledge dns domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler http } route /ftp { handler ftp } } kore-4.2.3/examples/async-curl/conf/build.conf000066400000000000000000000017251430066302400212540ustar00rootroot00000000000000# ht build config # You can switch flavors using: kodev flavor [newflavor] # Set to yes if you wish to produce a single binary instead # of a dynamic library. If you set this to yes you must also # set kore_source together with kore_flavor. single_binary=yes kore_source=../../ kore_flavor=CURL=1 # The flags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare cxxflags=-Wall -Wmissing-declarations -Wshadow cxxflags=-Wpointer-arith -Wcast-qual -Wsign-compare # Mime types for assets served via the builtin asset_serve_* #mime_add=txt:text/plain; charset=utf-8 #mime_add=png:image/png #mime_add=html:text/html; charset=utf-8 dev { # These flags are added to the shared ones when # you build the "dev" flavor. cflags=-g cxxflags=-g } #prod { # You can specify additional flags here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/async-curl/src/000077500000000000000000000000001430066302400171435ustar00rootroot00000000000000kore-4.2.3/examples/async-curl/src/ftp.c000066400000000000000000000041121430066302400200760ustar00rootroot00000000000000/* * Copyright (c) 2019 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This example is the same as the HTTP one (see src/http.c) except * we fetch an FTP URL. */ #include #include #include int ftp(struct http_request *); static int state_setup(struct http_request *); static int state_result(struct http_request *); static struct http_state states[] = { KORE_HTTP_STATE(state_setup), KORE_HTTP_STATE(state_result) }; int ftp(struct http_request *req) { return (http_state_run(states, 2, req)); } static int state_setup(struct http_request *req) { struct kore_curl *client; client = http_state_create(req, sizeof(*client)); if (!kore_curl_init(client, "http://ftp.eu.openbsd.org/pub/OpenBSD/README", KORE_CURL_ASYNC)) { http_response(req, 500, NULL, 0); return (HTTP_STATE_COMPLETE); } kore_curl_bind_request(client, req); kore_curl_run(client); req->fsm_state = 1; return (HTTP_STATE_RETRY); } static int state_result(struct http_request *req) { size_t len; const u_int8_t *body; struct kore_curl *client; client = http_state_get(req); if (!kore_curl_success(client)) { kore_curl_logerror(client); http_response(req, 500, NULL, 0); } else { kore_curl_response_as_bytes(client, &body, &len); http_response(req, HTTP_STATUS_OK, body, len); } kore_curl_cleanup(client); return (HTTP_STATE_COMPLETE); } kore-4.2.3/examples/async-curl/src/http.c000066400000000000000000000076201430066302400202730ustar00rootroot00000000000000/* * Copyright (c) 2019 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This example demonstrates how easy it is to perform asynchronous * HTTP client requests using the integrated libcurl support. * * In this example we setup 2 states for an HTTP request: * 1) setup * We initialize the HTTP request and fire it off. * This will put our HTTP request to sleep and it be woken up * by Kore when a response is available or something went wrong. * * 2) result * After we have woken up we have access to the result. */ #include #include #include int http(struct http_request *); static int state_setup(struct http_request *); static int state_result(struct http_request *); /* Our states. */ static struct http_state states[] = { KORE_HTTP_STATE(state_setup), KORE_HTTP_STATE(state_result) }; /* Transcend into the HTTP state machine for a request. */ int http(struct http_request *req) { return (http_state_run(states, 2, req)); } /* * Setup the HTTP client request using the integrated curl API and the easy * to use HTTP client api. */ static int state_setup(struct http_request *req) { struct kore_curl *client; client = http_state_create(req, sizeof(*client)); /* Initialize curl. */ if (!kore_curl_init(client, "https://kore.io", KORE_CURL_ASYNC)) { http_response(req, 500, NULL, 0); return (HTTP_STATE_COMPLETE); } /* Setup our HTTP client request. */ kore_curl_http_setup(client, HTTP_METHOD_GET, NULL, 0); /* Add some headers. */ kore_curl_http_set_header(client, "x-source", "from-example"); /* We could opt to override some settings ourselves if we wanted. */ /* curl_easy_setopt(client->handle, CURLOPT_SSL_VERIFYHOST, 0); */ /* curl_easy_setopt(client->handle, CURLOPT_SSL_VERIFYPEER, 0); */ /* * Bind the HTTP client request to our HTTP request so we get woken * up once a response is available. * * This will put us to sleep. */ kore_curl_bind_request(client, req); /* * Now fire off the request onto the event loop. */ kore_curl_run(client); /* Make sure we go onto the next state once woken up. */ req->fsm_state = 1; /* Tell Kore we can't complete this immediately. */ return (HTTP_STATE_RETRY); } /* * This state is called when a result for the HTTP request call is * available to us. */ static int state_result(struct http_request *req) { size_t len; const u_int8_t *body; const char *header; struct kore_curl *client; /* Get the state attached to the HTTP request. */ client = http_state_get(req); /* Check if we were successful, if not log an error. */ if (!kore_curl_success(client)) { kore_curl_logerror(client); http_response(req, 500, NULL, 0); } else { /* * Success! We now have the body available to us. */ kore_curl_response_as_bytes(client, &body, &len); /* We could check the existence of a header: */ if (kore_curl_http_get_header(client, "server", &header)) printf("got server header: '%s'\n", header); /* * Respond to our client with the status and body from * the HTTP client request we did. */ http_response(req, client->http.status, body, len); } /* Cleanup. */ kore_curl_cleanup(client); /* State is now finished. */ return (HTTP_STATE_COMPLETE); } kore-4.2.3/examples/async-curl/src/init.c000066400000000000000000000017161430066302400202570ustar00rootroot00000000000000/* * Copyright (c) 2020 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* Let kore handle the default option parsing. */ void kore_parent_configure(int argc, char **argv) { kore_default_getopt(argc, argv); } kore-4.2.3/examples/cookies/000077500000000000000000000000001430066302400157305ustar00rootroot00000000000000kore-4.2.3/examples/cookies/README.md000066400000000000000000000002551430066302400172110ustar00rootroot00000000000000This example shows cookies API usage * Simple key value cookie * Complex cookie with RFC 6265 features * Mix with cookie formatted in the header Run: ``` # kodev run ``` kore-4.2.3/examples/cookies/conf/000077500000000000000000000000001430066302400166555ustar00rootroot00000000000000kore-4.2.3/examples/cookies/conf/build.conf000066400000000000000000000007471430066302400206330ustar00rootroot00000000000000# generic build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/cookies/conf/cookies.conf000066400000000000000000000004421430066302400211600ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./cookies.so domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler serve_cookies } route /secure { handler serve_cookies } route /vault { handler serve_cookies } } kore-4.2.3/examples/cookies/src/000077500000000000000000000000001430066302400165175ustar00rootroot00000000000000kore-4.2.3/examples/cookies/src/cookies.c000066400000000000000000000037771430066302400203350ustar00rootroot00000000000000/* * Copyright (c) 2017 Stanislav Yudin * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include static char *html = "

Reload this page

"; int serve_cookies(struct http_request *); int serve_cookies(struct http_request *req) { char *value; struct http_cookie *cookie; http_populate_cookies(req); if (http_request_cookie(req, "Simple", &value)) kore_log(LOG_DEBUG, "Got simple: %s", value); if (http_request_cookie(req, "Complex", &value)) kore_log(LOG_DEBUG, "Got complex: %s", value); if (http_request_cookie(req, "Formatted", &value)) kore_log(LOG_DEBUG, "Got formatted: %s", value); /* no expire, no maxage for current path. */ http_response_cookie(req, "Simple", "Hello World!", req->path, 0, 0, NULL); /* expire, no maxage, for /secure. */ http_response_cookie(req, "Complex", "Secure Value!", "/secure", time(NULL) + (1 * 60 * 60), 0, NULL); /* maxage, no httponly, for current path. */ http_response_cookie(req, "key", "value", req->path, 0, 60, &cookie); cookie->flags &= ~HTTP_COOKIE_HTTPONLY; /* set formatted cookie via header directly. */ http_response_header(req, "set-cookie", "Formatted=TheValue; Path=/vault; HttpOnly"); http_response(req, 200, html, strlen(html)); return (KORE_RESULT_OK); } kore-4.2.3/examples/cpp/000077500000000000000000000000001430066302400150565ustar00rootroot00000000000000kore-4.2.3/examples/cpp/.gitignore000066400000000000000000000000371430066302400170460ustar00rootroot00000000000000*.o .objs cpp.so assets.h cert kore-4.2.3/examples/cpp/README.md000066400000000000000000000012021430066302400163300ustar00rootroot00000000000000Kore example showing how to use C++ support! All functions accessible to kore must have their prototypes wrapped with the extern keyword like so: ``` extern “C” { int pageA(struct http_request *); int pageB(struct http_request *); int validatorA(struct http_request *, char *); } ``` In order to run this example with the default C++ settings (default compiler dialect, libstdc++): ``` # kodev run ``` In order to run with a specific dialect and C++ runtime: ``` # env CXXSTD=c++11 CXXLIB=c++ kodev run ``` You can also supply your own compiler combined with the above: ``` # env CC=clang++ CXXSTD=c++11 CXXLIB=c++ kodev run ``` kore-4.2.3/examples/cpp/conf/000077500000000000000000000000001430066302400160035ustar00rootroot00000000000000kore-4.2.3/examples/cpp/conf/build.conf000066400000000000000000000011241430066302400177470ustar00rootroot00000000000000# cpp build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare cxxflags=-Wall -Wmissing-declarations -Wshadow cxxflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g cxxflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/cpp/conf/cpp.conf000077500000000000000000000002741430066302400174420ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./cpp.so domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } } kore-4.2.3/examples/cpp/src/000077500000000000000000000000001430066302400156455ustar00rootroot00000000000000kore-4.2.3/examples/cpp/src/cpp.cpp000077500000000000000000000021541430066302400171400ustar00rootroot00000000000000/* * Copyright (c) 2015 Jonathan Goodman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "example_class.h" extern "C" { int page(struct http_request *); } int page(struct http_request *req) { example_class example; const char *str = example.a(); http_response(req, 200, static_cast(const_cast(str)), strlen(str)); return (KORE_RESULT_OK); } kore-4.2.3/examples/cpp/src/example_class.cpp000066400000000000000000000016741430066302400212010ustar00rootroot00000000000000/* * Copyright (c) 2015 Jonathan Goodman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "example_class.h" example_class::example_class() { } example_class::~example_class() { } const char * example_class::a() { return "Hello world!"; } kore-4.2.3/examples/cpp/src/example_class.h000066400000000000000000000017551430066302400206460ustar00rootroot00000000000000/* * Copyright (c) 2015 Jonathan Goodman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef ____example_class__ #define ____example_class__ #include class example_class { public: example_class(); ~example_class(); const char *a(); }; #endif /* defined(____example_class__) */ kore-4.2.3/examples/generic/000077500000000000000000000000001430066302400157105ustar00rootroot00000000000000kore-4.2.3/examples/generic/README.md000066400000000000000000000003341430066302400171670ustar00rootroot00000000000000Generic Kore application that shows off a few things: * File uploads (/upload) * Authentication blocks (/private) * base64 encoding tests (/b64test) * Parameter validator tests (/params-test) Run: ``` # kodev run ``` kore-4.2.3/examples/generic/assets/000077500000000000000000000000001430066302400172125ustar00rootroot00000000000000kore-4.2.3/examples/generic/assets/index.html000066400000000000000000000003471430066302400212130ustar00rootroot00000000000000 Your KORE module worked!

Your first Kore module worked.

kore-4.2.3/examples/generic/assets/intro.jpg000066400000000000000000031430361430066302400210610ustar00rootroot00000000000000JFIFHH XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmExifMM*V^(ifHH C        C   " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?n2sUȆ~ѡϨj_v_RxW ެG P¼ҬGf2'S=jxEA1֧ }*+2xN=8vE8K8 %^jH MQޞ3iĨbjXAPpzsJE\z1I03ҢN LyJ:RpAbQr4}=i<*ڀ 8>>9PKD|z x8=񎹩;5"~4xOQv2uG*_&X͊<`gw֢S:~ac֞s۽=G>%`uTx^=?:1@b|R8t9vxJ9{@<ԑz2>X`Ԩ31ҝ"â7ޥ b S3g9"8hGW$.92>sgKpT}: kp10|.8늒EEg8㊚0=L$`Nj$sF)\$vO>>NN &ģ; H@jCO9R-DT^3'Z`cԀ xc㎾ǧJHjPGP .4Jۃ-c=3Unwb-M]58V sI9@u2uV1V6 ` T5fRW^Ĺֺ)= nsjI(]k1Yz1+RZPvpE#0woc1@Xyrk]gI;L<<ï_NkN_\ӡrI+^d1wLTztd:Ժ2cLѵ(h(<һ|'C=:-d+Iǿ5%#ǚCqHܹXr Z`㊍2xQ7іd`T,8w= sXU+t%|d0H*t5(>՛z^ԱU["Z_mgԡ~Tf78|@N*d*!Sg&8MW*y}jUAk-D0^^x皙qv1\\FH>(G=1O q7#qR㊨99ǭ+:JDJ͖Lwz PK'ˌ|hGUy*Ufsh&[pIO+9jq!56+1N?¬J zV rq0˴1ШFyj 3\8 . 魀َ:Mk 'mUYoaMcn<`<MAF6ӎESõˀ8N+b=)Ƨq8$1zȷ[ze:}:7I,s[T.]i@r1]tRӯUyw2cYFhDBy5v$IVJ6A?Zд^Nh.qQ]}&͌~UjAIA,yqVҫ>bzVH'|sJS;w60kDOA0F Oڣ?O>czu0zt&8'816<j u5br@85]?ZsD/&ٲDHյ i3 J>_\#uvL$j_s~Y3֯Bxⵃ! QO*vN P~3W)Xg5FF!'Rin\+nͺpj>xm=jIUML'vI♪ql}qV/; tt37"ԭg`s}y1 XmqV`eGszrShn7735<p}q\o0ȯM/تrHsxY8aFR$j峰lSC`Ρc94 sҤe`ޢrsϹ'pܜ:cء,!M!8ڐ8R %Q@cv1}4rtǠ9B33@J( \ZJ(s3IEQEQE򤢊\*Ol(iH⓪Ҕ{P8E@&x@ =ix=)=iXߝisNw%sQ8j2Jޞ?Z9NzROJjN?\P)H:tA޸}h׊u;p}*XƘ3T.dp9M~튕pU6JP{txV"qS TJR N:*T`Tь>DT122N&}:cFO5:Uc CR!"Iu'Z.=I >c9{)\dS) x=מEXTS2jSF3q'qQH7"A:(yҦVWH GEC0O҆2? qӜbN~?DJ: g4`zS9=׌sڜ{C=n=>AT$⋈rqRLNghl:tv'Y$py9 Rjҏ3n֝jڄs08OhzyloA6<Ԑ\#6ќ`lzsNRU2 j wgE *pI+/N~ G_J9Rqܙ3КpGDcߚxd;fka~MJ :}ED:c*tqrsT8=j$qR&Z*!R)<ЖH.1"`8S;G) qӌӆ3ԃM_1RKcOœ-rctQǥH2HJwN)~itzq?/9LmIsLcҤPX8i0 &w8C9>SLp); @pϥ?9ۊjñCӭ+9@rF{/A׎WOOPy1H84:R9yN8ǜjj8'r8qSzց(>Q;Az4 \qJy#@טhz8z<~TÜdzSF)}ǃҗҎ:qZEXP?74[XR?L1ԁw&Zf8VU=i4w,ӽ80 u۱L hY' 7E@'=V'$:jK`?4~x$v;x8'p)J?/;w1][4yQqOS0G81JR$qjGӵ@ǽIAUD"~kVNG=H;>w{TyUq7D=*ߞ~N8Npqڵ];6|sZvz;5mnJ/~+xNmXހzT99/b=hೌe+.A3E@sUdz(#( *Hjy=zԎ8S_Y=ru-;8l6IVmC) ڣed$sOvF6pEI"sj-ۏzp,ԑE ׁORIF)>hBx9J|D0ԑM| t=c8Yչ95.:VFz'(ӑ=z*Ac)( *2*|4zT+X+9Jǽ9=8^֩r7_¢x` UUk2[ |V@M] :➣RTй3T.zը\DE+Ei- *9'>vֳբ!F+ k eO'ZhQb/-6r::l¸g7֧A=EN ΔJlӿscU-&㜜s]*O`r:vq~(ёUE\OBnF8#yQHu" ? RGJ,!M;#L@d[TlZsɆ=E4&azZn@L'%z~VBs.PJ>j'mFG8zγ,#bT9U.Rqv^oӣrxˡ'?NcTo.J/Bbe.> (8 (4PEPEPEPE)I@J;Jp?-7z~x!#f|zS;?)4 ڀ i`HTH ]:♂=qڄ?+dw <NF:wzЃR'Ɛ Qր1zq:GԸ/Z\y .$TQ0s>>:w$r28W>j(0OO*iWP㚚<\Zq=:]I*J885 7zqjT?82L$\T:1D;ԉvɣpp*AjpDcRbMDjӾjD:[Vv+N=+XVθ`jODB@&)$:ⴅu%JvJX&9 ǩMͰ8jD9ϭR UsRrm>ǧ5$kF=ӵMAq"PHԉ1B:"r1ϵ;KDOA2>=Li\ih=qޙ$ʊ9=*ӝBYFEB)e=A➄ ?JrlMGbE\Jz!f3 TX[BpN1VǀzS!g_1MDʥT3Nd|Tq@j]&:ӝɓI)yOR1hAr0)CtD,T>WךC7|Bz.G} !s]sΈH[JWԻןzU3:>֋ :qNQFr9 4},=~'N=Fj0Gv5';x8 P{֤ᇩN1֫q"@87wJ:.2A(9 OL3p$_CNR00>ǯ4 L.;'ZzM5ԃ9ナbBiסO֤֣iç'S1Nz@\yPzfz`͠pz9ޜcp{ҏ\/zRw#Қǭ(' PXp6Z;ZqJ90BJ◽#r EvoqQ]m1d`p)*AN )} c+Kppq֘34+z3-۰ 5r:osҹfBOCۭI`GR*sS R/[ϴ` E^c%Mb*1rܵf͟k 5g&n<dwޚU7*NR!'Y)l1L.mcA/Bۛ9O|J瞆 7өSNǭ&yL3ގklnRM?J^ }\Vbzp\O$P^8p}i; /Rۀ0;wpn}zӽ1ޚ>(=;S2NOQQ(i14Huө))1u~;M̑})GOOzju"}j"֔TeR9C w>v4<Ҍv@p}h#')h'r}*9׽H`v4)߽#GzÚӅް<fmLH$ҮT֬۶1ڹSEP>4.O=iQREdq[B,GךT"[:UخThCүGg6'#>EҧLv hke86#'MT+aj VrbSJoO|V*9=)DcCfW9!?(j׵@o|QÝ@hWN#yjqIL'S|Sb*'5mSJs\r z xP{ Hhy|zR{ 3|?aW/=1MR1SO/@q҈@O=a8hQu\c(H\dqVmmZ}ZpWXM@JԱ>$JҬAߊ}5OcOJ>J;ƀ"pE*SdR/'N`qֈ)\8EQJ\P34ޜšzH?JZ(hSqX<S֚>*zP( ( ( ( ( ;}(){P p(''ާ€+1xi9=:O8a)4(?/*d 0~\TH(*=(ZZA֗ ֆH:E+c#j ?AiJ}Q84ǾyM;5?JI8ݏҞy'Svq[q})s1j@%aezҌqB~=)B> ¡M;?7 =#zQ1ۊGN'x!0:\[c\@ w?J.W@S< 縦ïRsN 村ӦGsߥ+lN)6qzuW}Ttت"TSAՠICQ(F3Dx\`dtk2 Ua8huR(995^@> 5 MFy^ԧN(>xӸO콅Szˠё843N+qM#\Ӑ֙ בBCJ8w T00?3Qc$3O<Jn2n)sh8(G%OZx<`dwFxŒS*& p=3ޢS2ةS;}I>Gj=y"p=1JL(ўSr*DZ~CR7$I,zjXL~^7>$nXIܕHR@_9=h.1SKMImX i;rjz4wZ$VZO|:Ӗ\&ܐiI\$E lXuP$GP4&kj 1TBOZ\pjD۱a_=(][94+q%ŭp|jnƜI#;Xy$ z:z3Hv$oǚE=BggM14=HTD; oI"Ѕ2x8P!@¿-]O,dLqW 9jԄ☮~ni0g֔(Og^Ll>,=rAA[uԱJryOm IUAըOXI'y˅Eohd7<⫠W8'Y;15=kURY\ѵGdӭ͆v{Oa2*[VBy' qkC Th*U3R늧ar+ITUr$iʸg.0qͼE.p9rhǶiHC֗rjv)F38;qӊ^78W&ࡻ cm楰zgcֹ3V/($W.:<4 Q7Unnc[4V ⤁ݹkAl7Ɂн ;i t4t'EFw`ךI($;TrL֨z<}*VN1S*h5_PZY8-(& }j,8UB/#ӭKUiR3q.(ӚLzUFyTCVD2Ф­[V10+^[{\CV"PiU1S D:ۦX}㎕j[%bztJxE0!qjh *?\56ninvU<#KW?9g<};}*OfsK,XS4qU߭?8^ԄZȩ?J tW5z JBwZqKQtIMǩV 9~F*E8}=F֢ .@<:OV,>w5%-i9QuMR*1)Ѯx ޣu>?.=LA@8d4iH9crB9#i II'4㱠 Yh ?:ێs@A=lt40*&< 5$$E$d@1Fҥ3ϵ:AAzF<:)(((($zҞ }{ncF(^i6jb1_΀ Tyڬ08d?ZTJ9)i䞧5!,q&TOZ:t iN};b?ʥ zpy4z. P0:Sg_@M5!(nF";|=F0yyvz\4@isOJޕ,db{rnL֦> 9< |)săO8Q{ug=(O =ʀxy4*'J8/q\vz{qN_bG.3G `sK\ldq֜2*"$N ܫ7%};T1[9*E ϩrM<9qMBJ');KsENRQ8 i]cx8jIm88z}"H?~BJ 3N0gzt^?.)ǥ(9qzҕHOzQ҄&;8d;P{~t ێœқ3ʜ?ZC)I psZLh\}ʂM8˜8sҕ!ݸSGr{ ?JPpqt(cg&S];R'2)32ypjUB&j<NGnCsR&A'2Ƹ+6E ך8J5]jϽAFc@չb~D 3%.EJ;+ ˏP5VIYFF8<*TAJn:M cP뮅zXŎؕ9i2 gc[O5*ae,(`Q.0Z, `ŋhj[2ul=߽S#>TR[%U*%K# H6y;cBmd8=+ֶ.G'8[SIhuq PO6`gJmr6F3`c#5m)EܭD5=*Ӡ9 )INhB;h܌S VC3ޘp=jzy9⭭*>z`o֣rsB`)!_ Lz$TTvPv`j5&8XۜV줱5V!v<|Ͻ)|*9= 4Is+u(#V}k@Ii1OJT|/XNg/sl[^^Orb*"tηbH MDWd3⠖[깓6{!Xߚpq֫niwsN&f8 T[ w@IJޘ)ïq@ީB7w5\B=)Ӟ)hԪ3R"㞆 vE :gQ@ 4ă&p1ހF)IL9֛hM] RN9#A(R}8w49K@ht (':ϊjM(Za)sJ@&9ɥ<)N( T.V_㩨itJ1NހC0sӨKEb֔z=hN1ޒq֛)zv c\.rsM3?:p:Ԋ28N㽊NJmwQ*׾hPNGj0jx>A#aJ84gҋ3R 8>`95-Qy#sdtrsR#"Ƶ%@BjxT8SsEJ>\}i| Tk#<ԙ;.s׵H@)>a ȩ`g<+HH"*dE;_X*| 9\i%(k'HPH1}2d\Ԫr& 0@JɆmr~eeu54lx8۩P 8I늨R{tՄpHh#o\ִ&a)ԌTIGZ15$lA)dp:d}(}璘Z88 (zbڐqsCЕO5 T(F{pM%!12stcb0׊r;tp(Oo>_Z.TI pJ1i~{( 9ZR Ԏ<&j'G&=ll {u>Ȣ1+/ݾF}\9rbOR\/ UC glsx[w*Ӊ <$zlg8n tvViN4 yZӤ_$ EyR=t'5_*\zPJW-p\WncRښ:Sn\)$HǴON)vH9HSqJM "h p:Ҝ瞔HRN  5=ێG5qr~fݑr lU-ԏJ4cNyXo;)-$l6O Y0GjɎzG\F:R YF}L<7XR~Fv* u"y+"_UteBpgl19#XL-+tLأ?J|N|ª#5UR$Cv搿i3Zd w2XVQGu-dRfdvx~b;!j1X)`<)FۜFA0x@_$vsGبR9=G- žFhw7(w0>)UFCZ`bҦ}> ֤qߥj}#[Vs&gƘN;U yzXS?[HOM✱)"׏zy#*K{rT qOIj88VME㡩cC؞[6 !14\3R*zI)hNv^q֣hL uЕ$ d:1ޠu.! {U$qteGN*30j#lx8"ZH9?Z38bٻ4 ;;ԭW,9"5b(=j"MZ` sZ65GLw(ՈuSo#r I#棖'ȨV#t8kI&hFwco d<{uJ2jk7bC:+&8A⺡+hYzTSp:RqQZl P1ґJSbGZ'(w={JԑZmq]̛鋜l犻{ 8n@*7릒EC:M-_1Slۇ\lX=9(?έ}?G=hcЁMzҘtGhAd89E:Ӗ#Sf3aʐ8sJO9?H8ބQwz ^JsOG8'jz%ۨx4'=x"zS҆!=iI<J|̗;z{b'zB): 8ӭ9x40x݊zM1;Ssr`g$GJNa#=jJOn$g 9<ˠuǽ>8욦_f_y<5!=j2dqY1Jp;T6\Vd8gY#-3C D؈j]ñP8aK UWS>Vj穥YKv%2=?kwqzt5_'O&vyt 23ZSͺ<&qk4y }ь9}* 8Wz\ϵ8gGsҀmӎTi<#*%ۃj1u5Z!xr2/6⟟/Ry{U:5<uɨp4AqҐt=›=8P>sF:O?*sJZhc:c=*D9D늕ǽTD??-2LsJݣ4^}Xp? XVXӺNVǵ9֘Xi sZM׊4דC1Cq֤\fRy*d>C.UlmEKGaEQE=*\j$#zv{Pzޕ֛@lR{Ҝƒ )sJ}hހ4!OJ"y1Q;t4{4RaVbV*ɓnGLZqY=3ӵ9cϦiTSDůAF9=Pd i$RG@7q*X#t4#i<jU=K{1֕R t.8zSӭKx^銕29wVC=?,Tjr:ԋ8+V&0;S|\sPp08,Fp=8v gh w X2uSj99=A<̕xzL)9i^Ho3OQ}3L9w`JH4~DW&1N*%qRW:@=E9x8)Jr&ytUzcS DyZ9*GAQ9UIJ00G d瞸!x!Pur1Uv:h˨fYz[MAcXH\Gz*Z嚺+3p NյesklV֝rA^}Jv;a;eVgXWՁt^)yxQOB&+;WA֠#'=lvR5RpLs= shUALj 8!5Ժ38XF8N֘dR(Vf2OҤ^})ޝNw!!N_z)})ˁPXU'sӭ(R&Q=NOO1EבN4A>4s }G\`R R3N RJ?Q&"3O]Gܨn=? ǖR~sҠ,HZ[$މN[riHTq=Eg9XqW'8P9$ >܍ӎ'w4KK"0,9nV8$qո@1ǥg)hj>!XaI N׀+Wj)+ =UՎ8bbF2qڥ;Vr4$߽LxϵM0'>#9L?E`<b1U`izc 5aҫD@*@''""{^oSUGs84=f6pw;zUVޜ'#Gɱr3ҍ/ xh4r׵ *Axi<+Ek0" =`8ղ˛Q:U#( \.=JLS!@TR;`;tx= ^sJQhR+s ~Ƌǵ]TF̑EWrGҜ=󊷰 qXy)6hzj`}=YaoJx~Ѹ3mp=b8qӽ\HʘZ=Ewk|8#A>g\Vc9T |5(zӀ"zgެJx^|s`cӄ|9ڀ9BR0/c8glqA1SLKE!qV}sK R½3#~u=n2c>xB{Fg=Ō P;Nщ-2ie B5+ht)VkDsl*6=I'4q[Mʎ:,J'#5xN*e0FkOd6Q[wN1R۟Zb<0+"&/[׭KY$`ը#n}k&kH׎Fr=1Vaҥ,/h'#=SVXp;(+ =@ eh~\TZQeUbPڠ{E>ƶQJ9nmd%6a}=O!\5|t=R "zUaEk IʫbC 8&s)ԅnb Pp1L*$l*M&vKJЌ+2ٱޭ$^Jph$bBNj6n犍 й zVDrSN0=iLLR=ɫ 9j..Td\X~`[@~T;Rtإ61Z64zAlR'F=H}<j68[@SXzNG$sO"0:ֺPSҳ@{3+F[ETpyf&ZB6Q]<[DQ3b8㊪ۉ8<⛧\ɒ,9:rǥ_0ϩvvӚS$[dzSjBjGie;@dF3g=rjs9R"qӨRosN q>l>SHmBsR"<Ӓ#ҭ'Ћ31H%nzz4݀M;HWCLhd$ԟg=0A)zuis"IXw$P=c8M&B<{=t4;\"脹>Nem۰$TCjbE ND9kKh?#RE5< yY)M9zQvt+1Iu&\\JF@<{Qޙ#NJiぞ)ozBu5nxJkP`XE0QH7`Ӯ(sPƒ(2?*\pJNޘK=qI#F828㞴M @ Z 1Q"zԩ)JV'SsڜUYJ~qw)8 $8dB_H7z6b=)ez@nF:԰g7$2Z,`.ĝvjրN)7dR4@*C c sS[+VKby?Ƴ[Xp;歼+;SRFsuQgzwZfMl^}}PdFGTy_qdք! kp<֝*جInTUe0c& +;WS>cPO v$)ѷ'ԼUxƦLn)xPscsQWL)#V-s=+3Q9AJH[F#jH_Jtk;Rh KTRB$nFyh]CMJO^:{vh Yq]zjy" =iʹjXrEW=h`i&d ZhAQ`;r~l\dV2V*V4To;r+EcU|n>>HrF1pzT78-*4b3zQ(T妥SN}1=vQМYܻ t* RtB#qJI3XSԾhҰSG1cW;v:N9To\Xf!"1حT'jul G^Vx9"5etwm$[hp; r1 I% cKؒ3Rz5f m&)NR IeUGFuR9P皑p֪|gQ{N偀:Ny$ipO={Uɭ Ԓ0Zz=Zz枹4}Kí*Θ\S> `$~Oit4ORG|dSREHj3U;I98mЏ*~&j91y8ϡqIkLr8$qзn3 NqgXwRqRV"Ul0/9 l}P=s4nպ0r!}as0ʤ`v0+xIE?QKWgk>Ljc;oOҁ=)D۠?:pzP0=sTc"ΞGg@XAN_QsԠ]$+19Ԍ?ZSG0t$S֏8tM( i$a'):9 6$lG^ȐA) )yǶ ȗ \qӽ^i!jpisSҝb,aGQ֞>*_CWHڠձ48՘ăp=}+=8<<x\~ѳHӁ\Z$)M9UvM^dV VghdZP4$uTs5@ޞ]lQu<RwJ8sL-Z(;v;~@;.17L7`'9>2.W*4#늱ʌgI oAx3ާaW mYv&@:qmrG!# },Sg(ĒbޒK}4wF_ r<̨h5* Y-Bȹ HP>B3КӴW qkOR'g*w4E(9])$`$@Hf{քAdCsSǧ 94==F)?EXzIz7pO)#f3Ҹ nv?&Ny^goThzCZ NN(t{Xך/X#Dx8ҤRq5䊧f+HmTzG8[qZ ڝ8lj۠I,# H{jDܤPtPȀ bP8zD.VX?Z J܊EG5 )`Zy }E7 Of N{VњBjW;2(?G-㧭mkt9s tv9n];c#G1jf*۶F2Va8kDF3sOPoΩQBse$O5l t⎃+OfOlzykH}#擂)31mI{Tl8@yA~*C#Xqԩ JMm(aJ׭6I=~ ?2Pyv\y?QR 0Oc@A7|Ӕ jX5\ȥEeO4Й8oN(TYr=kd H&14`z)Z>>:)Nt~5fj8 xh/8|Fg#S1Ž R $@ +1ҫ)sހ-?Z8EJJU4qA9JE4A怣ޝE&~PczRO'ӊ\~x# $-!GvКk}(r7ZltdPO}j'=9uޚSz(M(0RF^iة!8)#>@R>PZ1HN ;w5;~xYqPZfONxAִ zi؏`Q]N08jِH2%e9S9:)3ZsBͳ ןV=:0KF=hB3J[?JnyV-%Qj9d xJp6[B 1PzN*c5 S9±= ؂z2z.cZqI #N+FėtA 9m;q53֚RO&j̾WacYw hc`NATt]Ja8jDaJqR)N*^=)sQgҔGK񟗮i5Q qc^9JOUh['957j`E^nYI:z "[}ߠb>ۭC+~<pZB$UA3R ެByCY]"H/t_JoA EqUN{$ w&ղe)I^I*sCo\NQkzRX<{-^U`XldUԧi)-p uUO?ʺ'֨_D"Pӳ1qNO AFsPsJNG}sQxjxXwۚL= a@zUPP3۽^۴- Z HϾ)Oj M''ڳփ@y8":(ZZDjSO:=j*{TMТqۊUb1U! l~0Z^T 4{4S7S)82T.ү)acj-)>ST;8lg'QENZF臩'Bp)y=*4?/& JM8^NqLRNxjnŅx=*E=*qo<6ZnW({qP#ԑ9UX4R}_֣GsR$g`jAN'#aA)Q1H W4yʞ9={TF3PCqxb:;*}qҝc5[폩0qhc7)FJWU#>Ocu< =FqC/<J%q5:zqn)qB3ThG:昣Tr?&ߛkuzr'c8:WbyF88߽ZDEP3Ҩ81ґǽRsNt֚—?)JOiGޟM0`/֕0E4Sէp1ץ*9Q\Qq~xkϒ@lq\E%9}i=l6")( \zRG^1bQ{ 3ҫ2FIҬێzОbNkFUV 1]4ь݉HtUczRZf _hzi;?SD- ٧g qQ8ǵ=1T<9欤3PEUWОqU|#AԱzsP\ Or gQ^SBp*^@bizw4Ĝrkr7&SۡE8*pFI⧇M(K֖N=*q~J`sޅ۞A*t6]&]o*zSNp?fW56-&zt8*z4rj2=~^ChN7$=iJhiNS?J[ Ԋ7 Ĺ>PqMyh 2vp?F;w桐g85\bTGC96ֲL]PF:VQUh]Tֆ^j$ԃUP涋%U8.{Q3j'sL~[88ph]E&xqHQN\Ӫ09.qTsM803ȧd`s_)h =q^8)Q=ԴyE7ZAӽ\;msӵNU*y@ĩJ $c8W1X ߏJ3qRQPPՐR9/g/jn4Dn*8Jk7#N1l)P:ǷjG= 4RsIJ(@iy7# T]T߮=(TП΢c`/j54Kc@5":'ґǾ)\S`dzS{RxiGNhr)i\J޴XxSsBw.qC3sJ=0jB8)Iw:zT};PL>QIqN`7gEH!^4`~h)EI0iqGLxSN4U qJcޘ qnH\ T N1b" qQf!qS+sךH5(Sǭ|˚=1S#9qL|.A;HH.IըߥSE'E#=I+E8\zteGQV#9 ڶqѲbHr!Gz֞&ќqV.'gIEj@8\wq9[;fs5cGtc&PHm&{U_ҢZD~~5$R ɮZ5nUrzfcS^C 汌,k) !ǥOg d=i])hs_sb)<ou$ڻA+j1؁YN8ⱴY1N.$x⥏^,=6L#N=3MqQrnUQ8A}0֬͒T49*- MF8^3R8'E5rXu(W^HGqW\qW1v1`N3XV$PE:+pRُ…Lۂ=jͯLAڤ4rz*1;xI'*\]ˎ)(BVVM=1E|bT\=JйF:T2QXjgrnvBGZ, לU{r}OI<.OZjsI[aEOy2z]E;pEhCǀ#4L6jZӎ<#{S#N4m@VP fNθZ@Jr`t4ϖPC aLqJ%bQUؔO8'*īt9".䶭rq֭^{kA_1kFK]* 8J(9;GH\q|UGiÖCwp梸&n: *pyV3$.ʌsYS1>GdpHy9[V@dNR1*)HL*H3VT7k@'LnXf)ɓߎזvs(FFO$v; QRմ)+J0^qwTKN)<~ir_YsRDω_\Ոߎݑii^2~Z]F;\`qZӳVӨccSiv\ ";VtTH\0՝+Y&l{P:MSk,BpI9$j"MNZِj$j8D͢ϴүBʌdTPZE]YiOQTf$tA8N +X+]40:*5oԊr:+YKK *ȑz`ujnsY}* Rzv{X.ڗ7]xS/ןzF%ҝ84cSQCtVZQO#9uEv%μNtMX$Dy { ; uSgmȿ4sjγO|#SSި.=h猌4(IU`V|O11b/>^Ԩy**dcd/FN:՘ysY'ڬ&8*4LЄYG FOT&JBo'V^Vbw#2.~8⠕jUevpVٲs1RFzI29TDߧj>T$D͂8 In*?7BkX6cJr8j:VVԔ8z*PA`UxT9X[8^:Rp+xcr~Tޕz_զA'cߎiQ!sN?t0&<89w_"׊\ >O<8鞔qA~iaך~p0#5&ӑw R$6 &4\VՏzaSR6JBWR3XԟBphGHw=H\sБW#SO1BX\I dT7N\G銥F*Rq ,Hqy !x[-LY00 &sF9ElheiQW8⢒Lt'nabk>^5FYO4/`g֫23gQBc(6;|Sv<Ǚru3Ȩf׵9xl{u4Ǧxj<}?KHa0?Z :E:wroV8}WLsֻ 9N3QR\GC1oƪLĎu4g 7*\Г qMi8`Gݤ7~(ad>6;I?Z#H?5U<`dRA#Zxp`uϭG'88`=*)ho@H>cH=:ROT؈~*߭R`y@ۭ5d`8{g#7;5Gg&Y[2V i94ץ5~H>ZQG9J*G'2g*V@nj^U#SXd ڢu9q+8VfLTUH;R1S!MDM8g7w9W|HcJ|9U1Ԋ8R]1NAXs!84`]c/jۜ &ZP1@ h#ӥ%(cS|B>I2⏳1;("}2'8qY\V1=kJ1!\h`æ9EZҝHk)*Wc3{t8'ҝ59c8BpY3A"[ d1EiB՝n_Z9z&erqץ?$ Tu߆'44qکn4,FzTٳnd.&1ɫp5Zxlw"FԑaLڠt_czwy*B +Hz~03Z6" տQWg:e\snDs9z!86XyUjeV66"n1L=+.;ɫq)⛋=\szz#ک ?T\Rvqfr=*c8YQ9rTz%h @Hji n51NCzQ=Fa`7aG8BU()'Ң'zT(9╘$|OJ&F (=ք,[F:uxNVѹПNݫ9))0jV$`cQJ U]Bg541uju ˜ૌ18'8uzU[rMYҥ,z(Î8Lܙ"aJsH? 7"- (E&9ǁKz*zޣ^s{3fD'*kpjzsʎ֕>v(+w!9֭O,5@C1[¢$iدo,XqV%PO)VmQCbMm$vPJC|L0T7Y:Npaxl.C0O0 4)>[D GSo`g=)@SLUiNdUI W k"ܩ׵_[Mjа{$<~l`Ү[| $Do7rj $8N 2aceb,Ri9CNjcSLqJb_,Bajfat#Z} bz֭B$cPlpIVr FDc= <ԗ}49G'˙QZXM/V`#%O!x'Axj&Lw)gU$9#OiOִD&=ԑ܃4/8/<QVԑXTn9L=J# 9G+@&9Lv^FARR#1LTpO\Yr8c)ؒ"1ٳ K45XtqOq҂0qT'&dn㚤<A>mdj[󊶮0zVtB8aĄzըWT~V^ Z̥3.GbF;TLĄSsT} NM8+=`*Ӂ]0Yɭ.ـbb:?ZоJ˘׊Z+38Et,yݐj1BT=*dn9":YC}zT8aUb댐jt$?NqֱZcl VbpGF*6<ZŢe/\R|=j0zX9GjQVвMF}G;l/<ՈeVm28_ >*8`皅惿~֓=2}sY+4}zWɡ.]'n4O<4QGT؎n= $U9ztL&gZ@rZddf:j>qLA#ޞ!䌎{Sׯ^$@S !84ߧJ%AtQ^HaA!xM9y#ǑOH94@p*uG򎧧 |kdDE0㌏ZWr@5Y[8s(w,`~A AR.DK TejWI!WR@ipiˑkeloY>Q[jeo XEJi>^xt<OZC; =?:zSc##=K,"X qU`A$ⷌ[,)cj(zJmw\/i3Mi0QN&85"pZ Ԩu?Z!XpF3)xǸV9㨥&ry)'<Jþp)&0|LR_^֢qSpez*ju>Ì֩\ܒ8c*]T}ᜐ/Zp3Qrr:D5RtpjgqUGS swVnOJ^:[xb}q9~QNQHtS #(O p;DYu(_}]Qm Jbf\dBvNZ.ZQY?IEӱ\A>š Px>(3@r98H=9ހ+}-=qR\&[J\c9qK{c%ڧAT-F 5l(8~u27Z4}HBJ1L hnzQf~`7<0'{zԠq14|RJ>I(NO&}\;P = 0uҞ;FxE*c=N)3zSi?:M @_zoޚ߭-K3V#~8w6NҰ3TPj@Ns"X ^r:оK"h$w GAPF0p8P1zQpPq{1y⛝gVABwObʶ[j}.& L( cW=Fވ)E~h@A8$qR-Ԛ%F% SmX4{#s3Y`>jyƪ\9MtFZ56V #V7I}[9q׵k*)%ax,W "rڱR&el}7{HPǁҫHN?ɧG\Na:I:p;:V"@h*î@泌N;rnY3[@T#Fy\:=8/I$RW Vap`ՑYe U'w^#Ɂ׷TL\4rj4*6R{`I2jc=xpGJ턚9dܯ 8գ~EH vLg'&}L0nۚnx<qC>8Z$$QUxrG qQ9ϥTea8\KryZ؁ZQ҃VjN;&,VAq$yW`pX@LpDԩ G*iZpM8ڧkQ"F4#vUxnB=)cqWFҹTQI\G@MO2(= A!}݌|=(VvWti`IÃj)b隕vӭl%"cVL(#=kdxbǴ [*V0i7s6 sйٮӞ1S$@='WK+Dpr6Rt=+bku$bRmY-k1jxsWߑzSq&lШ{VbwtJ(]_F>%֬Fqcuue>D7KB&PjHc4QBw#BnAWTжZ5@AКWTq>pkE# g&y~2( sU[*Z-u4mdP94Kߐ:V@b<֒Ef玆)IR sSGzhqץ8tY l4㌂3ZN8uL͒4{J玔oִmw$pئf( UOmy5d#5UFjHi+Y vӔws:ѻZdBHX$qXjGS)F+XHq8tZy6:֬;mPF,HdUVZF89lAAYvщHQJz şNJz:Uuf'rd]*f#BsSr12ZKuɠ5H\6@*Mw"1hAM@Kboʺ ¡AyRj%Ŋ8w4!lwϵJ"zP:hblO԰[8浖 85" ߥZá:|V( }j@vZU8jEbŽƤXjQZ*i\1¡NVͱ8+*˞qV.@eı%b+?\L$9랹5<'8_isrUGnJ >QZd:0=jDHq&H9Fz<*CҴD\Sv 9)<Ŀ(Rgj0xԌK%ߚT5\7<,M>q-ѸNSԟl:LAm7rq87uI*%#"AҚbsJǧ<搏Zd1= 9H1zs5E 3b6F*tOSz9b~~_wˊN*Y|R8OjAS;8LP£~})Ny|c z3G83j皘1\{)H'OGJ3Up,G曷 H Rr* \†hOcK}ƅ┨>P!t>F=Y4`~"\OJj ș:  )<p=zS )Z:g9ߚzӿwwiRqg\NI*Cڭ66qUseDR3*SFy$j3S/W*TjJF>#B@ 1WSϽ;q{֚&NHO捧ךu9@@q |@v3G$M.i:U^(3R/w~7B@Ґu:r@=A4梐u9pjݚBpjAEYuOΠ29'[zsn$)Hq,r5 V9nT];"|/MTS%p} KaޫFqJZweHsNU7\J,3s@a3U}iۯ"'?( ~@'PIZ745x)a9YP8ʁKs9C)qV8%cU-ͩu< P;cОGZqDƥFˆ\'qժT9+AjNNI8O J8f4#J"2H8NW%A (.GSS~CIbN*ʷFTX "Ɠ' gۊմ]Tcs5ӥf[Zƻxn<X**V2=ہd3UciÞP^UHqՀ^1I'NT}*|N{T7S.'"6LWa#48*G$Zr_Ke; Bs)FMia&!|lSZz.Bp=)$W{!(#qRB;umVm$4s '=*͌#f[.Gj# V3F4P'2,r6Ar 3H3zl3pې= q4w֣{ظɥ#<Ȭ!pjH$g9ǭW+b?9":U(dR~oRjBw(䁂sXΣXL͹ՍhZJ0FzkNH' (ߚH$cך{yҳ-jeQӞ*oG%Svhr͸:[v=3ک犹n{N*dZR~Bݾ\zUdgNkFlp21WẑL3ӽO>4r~ `wD˕9$D g&MvTvqK<2 矦+43@<]ʝ~V z溣#)Dgz͌LŽ:ՙ8g,pM)4gœq޳lԪ#n)zjBt:\L#Hd'{So(njT8&^:dT Th\oԐ*hqpIS{H .2*x^vx c*HϹڪMhdZ\g u4Q]i K20*$ djHM=jO$m<ғbi.%lɤݓP>iː}1G5Ô}sNC="?SiCz1֡>bo8Mv&nSOFә1Mv~Nj1c4"E8I=iRZDvU;1ssO^樑JUgHy#fۜ 80܊'8S) \ڜ{xb;v=94??6)ąi#=ix<f@p=jeU2OMRzA˭hbGNK\ך4>!>ZqP 9xE1L?HdwrjE1R/)M 14@@Q@ ;R)i+JRpi?LEPw#`8=*)zՆ=qMeh9"MZ  _6sV(=a_N1WJPT~4M#'RX ),o/zXLz<28J.sW({V,ysvI9ϭrզ\'+3ӭQQnUcpks)ȳSFF&Joc6qR$N*lK5!dx⧊PISRG!lɩMc- =zR0H>m=8Ts/yZUn9j8fˈUl*z  :EPl~2* GlXVR2cgSCahe<52c c5"ɧ`NŰ$fV;=UiqQsls֞OlUcnH8N繫PJ PZʊ-0r!5J?ՈwfH2:FN8k?"E皵֩8VPq֓lV, C&T\}36R傍Z`X\rM'znZӥ}iw gjH*qMG6T9^bYLg\ mnMVۜYh=jPqTՁG&ޜS'GF21MA$'Z~*8kBЍ C}Ih m@=`Rr}WgQP1 ]4T婃axiI2ܝqWW;qYW)&޵QUoӥd~L>Pp)M舔u#5JpT#ֲ~H#qZ$hrG-/Qa5Ҭ!3U$i v-4vb9L7&qҜ$Sd֋̬^2f dYJܑҬDezJQM Sx<J/f4[3ppG^z2æqJ(M/֞pHzzq Yc9-v7s(ߒOsMBPOsRgZi۵H3p`hrN9s*nqCV2 OۃNEJUx"[:Y`u 4XBj :z֨C:PQ4S ֳss<\f!G48TwS;PBrpgy4P͜z֛#4=iU:dINKehK$F9`Lrzw)S4;JQrzZ-u}D'zVpa;Seo֐ݬK ݑ{qT3rjh_t4Ķ$~v֑gقդnȾT(jn00GWc%`p=)K~5dQJ1U,=pjlTzposPٗ ͜ sdvU;; W{㦦;}MEH9#5إ2&S3PžšdtPxF8P3J8cZWN94~U#o[@jנU3)+r>C@׀)s\$ԩ!N3UccӑלUňPs {RәIJbW;'jnJ$⚑$$}zҒ@$\󞔢D9W0 B4hǧBH*Nh:OƑTҟ隞 %p r 8j)9js)>tE&NdƥTǵCY ^iY)8 kBAG2&̲"VYI&O⦎R=E/hi=\Uvh<ӥ'B;SA$ς>Mӯ5 `sl(H\6;^sZKg)ffۍӋӓX0qڜ.I={V4@9f"b{:PYK\8DzRU{龅Tv HXNr+Lj;dc I䞕v܅lyϧs'VS~WPjh\Ka81E!VWN[5`r)N1Z̀#0ldd.$KDZypz)]fld3S@x4q:Si~Q'֣ҥ\p:fО3ɫ08gX$*eبh8  #=̀3cA)Azh~~`*SU)'Ǯ}(Vl $cb,Fpr3ҥR1<Й8IO=MV$Z(YmL?9$TNz).f[ߜnÐ3g5nہ{h&, U/US8%D=P<;Sԁ=*|PEtSן\+85<&8`zBHDJ*b`z).q׽BI&6N&ǩ<֤3PZۈxҔsNEjG^;fΘ9`L&ޞ +#I"@ScJ|t"鏦)iQr*M8?dly=1Ss_ZqEձ"ԁSS`"SDQV`*L0< ;JD޼r\~n*c||ܙ#go^8J9:T1@0=Rv l@㊖498S3 #ROC9J"f'788`N(t\p{xqM_9RH RdۂۊzTdgOZ9\ dQ5Rď{`H=r`לUFyʭmYL=j-ǵDCzS 8=MM~RxZru.Ő@P3V!`U#Fj#[3LzS[gezUv9lֽ )cSwH:#sRZ#:Ul֧Hqlj1;1]t6y"L9瑚r M`D[kn(Fj6ҤLgN;b8?M\,0B'C#yI:n]hQ18CN ʢF3 -ڔ͚/(WAќ֤V9ץ4/Sq֪%+0 ffO(r1i=jD ?֘Ď4tnq*yd;y<J|g1Vg朖qG+ 8"md.dT~1Ob1Ցl{TC*) ul pq+TQ72{u|~U2hD2y`zqsl/3kr+ ]HjLx'8?*.&1ީHTNcH-9#v9%5cEhV=iҨb K@O,|`W} {?7:1 iy曁)݋A3ۜ8wH<`COF :SvUȪEnS!UT\~"Lֵd15 _{<*B 0Sb:Lq'f-*~_zhiN}HMWM9UHFi SMSw֣i?f4u#*4֤_Nx%N4y3P.qЊCWZ'N8jHַ$ 5 =iŸ Y; ښ[ր^);q@~ sRPwiGZq2S j6n*249>=(iAӆ;P3sIE8斓>H:ҟSԊ^بGZ$.9:B "@C9;i~Ghߥ/z(FZu!(8 b: &@*Hи?3u`4t(b>GZh3J֛|ҜP SR^P(: ?Q@w (Q?[[qc޹&Yk ^Z{k? B*ROVcQ)<\t>#2Cbvlz~5/֥^4N1qG3=+=Y qXWqz84.cU^qOrp3ҞIlUiQ㎵,mϸq]%6ʪMK u?4$ԍ68NLgm rFA9-̚5PG=jL/gd#*2p@Qpɧt7#4S(l#V!Ajr=qRp3JlcAX r2@fy(n684V'P0W!@֫c94}ӚYCSFx3AN2 $gfs*Q21ql D7BzS##>(H*Q+:e&5ֆݙ.OzEF`sG#>iUU΂ݐ 殬;U9{xhFD]ETpF:U:c|A@✪hkЭ LyjH77оRgҥH[M[{qSO7pZT*Q1VTe$Թbp:cYGPya5=y=Gg)hTbJֱ;?**f-jliURw`gb$ΥSZ+3بҘPhsiр9:͕2(H[`h3jDأ"Ad|pqVut+Gqxz;szSҴPF|Dp).@"('?)T9RFR=y<D91϶*EC q;FIi;gZ9;'iz։x Oy?+!ݍLIڟ#@&v Jz>;`{Z'H=VV>XcJZ횫 teq3(rZ4z>89S[Gbd8qbhҵji ZO=\BM; F|q1,\X6{#ZC9ޥ&AޑVpOPG<\1@Fdt5m:9aEj~iZ2׎Tq֯H4W:]'R)"y}i5MS1QPV Z7+"ҥPG5*JrǵRBF) 7֚$TM;"1"U)s2HȥG\qJx*S3 )\T{Nzb(]杒8d攭iY@qJXL)zN1.qHXר)?Ts"z|}~NSOޚ`L0*==)+`tiO㏥2=Av0RP:E0cU{i3ߵ-yQCq@3sޚHɦ}֖rO\T01Q'JvxSuANNR9czўs 5}(b=zXKPB}3@Zc}i=K=hhtqGoz(_*Gozp#8EGNCڀM'44ׯ4Z?h(- <ӊdJ(s@֙5ߑi>.q+b< z@ RϭF28J/ϥK1E@BᶬO 1WɟHt_V54GА1P-*hjz =Md5dSz|sRFNE6rG-U=Mgڬ#(kZ8r*r8@1EN5Ա 4?Z1׵OjMv2@sRvUxbzQt)H> s=)$2.E&^1O{u5KpJP]صqV`#5@jt^QG;MmO~RB~qRd:ԪBs_Z+jLHRlG`zb2Z 1RsSn]7~Ow8^zӡbqphhZq0$RJωޥVfѢyu9V`lOQ=*xZQ& ]o+ҬFwY汚6<4ˠp3֨Zɘ񞕚؉;K2 qpsq 95J) HPgzE<q)TMZZ&F{Es֟֞FiĶS;`H}sOO7ԫv#H[a|\5/q8Yobl),X"tjS0NhRd]&5#V ALA Ay _\R;q o\Jq(9<8pMT˴>pXGM]pW0sTG\3Qr 9V"=R1STvBH@=*XGLipMNFEM`tpG~8+NO+\1 =*W|sU(B8G:Q$M)cP֤5VQpVq6! >;g^\ jv=(.N=IK"^is)=+R-.쎽=)Tg >S{h$)^x5֢q#& EtHn+NHc[x;x = ] 㱩UU:Rb:e$YcR<@㑐E82*.Sv'l Aw(L` jAgqKuCR(9qTL R1j L^A<){dc"E PocT1ޠ1ߡW&#"~.xR!?7QqJHےpO ?։#/NԃӞheqj &L+u<Ÿ"0K1?_ZWU#R0kXvyv# t*5~1)Q:c`{$╔5/9$sF}NJr6Д8-yr3ڨV OV8Jv@f"69?Jʧ۵Uc94nA#W0I|SsҤ+/5{֗І 4vM3S|ԂչHO>HHv3Rn5=i뎹1"=q&zRG^瑞i@׵MB~t)w QO@m*21M9#"d3H`ri(Qڜ01n@4Ю=zڞ8wsO݁ןRdbuF {Q.[d}jb`xǠr')H:%N[a8$c l?Zp8l=aOZp8n{U8+ߟJl_<֘4cIϯҝɱ/!)ޛU#94184#\R'O5Ex#488"0G:wKdԙS'cpx4c#ӥ&@ɥ8@=)@R@#==x0M" ~֣Pvޞ:rj`8zw~S' FOjU>׭(1?{h֛88@y ֛K\=h=I'@=ғ< g"z@=qAP Sޘ:sNCpM-})WJ@N>Q@ <})(oj\ǵ6|gTJxw~G<ȡϱ=TbgO hRhph`i#H染L9N,q=q4(A+Aw9,8S {Hz<Jhtd{Tzcޛ$SjXJ֥F*88 5,}0VNg4DdT0#tiňut0on"`j,RDTd Ig97py2[,w5";U@y#zQa)X ЎjDv8RB zi10ҦNNzsT`?ʧ+&j Aը8SGFj5=) T8wڧf{rW oO֪03r} alHG*U>[} /^T}[r* ✯9IX+oJ榈uNXCs) 5 =H.W!":qR`gNsHO@9IҰQq{SS2AzԨ1P$R4{x!>2H"u9l :S? p9H)IOQ>Qץ??1Q1构ʪNC`U5lv7gޥaߊ}B⦍8Klp;E'vCRlI*E8=~])=X[BN}h9QcOޡ8{DHv@ls֠F,l25VBw& |zE7~0sqN099=?yB)4J1qRoQ*_1'ݑ'[91J1!LZnsp'S'5v '\~0a*8"L`E;' tQYԃNnBtr?J9U*cޢA'b7O 8Uɧy(K"NT7_T8=¦RA$6 RVq֩Q}4QmX4ST 9_&M;S8⪫ p{T={Pw,b @N"z,/"ϙ"ԟTSԱ:qC,<ڔ?=sQG'79`sOSӞRquiwuTVn䞔A }*X R!p)=h8ALAqT RdvJUܵ~'ԑOSqMZc8Hxj9+7E>6⫫saNõ,NzzӃ`cX,Fܑ۵HvVV"9 4BڿLւqu$/ڗqM Ԝ=iN늀vvVL6~L< _8<,+Fip UÁp*r:NUO0pwNW'w%d#8W?/\*Doy=))u. `s^U=)LB(n1P:r7~] Ւ*T<3ip8&#pM 0 x#)[m)בO pB>^zR=v'S֞zzsIа֜6=)ONӶ%6xzrj<Ԑ:\ĥb8y8PsO zIlN U)\>N3n4;}S?8qzS_0ː8%@Slq2!YN~n*?0`OIo9/P;}Nz .,(Q5䑊#qRЖcqL zcz843B*M8:NRqSd`T'#z- sI^j~45jbQ-! H'.NA ⩙>\{Rtiĸ\,2{;m9# .I1Us=i9U)as&NzZ$z171r;8K ^4S.R ѻWy#]Ċ|-pHjw?yRR6yqWV9Row5"yI(ZqN zs37#,0F{vwĚr B1zw59XԃUp`>('m늮bZ,t>8R!qTt&FP1BN85|X^*D &gcގp>[zsN.SBq!qSrF9)@$AsiUSO3ue==t@3qUԓ9=H"ʌdS`u4])*<)@$/hǮi+ >qMLvlK߽4"DbSҥVץU-ךr6GRQ;~F \zG98&vץ0=(;Ni\ C{Pgq֜RoCUQW+څ?7~nNϾ(OuR x)aؚbldžNuE?pi&qJO`j>N_iM^2E.N}jabUqnu0҃Ev[9M|iJA֔rKH:ҔVINe*S899 s*< r}F#&~@ 8Lp9=h=>=sښO:н^)hu1P{4 Q5HϽ:QAn)IրNri p:Pn9~))A@ )(=zi%!i?.Gq@4>9/N Z\LRqI@oZ3i}sSOSj<~4#Zp*0~QJ[ӏJs7 爴 nM_\O`,=?J=}ZDʱن: tWiJ=ޓ>ο+hQohPZE.R-Ji HS|/iPRI8 q3(Ԛ#Z U8N Ir2+ñs=Ьwc'#cȩѸKeݜ)#*FޤuS;3&jXpHS;2zje[jrj\aE$"sNa1WACԜTS`::T܃OZ #5*٤f,MMJqPxS?$Nńl/J82HI\E]82>sITNAmsQ8zZwuT=槖 ԁĎ$L}sG"5&[ A=~P:X mpi1U#Zss܊ieaiA#3Z $䱹f9O<{b~@վUSqAW,=c?Njl7&ZA4p ҩ$&'rGaNrzUu2O*A֝-FP3iTqץ@?zvSbb99p*`8:S;:*DlýURzrJcI"e[3Rl֫)#$W5qx7yңa]gfe)Qu'97~tX8R=jt fN7NZV` |&ҞJ,+s X68SrҾNG*pG=f[,7a[ b8E.sK/*8qN9Ӂ0H],8<ӢnxB[4s5qMg~N9Zw%XzO93jLrK(<v9C84گaqҕ$žqU~Pd>a)HFq֔w# |гOSn:҇W0Jzpp{҂=y]M1Xpրrq}j=å/ +1tj?.GZsgnlU:iRb$Rpz8#9Ԝdh8cU|4R6%Όc4wW2bzc-3w)Ul89!q{Pj`~d9=.==)26hVh[BUhw`8=O"M2lHzq(bx߅=ߊqސ<4=x5WDH0?Ɯ* N<o&+ 6*4n? P&hO֜94q\±3_]oZw `Jp?:1JqEURȨ7wWGz"KC$T!8M4ԂŌtgE;TO)!8iC~bJGOv&j )sKV&SK߿JPF89AKZ?dMH sRQ֠s1Xqv:T s&C1ӽ(?.j<<5"lHE80JO?/O2usP֕OODTyj4'u5IVZ9OˎDNqҞ^y38.N "lJNREh'|O<Bw<)h8 (NFJPNx9^;R}M.}:Ӹl=Ifv$ZPFn .xお<zT.{7<ڀIzSS=48c;%3OGzSL-GG @sM('!qE/AM:U&;d})4GG0K٦>xsM0uIE0zvze.{zPdLZFzsQq\J~>O.s@?*\֊vP{s@gsKL4g.N:#R}8h=7#z3PiqK@ E 8Z<=y)GZJ(@J@X<`\cN4GqFG=FhvE&@PH?ZZ Q@ RMzp#hO`Gf,g+̯ӧ(T"ӿC//G3$ڝܓM wlQ5J KMu_,xZ5{0; >]IMyK>m(ʚؖiP.qϙJx\L⪽z !{3ӀJ:d0}ѓڜAڲ:&sOSP)=*hz!xzTpH隅pzYU`HަEqMPqdu2mǩN0}*h3"N01?ҫT@P2x udʣ |xޢq,ܞ3*PF>_¡JØ=859N烟J'U~2GC3$JBYBSQ5]K2:S&Lx9GQQ=ڐH<3Q1N_Zb&vȩyǿZO26U$LRP >#h+) {T֫dT&Qc?Ҕcp`횘1STɢ늑3J=jh̼ƤL2HL!u7 Dzc<~5 m_J gOV O=) Oq5W2yR.=90j~^)Zޜ:*v0qSџ4 OCwVϥ)1(,0zSո2N@#LgH& xҜ$PçCNSpxKp-FGRqR㚩qXzⓉ7YFqԠ`c>Q[ Ԡ8׷8T)UOa!8TP/zԁ9jl:u5L z 0yKVN1;b]}*?.;椋I5 X:望iMK:%uӌӕj68Fy ҹ-=9ȧzb`@d;~n+ BX zԃJyyYaN_01Pӕj;&FR8)=)1&A֕ޞ0¤L#bϿ"5&z~T [=E@5"ac=8w䫜OJGMDzIq;v'OAFbp@K(#Ba zg?QBbA^)H91H +2:МwjO\gD/"6\;<Q`L =8{PpzG㸩H[(lg9sT2'S8ZO-8}qU)\~e wrVG=y)K?i Eaӆ~][ޞE;2px(bOQ9zaO9yҡU`8ȧxއoy,ny1P;vtIaX/9+W-p;S:GX G}jHj2zS^Ővs=_U@yNmGY“@XcMYV9pn;U )Ay"pݳJ;u;s3֟1֖^ldR+ߥV3JNXW֜f@E<7B'S<1qUՉ.2dXG ә\WS )y(b% r9<;㎕8lӇ PtMc5nx<);-á.rp Hc#~@X' )WYx<`(nuǥ83ڠcqޜ89#4'$h;7Ɂ֜8a88c)4tH U)@ P@5"lJԚU=BCNSjbS럥!l(f?\xIIɠ:NϦ@pBx,EpM9H 8^8p{<7QN.qg.xOw6ֆ *u)|gQ@*U3~(b ݩNާD;ޚbR縤vTRȦ֕x9\Dsv@>J=z3ZuϽ=i ău=)KsLC^R}3QzU$ gWS =)A5IQҘJ8?Zw"Ċ~QקZPpO4̟ZPN= UPx*<})LC?Zp#jfN"1UpÁJ:1O{Q ֚O@ۯjrchzfibl?' 昇NJp4N_c0E87{Tyɥ&0:]PqQ0׵('SNCLx<Қ8)œsL{(()?(zP95?Z\j~@=gc҃Z|°=)xN)JF?*NQց 49(/zzn{҃Қ`;i{f˵ y8AIޓ< f8Qړ8='AqIE}7p)P9?\S"qM$AIށ֣J\S2;g4ќzLs׭ <4O(n8LzR}h4Q@'>g=)yKs@'SzS>sӷ:&sZw;n梺!iGH]47m@׈ѿ)׉gIolG )f`s?~[w,۠"Ьp,#&m+ݚBJA\y79>jD s࠺lWV^+BC__~!k.Y BĚn%7sq^}lr(N׬}_n;-~3^=B|t/+ľ'aWJKQ,#wsUoe-4 9C[+ԨݤݾGLpT/ڋC"RwuIHٛ$5_o+:TƩ4y6֪QتnVD2Hfcm s$[3Ұ/JU[2IfQ(;Vss{5[B Jʼ'\jn8, 9uRqЊv(T)Izr>"G\Յ^ނHN&#J =X&Q=ԑ JHZobh EH TR\88cl/S>˚#NbtzTp?I sGOcU+j`:g HYdչ3Jb'dܟBx@p*8bZBH|urq22sS*=MBzڤL} &t`a@`'O=Xt 1Rz):㚖mMB9OЃbRyP:T=DJO9OO΢CR3R_Τ񩾕ׁN^J3ȩ#psQF>QT})Q4|~5*; ]3{zgLnhCH"U?p>1IC=p)!yƤI@H} O\RL8=~~^8=i,)֜:|BA=N1 ]O^  'o@0yR?Ї*L9iNBe$OL5 |w.bʒON'ByH:}DDZRӮ= H$&0o0lp;IB֞8$GZZ t*?7ZtggӶly#>:T)qFr3H,Հlob[1Jd{p \TOJw g$)"Tl1R!c#5'ӷZtd{P;ڜ䞜S'DNǵ72:-AsRHzҩG1U4'&$ ^վa cӵrfnzӔڟc+̘xёרOoZv3iarlץ;u&= 2S$➭ )PH;w;ҜO3QF{cSӚH-t5 a5rxJH+/Q;ؗp=8~}*`iއ'"3)I֕qbW%yϧPu枧3Ґ'K 8)Jq{qҢI3LCԜ=;ӷ #={3d;?8 NviaяN;qgCsrqUryX@Z:'94'< M&Ȫhx$q)펕=Fiޔ"E_ȦtSV&(SN=)8ցy<ϭ4|U&uMB1֔pzӸtOҝFj`y1T!x&P;CZz0*&{҂@8!'zw$xX6|s`~_|q54ȴ%zž)+I1?3eNJևU<+7:/.$VIv$5\{;J䧾Ȳcu9&J ziidGw8}OtdInbpYR˸Wgت8ޗql'дSm]#;]:"Ҽr\-o.~!\4+|c #ִQ̿k),=0*nًuF7ڷ~^SbqF .TguJCNMLߚ$jx qV ŌpO2:b)T9+h,lA5A=q9*U9#p*E?/֋hRob3G*XϭU`J6 9#IRX9=CR&=t*AQVړzdgpϵ=j@ӵ.F3DԪFW\dZw#o=,XLƝ_sN#=~yӽI9#ޢ swj[iAޠzp܆ ReH)ccȪ;չEȴi~~OZs籡IZHqXt?>UP(9*8(co0GHsZ*AI-!AO=3#0QT۠sNBr9u?6@8bwxz c e~zz8  iA=*aךr=:zT3UZ[ :zb`qijNzJ 9$=I"F3U'(nw%c|;r  ө֐YRzr_«FqҤ ޝ$NIԎ'\H?Sш.kaN > 瞆N:pM.N85&叵@  `sҞ?PPoBp`gv81UЍH*iشmHπj@ЎR] z 8ҡ ?N8=? KԝX ӕUi6yHi#)O\glcza] gJu ezғ Q,!wjgCOF*l'mA;T @|P?"Q}){ OySGcJdpbNd nRh sPv@NE~ӳS ԗ"~]j88SHQ1ST6%is#[$b0=6H2piH%qFpj2S@ӥFqڕ9:!\F ҩyq}Tjzv)\4Q3Ӳ{uI"@!$9sS3RHnxvF F zU) yE1RtM7{t1D (oM0$'֍fbaF:ўNJ0{"\.H֣֕OwLI皉X.F9<ӸJ3ڗ<5bh^O^@v.Rb)A4{G8;8QKך?(ScҘs.j2q9x?(\`19O:pIϸqF~S֟0>F S3ӳPߨI׽Ҙ=OJ7si !*6>ǵ<n)A3NIy`94 p"zdaX/(< TJOwaؓ#׊ FOjGs 'TEsJץ;4?>4[]hSNx㎴y= 3^*0/֗pu$Mƣ/,Kץ9,pyLh∼$h8$V,ҩjpx==iI8- GPtH׭~E\1x;Ǿ5;G%b+\x_$)Pnw{յ? jzfk![/~p_nxSW:~eous)VP}@_^$&\3~76w'FQw N7?c~ :gZ̐xP/h=:WzIJ1v !>Os7ق A4(~~-a[>iFw&g]CZb+Ay'"[(bYzkJÖż1r{UT$C4/LKh96f?ʠDGNK6#9KaK1Q˘aצG=hm0%$#Vݮ"V2x9Bht&EnxC5jƳ!֦JL! qS[0q[wF3ɪ(7d3PqjXvRQ7r*:{aSCgޫ! R@&{:#>n;+ALĜUxzSA(mr$dFs#Ԋ'K\?0/@: 朇{R]P5"`$H0qRޫuyzR&L㩩dpqQGZr' cJ;q^6u5 a E8) ֥SJhXzTs2# ܖ FOj<l'Ҟsb2sҤsP`朌0:e Ԋ^8Vq.s~/1YqjD?(뚬*E}kBZ,)Ξ "evRAbe9 gpV ҥB6UaAO"g+t,[iNU#֤)C ԜwVGcu杆ޅaҞ:TE= ix5"pV6v ;hX =pjE 1]V:M 2N3ߥHzZv$w敄[ r1PFp9<"BX2zT91=Jbp$ԊYX3ԁsӌRkBa_R#z;Px8NH pHhZ= K:*6^=sN,zZEBۊvO=*c[h$c4`qVF=H@Z@8'3UCr{2ZVIW1RnsKqX=@bH1U;)~c :wqԊxp;P;AR&Bg/¹lr)c7#ri' ov89_ : q802*Xj[/{ j@=ѸjV-z#sUP}R ["xTI#)3SO]8.rUC9jPۊm0bʱ$❸gT7͓x|㞴;epH^[p_֝+u=? CzgPAnULޞqNXSzSYdOJrȥqRٓ9'ޫqJ;ص8FGnYIFir ϹUmTE`JD\q;w9<0ҏ5qZvcrڐzӲ8Njw@O)'bFA43b@ ޏ98*ӭ>F.t]Rqi;?FGnM>9(Ip=bܲ@*E|?&ExR qݲo8/>1&O=G-&%'ЇVY|6^)}@]Brc5#1OR/w$VpcGx Oj}$`bVO [Oq49/ӷl?4OOj]gM'+ymLH?Ƒ1[o?ƚ.ĺQS޵ D}> w$G9mj*^֝g@txEI,WQQx|O?JJ^ڝgRp?:U=pMr_{H8V*?ɖ&Gf)8<6ҦWU3|2?g 35JNsݎ19-$?,,zLqT{:,ޒ;āpn} p|)}~Rj<(O3=**iX=-On{ךP~z⥋w&iԶļU-H }A'Ur>3P_ 񋙛ЈW2V&sԁySW’|۱H/ mg\>\{*ٖgӔ+ɿmxd\Нoj>0'؟k 80Ҽt|`6\8Ǡq ˯b1UU;bsyKzW/- \bEb8#H}ցJr6ä嵕ﺝ I-|] ucڋޞv<#I=[ɦLH%>жdS:׆nܵrTmOmi֪a{Xwpkjĵ̕[4yBRgS9r|dO!p.j|iؙm9r#BgΜXg־zyjWN+ Қ>3Jq:/wECsGnO>zΒ|c*Z.M4|^ *--4+mg}*_Hحbi[?d*IAJvvh9X ufs־q/ǝ;5vꠓS`һ`-Tr)V|{(k &Ӆ<~qp|U 1;~+xeKD2r%ծcg4_0//[GQ,_戂#F# CRkcS?^)@հcMV'L\@u2?:}%x 3@M7o[@ XA)ZwN0~aaFjS&zeK )ASx7]O||M mиb}TQqIJH5YAN xw q| ˶5kGrj+#r3*m` = |z>"QQ)彍:?۝Nk!*rbs}ih^yQc|7|Asjܚ>Oͪ]14B}&SNTw+']׷sUgcxJ9AAPqWƉ?1u}WH>CMbTWgٍ2t:zMō n)Lm{u 94ZԮc-Bɭ>}&qIF3"Q.fRa B|SRe9bN׹:} 9Ԟ.o'8x68v<_{Fj=W~95_\?Nd|CiD_J)~8c܆_0}&G? g+ɥ@& zMLqukկsU~+݊|W Jhޯ%xKpI=*.HbnxÇ$v1jp9{OIjXj^*ZXQEϳ$#'&Qφj(n\,yFR8Yzmpx9b1Bb<Ђ[k6E=ʀN |nS[\1*myۻ=j~7'% ʈ|NGWs%XP}pH.۰?:WĽ.t-n} 7!XӨQvPE[¯RHƿ:"|h5i,.u hX~q^WFTѴ%S6%|M#›?Xn.xR+\IOaxí}GY/\faK:梳\jڕ#~zRܶoy^&_~|ii ^6glP_rƾOGΨK>?ZZǸZ|2M+oc*JFkgQZu5?Ya#Dt'ę+ !1{+ǭezofm%Ѹx kd3UI_1ыn(Ƥ~9Ay4"&=1eYH}t}~*=jKfi"fX62OnFW,QW]u[,e',N sW ܕnks4eqk>W6}.spzWEqdԞ+e2 OVlQ9iV %fRڕk1 GgUFIu-yH7uO mf.^ֲnVIf+k{tv'Rs&(cUhP 4<+YSrudwFXNME<ȬT-"nxw/br+Tu\111Tt;ܯ##+b(HUʀsX,;&`КΚ^&ei;Gpj[*}j瘖DdS+_A%%65 ȒFsX׷-:a'&{_my\N*ZDIMze捣V;'55p\$Lz޶ѫ-XI:9^ܗ=%qjJUO<TlsپS)9$*#U"69h-׎Sҫ!=3OqR4.'O^@13Ҟ\m["΋h20yR '֦^½˱FjP},1J:zu4MqIp>U\QJE"I 秥Ks׵SILR 4X2n*f#0iJ9h[^8bnZ$ڣL3e[ ǥJqTb%fQyR\֥R(x},n5. v>:ҞR)Sï\1P89i8$;㎴(ԏC孋ўȧsE&] |s`iُ➤T'@}HsE?`uBUcy?:qp7bn:j`0G:+tZ Mw4Q'$w c8QѻRО$v5Vܾ3n*E 8 pR{FzzГ!U.=]SYs.a?/4Y▌JqJ`bEޞ.IS뺫{Qpzt=:tc=ue@M=SO8\u>69ہ1ȧ_>,w 2kFkn8NF vԭwmDy*TIVkʎpGYKZ VG 2Rfo㞽T8Pr} jzgN?m#isFJ~q\-l>?tp7RSkD7ӇQQk_yF7 E&}~[X>M߆zX޸:'l5c )<%_(A}?ex|tvqCKЀVL)4WIxKɁ֦V@⼉(N =jH~(e9iTWYaJT.F0Fy*Rqid;cn [G=^ӒLɊuRŝ$&9yb>–|ož~}⾚v ׽#|ZV>?Q{Xk#S Fq^1[8׷,blU'qfBَAw݊gK{3\A xA5r?ycDvOџ_ٵLA>I: _<ū- ??_ٕ8uGApi(5 xG4_Qb*F=ۥ=dk¬@{&0Y{vQH}3i(#5F֔&ke UO*5o$q{枳Y66JÊ|Errd :>?OUSNe W|A 7$ `*9> d^ʨ 12,@oBjA8I_0 \At 'AYL]#[F1NYUN )־L7v?x[+Ơz_S2r5,3^\_!o 4Yn9~(ɗqDPqNHxXf,o=)Śk=)9厞>K35sQNKA]G b89|A3s?йC{#OUt)b-3<_7 f$M6sy$oH5+Z<­H!5 b$=Pu Lτdk1;if|vi0cRZX:֙fm6t+6Ԯ e5( h]dıCu7t)_:ZװMxli/,8dUmcW^7?t~(щ ^< A8O[ʡm1PF0=I}||..4$9Sq}K^4_׳$FY?&TvLeP{ [9@nf>vghL;dLyp;SWNA>};i'cżR5#!TijJT#tn$_lTgow`N{pkVi F}Y8vJH.}H>&xO D~'hKw#寘v68Ni&Iym+hXL] S ޝ- a_2#tU=j;sUI쉖2⦎[&ǵE/M,?ʒbn 9ǥ.DbG WOic4>+eN+"x'>1_b+|G f-|R'śpN6w_?o721! <9^ȟϛYh{mj{>.+e=2d0$5 Azu?gfM6QzŦ`u&N :7o9jB .q &W=o$eaGoȳE g&@xRѰd1'ߊilg@qg gM6oĝ6Mxxtp>օHKuY3T~lSW' u)?54m t4 9-jvƞ!w &F6$D;\,UCI֏`hԌt'!dv>gx]5+'ڒTL`y ֊mc?hOXJj NĺY|:fPrc!{mƥ{柰Di:oIuB6=XĚz5HpxE*x/ u9:/.v95K<λpTl'ki5{qs9oJm^47XzlcBtXԖj~ȇU%:85 qdd|֢SY?P.ᷯWr :Q ^_"j7LcZD9e5i:J㊳q 9U苲W`<҃sIos{&|v95QWwE6v $ջD6*NInLTWn lZ,~Do/]Jk#^Y/r,6,Lrɪ6ԃRGu̯U.ƞ"ah1OJdwrg$R2*neP'&p#oGoJf^.3؎Kr $x߷"zo ${8j=%[$@rjKs1@}rjLq0"!i*+bf3iv"WVMfc)SZτIcT;;2י&VrS**q+7CSfhՕ`76 Eӯ:I%Ϡ^2Nj;R\m9IuTmAΧl5QCNy$8#J߿qEC,WYö-zj/-7 H5`ZZXԞ4gg##݊@ѿ:f.Dm Z&rբ#K2̽3~ni!)$$' jüy>z0 0QȺRu@o)} 2n@q4ox4qJ6JǡT?;#$ZP 㧭Ev= )#v#5uB#C3 {Qpi9ZӭM96F\#UR:m]V$PԲH%lF, \t* s c!gy0@٧xv sIAPzCZBхT [Sf>]#\+`krY yj۹?QБU;iRZ[j>F^d+XII?"l[:e[]ۓG&soq} G.'䊺e3+`՝${?5#fVuwxBݝ׽'IdR$ARǑ j$qK8XKuqyՔcn {Ukʎ@qT. !{s=Q?)@*1GcME%mٟj%j/jFIkO[60nKKJ1cԮ{PFEeR Cu#V*W1,*:9bjՑ)A*.Ēx9\CaW0%=W!Xe v-_ZHeHUGI!uR(?Zv-y~^' heJC*&ZV[y-ײ⬭Ɋ' W4e:S]A8>c6##uE#9N[j]/6*[w[RH*TAe\Bvnʱ/CQ]>Sȩ!kSKYˇqM|pH錊 ZKpB NQ&ѐoZim<kG$Bی曎O{.D6FrK+aJ1K,y9SXKbD=OYlTkv)G2wwtʲ}ȫ:'X5oLnq‹sKǩ*3DbKcs❪+yry|` ZEտ,KܫciGt.?g8ft &7j_.79*ͶU,ÀqQa!*^D;%FiĪBjuHդ 2$ o\Ʃc;Q06nw2X.OWoVTʑʎhmV3Vn-Ym\sm$1sZ(_FO:%jꛤs(+YpH&!PwڭZK"R28o-Ԁ[.IQZj7Z|+$ʓɍ*ؒbU5+Z3RH5t:n Uݷ*EYo$v OL6qB1z ȻgAh!l%IE?#>]\<9aV-VM_bGtq5 ,8f<֬mq&W4tF>j%{Yqߌ}jv@z!sfkkRZ]|p8&m.c(>nf[i~W[gB O Rʼ@~ec=kDOh'exS#]m"TS-x[GȦjEM8Ќv9h偐>^y .0ϑQȗl.3qSn7NYd?bjVo֒:@es=fLyy(WP{iqfkYSoCO+k^Zyhk d%846b+ Ӷ BHp*L  f=McoA kaZń"MCJZnR>TūCi ,2"gmD|IVK:k #9lQKrY[W8cgX\)72OF dbG|%Ghc[*694R=J4k}_-H,9Ȧ]\#i:f7SRZ p=*8FYw!`:Ryr2Y/[KhL^[꿥p<g;oS`"oƪ0Fs;5 q._J1tX1+T}kY)u<[6:䚨3*1~؊s T7ҽUsbZ,)N-tDWHwfey}kb\c1U.ʹp۬dܳEa9P}d)ٴbj*XЊ$GP!W`f95{jZ̨F484.hiGIrRKe`pI z ˑ1qݫ, ȉ2%?wNFPSUy.RwwqLn<[ գ) `[ns54f;O*sjfwv6"1I,vݞt$zUrp ziM䝝͔_mSrhzG LH]ަQMWiOZjV:q+u\֗mĿ./;wkICJBe{~Z5Κgvp9WEqԫePք]D5[_GtwFF~U !QҼ{^/Xo`LUj;=j{M" s;#W~:H "%o|W)=9p#kb4j_6H~hj,R6{zWɥ[Rϥ\ɞ~ )_%kf: _J*T/#`ҏODF؛#b$-~xOׇO>a}f3X ~ԩ(9׌1M&glcU]My |O9oO}ЩW~%][Q.#&߷+*Os^L o;U}B*Ӷ%U4}8XZD=yLˎJ_Q؟k.rpMOH\z%:a"r)< GnOU5,#19p*5I-=?z}~TO5?,W/=i`YA'?jGovz~;ׇ&cFcYP+7ہC"ˍf=WZ3s0D~y(%['#wj򐪾} _d /u7+|LOVMto?oNƾ cL1Eq#I:0\(=1>OPuKuS+\z3 Fa\JJ04d #wuzʆ%뇋3RI;6VwQ>1 c5bXpGn9<+]tH-]Npu梷B[wSAj>I wt3}ΌxZ pOj`럡z(g`-1ޤB+diƯޤ jz֐3p4E}OW]>~=}a_EÏsu5.#2r}Փ L.:Hnmp=֤|,+ J#%>/:V4SXvɺy+xxnoy N*Y5L!ϡ BVԸ7E$ZfGfbjŰ7)'ٹT2bߝ802u<'$6!YHjhd4Ö8)gCG樒G $~Uc>[̮L'du5eq#6#k䀾aӞ1Q(Te39 펕$M.Y4j;'$ۭ.Ek ZY&L'c{T>5XW b@tf#`)W9@09;ǷM,C3*c|RiCq5ee7 eAM?ga|ۢxل\7=:̓a@f\UfBwH5˓$ë M- -~}`(mN۪=RDӑԌiƚhUm;J0s5n͌zK$Xh"#1#4r&hY&2Ev=֒p `}"T>LTlJI$Plf%s,v4E6Hb<䊯 IMjsBJ_YpK.=ifVG3T-ڒYWj6늗Nۊ59ff \?!{R+=Է! O*"Hd w]j13zEWg1|\f}, pBroCKp `zqL૸''~˅M7/O/8G]H ܹ0cb(eg@T,54ɐh&"#'ܻk@^}N T13ly=۱9=/5M+Ij  \EUȠ(sY6ȻT烜=YܤkPMn$eFn5.t#R54l WFD#VS*Xc}+!,\6͏L֍FPzRimL-9gz*d(XqȽO7`t;,d;f^;3Vl.#1l2:b&~V&\1ӂi IM)US*0?JnZ7"v㩤H'! *ȊlcɌDC VfkqƢz! v8-wA+1rޛ,Q!A)v o,W*(w)+[jeJ[vXwUΈU'9= p.$`rVB%5q"+̽vm7FW' IG^ 'aHfU#= _{t3E$`sWn4X1>UbbFЧqUû UeVtCД-S#TY׷Qc5,1ġߵU$oa^?QZo-ǧlԾaf%RA5mhq]C6Xf1E* rO+^Yų]2HlsQ^Ko#泅٥I虍!_)5caveHֽI q}jX.c]7"+'s 03;n>9ֺS3׫/ H![\0- glUf *QQAf\{}&_%vc;6ո4IE7ՐMg%NT&VRvcͶ{]|ը6=Ҟ kWdeIlĄUӒEߚ֊AnÝ : :m8,.HWgqRi $aD#L$Rd4Ds QZ7"cNw $'9GIm4!B[opk/++S[Kff#Mi[:dS޲.K`(=IZ [b]41MFjXRK}f|.0$\Cp%al\6܈U"PFpE&DZSy=qI顾dq1UUҦLCQn=zqKf)[a<6ڸS(%n*+H8dIniAx݀;RNI-L•ɕO^Ey~]+4OesLICV)):N#F2ȭޭ_ZX$˴73\=k{U8sWY$)ғ\3ܽקpzʫv :(o.EU! >1UNpy7m;*ϕej#_3$2=3zy9Ij̥-rY*ƍ|fOXҭbVt! i\XpVi6}*jVLs2ޒ-t gy4$1T糒9ZHwdVU(Mې|UV+Y7;utpk.(wpiQ`W rij!$`9dɫ7kuc+:c-: '\\\~̺ٝ} (v|ߝk[x6Y,-̮>rIh]7k90AcTc nBw,>UG-M҂J掝Ea=CtJ4X C+vHW"éq\tk,Ö3HAf;}J01Us"D=ylBmgiU˅pj{{kH`2y , kbt~Sɥ[ɍo,O㡳̎ŦJHf 3NH/ִo/.0A!?++6;dRK79:I 9@ZiYXLgu!G7wqrFeźI8$r05Y@(8i㈇`ߢ_ v:vV| ҜJ¹-rf%U_i[Ci2J\҄Zjymi6I2H2}zU#PcDT$HL$ ##>IZ)E׹~|x7dTA8o(!巑?%մ_;۾:y':>WmH;*{U b,.RLGְ|C)ZΘ"[%Ԑo% ¬Szzl%[򳃻uS}PE)Gr*^cPKEP: ّO)bNB]Kt7/0rpqZ7%cUmwQʼWL3"4m`ÀNJR7,I.nE4,z՘`v9\ XS-+>dq^U%Αn!;wO0 ZIkaL_rcHo7g8EO&?2z$@̣-:R}-xAYܙ&ju]J2èB ƥݭAI=|4 7?ZЀZ]DV\$s\%ݎ4w0(ṡwOѢ)WYi$oqSvEjTIqTV4"qaXK"]8T&ѭ'Ҍյ &iwڙPTq׼1Ӄ\^g6յj7?5F ;\,%f쌠cv:8K[n֙>a%G9+ڣv{q-̬JVN`-nW{VΕ.>rԷzLꧪ/aomr| )U7i՝{mc`NUm Yi\a!Zby5,MAn@Нc|6l{-r$n>UH !k-+Csd9<'Erv2 Tlnҵ[e4]Cں1uo?FVU͎mm[ѻ`nG!ݚdQqV!]Mleb# ighȿwG9iG3˲|Gm+1Iju1+{/\=l2W5]o` #Ů[o{t<m;ID^":k\TB*q]i;]OU\A"yTBHq^;BYDE\~ZKU! L k:tgj0̽[ e <цk5dMP*;l/"h_G G薑Eoq]8_19)ٷn-[-zQl5+`RaK=!7Y.Kl%[=&.nv}T:Υ4R4o r-=涗}kv]A?2q.nxcH9iھ\䔆/uXE2s ϖ }N@Z{- @Zek~V$1|#>]%aT')S$оTOu1 y%Q@_+*7񩸐\ڱAq"OٮsGXҦHUoA5=ynNj߆F$ l=%#ycW)E7vTErJHQUװf;8Sqiba]Oo*@/E'RVHCgXB3a] F>sN&o-XrA-|eo4sIaɦLѼ?wKq&o} Ԃ_6T<5? xcOU<ִAZUҌya9O/7Zv[Rw8z WP/ifQ~jIwZ~x ]Iǥc^b쎺8hE^_6 q#Bn%85Fi_2} \\XC( ]mMf))C tj>!(6ް&\9$u5nQ$#;ⳓ%O]+RҖp Wޫqc v g T^{fY 4iKUQU,r0 PHۿc 9毗kcJIB,a szVZ۹zq|RfMDR컪K-̨CUTk7MAu#FeqHX1:U6+whlnbX'fa-Z&'xrDX@ӄyr=Mszƚ^x؝sZ:JLHUjV,P`ޙvdim(O |VRZgrҲm:m1aS]ga*[XWFGw(nTEK#NOݻʹIcv+Ҝ&;^;9fv;k3THXf?{"q⢶.ds#62 ^} Wg61 UiaʛS>aSJ[ ȻV8zeP,P"zTsX>x]7őVbtjc!p]Z~B$f-7Pm9$|EJN nt5~紦gʃ\ [[1ۋ ,}MXkBX AkZu9U2t{.s*m㊑Kb@>pqS6ɕ䁆D{ FX  +bxUV%tKv" q֋2A NnO>&bYI~cV.!"5q9"ɸ֒ݟpCޛ+(XֈcDPƪFŽ q`['8Io1ŗΛ:`-3rBEX>@Bj`J|C/hc2'5P9,zTǟ-ޤǍϭ Rb0sBqQ˼7& n)r/!yTF(OjI\NV#ьfpCFdidޯt5\vG;TcU2J4-̜.T` մp)ؤI9#8JVݐžI6MEkpXF/Z$ʐ,**+Pr4'U_K2ya!2`2d6#=*q* HI2Haw)T"R4ހ}7`̹ jy `a9h*p&qT#\UPJӰIJ"MEt5fy[; :E 9R}KG0!5s5`TsRs9-l7Y%\qÖ;KU I/vVガ<ȨgE1s9#g'qVP[ud:ct95.2<ҿrrD|ο5b Hi%Ao/ 'Z+aۡ9E[Rmt5(df5G`x$]}MY5_RJe1.H>,~Z:Ɋ.$pQZZDyQN@_OS[@K"ī('cV-6ȡ0#biL~NfyDKGBEȭ+[̮1lY73*7gnvZ3vaD`ǽVӧ͵AU=>홛'O^GZ )u\:Uk"Tr/8~ebzsXN Vkra#^MhԗGKj8<ԈE|!\p]su 9SnTPr:Hz $e9RN7zRZR6qX2k62p1S%ƞ=60 5NT[[m+@HD`ST6mbxRWhf $iA8[}j#%{4ofrͣ\#T^N(桟QdnlW.[Z)=H|:DaD,v`氿TA9Im<o`+MYM+#f)vtYy c޹˒;?tHfvB&D%=t0xn8Ac"/.E[b;#򬹴ܒ1U$鲻pG9~ϭ;Uy5p2+/x1!+5G<W!=J9wwȬ3 npMU2vQ銱qrӧ"g#RԚw2ZW3ڭ@?Βɕ >`]ȧiO5]편SJoc1{m ?e=$F>a*:JB2)n'if,G)Ïx5L,1GyW@>8PKGV3TEGpM63H[RiHc^zDvʫ |g5.d-Z{}^+@[*ІEdkg~GigP(\[CP(˖2ip+:Y[y ڋ6@aeR~S!RvVcwжz3,m pjͦ~πSa=PDZ鑎Ӯ p`%cnV6#f]nv"v5Iu1 *9vA w\WBQF+>Y]v) f,zdvr(ֆU-3˴\a@3bHlT wܻcdQPT\ƇK> CqHqv*hm @뵫IY- 4)hSzn2NkN;p;i-bj,/a4rU#<_XthH<Tu֪-X]}ѓWW#<$ q Izٯ`|Fi=5a=-s [F_-Yy1`I,՗Y3p8-Pn)=dvylr峚ˊ' '<}Η207Tb|EERQ\T[T3l cuxf[Jukp!$}Û3c-f5gQe3>P $(\lΊ0J W@E,̼>b 32:"H7DAӟYqxY;XS-yl+YBÕHrB wom!VN3ƠnU<5s4ݴYYqV?X}QJчPo/Cb[FgE Mt{KRzS.AbzTۿ 8\t4&e56[Kdr6C m*rc9ig$( 8`t+Ic Y*vz8XpF S,śoʓ7lhC2*\ޯ]Oo/-h*7l_KPŹ=tb /sH4 <ہ+7a?N<ђٖ- &WBdbf!(Iajm7M[xy.P3WzNV0r͵j%BvFWTѦrN \~,3՛x,.Iڍ''W\ +7N8?K{BĆ9xBYrs@Ӯ/UVD+kZA(+ڣ,u Y+ŎH8v䊣quNn&L^@Dm b䢒Z y_S1=kr;X åKg^z⡃[gsc7Skyx#[m5,입q0 9B%;6#vk]Xn⹦ffԢ欕g]n'cM皵e?Ҡ]4FS+=V-C"`8R+]֣I~%8<0r''" ^,OP [yHDS@9-heN>v?ٛVƚ]Eeڤc湵!_Hj~ >mGi@$m?7&-$ ׊kj`oƫ_\3mz~})%d͹+ٛRAKs `T^S[VXQN:jK>pCc*H5c{$vnUL4A4Rr8M7q5j.75oS98|ڌI {Ua7Vo*pj"Xգq^3?+-ɗȦ$idU$kW èHnfX_ň- z}%LCp~7SeN1MK&═4\Bvͦ] |Eg2☕nW̧*jL)RWun2sTtk{_U1[:)2[:axl;n.Q dU0'()%/~B5HJ(7P*J^!IQ",x Ѷӻ%qƋ6TZeuYrk2N xQcsSӠUmmbVk‹CᶏNnGbc %ͯ{@rSv`3SvM.${*ei+c k.<+K<@7P09j:Ev飐7 0W?ysXy9*/ X]?.t# /2N8[0xy^vMfYmu ~\2] .g {s^$olnדbF@5T䝢)FK>'!_#'֚;%y.\֢HD*tRCm6'a:tIimnfݬ1wNC3ZHB8;X;"g&{3hO)18B1nu\/:d$gQ{2㸔Cq yN E]^5ƫ0H4np^2QR 'Ҋev/r5;;#x{Y@x;^oqTs+]ݓd\40ӫC\& -ʌ3kҴ 3 4Pxuiw1Y # lX54ŽwjNQt<{Y-YQkQQ-5VB3kI'HlcS8~=cRnP?)M4)\^a৐*ZQ!UlbH  UY9T {vU8Y6Bl"htגVV~?|] uZ &k{:dŒ|~n⩨&s% OI^ǕQ_&,8 A/G\̈W9 a*7@5V@_꫙u/RO _g[Ml#Ҷ>$h!\돮·UȻT˻<;qzHRSQwLn kq $iZ mcg W8w)s=Vj6w6UӠH8_S^ <&9b:lPAxgBG2D3X6slpZsy$.o,+wxVZLm&Ľf?ZڿѢ)VTX DѢ:,w4J,c'5j7o PrA~>\ r(WKgbXW<ɪϤ\8"CXF[jZ}&g̪O8*1Z6PlHqTմAL{hݮbb~ *I;M&~ֺ\jZ\K>pzDa*K}>-!Zm2;>o5 it9P>PYyd8Xȃ;Y#eway I>?yEZ2&c8焼d+ϙ3 ˈO9]g:>[f0~5M]0hZ?onWq~9]/șF=*;2"Awr 5kuZ/݀?|92?sZ>C%YNJVtTJJ<z<2E#f=9\ڬ-$76ޫhS#Goz%,tA\|ӫQwpq3V{<\NXW7$4ȭ*7.rkZԙ*c7\t>G3ki@2 qިog8jet[;[[tDVFG+y*)RD2UOw+h9;nBe;3ȥ%mM[s$y I=8L\zSq@G6Sjow 2vj)l$+*ȫ.K b Y5ev,--d$i|liI}π >İZȰc ޔVDoNwױ$   kd#1#_lG Yyr8-O rƱCISZ6;JOqI]g>s2jsL c;ʝّoa Z"уX_xVDEz|U kEdg]HTd2}5 {VRj]M_4Zc,!9Wm$li@MeFbP1~1T2;Ėtw @(YzVGȳNy?XZZp:Ut2B6 D9瓃NT8;^ G4|;Sȣs4as= ]`Q ;d1o<|1MT @xVyHc1;*,++]X0bI8OÁ=2_lf[ڇauXO$$Gҹԯ݋r[sYuy,W#7 i8OZɒyz = MءR1V;Տms*G4S3&zsM2dr!l<)xekť~<Q\ɸ0su<.;T"n){\c: ImZI!IXLnpE6r>bF{R!9Sd=-@Ҝ7nAI Q,u&}ZX8޴_60r 5~8[$#{>r嵮T;Aqk.YU;qjѕ4lƕIqVd@$&=3&]x0VsnmԿY)>AC/bֆ6羉f_(zTM %0}jG_0NA)&6jVI1sp#.7! ʙ'$tɫ=ر\ĭ>5ͣV,"Y\P^i."C}(N~]X{TEa1TȬ\(eS=aqR$+_Ut4S',HՀY>@xþ*wA wgu]}Lkr_J؋[kîj)!=AYH!w]%f彙C٢ KicPҧ3CrmSNP}*]=w:X6@,=jйr"=si&-OQ'-+#w`>>FmZ)F'Nֵ7-L0Vm[x;#5rSʷHwdk7eh 4YwN3QY6Rzm椠[ܔ֎rd~Q)m P,7z%TYۈm.REjM@) >kQ)'- LUbXHb-ch Ī}%rO|eb sޜ?1s8qIY";꼯y*2ITW 38㱫v\0ϰZlN6QJ+ȾUL f3S9Uwjͤ-dIQW|*KxRZ(>z/՗i@zrl:qoVtQcFj!Ғ3LhpTiɝBZY({e3(q3OkswS#av۞H뚮<9eg =2i5m٤m4o&9/3#=GJksNkr=!-+sUlԃ 'M`rܴwv'$HPUJ8ɔ30OZfH20>=o]ʊO֊c)PҨ&ұ4فHEuCn, +'DJi#<79a`R{rais=8[6)EY +ջ]NyʸP v-MçҫуI靼UV7S~״ k_"8 6dv0nhL;W V=ޏe=Z)T:Wqɾ]Nkhܮh8ML`G5vKg%c׊gBQɍm5 XNՕ#1z[@EA=J_:Q+)Gy+nc=Ji h+OOYd؊ǻqhfu~Mʩ(NJ[6 YxLmIep]dwZ{G؞fOUF.s+toP% ˑV䱰Jƙj5b6+F!A%D?m j@#*k)1ޟgrOHsv]ɈFHhQܭi2)#*Sp6S--3* IM.[Փ-MK W1f'mTDfbyҧF[S$A>lusJ*Vղ48e@iM(੭%юGCO˜|ڷQwm{ϚrPhzQc`8(K(\p2\ 4jgv4沲f-§wpj+| 5fImwr9 ES]9.ub6`QR6[5vebIF* Ѝ[-tk$nlb; &p(JRZ QwlùY(듌OY#ȫU&UX(<޵ 7y幫$屟>ݟSO.'f]8Sn$BPu")$Eɓғ8؃NIUx9o^L q%b8.l-:g$Tn*/B{7):@ oaڪE+@sIn"RAt=*X^;DP񦬲\ [  qIC1Ų.㜇9iF.mfً rr?-<[Vi|y?hvV,PI!gtG߱Odac dipqo*ݤonc0Jp2X)$E40U.B`jQl\UwY2`۱%c'{kl&fiK! frXҥec8Ե63eZ{ O^bĖm47dT@3P V?s`2r7/uLI;r_ݹ|ю2 'K*!aЃL@aOP_FUcM^h|c7SS4zյF#8-(G 늵FZU"ԭnM9ߑVZQ 7 VE\@sEٴ829Z*I Qkn`Z;Gs4@*mZF@н×p;Ҍ~Ԛ[…2O^!5=W*T5Zy9@8&_ՌN1r!&sN+ :V*8+լ082s[e.mv㊗T&לdIY_Z\ *}nU[[@ ^qe5kQ-NSZ4uE>[nj{cY!'jeȋK$nhdO]m-2Mzݳ 1P27q,F[Q ç:MHIsRb.T;(fn̛u=fk[VǩJbXl آ(m, S $iItCP <.TqƺIwTgδ%L[Lt$7&WdL$tFȴg;(`[O-!޹o-Fy6]K'3^΋5Dywv]ծ밅Q}+UQrXj qIu/)Fė{R¼rκR퍺۵yʣ5~{QyNXSIhcI*RVRl5.aks3D7ZKh䙈cp뾉|:H@?dxT[4V]q43,ncu|.>➞u>=2bXZR`F8R\K-fhgdOGsiVG-u 8#*jKh2wvѱ?ҽ pت#`]<$iÎNMCeK 9i>*k) [m'vaVio 'cZr& uWNWவnYaAҵ.X!qNkKKk )584ΰeQȂӕIҶj2I\O7-[V^&k1mw\ I!8ִe"6Ov N4#H֞R qvYIgqr(p37}EGZp܁sau.H]ƙ/6ecedQ>mRZżQsjbLȳ[taV*tk6YZjzx|;gLIrȤ/ݧhO^\.k"H[4)x# 1ڻ5{h|cg'>y9Qm~%Wu-z&fo/g#F*މc܆[ps'1wU563XlmlQv3f%ʓ"c$a?]v|`dp #> jݼ࢕#xuՂ<)?L5M(K(\c{/B+ kC, m00$(K*I{#jTn;A&pZ-5Ȭ7anx'Kmbo8$DlnD Z8M&3$JjZ}ݓ$ѳIKgA{3\_e@˒|?z__ږҐ1Xtl_\!gij<}iIݷ<^?*CWo YVm|\0'+$nt5HnX95{nðnn| cz|Y 4J yLq#$0I Ok*=>Y-Y0\:4`x?JiVh;$tR3\#!8POZOzvɴ̤z^is6Q ǝ*=Rc9h.D? \QN]q*kF[dRZ夏fJ#fJLyJEަT6+qꖱAi껓VgNKJ1˷9vYD~VQG?H6tuJ] f\^5 {˂âF}{YOrM,znst)vOm/wB]aA/@=gT]˹Ts[{k"7[sp@庐eS#5jŦ"q3Y5 .hX+bViO0G5å]PY8k*m;Q^'GXjAᵱ_CvJv֯s BOρdИ5,e4X qS HJ} I\&qql˖e\Qn~`t$" 9ݙm8IeH8?wW V+/^2Vl|% vlS+ך6Et#Sr{]+lRSTkEg"N {nb ΍\ﷆ6Px;rÚ|P\S]/n?-y+ jMELd LJ5Ar ŻA\NJ ƣ^I+] "*6QC;&[).`~R3Stifup)[0::˳#R)<3X^)~jn|5pVmH?,: -39!S_4ϖ]d]/Q&PHr+?09-@~x] Ŵ #ӏk`j:\v[÷g >2RvjjDv NrzEe-]'QF1on/oojn^KȩNOPEi?zlmYtzt* мEi+/fleqX?ٲK3`VnDڶnVݲO_0jmNeHc'*pJMFg1C' U=lTΖ;e/Ď cc5hpk -GV׬aWL#n٫hoΙ`kНȬKX.'.|C-PZ[Ϙ=}봾kePegQG/*J.O_ʱr!I-}WŎaq,pj-BajϡVɻ!2@ $ )_S:69K<n$Z}K5tS֞ڋ fIoJ|c,^m5?S*Dylf^?h9lctE \l'U+ss}its>lc< ͻP~&m.cj曬iVwr=}렖kq-ʹv߻~~uwE q`xAnf;x~Aj4#)9kk$3i㕧IqHLvx ?Zd9ly4V-yZ)ŴaWL9(v6~7 :ul|t/h&+ u废;IXwKMh/U0*(Ւon8,hҹhH9As -@,*8"ѫ9c|vI7۞?030MhOorjPHnKsfF>insͼr#0Rqlj,qǜ<v=+itF QmIJ$iڄۛ|b59-W<-(xa=ހRoMEI"O:+M,ѾlvGeYBΉ|QЂ{ 'e{ʱξf&Idf9P:zzUR8dQZ@F l`3H)ko]o`ә%"fYٓhuZ"(Jr\W=ea!>; 5mI]LIh^|RES=k5 EUKfVPn%GZ<[lm㷾M="񭥯ë,)H1r8~x/fëg"O2?.0 V*8ʭ8&^hd$F7uj@ gT͇R{bٮy9_Fld<.br1hİQULjL$U&)_[|2rvUcR@w${ք"]{"Op;fmPP n$gipJ2g>=PzϷ"'ӊ2X,jl1A=趄ՔHڝr gp ֚nv\p"TM"e$̃,G8ˁVŻE\J{rJ,8maJ1'q4Yv0F1Vc~{:j4%&]yir )n}6;de'5`诹\ޒ jF'1wj7ʰV -1Ֆxb_NTPN1%gE(;2r"R ##TRFnrqӞzD\qRIjb'$u"Ϝء& 2V9֛p造|ï5#Y«[UxkBc4vw MIT`(`,>E^RzY'BAf0jeE AجLq#JMhUL1eϨKkcr$: 䲌aTd^)d`N15r).$oE1%NݣڨÎO8V@};p[Avz$! c77 p Fǀ3g;ITq)X㰪pPw R EK1nN֖-3'|?wV#ڣ842tz]ڀYmǮEJgD+\f@SJ͜ c[3ZʡdA˨͵U)hR!Mj(8VR9P:V{iOLU-.G;dcZk%GixΫU/|B2ELuVY"08O#]A:Wc6rV" PyC}ԭf[p=jk`@'xBuTǴW8~*E?M(gT94d̄Ft)5 mVG4J8b~oϽY#+܅/ҳܾ4o+J>jO/A}I[l1:LC[m &7;<`9dҎݜH _sSO$"qb$"N%*ƛ= 5mV#i'R.FCfL>x8k^=Vju9?L;Q=O iJl%g kvqW{ 'R mK7?Ր&Vg#dTkkV$t#5{Z 213f51,A8&"ɼd>+^+Xf+F@=HVeZщ\.gsYN-$) #p5sOط ճ m0~-j;\2v6H؁Yf/'6A-ˎ ;0mH#[Q)i1[3I"(95zYl-* ъ;uwV9T֥w1 iJ'6ifRǂ><>T}&Ԍ-7L,%X uzEV(1#sodj1.%qil^<.|+6\jq uI1݀5<*oc8AWY1YwvE&2c/l+SІ5IKXZZZ D0wbJ+ERk?KId@+]E1$cW.DnrÚ [Ș`c f+؀4-,NUlU6\<p8ˏWVKF*U"@4pq `*Փ[.Dr1=Tܺs9 &,yӅ8&#(bmaͲAG@kJ@p 5:S 9m]H]1 x0z Ս]|vζ7RZ?L~kiZ] <#sJ4MZҎ9ɳGl)O-tڹp^t92MO޵Xwt-Cܥ9J# mhXK<-"`O$Ĝa:U'm C4V}ZR K7{m24Ź<֌SKlgk&vCr{62}K0,pfF->  ѹ!Bm k:c҇x.ulxd rH]1;W?j}6[h,1E凔nzEsQVZqGu h=rx&I yT%麟s8VK'ҁӡ8E}U`nc겆O\Zp)D2 qZ^i4/Is";}JdG3WvfRGji_ooP:+3Vc-qpJWex{,'6BxwUQBH8 |*dVh=JYaǷZm K8W丶24mr#QZ̶m=MF2UI{_1#օ,]698kK{D*!(d ]GOӥf%-H5j)3畴Fq g?+UF&[G2q|"bph˨ 7BQ[Zՙ-HM,@*[Z'9.[ֵ)ldq68').3xcZHB.UVG*ou9dTZD>cSe)ޠG2 ӼȮG`VtSL\DOAcYUf8錑T\ qM&t;mPm-7F5nZqV w `|`i-ܜѯx{ deFVX(jbSE9"X_W·-]J]JluLKB1`1ǭEe< ڴHC]ғop*kx-e I#uHdHSj\^ ͚k3Z%ݼ*"VBK ϶X( 5vI0b ˍBnF?iNW٥v%ll0 TvywXK*L.|ѯ;vr*涡\ǦjׇO͢#bt6̸lgjսn<ݪ9íf⭩d4r߁ DGeaҫG!i*8_ZY0K s;`JAz|Hk.{6(Y]n%JrSq(M]NrTI9mھqaKWThVdG Vtx?isSd\أ Gd D9̘9z_fG$N9^#hg+!SE+͎er$iA$mSKhe#sUte/Ai.&leX׼9UoFNc`&K, ⦒NDStg[դvEKp|q\“u5e+.[SfX65It}:|۴D'5e&|9]1ֶH0UpjyURE&I"Q茱h62IZ,dlEsrxOJo;9T9;A>J<,q932jJ7 MHDrt2sV|5b*՝!bz) F7(媙?hĩ {Oˎ3QzoeoQOOzc\_MX0t4dm> h Ks~#@qYk{wi+e⍹! ."KyZh,Y_y9I CV+u DxQp+Ե[6-lGAynouŽLQ+7SQZ>d=]W?[cyn-~ ,cYޢ[ 'LYnSNFkrm|<|3e&[ VMۼN24{p> ḩ[xt@A }*DjWg7o&ZZZG,m^1qtyU5Y,fQg*kҔM0B6\[1:ך$sbxYX!I}0>meYIC%c5Qɝ$,yÆ?W$eH@rvz|PiR g5_Z]KF|)}NqnZсjq34l `mLǏNXf O An⮴7U'唺}L @?:554v\譌GB5k5娓jCYCsJ;s6bjAgF * =!$XoDEO λ xiƋ \~b|Qf1a}셪R'_!NIG/Vkma1&5y>cfv=88Xdk pkRl-^7N`V҂z=QCt[7lU_ěƥxmmʺŸp\n3 /УTgVu5=J4 YkI'vlKGA.2Hnض[)jYxz" h.}+$ZT[]~n]gYsҿ2$ԭo7R+,n:GZ6ƪLzǸKpCڇKc U ʠfrQWZ6!RLR5GFI\U5gY垧)1?PԴ}*Hv<4i e&0D4^3Slr6zeH?wn`ԧsZPIn1PGϱ΍5‘H,r_G Hry--nPc(Fitt1#L6HeunkIO@Ws3KQISΛHP%ާ*ڹFH=Hkb ZV@1 {udr3V2n’z(XY$j1k֟Xw+W>&m2v g$ԨIǩons(>?B3K .t0GSUI3]&:F*;f}2NJ;j>~ՄnR ^DHڱ|o,,0 **k1-tî_۳ F7$9Qu/ Hv˯_ά j$2|ǥOM-7 ҳg%dB+n_E: 8 6¥IT`fy]}|#r /C َL'4{עk^ľ[ݳp?PWD帶f<;kI>hU)+sGHXFiR ^E`_MeyY7|ŗ%}!ҼPjĺ噍Cv1?7-ܦ,kV9c3J-"c{RԬ5^)ծ##(!Ȋx@#h^C\4,owq[CMĐX#?!vO4|S^ܤK$ijF|L6=Q{>xO,`f]?LKuk FWƵM\,/1%e#+Kn<²~#ؤzd^$,1]ܷe>Ў䃐 A=LSgD%nG¾15;+#n?Ţ5iR+vf |gra}Fh߳>{*TcҴD\m]$9 --ݨ^Ukc6[kҤ>9j9 f|'uw$ͺҮyS8RGmo+g3me/X>*ӮOK=ĎV`h-~X)OV}еCe95/-Akl\گkZ^m^BpAϭZgzēi#,pw5/'d rW9k)B5('k%s:ǽ uX>2~ѿ5xRH$׀j:ơ9ig,Ie6$pT(XKw魏_mgǚ7VO}^2TڦK}w+aA+a^]8k.VGaۺjx Z$V#;q/\=f l^ Q3TP̜Js]UǯjӡD:wHӃ0pUU RGeg-m:(Rr#4]ީ\cGZŅqu:wS85K$p z5K9Iebp:䵑C;Z]$eܮKI $!v0_enȄ(Z!X`c4MslRW,1p&B jbV=95>Cuc4kH/Ie`<`I=)&sAl*z 4$pTM>0pO^ZOh.P+:KZ0T|t [c$gfXSՖŶ `9\ȵ-49{| .sashac(ˤ$}0q[F[VG/7#Bz>9PO- c5h35q *[dkƗ}D=P3X {]&@ÏEm0sd7^QtВc(@ha54s5YQVXSwc4K"'>[#562@^HcD3BɱULcڴi$Z6[TNf]6[d}jӦ@\6;)Wpl/%35M$ gAhIu\wjXDy-޺.HX1w Pt3a"5fYW+cֵDLԐ*Zە11s@JYcj;(Őv\ )|H5Y[cYziMjeD-2vO -ު̈́dUY/3SQS^-em˜c85n+hF W^ D5;H[jHzQ蔖yd8Rܥ9%"\mRX '1,L}$)RWY`ET۴Ӡ8RErቦi>il~Z ո)Kﻚ^[ yf כ\S.[),єCu҅kΔ-ʤ=,yd2?uZivӎwMw$Ƥyo, MLcg̍e'f:Le-^bĆt|ƲMAJca_ J)(SnAq20V ]*x닰9#6i^33S[KL$f'p:j*^IUneu=@XF :a:kLC6iL *$Bi+g6ݑg wIN%2 [|Qw!@@Q`ϥ5&1~C]J(ax+>+;f] q[wr `unsU y%\ns NN;8):f"Lie, ]4[C72y核b|z\"V9TfSP6 '>Ȳ35#5cyesQ=e[}j9[[s(dfr܌ӭCF1J@s&3Vem7 :2 =ꍭ7%+Һ HW#*Cj|ȮB3MrKN:7QoWQI*c3>eQxc8Ӛ>Uϙݒ[f֥:i+(p{hBa@쬢}@>^,滝iʺpđq2Thr!8sC"8+$DZvϘoxkȗ"$ȬM/{gX?A*'=ݚPEݹ&M`jחPH NiiЙTwktzJ):D:ޅ R~n rz~][$`xDf\#ֱrNQBdi5ҞsC$6#UVǽR8GsZks:[-R"C5ukWfq2͌GִyGX7jrb&&/}ǡN@+/tMlS^G!?&"w.Q+uC".3l!:m-HOli6#)'9)5TGPxp#VrF{78sVttDFZ6L6$XLcRKkkx]1Q{/f&VٷäGڂFpλkWB nur{P~+iQhGKGfyʖ b?n6I/:U|?%= 1bH(=%ԛt%zTRHʊ',Glf[+(ɩ-Das Vq޵ZUxtV%<ٹ\tiPb`lSlE"~UjMM2^cjw[S-wgv-D:/[r夎>9_4(/oBжU>b0j;^F^$ iĖdȽ>կc ׬ @kƖH&^ 'QԄd-$${`>VJO ]Ν(##>`Rȯq:N^'x ,du*qWS#hw#_8 CZxu&!Cuܝ9z ܩqbNn,7Er ;KӖ3џƺ]T׵MF qVN>fT:YDSڹ{dV[}B#vu0I?Z].M5>d ԏʊrZͪ#,x\es!+cTčlGMírOIo쫠Z VMyvW 0QVNqZ:=r+t3uWZ$x`c>GI6G '11UxKF@$QA[K89]AV1]ز41!sϛkJ]iElTd-dfN*֋vY 2!<]58͊y`5mg/s敽մ(u8|z(c&VuO2I^QS᷼y"̬ ?^+cHK4T)*ז)ap(1Ar8[S,KN$gxULz[Ggm"ǟjHm Vj j\|=IinjsE#'YTGnXKoq \FļgɅf$]WN%\FiSخY+F5,Cl%#?yFk"Hq\%zC+-/4o }f U֬O5PIд~hVі>ziڤ6F˭:[2HyG:塍up4 [N23uurmTfoZURVI{O?tL}qZWmVPd 1OZ 떎nK .x{J ݴ2Y SxRw/>4mhTp Sƴ%cI">RR5}k!/RL( XB;k~#c "_"~9k ZچIi%O""Ƚ#݃'rLaOX9s"n$z)=IW3EM7%yӆl`?҈ն{G-Yp o.cTx  CG]Ћ2[jH2"RWOHLs+A9*擫ʮ z|6w~,6!Wicq=iDchl]Fi-{ڼydxJ`p\$<ҟ P<  1 k >dmZu2j! :kX~|X?玼9-^$70)X\'9(#oNrluf%%`?O xM|FxQ\! 5+G繮N+jhRU)Kݏ{O^0u=bxaVc+Gdӿ$ v1#w^iq$&1g=j=lD SZ~0xxR qk6퀏7N5<1a8/=LU~ήdVa_kF ,RFH8= ![=7Yf\d0*9kF\$p/{Ed$~bNUa$[h%UBi= uّA$RDKJ$4ܬXUbv.NVbFxۻ.U]i E|rܜ⠺aa#9=.kcA'ޫkHB,1Q'knz e9+jwGg=9QA`r1(.(6&H皞u\{ъi"m;9ϥfi$nzZvvibyn]I9BxB:s ,yfzR}ߊal}$hLmX0kKw46{*f -x2 Gl63MXD%W)!kn֊D)肤,dMj ^H9⡕ ó'dݚ1 `8\٣#;RZjJљI呍͟ZՃb٪#zAnjySy *EV[7.KUk&"`}j+`$_OJ1\㨗T6Odv ,sk`H<>zSX ̻004vhfЎZӆoܲҴ56pJO UI0wiIaKf3F2`(?δ#Bd@\9jYtC"׎j4wfE#*^mch<WEFm\5#)Xrw-%[*3F9V#62+Da6RSӽE|lR$u) rsa\v+ :$W[Qty,C6F;Vg*݊מnHQ[i1]/=Z]{Tj6: @d\WUQ)HVNc3H8lv@3mV2aFF=ZC)Y#&xbeTg+Ԩ5> {`/koɷLl= RrBHtG|g V,.db0'jC[^j)g(r?v+rՌ셴)n'ҥ{p:(',kMß1cT" mYySS>ڭY +iLqȣ:|

k鯭>i^2YH~fLҥ62PնD_걞TGk3ғnAn_3$e³09ɫ67cȲ2#b9_#PF(z 7eb? *U5z@JG)aW X<آ -U=HB贻:w).bwS s,ZZz9 G;NNN<ڳ ZBיAJ IE+ ӏJǼաcV-B*Qv-QaZ,\&f"I2zm}rJj!Ϩ'حw @뺳-#ºq5jUi1zFcn߭Uܵ4#nP6\SDp *qU L4KױfQlϽPO#~> VhM+ 45w][Z;r#ˌShٕpzu_ؔϡ>\>4Ac}F)[NI=úC]Qniڇ]MX*N0Lմ:Vjsi%9iz )`gKyw6$ -߆V{jDdD#jp95jQ{c\ӭղh&IpdZvHK1Кd2zdqxLz^Nz˱u1q#:on`9$j3єᦍkUIIV 'j \YK8`ySfr֥ \7d.7>RIzt0wA%ǫUk|HUK*rR+g|ۺUo4DG?-H?n}~1vF]I˖MRLRbiFX4ą~Α&hYI09;TSvf"<0\é,?;4{F6OБn?tV ##u5-[q)s.h:|,ۖDG;zku#] jDX5L]بn48d7Xg3FNjE[^;rP3"v3%iZ3Ir+';®܋8@Ж)ɽ*U٥>d".cGk{,\39=?h[ڻcq;lB펇8RICކ-upvonp811:F;qq3fhW=*n[s5;leU}G5.2f$]m1֖kyН Rzs c,#hQM}$֖A EtJ̾^Y'ɰ08T3-ki'3$>D5'mс͜RZ+:+rDb$m7,}qQX]C$}AiF#N1{p7KQ\)?kZ)X!#9H-9ȏIQV3w,/"04 {(;m8m:][CIJcJvƓtws ޝy@;=r.ւC3 Rl =W8b-QWf˻nÀpFkơI8k:k$*ejN+OK,u$@jJ㦣kEgc JN>A"4kMYp4a< g4C)Ղq@swF#D""N Q%=?~xD\дNk\om r=JΪNC?Y8){M]YACe[rI ?P\0^0rxpzG5躰HB&G':#([-Ϩ|qQNJCGgi>l e1[Y²UnHsQ ~uha&)G_3J2(aٓ,I4j:E'5""V`3VEwHЎFN;:e-n&c-V+p>+JV{YA0sV4Hb(%:N&nif-.;!#5KNt٤=Я5O jJI!Qe1VW'0hdӝE&K Hs|!x{fjWL[w6EfPx5nwMfi '5>~BV7W Htس[8ۓYzZ4/ʀ)''4wdk)cERkVn0xujh^d4E}pX\3N[= %\Ii;qi/=6X&9T%psE2ZcqjK"IZ>n#JFa.[jVFF0M]-co=-C! gwRd*syp//U^IA[OMͭ-[9->.ʵskwE@xs] mVpqIs|ڮ g>uXDIʹ,&|EXƴ-ֆo4N' ^oRXe6ғ:Mf?89~}yRU㲿6dt`5nST^PCsXkVpuwvW?,'9i6iyn/exfmB@dV%ty]&d:?pz\U}.(RHS\lb38 10qquJoPwc"t1m'XҚ6JG1P堸Kyv `]`jc>ESrs[Z50n\Rg_nxSA΋{E/O3?v&ċN~U_" eW3y-;]3ҴّQZEc(V>Gxc VGu_ϧ9pFrشHFTk'U,{$M2r~T_/.d$A \ի[+({3A'\ |f-N}m >R{&KZ3}kWo6XVruӃNz~c[^,+ͳG!ň3WS05Kc3p3U6v[eEYs|Z L=jO]\vIfџhx|RudzԏIPn Jea=qg$]ܑ' z}̟uZQl,]*rUZnV^o8~STƍuuH~QU+]ighɎ&QVlZ2N=EWkTLqIaF(ڲHXܷQa:yVr~9XYyVJx5-臨ބΰ%{+.[$NdBgljp>W,Z!m0ou8ʔQCO3c~yZEN::)ɥXi9 {+g$1T=OdR<5,΂7 *ƕduͩb352qY t&yqo0E]hP%J6f-|R.b]> EwSJSjRMXlIK$ge<{sYZǁIᄹn~L?*aç]с_ֺ=3]OŹ lV>v:o携}j%+ٺgYP_Lm%Ty r|IEjVȠZuT8/N=MZz~it%+)+O77jp*ZY[5ݴ\"l>׼!!AK{ x:MUeԮvnsעN)hL$V]B.I\U";aƶ^akI4>aχMkYEP(G>0Tӽq9iwa#7R\0VP7 gѤg*l\C &YD0;5MxlGLǏJRLSm]ڪ7m&seW,-o_t)msqssq>< ܢfvZtmwq5Ʌ9]?4R-~J(© a7 tJڹLq'bݝ槲OH@nxug5n[t.zL~^&7be.W^mBk-iQZZܕ)T*^<дpwfebݡ,lAy YXZl&nc>έYι-(#ŷ=eüpج-SzjZ&x\|D@:Dzuxqֶt:%qQZɨ:y4̽Gn&2c߷?@)/'ba}rwZ׿B4d?x¥Q?(jI6{% 5V&n/EH$G?Y5N4u䌟qVi;6H@$D8(IYi(i$ %ei05/j75 */ c *ִ5Q A]٤.b2?ۚ%6չP[Y?^~5}V4x?Oרw (yg>j^\ +v0eAc?]I˜%Y9;/G}%UTυ-`x.פ#OF7m) 5;ٚ^J7S߆YX%9kҼ1oi -|.*4Z2&=NLC .7WoF̝HY~MD{ ]-β$kd/& 2=C9t,fs*P{ ?V5{mܨ+KJVg#FX=k5Hm;V]GiS+&0DQؙWw=z{bk9ܱ(HB3^],k`k;"|sgzPX8n6NrKC9[`6@v4yXa$1]Vm2eJJ܊TKMc8Z*>\Iじ'[\* Vv’#Kh)_W5BsL,6inNоxXT ՝57~Sӭ?Lm8Ek/(rOOvYnAFzS]C0ϨWj.w&6Fzȉ]_z$D97vw<;&IX9ޠE2HXU`7w+'W] 懫W.6˓K|k5QpeTXpW`fUdNܺ^I#[0ZY3׷"F%ͷ6Nȝ95>=\ft?mÿm[< ֢W%X`,HDc+,޲'P] msq@gl8ͭ0IP64`:Ћ,Py|5[k`Wt9;15FjإSەbҙFM0^dȩ4$#w8\dR+yVu ݉9ms޲oIYnRr.W*w~ pNr8[cnHqs 4İ+XmbZ&@!y>ԷI$[ 19$ZQ]E=hXy|<ۈE#Ä`S:HQ F=څ,"$^ÓZ73ar' +"xQ&Ps sՉ3[\=bLK'+G]KP%|5_yg9NTZi򛓺د_ %;yjqs[[yw_fZEi&4In-So %&LA:Z- |qޯ[[]y#֊"p1U."{ n3VJ ]-D Sm{G D7)\fg 'a?_̻TS)$U9s-P ;U֌zVAAj%﷊5 b_b9zHi,H-tlխ53FN/-]R/$[mOaS{ #d!8H  ^7GR"$xt^Web@gg윥r6ǵ^Ao.d-JUثb1M wZmX#6i q +3v\no2+5B>RStIC gDwf2 n<)]1[\ jRdSl&DVִ DɧV/,0%׌NS ZSU =;BqM%mȩ) 2Jɭ BUL>+%C*X1_:A1 T%w= 'XCV6\허-"1qO#V , g#ԔtlSA\B93[3.P8`J#FLTTBӚ.QQ.~5HjvB;y_Uc‡uzқK|}@ =4W0ܒ%=ȫbDC*)p+xk;Qz[ǝ3'p(iSt7nd[+wϥO]G8^JòWd;=ƤDLi&y3 1P:-hLВoP[hv'ǝ$#ypl5ז8N+WՃ%q'HY.|d- %y9q+JZN" 0x,ՌpF +bes.a-ݫk0֯bmB[+9"E~56$[Pi2ZI ޏhs+:iF%HhSk{h 9͚mWO]TX8Mm,#:g%'6$-1,m$`LSW&K{}/U|Y}|sz9Zz1f B`jݯ896P%D`ヶܴ3vNۉL\'OGw;6vee Iʸ]@HKDIs̆Pw{lH&5H'ig2"o*SV M*$(+sj|3t =OZsq{28qWM7bcq3FǏ9odB٬=#QB{Kw璞KOՋɎk%˱\,e۞Y^u8uxǵ!K $8|X7`s[F:hsT+=>Eka(]ҫ (4x"^\;py$Eh~jm{\`7=9=;Iaf8d0cWZ"RAԠs־%ie!6)#+f+ vc ⯚1|ɹ@S)Yfp7*Ɵqpҁ'CpBva5o/B`~,TЈRIc,u:mJdUSz's/~ֹ̫SznR>xU9?zD蚕ݬ3f'皏I2ᥘIvtHԽ * b(K(iH/60V#XwE$?0F;cT2B)t8#>8"+Mj˜bMu@:S!ӮD&FOXnԔKA/qԎ՟$ lU*F}뤵E:9 $r"g< QKAr5?m(6?+贽NIj$"lB{by :j ;LߖFl/a@&X^i+ˑgJNqG> fݶ kX|AkipwP{ks~hqbsUrGoV# |+c]OteX7IuB*N@XJ"-ݗf9flfe5±Le9i,<8vBynSH4g '" LUbP.g5ci^N2N E.D{A,F$:mrsӮ,F0f  GW;W6\="af4떿"M?iT72Ucͱy!H۴rRuǖvdhRf8Kq%%nS*i!qn20iNr$ֳS[Xъ87bm**]OLZNF+!s+ƣ#bg5NoI {{OBZwuC[ "t׳F]]v@6ըl.se3C  aCp.1rE\k CcUuX[K"yq 61ng?L iA\\iRŏg/JNrju>##ky)6F[Ti\3kċmfJrKvxSndM ckg7N)Bj6-#>.#ep,s5Zt:zջ4e'>MxFh<دL F:C0K.0T8^+FR|:Ẹwcj-ǕY{Woy]X#Nז\uxbh"wnۡ5r+ke-9W+:M?\ү51jӛyqӢL0mr:.GЮ'i/Kw7`Edj6[zE1Xg\q4\.omM7JDAȿy-ֵ#v(v ,{>si=>uM&gMuEpΤ0<`d^!F8 f;d锵'-$⧑b笐9bN:=]?nhtY3X?˥6'qjJ#CҕYu[5x"P~fg>kJRb7E\H@z`jj$rxI6ul&y1Cx0:)kww0E V?pW֦Yt6=U1T3ዃ\FWH+DA֔qfvFP冂(ܼQ2~ռͰB˘Ͻr2@B5vȒx6#I #2 ´V"Ά;*M>k:ϛʷtsB%ݢF$~UX[;#~Z6z +南3Wγn6fNowDm&rxO3j,OJ6+tKg)Q-W{f52-uq3(MH=PY5_1JEy+?ۢcُN޻.0 Ig*Y^n<8eUNՙև6Xt\HT ΩGdq%մ:sY O]r2-*k.VkSPԼe'48ҺGo iqHˌ Ơmo4'HG\Ig͞i[!6vbw]yv:/ݮ:Onj [ ~5x^wrWr{C?Cmc:΍ 9hQњrO7Z"x >f=+;Ot6&T:V"<.9 yAb|Z|̭ΝG֒ΣoGc[G%S&፜O/U>PϻKhֆ&;|V.ZF7Z679U4!LZU?ՅJb`rv'?jq-^ ʂk^V1wZe͜:eMCWNiNKX:X]DkgWNkw,qGյ{,1@q,@]a%ԗ1(ʱ5M`ӓX3t^ V;+u~QJrԴWȹ!? k!е OSԼ?LVO4Y2[xw77,@ªU{7 XZ=lcIo,A=ɯ?j/V--l,,~f,,k03UWǞ2.e]On(ǥxkմ:ۻxÃ\ԗca#8޳;Ҳ۝ذto֖ @a9^OF9wCNN[nWD՚K%푂sQ^϶%|deم'HZ ?ZSZj);ʷ,:3*%⣜2[JʒIݝTJwVv mPHMVQ$MdECs,`ӎ?:wU(¥NRHOSS _3bezi%.n#f3w0iZ۲e W=2nv9T#TeqM]d]_<>T\snT2d;df9OkK7@Gn NJ̼ALXe"U2u+iXw67J1WfІ]og\!?.Mn:DqCM Bьy!Oz*;̀NmM[z!F] M*2O9QBGcQ-YY=*ZFr|Ic%UXzQ9ݟnznrxJݓXXd%LG-<0<{KMEmn?avmɈssUy*$I Xa)ʩԖOcln@!7ǵenOf( %JSB k_Z֬L}k'MҡG`XVRm 0D6KԚ{-4U-[n+;YC-ܲ^<CjCNI#0,`fuQ3ҒQ`Yc_sqt&ʐ=ke&:D>p Vy-tQg'`꿅TWf#s9*SX3JXݴp*(q sOwb9^֒;Pb dm8ڭҐJq%ox;8cr޾}ۺɟ™4l|a%dFb%yʇ2VL'fw9%V,#zѴF*/>_Qu;79@dpO5Z[Hr󮰙juI0G(nFB rdҏ8mYrUqj71SCW$ֆ݋ Z;Ҍ"p-޵V-YHG#\fQmh?X yP4 `u R "W Jh_;J'"\AT6>lBsU~޲> rJD\Q]Yx'+c&_!G! 2I#GO^I_D);nLf3zZįesޗI"K{ 4S*1%}/WdVÒWE'Ycq&4hFzM%iYvcV,v/$2(\58'8g\FB*Rd:.Zx UO^p,~xZiAqqnXe'9܇ۉt!pBILYJѾ$i H׵GmRX\'Ӛ̴BvcqbpORW{SQF*8Z4;Hzu,=Ğt[B:kɯ*뺍&=x9VrHNv),kk;&3G|Bqjh$pAPN\Ѻ8ɦv&e 7J UI?|@9J1]ĭ*Q$$CqU&dɧhL=.TV<>j*)uKqȘb Ӿ @gZA s]3Q5匕9QpkCJ ls5oP", vܞS%_U8Z)U`FYgEr: ;2\B(=b7ԉ>ejq@O!4!|Hs\mtJHJ,k&[;"Ҽn]+d>aH:c59my8Us\V`xW\ȼvj|9NnIёЌU9lUN*'f%y1I 7MIsJ+qhlj_f=8LUKX潄Ei2nq"{:eqU;XR>M&m Ҧ]"U7q "L(~_?Yr$ȍ=x<єeMȓ%SMTvqrDKR7+5K6JpiƝsCmXS4P,jqҲtoT+fq&m/Ӟ1깬e6mNGqiW/&"zVl*w# 2ö]fUmc*Putj~Vtϗ 'qN*C&ré=j$TLU-bON팏.k5v2l[;I%l5u$bxfՒ~/UG^j]<̓ϑr*ԛ' $j4.çAPiZߟ߱;_t.bBа/"KyXdn&+VGD)#46@9sj|͹fBqBc8:]8|\wdr++K;YY_AV,jc8MݩӾ;f#wbFjz<7RQ<ٿ,lܑXqYڇ߹f`81:-+O/ p0:jB"5A!t6tښJQ孊~M::g א 5R$Lt-i4M1FΠwWp\k)>F=( c:MCG5Vbe 11G4e{0`Jdis1N1ݔ|SCl.5 N?"KOvN+FO&b.sz*͞ 7;OPwfV5(rn/O]KN}ݑO5-^dT9X/#nt"2k|;Tq3=rEii9>9@'΄NVDWb%9Z9K%kB}/g/5[U:ʤrj\?nzqh^q2)cmG#'D5E|drs]aD7e,}uP5F7Wu;ۘ78e \7SV|RA0k)7ZT+b8)4%ܲM,g:~U~;YDwd(z ⦸9#t>nKy?vPBg}庳XjN0[?ң_*|!imᵔ!Sxv9.0 M[ 0Cjka&ՖxxP9tz9-J[op*y|;!6Pf=Y,/r0J5-*9XYvZU,7A jZk6&8<ӆFMgm4(FySL1OӴ? *%'^j[G}6s*14v6p_*Ȫ1|F^GGzp(UѮ\yV.)\`m0XsWҥ̬atu57t=pU!FX/Z3b,).5c,RL曵N0OǣZ(Db*ݮ:)cZ㮵VY Gr+֡Ɠyrcm({&E){ V}ľZR?1t?4oi2=YWZĀrk8ŧtUINsB.(FQIo,oo>V[WLe21Zɧf&:֑k@8[&syZci0K(aZM&֏υV ""[u3[H"`yI5h {^?:M^цxn[Zǚr_4hҧf u,MA颞ؙtب浲͍~U4 (I(V`rWU-c?j$>ZyW'mɄI!7~oó>ps &\XP)"c!p r]1MY UeմK$''7Nއwx}cY!Fp!l5cQo!T0 ͧ+@Jַms} P$rF_OJx ni40F8uW+[cDfNU/٠m=r+۷.HEﭴVuɞA%hb1b@ k)+a[u+#Ʋq6Ic%2@}("28 䜊I_\%K{|Ցps/ jj;9$9ZNt婕*%?gk%%bhz Y_ =kѣEs64Dd sz{vO3D]mYªM'.OOȯiE#fanjڷ|5]28L>k>=A|Z汼#b`$Q8tTgIGNWS$b2AY2CX߅af{NH>;!F޶.I |\+(Ք[w^V\#cFKMzIч*ko^܉9ǚV":3BcNp@ Ue1+Y){P^i+%J{t)~fל 3`:U.o'!-s;=d\5\ FrbTqœRWe O1rE7VA}6@ [}w6WA1G1밂|w^(ܐ1nnkE}.3qˠtM2 13? 5ֵ$7 Bx4Z\FhmĶyU;%~G:SoދȚ客jזgVΣFc{}6t޻p4+ŗdq6_0\Odp1N^W8UR45 r-MZ 5-r,O]厓\yulm=҈w{VcjS*Ƥ~oyU_gO *н}|sSiwK,c5y\O[gKЖu.N>dCtT-'#fMwܘ`K}0?֓P9$iI xF}+3u]AegN3ϭw?hmR劸GJs,T+\G *Ռ{0ݪX[Y:[sP:Ɠ}^?9azso^>:uUR$a+m7&4\ 3KÖɆIH'kSjvB85[K|)V8{Yzœ%myQQy̋ w?\W66S#k:A+r":&u&]W?'/yq"4JGc-Nv@kOqU޵m$oClne>bǖ*0f'BL  -̦D[xcUtɆ|@ASIEȝ ,#y|`ԭe\.QkiXݎ>&iVVȯ }ÎjZFT޴|QžQ4r;y7jܩiT²իIlұx}"U-ըHY1Ziro#:!zv_ |Fwk/єKO?%Ս-2ӏl͚į٭-nƻO׽{  xßi?bv?#W]SS,3\jqr RU=]?s Rx=\5/r7zzNKvY*m?t\\&fQ_O{[7zMAC̈wֽ?^+]O<H@C۞¢Xu,<>ObZ'9'x+ xOEFk4Ql%\&G~k'/~ҥX}nq:mP[zdW^4׻M7NMm_ x>KNe*6ܜvϿ_|[xQuˡ%p- JV{{"#Uf67o7d5jFq4|他䞹&鴖GȪшf:Oݰ!osx5䁂0}* %35ܥuZwvHG zRZMjCx`}5 EpM*0pï6AFgۻ.\#^)XB@Cw+";$/jK_mIq.M>`7rMXH;b֮ۘl3nO6*˹Un<ӄ_p\VeT8I#Kg z֡ ap3qP1ʹ 9ӕ\RwZ&ei% BqN[m̌x۾r1, j?+aJɑΒaÌ-:C X?aʤ;ҧiĖyί4z˹3g3vPjǻf]b5\ا} =JZh$g?^jucP'j8h!sTPǷn{Hij%bd~1Ҫ8*_,=ӹZIv2o96 \n;=No7*X cҴs$S|K`ton/u&&[M#.SIJPIY>w^0OBf}rjj[FQ$UL3ZE 0ry;d+p ^]YùtpiǖDc$lA>P3Qh+{4z?na_CYԎ#BFwֱ)Y݊RYAmx e$+A'۩Cn2agI ;GMEWL;[ C}ҝ U =i. zKdVbPtæ; A|)#D}$ f TOZ%#{8Z Ap :khn{ըFAS̚p5 a;\U.d.*N>{~>դn4!JzXb3SZڲY8+֠HÂl[B-.~ɴճg_v>KoH1T޴i *m.Ƥ"O[8O(IP8ΎLFRsԬ!-3ړOspZRyFlvFiglr ޯZ@sڰt[;jG Rp$-|%,a䗂B\j>W;hH}VIm3x:2=)|.١'fSB+):ꖀKp̧?1Cb0D)$;21LF# l^hv7@hwB7G ٖC#5nB(6YT+~O{Cw4%8іwG~>5ۏI#|O(\86XӴm'<ͫSycdC*.FP;غ8fXw+(plRKT0,p) g5XA(XT㑚υ ?8S-\4l=2$4r:b2Wjw.nGMly5iQ SȳYf"T\sS$QzY1*)PŴy@[ձ s($qYWZ ' w"Isjj$E?)fR~ viJU4H_cF4l3$lc S ^3yaw(3/nmM܁Tfx٬=g$VE]ΏLmJBZHQq\ŕ#8ՃlTN1}mX[QI&8#žosY m5a]D|cnI-Lۺd1\C,eee0[de犖wwU’!G)ZI5/ڜ.YxS5kEOa5΋*/0JA*hi%denkBΡ8?Fu=5'fz5)IntFńI4SIܗB-a5èo. dA TtcPG.k`s:V쎉4?.<#41Idw+޴eo&^O@MeIH9yfUb7` RF0 &5 g lTZnWCz1Y$ϸ}Z D,sݥΨc%@2I3T9Frl `RNynkT+X:ٽN*? D2~V|3%.ўC˔hۨ!?^IU+K9LV9VCQV6h[G^yZ'l:^>"Ka+ZTӮ]֚j?Λn9Z@LH&:gگ!GJ-6育HOZw縁0Hᚨ;ʴ9c*,]E+_]79h>T sZzz@B0WiI%bVԭxQ }MHkډ߲BeUB+u%ŵUrN8jQ@ŹXkw}3@%=*94),=qP䖈 I]=eL fw=Ɇ'5.wV^*G AF!y;":YTa..9nN1Wt݇{)iԚb 34Y,bp Pȷ`nckK"7s/b5oNr嶧l \`lqPER4Bv6VΊZjи+t\SQJ0taTwp Kb8W"WQt[Ŷyc`^OCh}WR{Ʉܜ泬F/o4*%CvOWOjmc8르+$y$.k֓tbY#VU;UI녮S()(9:%URNS 2u4HU NQ+hJ*x/leڶ?tVRJJP /ү]-V&>ֵ䄏K'fZ\]N>-#Y:V{[l=9+I\+noD0]+YuvNhÜWi.Āgx bBF*=9mc9ӳRrm/K֠lF99*QClͼ@r*Ưx1KY`تKjWq\OlwP|̛h:EԧmrUWm|#*yjsi?[pFK#cY:f*]I,Hz|~u6i]$uiv\cۏ´l3ES[:JȊ QVvC"9h$abaohlheVe+,dX[-gZ0gξMdn[E2x.k'n~]wi>֓Y% OEs:Vݠ{ nt #v$qD~SnzE)Eŧ8HR=g )eZʖyH/Zlj^ZO12+?>+ZuUP;9vz0:+Au+Bn*Dձ+Ϭi,an4Ԏ~k:t{M_2 ۢ?*Niu%VRo2tmUP˦\J \t^|j[gʵ`Ht%A+Y}CF+)tJܚ\QhS͊^idPCn]8_\3OξXW<ͬqҚ՞ AgZO\I֩y–ם_Vwh݊"U$ʬĖE{;8T7I٣y=Vsưl41d.cWE~Ё,dfМNJ+~Ri}#GNЇeOT/5ȎqrZ5zHK OxF%d ?8*`ci_Q+z`Hij)}q X⼮?`<.IU>8ծ-ʶڔDnuo^lOE\fgݵ3"K2!qJ8q\\)Lm.o:O#~5mmu[k+^Ő87/6MmZ^Fu] ?/|w[8[KJI@w^wa 4"-}nTKkoþ.ТhḆI0BXI5#EV٣{R Ia{HujMAÑbl/^"Q↞Nu\eZ!vIȀ*$'_W_Yaiu:Eu6ɭVys#BcZ6d9+oA}?McdIx:tܣ&.hqV6;ndKG5jd7;dEQʪ󚩮 - P`jh6kֵ(#򫜴SeM;RE?F`A&Ggu $aE[^[xLRƠҳ|2ڤS yܬ ԟi잟ycP'Ym"'=:'R/Ms t3xk K?S`vO@<ͅ*P4ښQEQoO#\fXaCҡ+ux-ߐK<; 0֮Pto(?uh66yd KvxtUM7tzM޺'>Y^ أ:|ܟҴgk܊Uj=-K=!/g5qҸ]sZJdT o+;+ j~onbMHEnL|(RX&̖4ϋPèN&F~-.<)4(QHA^իjނOYk{Os i]FGO[a8 5GtS]w7+  ׽̺|+9T+IQT##&K !Gz^oj\Ix6a[ME.}P6z^ΈwjmkOM|Y;QIsӍw٘}ϻUFŶqj)~|""BkT ff&#Z =IoN& Մ/i<Ah ں}^=9A2,`8Q vm^\ü\O)#?ְ縸*$9VvMP9JFwU9^fT읬ie^P`>ql Å K1, lcԹo#pjҳ2v] aĊGd犒Sr|ZϵADGJ}bI|UrZmw ι>Sj"#RQKsCd[`}jV߲>-fLcfgQSIsFpN10Z`,$vi'}Dɨ`4mU +ف!dP]ɧk%v*(CJhL~ҼϧV+j ۓY%PGOabVF.ED[qLeL݃@3'$kNBYUV)=YD'CdsM}{ny' J=똹_4Ǹ ==贉3LiյL22Y Wg]0"r d<+ ԁJ.&9fȑa#187,Yc]t̰xO'* F+(Ź"Ls>,GٱJkvlHɿw̱#rx!>уr5c bh-H֜Y2i+\œeZVƐL8YRi#u S\NNYMmIsyO5 0O 1T1ͰbbޥzjmRinbr8&&zI_vРc'{oOlXj+$A%7>@T'Rz2H=7\ݜu-݋ Uy-R(VvPvQ%5fòA/"b9Ċ!5vXdsCRv5J6A1h,1WQcsOYӤrT[v^rOnKStGrD@y.-ivN3FJXB8jžE$sP?yr:F5 \bFjK;{U]ukeH]dBs5i1e8% WN4N.:"y!mo+H\K[K"usȭ P*!F){QuQo/#Ofaa`LUƯO5UT1Pڱ:Sү.ms'>V}|b l-k_8>jYrr.YRCoJU)f3+x13)fO)h_޲qV$wRKvݝ^OCER_ GS趃GJʤ`VYhPVgk5 BtHJ3>6R-Y76ĖXP`a+&.fthmKՒ6U8uD6)grsU}nbg~RS4PqZZQ3Si"bO S$R od+/ lU$S[YZ3'c!ǭZT N*(d}+ ة渆$gך+|.ƌ"ۅ9?t`Sd<Ҵwu: .D.$zV}Aq1Ȭ…wsN}ړ@-B33Q5̂*fn;>qvܖ5 #ͩ< ut18m-d}ZR0qv+>!%})]Fҝ TjԊ+$#ZBJF.);R&kd^:Զj[Vh Mgu,ۄVLciGe=)ŵg- Lu9[>s =Yp*[NZpi. #8-Isqmfi74 P͹PkJ-IYH,fU.՛- nMHN<_FAy #Ԙm7b8nsJm%!$9ܖ8@dp31m vOZF[E֠{J(#Vz@1w4m8JnS .nWJ9*0..\~uU! 0=>QL ы_@qz44Lu_yK2k6­I|I`#+ql^P~۪ƓkALKu#ZL)u9֢eH[)sqZl.ȁTqQs0CJ0ݼjy.UfiEq Sn/#UP*363P[;%n~V:V}—|C2ZI*`3sKXɹ8]U\&2lڥ7kEpܩu曧\jVdwMkҦV' 楶eǖՌb'Id/sPMSK;ta]Ɗ%%D 5Nk&}^OJnoqSrQ0l4x cnbÎ+WOӮ]h>jWX %؏fEd-N5\NRW5ty2Yg&LRZIFG5Nns%$pzU{?XY@ fVK]9Z ip09UiQV\pGmֲ|\F#c4kNN_ :=?GiF9 Ԟk/MuQa_)j3r EDZz2 XqܲG8ȭ{"qL; 2\FÓmj,o+3OzV}XnlzĖU7JGƮZ?MUbq&keB=Ң!nyK[-Ǖ"<֑ъHx_0ҡ`c\|ՑUIYEmmm<֝;+|mhN+in|#j E) 9^^:qMOtPI7QC j:u"h^L95oHke2E._K&G!֢U$ 0hN{ú .75<"u +CIH=bfq/|d0^<$?͊on[Ҡp>9j橢Mz5l?&8ԕy&&SQ,[nc $VgL w#W]KpJmhi8,cEoTAErl1%Oқ<13 R) ZM8ZJ'Eڡ.%Hb& XRTg W_xÛ?v1SS>iQ$b*q~O4w,xZQqmoV}އ8g]}7$1uv5*:'&J*̯œƳI-kS/4ʈ\`q5| HIeX]̷skwlNQTjgiM) U"ƪnefFGaKUUx|HXNE*mz#lf,h єFٯxۯW8A\|`95j]nSIiU rzYd\HeM{7aqPZY픎 sk< LnfF+H;6qMȗI@Y$ku1;֥:mF?͊ĨKEWPjMݜZq~ p&ÅXc#rynd՗kN[Q#H5HYӅ{1:cjAlVLzP-Dp={֝ƿݪ6ٛsFu]JR~zͺt{\|FH8r۴FU80[RA]zT#+j^o[}@[< ;Is\em#Ӑv5_P%űY -`k NH]Za(!ȭG#!#БTom\\@䅓^ }/Tu:xTvjާYiO&O #xy>a?zRZ%]K1^ ucͳ0V1%tWZ}~$rI mYo)2Ct+Mjv™}?VekBmf9ͮx&_[Ǜ<(5xTE2-~v\hep/Z+BfHXk:U\O,.Y43,y䏚{;L\[X,R!v5dY6(x/ί[ʢbBb9P˖d*k`WEOV5e4{]X.:H3'Z_\[vI=?z0 j GfCiGT  G2tf>󸵭wTuY͐?ZĻ{jݿƵ,bTzk~#f{7U23D2 sBV0nY]]$enr7K8g5c,{T_ 464>XNjϭMiixZ05i:%%e9'%efI<EbFve5*弑Ju9p*D6<6h7cKZ!YvA_ KXn` ʴ}-q:ns4DHݒ:ي f"8XmNd)H$w*#*].mZk¤7PIVZY7ͷ?+?ZܚOcmxNfo!mKpiH ݜUɓs1V3s-{Kf&+yT zn 3LlTN($=lamyXAFkF"RLUb%X 挌ְ+#zPt4IJ[q,բZ6¶hȮAT9,,=js|f*mu[.gnҼ^=1hZ: mEb%*Iy(s")Ԝw:>w4,@$ ]WN⾶ q>&ݯa>"M&a3,,9`:eus~?N?Ay+#_]7lnzJ^ R+Yb<)ϊF`2QhN\;$UXഛMBnz(9z$Mg]\qcIiojbY_fpfkk'7W=([:O&m8ZE$RA yGֶF 8vN'NԴեO{eDڋ؃m汷oj8ۂq^n|iL j&Y.%$qSJWqx+ U7jrc\ԃ_y\ vY^Kpem9 Wu%ݬ%{"3r̸$V׆t _{)lgN[1^ϷΤfI8q{1-;Tf+&C1m 9u}7Pu UsAߎpMڔ1%K&vAx$]qVUuڹ<sY9J1aU'*ksBéDGRc#gj3F5?qh#a5.7 p?*蓌a{Rr [D]:ݼr 1gIpƫq"jvckVLB̻'h4hW<mN,z~G]XJ$mϽgKỜ "ubUW6%Wknm|b'%"RMjgZ|8#E{{Ź(]|[}Wh??I[ᴾ^>X_κ_ P,m&g QYB-97ՔB4-a#q4gK)^waj?=Q;y#=/L]U0w<@kJU-VҖ#ά/A>M8.u-jYLMq9+դP9'=R*|]F`VSoDD kH_E!K:nx{j"2$tU ]U`H ~ukIX5(ҦRq]. 9;,<{wꬅz"҄:Q|ߏ5٤WwO-w.AK;[Փ+rf ri7}NSP"[oohIc.Ydɫ_M8ivd-]6ȸ ̇$VV\'HNi5^:'bc/2vVxF[6XuY@?Jח}^ّ_SR~!Rךl޹Ea' m ֊{u咰yvZv/>mglu>&b2<򝑝V/gEe eXGMI&tOS|Og:4'X~UGo G$t aU|җ51]ڪۋe 9zVu##fPi1_3.v*ڸ ^hvg+ҵ!nWw$ z=ͤ]Dѿ>L)"a׽u-N9-X!'I-SI^G \p?*4ld %|= iԵ4-]"..-gΔe4yu+1x5X7ֺw +l@ 1,G[ZwK@FXʵ_#PRZRMU.1$zⴵl&ֵdM 'kKV&Ӟ#hV)1i{cԴ1l ~U%\1]+DrJ-FARװT~%u{+YܦS>cJ;]N+J4D/L^> Y\0̏N S`DV0$0PM5V88=xr9i,*ޕoz RIwup,nҩ-Q~P§$ɡMG196NJFFT&}RGm>+o iKɧ#&Lۊ ^Ѭ"^|Y1[&kCZ9+ԾIJ_E \hzqԍ;UY'/,>URI?{=4=Og4FS[4go}XD,՞a ͮ(~ӑ]֟$zYΘJ6Kqg{{Ls4K4+^*kU%v*q|P嗕= ['K?ZWtS0jޤvB뷅k$(Õ6gKK[ er% I|.gZMsAZY(.a1Cu  #Wԯd x'4{1TҙK`eգq+*ek_}5X^spn8 \![UL mrg;.VFn\[Je pnkd@QĊY2;}2,M-9R2xªT7v2HZ*:mRw ukˍ7ydL~P)*ܒQ.zX ̝ĘVQ+r8uj[i k^adtǾk/_[a8U)J^:7WJ֮&H8&^J负C2\aÂ"gPA-j"1Mb~YSzE8׌5&O:lƛxoMiwۈ #dU6 w* h'?\-fI#;#ִkIh/KAE>\!&.}g2A,Ι (j5]J|J[ (%T_i$)<騵mY rĴEG${YKa?:$e@Vps\iFC%Ѐ|G|e<tֶFqHڎԵ1b-Ϥ}^}-.!|ӜWkRúEKp?*{cjR[n,Cr'o'JZ+UQ>9x Q\Ev `VdW-{P4cW<9inTdV3=JH%ʖK0ErJ/ p;Et~6B+zV06˵yv.'`7 a}jRH28Ӽ)= zrK0X);,878^ZI˕tnjڒcV+'jZyIN95G$b/F ڵݽ$|qOM6QOe #9B9t3 FO/aڥ7s7sC*^z",cdr LpC&BjG#Zw|SMvT2p"04Ȣty V9 }GzdkIE/yj[,iy<_l~_0x$NqR4DR*rdU݊2Gv[ڪ܉"o@}EF%cDz*К魼, M(OT۾֛78Q<*8b[M{r`,Uɵ`0H6ļqMT ⥵y[`aXm7f27*erFeKK{9 I1xMu: U{* eHH=sdr{9s(j[L?Σxê1s3J3 )r鹵ݺ6Zp$\8xXNpESӦu ]Ll8qG6XR̄A= Hvhm*0jq{jBTsܟJm6c(a u&KFUAĎ2J*9h [F.[8\#qYg;lwIԠYKM68ZxB3ϥD7VH״V|O\T۴ҍqZ\3F2y'ǥj\T*?a=-֣[Mh K>Q"DY]*5xAR yzW;in/pFMF湊Gh[C^ 6ibXOWX&Js+:K,ojod(ҳ͔A +>%nCjqpƑf-Oiyp=lňې$Hܤ#ޫih&cҭl|r߬.)%s>yvCN@v^bʨ7n&JdS(rU8BA-(d8ҷt!)n˜TcXdiOCk'R6cFj",Kk-XVB9<ם">VY- >fшhrV1p=iw;SYzEH#zT:<̷@ٷZ]d_6N ]ݵ%PY09nqř1U..:?"&rȤ3#Gj[c~sQC>N6+CCX I&6i ε[A4Tmn&篭.p9Q:jUcFa8V_%;.: SR;GJF+K}>ټ4M;+*voVIFW~=렶L,k9l_z'.)+#NpHAtpy{oz\2*&y7BnousM|5mwn"W:kAR:GaB:YcSTD[jLז;w *P*ЛtJ WZ\\nּ:0u_zyWZͷYI?u\(n赝9ܻ=pmrwi -վ|AA\<3Z7w1h,Wg5gx~\wj 'ʛ-#5jjqgIu;R$bǧRZu ]MN[OgdadAfNqdrnfqOJ:M9E,s!Q2=ܳ7#?9pZo7b+L2gh$O]ڃ3&1e;=!Ӳ5qbcupmP $(`°fnm Lz7J^ of vYTug ops grw JM}eN i>~S8/_M:trj1_1R:o3,9W:K yw$5H.=oӷѭ stwʞkKَ2nB%}I&Vm# sY9h-V;f1qIZ[*bPZo{1W5&|-mYVT'ӴK8SjzJ&Q`5kYDKm pኩ/-EcP㤭')gOw5/. [Xt_$ _2gc5- ?RTUl7zH*;[Sjf#s+2Yxxx'+Q!3;k59Wd=NqF6<.v՝K v]혯P狙cU-pCkz$Ա$F{k_OPCɲ^qvIE{<1v@Z躝"2;7B=bmFFz#){I0#9V? ݬ~bPaS!9r#D2q*HYuMDBFO$IcEs g5SVI?caQvs[H+YtNJ޴%cV;{y}?Kٕ~t[İib5Uպ&85:K{^f'`l d}J S/mtIjo.j.S#'OZ(?z[CtO'hUImuN<Ս:ذc*OQ'^KӚzAcQ)wF 1[mcIHHhVfdki#FF@fg7˦V"fiN2v%Ӟ.RO #;X#5353,AflxnGlGRV5Ts+/i=+gDۙWqZ)&f1o8` ݈d"onWz4jVZ}mgm-19kkٲxkOyXZϙsr|t[u~[Y?,r~cu2kuVF䝢EEfsڏD_!ʷknxʑTkr%FYM7]"Je@:_9}3P?9YN.Ě ˜gλm2")~αYT􈮭8猜:u`uIU9d&T+Ki+پN:2w{®r9RЪBX[\ [θkݞyS VۭE+}̐HQx>w)8AjaRyIi#h2ùh~Q9JJwнkYcqҨ_k/gVzXH$Su=)U-f5Z*ה=ImMXX sTu Hl5yH?xöOt$9<]&įʮ5OQ'-|CK{ uOWVKvYܽAj AW)ZpkIGB&[wwxG({\'&erb"CSCgxl#k^J[s^Z'3RU#XIߛ8uyfS4?H9pȧƮ_C2]L9t"w%]9r\\Ӈ,[b^ /%ǜ$jHfAByơwP>R#gOfB9L?gx/L)󩴍Oa+DEkIY_QӖ5j?-~[- _4+8>e&tɧ,F>,mqݕohʴ ګ[ ye{ie֫: HYFщ;lxjq%W/sSIi(N´|C]M[nb`V*o/CZ"+k~`yt#+snm 0F$p2+:u(n5Nҭk_Zv}fIe-H+d0H9ܬ8]rk}NY5g5V͸ȼq`~uѴ*A`HIYq2<R?ǩm 8csU[eg\OKe \\3-Nm#\4 5T}No5v);|B[UggM+#kk<'ʹ̹&6wr һifRÉHs׵yp"FWi>|.jއ=8F3QnZugSj>ߤtr+^/\ :|Y"U$o؉ACe:\/qcn8 :IM:+3R#)“W-i\N'k5E>esKr#/Z5ƛc~:3Lcߧ0SheV->f`#Fi9Oqko[8ȮJRJcg;h7Xg,p@kBYa &?:4 >$"r;vLvʛ Qk%K6bx_ɒ+&b~P59ENOAFbGxm{* ( @aK'PĢ-]AP?H#m7<sƦI5h;v?kl˖I{TxoPK]$>"5xɵN)SWށy[+ #Oh !7cC"o"IXYamcZ:@ Zp\:3<KnZ8&oyT4k[bN{WZV6//^%Ob'Ue9.+J.Rw5m-̭&xSkT]G#HKco ~PJZ^6fONw bAJ{7iyn^:Gm Q|Ȑ:Wo{o&g9e#5O HGoL`y@dN;2F|xm Mg dΠ{ѤH^BrCc_F_:|V^Qϝ(Z/(R{t$2ʽ(7:6M`f/ac-mPl3Z_mxЏS9yjփx4QVzmZP:Ӂ] MxI+f;I!C^;~nnnF=A˟_{oKuo^.RJzϝurˌ3KW5N/V}B~~)S*.Yw;QE|j"Ƨ\}\  QJc.Kg$Vq ]~Wh3KTiz96A"qqQWh8HyО0sKFqiO hU7_QYpH/\cևk1B+6fSJ${] v8ɨ] |L)뎔eEhkm7gzieķJ!%TgF^q*dev~,l yA#U&w5%ߗp5(Ȝ+~z W;OPjjIG;֑Vg%=IizvYvdSVZ 6C7USYһlmv^B_pSN'%8ϵVev (WN.*aI qWd k+o ҳR<4!8 9 -غqZ;"eieDoE12~bsRG>$u^;Q}ߵ͎ Yc u'jͬeNrɫPf64ΤeA*#YF6gh.rH8`7cvsU/~vz)6e[[EX1VnkpsQ ;8(à5 F02WwFY2%,"NÊnb|1\aaӣe!kVB4fݶS ×R޺AeÄvSCr]dg#Gvfn"SmY㵐ےxݜO4=@ZDF5 1Tv%:('v6y죌Ÿ{֍W@ +}lA5LvR $RVWe9] T!\`\K_:\!`uϜE~=*٣#RꠒM-4I&̯` i\30L`yvێKu5=0E>#.}Ȼ3pёt>S s#'Lxv$RkDH_ )gQ_jvu+Į'xs)i+6)]-dէ\Ɓ3sҩ3z"rQOB:Y RO2ަ#CZ3GxWgHD@&hgy=2OS$QOѢvbqZ-E##q5FڋFYd'z$ \)C\Ҵ|n[^dr:{V\ʜgZzMH!"ubtlr67 >P,yj-sWuBL(6@Mr ,$I{JZ5Wk\^SHq+@ӗc k*p@ۏZ,[0ٴުȎV7*GH yJi۽rPL^[tVF}a%Ub:ܭQ^[\~Ʌ#* L(qW$$ p=559=qCem D8\9jcN=Nz)r2}1ZV1sm8W2Bv*Qް甹z5 >jU=[Ctp"y'YQJ Qrv*8d89Rv՜Kcw|ߕ`".$ +ѯgX#e31S(Qэ d, bK`ss܊hF_\eVgߵim.G29,C-a銿n4'"\g5=dp=vݯsx8+c94:7 {HqI`FzS[wש,[a<Q/nBԦ$贻d`".'t}+P1oM.N݋v3i=vj3VQlɜ]xv&i"3&u%ŸFUG՜]u~G,&6)302'OJmŬ )bPK1Ì`mI/#t U[b+};eլ`\E=EhGdqKfN)%"jOj cʘZ۹ѭGJ4 ]BIYUOSY*4 1[jɨf٧'zk9f;b p%It-lJKϱp-y뫕zj[7&kԵt{PN^'YjjW Դ:h`=v +qƂj&gi)r'U6v:Ұ]+%A&m;V Kd9f6Ű0@8zu ڈʚq_K-X_dm01ֶt&y#j=A.oFn#zsY9h֥S:j4o4GgsHJn.PkԼ=L_SӴAoMԒ!$Pm9яȸl-1FnRZ ȧIhOw-O鰉mTB'8K-5(# 3Y3\@21^[G/^`s@*0-g ydw8cvzW{}4jL k$@oKHvZs LuX۲6i:)a)]sV8Ck#SAȍ?κoGo3BB/ץyl?PF+ԆaX?idѳ1tmwmk^ZM2gf|PؾkF+-tyeTsI]Gk"X$bQ-їR `zW76@IGi7&~@[w앑s%^8zQFnX_B,G1]Z$Čǥ'[C\ ַm 1#;m6 êm!SoTȮZ&M۸:U%%r-7d73C,*W?=asi{8Z"yܬn}5\Mx-AO'` !$@ rIZS6(yrLYId}G(2z 4UbjP\$(H{IkyVy;ݻ+MWm-^?|>}#W34Qfә SYZd&\u=dB- uKX>\mT36=GN7f$+I&Ce,EG9~kD)9=LIJG v;>^.\[4! gkWd[JX6Zۭ^ⶍ F`ۻ[|TTGMO\WQ׈[KkWюA;6@"\Ώ+VJw*rX-bT1M m81ne m(QZxy]Ye /`י8Dͯ+zmRzt#ZoY+l/Xd۩S5;#5)@?Zӟr)RP4.t+ ).cUŔL0%u' ӭbO,\U?咺"vD:3\+,>38i3$>SU8OG h1Y3Z$HS=\枋cTWe[OyַQ4}#U4'{$ ݽyb,uX$%_+Lu:MSubrs"խn&vaYz$ÃcrNIQ`Y2d%k64L OY$:Ik[ݛ39=R}; ғ{ڟ2w [v< TxrY|in@YՠK&#*fTV۝уp՜x_NàV.ll@Cj$тQ Yoļgq|Jj^P4邙kzoCFʶ7Л[—8`jxt#Gk/ĢKI7LF ">yMwlbc[Z2]xty|Imq G)Ve\oHR+Q=>jѴ ۏ5`֜iqrԥi-^L=[rv)5h~7\ޠ'vqTt{yԤ+Қ)r|Ry$ qSe֣aaS{&c'H۝62\bM:zTY+XGݴ,tiy.].ir\mˀc6^3GWz7gaiAG 6$K™0 ]'u+#Oi+rݛzJ~K"PE295x<`45X,Q'zu ZS"~2|̅I(f3^![}B*$>Z߾O*)S k>QVT6ƃ>myn$D /f̥RQ\wVfXmK[p9pk;_oMeHu+ֆ,jilFvծ5MLdJͧNeBj#]Oɺeb0 ٳ$=K)[+Pخk;&NiG,>J+騦ݎ+7(|O,nƱ|K{r$/% ?X,m-m2v ])E[n օ%(^y.2E^53`]NcOjeˌEsiP:%)y-q[> n|<ʲ❖M%%iE!'%*~TtH.bkQǙU6"jq2/ ZAmI۰YhĞ,ڴ2,#u=kwrҶ !|Ek>֟Ry"kxM[EFčJrZGcOg6־g|0ͺd:^ |?XJ>ƭ,ZNyֿ|CyerR,(7H0 OEvcBi#4!뭧qe-dyBFF:_x.g˞ W@ZܰɣԔ~xd䝧sВ-E-J@G^1G@a'Fe/|rJ4`U=iv&7ZfSöo]O5H-/CN-q<^bKFcʤz;:CFM8W~&2~쥧:Z޷/EzLj4-.'y/Д<{[:K@nظt+Eų.Lk:畤S*J<cA<;]>^g?Za.Ȼy:pϵr>&d7G%SV򦦯fNx\{iE$-֫i 6aMpB9A*˂XKn0J~"ӲWjQʦ;=n[[Fq;SS|Ai)b~1!ȮO5{M֗W2HGU`VRW<׍=N6:!PsMtKo"Ac!ЊDvEk*n'# xnSib5;Ws2ԦӱgߩWrɘ' V4"vTSn\L&:]pL9TwzW'FnIΔt2{ִw׭ǓujdYKV7[cGP 5MPIb}G_lg1\4y rq\Jۚʤ$wL쬒)_"}"Wo`c 3sI1ƦR$"A=Sdž|ⶫ؎?ZjGE;1sx/5,d1]u6bAc+׾&x~{+f͋+7}E6ra]Ws*HXp4>kiOW9_n5H|I5Ӧqhyq:XP ~5_0厾s{+_qګЮԓ64F 9-KX泪[~pn0|E"d79DZi6+f%e m\ܿk+]: [wG<3Nq`خ,dAJ@x^qk#.uzrC?Gx6٥n]'v,TT{|pe_/mȮ̟)9mRj0r$ +Z\RjKvqQi^FotΚW^*c?:&; ZXq警7kfFO2X`N6^hVKtN7"XTB,NOIǂ W=VCc~,Ɍ򬔣{pڅ(fԘÁW>.k[7\Q|'yė\t)2o-+/3N:[Za:NRWռ|IKm쐅NcAbXY,L6ʌuOR-( }.i/#C,3q0Ѵ. )%o x[&7S4HK>9 [V28=Uu(cЮ kX<5Ŭ,+IqFrae1k.DA+fS!IJs]<}Ǿ*v$@9:JBy?jz\3.FH{թ$Ycf=J/A)+&`E9`{E y U9#2r23Uc`C8뎵\ƜF+̒)#QE÷ QF1\R"mN68%L`8u㚪.J9mrHy.l_v"ۀIWy0M=Yc*d.տc,{QEm K"×3Y4bM֦Vɳ\&jvLla{V-/$VQI~+Jv4 mnImVp҉'"\}6 LC w2۠4c T cCf8\]Xи&! 2[ SlS~T{e|* GJ-e%WV# ;RȞ\*P砪ZU[o1rqLFN n|zY%Tu[Gܲ Ɍo4TLсJRKV3XyԷc=ьpzcQ]wyշd:(汼׍"gv(JbBTF۾WOo PO\T1>l{BKpΤsMgv=KED^m*mWheCzel w0F\(mb}WK+k_2 VbjۜwC8dVcZ̠?j+hɝ4ޥA,$˖QӚ@31vt7& ^p '}*{K+"tFi+"Z› 9:j3&1p kl{wl?N G}ʦx_ZM$b]w闗еlp޻`ЉWV_Ɯ$yt`9j^(a<7$j yo+6U%R\F!U'*+Kbo61*@[ԟ<nS95 \PsRQM Ip`jׁ!{$s?X^gq9EuT@[e= {uyެsVfCBx!z{4|VŤ ZR[FOᆟ#.҃'8gG,3d W]vG'k~}ݟ]¢}jY7x8`OE\F|=.ǣ:ciػHl 䎔[kle}=rN!La,±\zxhGN*o} Jϩ͕7l3uSp2quq[%mw"ۨ?f*GJgb$]9Y㜐p+gIک=j-BYc,D*u$a4R3f+6jn/lYسs+.= `䚽kipXcn1VWSr]eV2wOK2*];V0Ux Q uYitrj̅ H[axHKH߶@&lk*Y-$vx"j%ՌpYdYa'>89]W0bQֵPInhrwԨSޡ9}:֍x%@9!ܽsW5$ڽ|jmFQH!!۲Fa(jc]Hݻ..*-%Wq\£RV34RYǦEd}Qksobby(9Sjg.o_!)A d{gnh4Վj=Ft!rM1[Y2i]i:ũ&YJ7_OV9|٥ARm{J^j"0CY8VR3 ahXB9 jiEgǰ.8ty5ݺesPh#Gf$ף-lW\fi+M`;mJl*qjpv93rܕkx1-ӫHIEmD b'] yyjpWr ;@5g\5]NMzEckڥ4Ee7#'2?:Μ12ߺw'k<`:.hd[-s)]Ee x5"jz넯Hhg*ωFw gNWvK)Ty^5)s% S+{KĶeZepеmɴa8뷜Tu/cZ~Ts~ !ɒ:E\|1|;v[Vw†6"iW'YJmhR\?#͛ÀnieaHiq.{. j+sip< :֯ 1L{ZqgRNG#xV2RVr\M  x_JĪx˭.c, ΪRW8ܬ4V@56jYlHFCp*hl3ҋˋ{Vjٱ m6Ǎl5l[꺔!I+]l.8[:R\tKُJ'LIM~%]%~пΡ_$0VL`tq7$ 5ۿ[0{nyO#Ϋb?5*\,R*dcqMx_ut<|s^p//w+ũh:N23r60n#i~*x3oˁ\Fkgi ۚY,lq[N0zݜt'7 k?Sb]rkp!U?6dsږp'aT VH-X!n0:~ui>fOb#^pW]5"k$h\k̖kFqm26^r̨xSZ&DcLhEB>Y^+ yp$@擢iS-ͼ)j:<, ]q͘\#@cXFoVt}bW[o٦4{J+(5[0]]X:H|IkaLXyGJıX(SiYrr-|4yQϢ"^jk0:+֦[2äXpa&Ads2hy%\fhilV\7fz%mdVh#)^2ѓVRݡY3Kv0Nk6@m!͓+5{YBy9Ͷڰy k[մUO=dbk-zh%nƝuuhЖ:U iFV&ړz~N3,Ȳ>rk%oX` ޲1&}sS+KqxvR<6SNO1?FvV]ԗ\iDI5iZS;$ Qt8zoFdAI.e`x[ƺQ4gξ#0 Vּ0ȶedu=_IgmUF Ib`U5G gg nA+ͼK 8+0ֵ̀t5uSv՝.:ɫO!?묥#VxwP2Cp͆oCfh 3G$j- g6u T%9EZ|Ra9sFУMMOkN%l&8*TԲUbh[Jz2?:TPm)I?ŀjz||r H(-#cߑQ8YE(׻RUI#IhvN!Gd=JɊM$n8p1Ŝސ ֊ҍұRJL/ls みV6mf pM3LM &PM$֚s)ǔHjzZ,Yָey Zt0#85zT}[ϐjݭ葿}Pf#FLunakc0j;-͎0+VWyfVR'E;.>yw#cUyeiԦ;X5-tqY_E ec2/5h%oqڔjZ7,g= ԗmnT݃mdRZrAk3VlұFD<[kFmIiwWUW2g;SW: [z: n|m:->$| FuG m "}\w^.,%RG1$z fKcTi~"Is .Kasmie Z=&ckQ98̴i"=$y1ZD G/Nukkb]v'/JF\&:!~">Ā@5kK<0`WQp\ qg$'xqC\cjj%g?T2i1 +wcL UJKa9Ϛcqbf\ck*: VQ>:,my&>C":Νx+Mag*S=שt02gBĨ;p$_g$k5ˆC4xuv $b_.)<: )[Inv{w{/%KE)RǂN&^KEdR! jF!u%k&*pIKRhHd$8׶'҄,xSnj!TVsm U:Fs9Oų'YYED$fv=Ac7(k 1A '{lPoe;i`?+ ]6,#5xu4xcx[7Y}Oa(BPiҌ׺EWw#iť!_=cY_Űfk eCqݙqes TthS#&£ xrKqC\ p{tW3 ٪~)~C5^c=V!@GL5sKnjFF"oؐd$sIZ2pei+P t'"9Š׾Mp8%XDfF 5JM;)!KYjNmlZFFB 0vY-S'pIa޲|O_21480drMk{:+Uh/٦r2>Oi`?4BPP*p7&񎇷6sm|w4_xS@/2H Gs^kzز}&.9)7Ճe8Xja]7/>Ӽ7[["k_NӴdg*8W/5dSy@Ӯ 9Wɚ͈5(ٳ̣cկf knd"&$vs^_u%ɒGRs [DbDk)+cEk9uu>9γik(te~\g(xnM, )k+disJ]i"c&+xz~}%VC]▕nl-H5xj!^{iPlX2[m\c""|>jJ@vx7@];sZ]ok=Q*6tjާ?kz5kNU|XbT\Z=7R{DG gsc(ED%14hCTcڴ +~2q^,WcWз)]9&?DeNZHt2ڍDNGpO{o \ڼW2ĒĉpguNG(qf+)lW[XRR\fSG_ &}u%''h^1x$ɮTm0[^ _G4n% G+K.`!ľyβC~m,K< -/H*Dh>4үb0w#8K3XrPoo; m \*[W@KvCyvOolmt@ZM(F*sxjٱ銸Y_ ZI cɺ;6Yh/eE?J]zK U7ʺ Mhr.{Sd]Iy(+r2`5].4 yo1Ui%0]i11^A#ҴYX#U(TҎ%fKw lºM.K7xgʹ+$.?jZHu*E_ǩC8Ó>V|qƥj3瞚k? [’j:m.,cP-UU]|7m9dyR'YH{V5tn1NO M>G XvD#U#J!{9}57mn4Z.6|5R.k &Qݼ]8Z6lFMg}x3ZQx+G%&XDWhC?Wl-QFZ KKs >V򛒺wF~ǗKY$ARV\u? 6#VDzz oi6A٠ŇyIZ$p.,D# R롤)&p^ Ԯt3AA5x6˹^1Pȯ= vfEe?2>ӧ+)9PE%d\SO?H|Y[C$ZCcQм9%.almYUJ%R#?Wd؍O3FFt}A3kZ%< g85KhYys\sk%lma'6:mo*? YuUPѼkm~$70EpJI({m VBVTwWpBsa\" <%3c,Dyi\"ƳF-tRi]qia닽ZۼԬ),wW}mu/`R0֕%NrqQ*i{ϩjz]+k=+ceHzhYғjeiQ*U#9 ޭCiie6D2Ka:Mq :}回2cbtVltm9Sw6M-*(%nOL)j5Pv>0XEAgV691WM J` iȏ;8Rt זp2zԶ0(v3uwamb"&nGB#a~8rsU'*JG@zSNF*č*$`ojX1A5reP0qO!왌6j\[ٙWNo29anj|Xr+O.RB{Vao]nҶ.^[h>#-7U7lEW32}U0㚟Km.!9=xI!QFe,czށeQ$[, Ee+Tz˹7=)a?b]ý7Nsm6v`*Ȥ*e QA8 KBmծف [MZ@I3xd)+s=m/Zq%~n.=|X[5߫[(bl~R_ca7k$RvI%eHܕN-SÕۃKk4MSZHAQA5ֆ&X1[+"6ҳnC#2[q.0RhqosKv.ٓf#s@8X#tfrLpMtZ}!WAr/y0I6J\Z&E9-f[7lf:1SK+'˰Qڻ\XCnI1|"ʖkwþ$`ǒ}*;w*ePO'aLs9NĪo 4@85z)Ч\ =k;T$3I'vmQ$g5e592euT^>zK[Gwdx5_k2k7VEhC$ FG*Ȟf;Z$xmT| khZzYaT9Qi^jʲyf9+F6ooj˚)Z J+^ME*6KkbHMJ@{716fCzx 1+Κ*:2ZdDގ@V N^gj.$ #ֺ9Ӎk>!0XK%ê;(aj/2ޡeg$5PJɄOd3Nub5݌WIg,dfV]}#nI+_S9Ҝ1ZRU)#; f7dROr)U$F*/.l!QI늫G4eD8qAs*$j88Y:YJCBȯ {bTs۹48Z3$Gݍj0lg*f5-;|ֲ36w㊚%OL[F ,` d"ߎ)h=r]o2OʌwM1$\hxS^ YJ&Vp{}rx~*/8GqCeT`*;/Ȝ#SWcV2vFŷo)bJӳ 1?.|iq!#gx~!~v9[~)=+w1x^&P{!, bT]jl}rOOV#]H,d%?>y88[SMPbӐw9Zu1V>zMi).[]g,o.?P\j {t=k m>EŬC1P_Y_Zi3=F Vtɺ^?Z9}lMu׶@D1Dk* +(nWf[h1DZ+!Whs8L_EٚmIcۋT(.8Eg7k&un,ՏY!}@H5~K+`` s:O(R9 T3k7рY Aڔ"tgQYJְbH֛ngny]&,3jqRGuo95MLdFA 8ǡ"śLՒfbz`yurc2T);E,?ub&HYmdͼl}1[K ֞uefIFz5(9xEYsCnR뚪F:gE&s,XZ6#5>fAr=CQmq{2j(K Z/ a"K9AIiTE6SOGycSRH sVVisۭ ZZf30?DbڵKA(iwDbqtk]$4m$ u~&l%!ձ]BHO'{qNQI.w mi'0HRk~+"^GQ8J2z3Z(6`ZVpv8'f6 \3uj1_M$ ̖ d2Ae6=TU3U'U:[b| '9¯=(8e- ?Ap !SX9:Zk`+7mDMܗ%1+2; )$}QWd$a@c# kC)RIriBܼC:M3~1h=X犏Eo'{Z i(ܚ5SQ_ nb\0 _4M>LrAGד{1k4DQ+41ӚLI^%"tv-QS:λ[J~U?g_4hl(ɸ `b1QQnt ^"E28)gX̎+)U#qzmn\ dmUZM1Kp*d,,5=KXbݫWiEKuir̝x" ֈ|&rN÷Ф;g@Gbt+-mBZEe5gq6͸8kM4g Ks%,;A" &_.Qu58b,9<tRJ5VWzj%v:-Ր"M7+FRxuo[7٬ n2Il<'K噷ݣ L&+КзռYA"ye*Zŗu-;NnޥK+(; a.zu1o<֊1;F;.f#?`Wݏ$݀J>/ok,9חiچ~)OtZw3(Qucx|L̒]=57ȒB~d4΅p,2͞޵w5tZTFj(6_Ʃg^ʮ9^wy[f#qYw-8kӺFޑx.BapOƷњkr+W%+R(@*4K.}MOt AM&PAx{ N+뚀#c}jM.@s҉2רBqJ$"WYsxne۝RM4QϏ֬E61%9MƩm& $x’qW^\c2A]_Yg[9%SJk27s9&WH]MLm?VIU? )Zx0G`Buk ai * N`>Pq3P׷S/JMrH^.g^KŖ{wZqġl<5+M#BBtVmVu(0FQ,Ҙm&qǜ Rs$sZIڢj͕92X ae95KR7bK{C)H`Qwi8MŠR ^n4hO'bJ|s=LD՝6`-unqPK[v>_3",C]jHElhwwyR4#yE5C'.B1!5(=)W!u#oL*cbq&oJE+Bsr ONDR"wCS\RSY0F0V_<1.Ry?ZxxF-O~X~sPudj/\\G2_AJ%/eVG9[R|xn)|?Z`:\1d|sKݺeҫ}%d@Ou%"խľH[MWUmuma|#-`7TSo&%:[x^n9g<7n!4=<@@RE5cAB9(zҬY$ƲMgk^ZGt  Z(LQ\A2o uq['QZ+A6o1#*QSoSPoLT#Z%GٮvveLT:nRC#5{^[?c&8'>߳rM2ʹU!\ ebV3}ͳZ$Wk.xb%llŸ2{*Vs6z7E啣cT-iɠY\ռ5]mh!=2&"&){NSYM{M5=cKȱXUe乀]lZ lڭhl͑d5?~SJW圜yY-τvf!T?\/5}2i1.*ҽgW :#?Jn ӌ%IN:}xT$;\#³]A1XuKJĿ;IU[yGD FGn?$|#m ofd*,Sh7nr~5|[$% ^*ڏĺ+tFMKWXEЃg[+:QX[xk1KԒR=+W֬p\8dfLln,QA2ϋ }ќf4MCF6۰nF'Q_.GCyyn̶8 75j Dڒ8?JTŌ96xN0.7Ќ".Y*eNڱsėV—x1]昷*i$4X#=5j ۽%`#=Z[RXSSSOCu n:^]B]lY?k5I6{l ձ?RԼY2C׭5(|,?V#݌ JҎɥʓ^ץun*i4?ƥao"Sք'dAүڌaeA )Di,ّnEUdᤞs>/ދB-VDϏ^s^i7Yo+7^͕taH2ZMjμ*9Y ~+}J̪Ih|6Kw=Z [qw[?CQi_ i*jԧ*| (Q _{QחUh)>Y^Ut.w27)װiC/lÓ&0C]k ~wuO.[3YcQs/a<>v ~v ZK2vNDV6Xp?d˨o岛U[;8_ݒ?>q62OgXYYʲW.zb++;+ ^l|7eyYXߣ]/6ӯ PzYEj6Zv8?G]:j\8Q^h: ibsץj^'fS}?2d7t?f ߩ'{em(N*#S6iשK#t潏FM5y֥x!1@tܖsZ {=jY#^z마᫃3U9gz7%m6z3!! \ad&ҾK$U1!TvVR/5K&&>r;Y.f-f1#ޤ{d &A,m%$Uʭu3Я44$^ִ?'+}mv8Z4C{h*[g ?:›:SLMxPM-yĤ-ZY VH@nn6ZΓNog+@N M,w٫4C$V'Rd`yhtxCڶصK=m6zƜ-r?Q&]vskk]>\mΫ[A[ib¤}JVL5;./MP v1yZQ qUL-Skе?jgӵyHjϗ6NP- آ{V̍m{m"*K~ |V׋F4rҸKk[|Һ0Pܦ?%UA.p[#'5u: qݮ>0iWIU8C~Xm4C)=6hdyT;nm-ۮe֡>k5WVќRʢ2R]YMq=$v8m'7VsfF95A [ zL$eoq]r?)2x8 7N@c %4.Ygxb0}x:IRٿ1maϾj=?8!+90sp:+oxvuMֲHŔ"$.C7aך--1_UwTnm4ۋBPΧcҶPZ1b'G~ a;++@J a@1Y?gO:U`T_>(xzveOok[vAWjlfYܹ25CМFX/TaI$ƬL+LVdG|ӺۖTp6pjyl9'k*5;C 4#3DSY[oUPvzRȊj+T(Ѳ䐌`:\܂Qi 9rrccgb*7o+pX7lnY#8!hC³lmAqje9J6] Wz.'eLdW; 1yǥd]]Hw47}ޅͶ; pF۳c,n*dKY Xƹ[h]ۣ,1n$Ƣ_'_Ӿ Yqaj|0ARqZ2$=iOEaBԊ7ݸq$IZ֎5 09Z[ 1AgdtBS2(09sgւ56av 229ؐN1 jʢMJm0T Fj[cw?Ҩ(#!lj?&K'k^f T7##oJt6}ीUSK%7\pX}jȤ9*Ãұ|B3/zX(d!*K\[<曧X&{^d A`*ww#$9~҉+&rIϽA`I$ֱG;4Qm_V܃9&[deKg)rZIؾin\+BH;R'gLE-!“Z[fJJvq3T-ܲ7HlOzv1b{Tt6Cʡ#OvxK{$Q1&e<m ާ;i:v3aUjxtf Y )& :3bU) reqMEɔ#%Ta\ .= ]YV#d0Zݵ6t'yJoUצ;Fpb5BM)zۙr40݇1s8=9e-k7y};Yj~,XSߊ STg5d|A rs{feܯJl=j90Taddq]0\^$2˙=^&Ctdy>n֥EGG5OMsXݑ#cI1]S 3Z^T'pQZY<3$w-4ӕt_7kBPV"WZ.b˃vclnzR6֤ԝՋh='ΈD8!-n !;,Z=zy#NwqL&4ƬKr@+ RQ;HsS?;x-AE4ݙ.m>cJ8f}{e *I銖3rfjq.=GҜ4Ԗ w9'Xӡ6řPOn_xsdXEp=jm晲6U{`SR&RK֪n|sSyzFI 6<0E AیV4Xq7j-m\ͷi3Cm5c5ս4Җ^{w.m- #9_2#m:V[j2Ź{Yn̫JQ^ay-@<Kp-ؒ#ެHJ.[X=5D6m 1\Gʏ*{$wn& ߹D\dsm:nB5\Ѭ5pг-c0GFO ԡE?㊕=.YW[>d2r8^($WQ7G5<ը,\ήRL Z.U׫F$RjFmrk:=m0oV'[N[ Oom4U]* !pd+*%HB`QS.NMlE~Vm̠t}6{rF2Udd_K`$yp$8u!r*ݬgi7s\zYsr9+Ic9c9"[IS&v3i&^sE":@cԌz|L (J,-&(vRW&V,Vb< }%֞9eǢW jvR=JtPx܇QV|̳lȪ2iݗќ8b]14RK! *jZTq%REhV fnk$YZގGV5eXm"kuvjGkUM ɱf g" ѱ݊Llw,~5oWfpnNbցL)OkB 8"֬)qM*o \vk{;r]JLӺk XwzG{Y#o\`U5tVcK7zj=ګ62c3gf[uU?y< fR LqOT5IS,۩Ek~'Ix"vEb=+-JfAp>'% rǭ]OaIw8mڧvt>5ז2qQj 0ϭ` בA>|p`ԭtc 3*iFEMuWL  zdXא2DTvqZTfs7N)XnNQ5.uVo yy~YLa*  d iWHtTtw8 V{LO[%պ}ZHF8:צr._2" +KgIb<}i;Z59uf%%֕lsU-Ɵ$f\uKDJtq1Զi+rID˴Ь= VԼO2)WM)DfB8-FM3IFcfe^V*я'>cݺwOs!n-}] _4 m6i/cn ,fpjl2aVCb2BQZPL^xk=Z&`fӯP^Dr2n|v7NȠTzJ]||-5ke -Ĝl_CeΣNޕIڕ)._sS[Z/b洓a8zZ+xYE7FU];=N?lxXYQӟ4hw+YlwA<͎C/\T:uq# cXcV:S`=>Rh q܀|%%y~ѶՑH-"Xsﮮ4OHGQ ӓlt#%8\7vDqFU5o ̃ץv,Y RyeO}RP5%};xqV\U_ [:D\^mLb$.)PVNisH)"jl. eZ ͤ'U-P &Q}9Cֱ)&Y"P3bn jpi|Ұ'5u&I9݅J|G:T٦ Ǧ*}՝lWPtۈ֦]>g hr?爥:ъ&%7{~(`'=GضeSsڬxfTxj Qr:Wͽ6;8 ZnӬH퉔_P9ElxXCNxzjGX@8_MrE˒W(qUXaI$8+$}д%|Ě3X.xkKiMq~r6fiz)*̇[DTs'pdS>h/܅<6ѩ:HG h~^pp3y -c )z)e(a$kz:%vLh>lԚ64nJdW6!d'֕<`'\ J|B;iXKD?yo?/ɃF_Mu+klAorEŭԈ[ӧTKeS5xkWhY'+oZ^\ȇI88K]mgXl-u=n+]Y6渡ۻY<ֶtoC$e\Ɀkr߉Cq 0EV 4ڥϱ|D޲y|YŦ|n3#F] /wFl~7vb >)v(k2 iD8EfmdqENSKgmb|7aXp9HBϵzb<\%֑@B=I5[C5Q([;zowaWoq6G@W@ ?Q\UyW `FJAAS \X;vq8IEm J}>Cb-JE 0>1]A1DDPk:N.@AMNJSdPՖWdr>:RjDk9nP3J-QH6T;m<`ETaj=9:\4ohV{MB7Vᆗ+q$ǫGUCYS /MӅϒm8>ڶTd2tl좸Wc|GlV| \jqG |Xu# T=}'QJ3=SډjF7oٜםiz΃{LH?t~%Qڗ/+ͥkWyfnZ tc°j BSfwZlQWjx^M CUO?$yZΦl=ޣ"m.v_]'"qlX kn-@P=k㲎hdW=rAyԍBZB'߹epX4MQnl 8⵾l0Hˣz[ޥMa1з60gX4e$s!' Mr:֩T]-&8%K_­Ne,4HOݓ[0ClX"c^AkqI147%"4ۏ0YRs\TOY[V\?i*Izɐ? ƺ?b{ Jr~z;E_oV- fNVVfGJqysկrd~rU( X-ԞN?*’DeU>gj.cgf?֯3. =1\/4rdVy5Nѝsn6m‘&5]kl̷595#$`$c( "ViγeA+ХB$ʑ[-o#';1Xu.SDa08Ε wk-K Ժ1fݾy~!/.5SzRk(r7FO4)rjL:;%,ErR;uL`ḫwRZ 1 $9+D2N|WI`XZ̘ibH[8M-ޥ"@;Q4b+]YjpU3u+~Qk ʆPOJ۶ +qm 56awoKSɚ[zw,ETt[]o/ކY"AT3]Geh@џ ʊݮdBRO#7BEiljoZf٢-H9G\8kg.X;Ճi:$ӌ.5MfvJ1o\Z^5ĢxF`lM04hђ |cwql15M U%15ˣ9kCώ7 zƦд{Hy+XCi ?\pnV;!~mr%d?* ;Mb5V$~) ۢ?^+:O2{{[xݖb3Sb[d\Zϙ)+IVP+5p*t8Ci`ZKs?.=6\$Ŗ3Ic$0[nM]nj.E*^5o]/dB~a$mp՛6ɓm^xOR5$SlL--Jd{ 5 {[@}R65gotqȣ|wI3'c1Tl϶V[?=cՋOAޤ2V7P%~zO4Я4 t8~E7mN&wOWd˺6 oRОEu ?:<FCI?!+5mՖ܎v4/iyp Xu#b Vȼ!eW$X5Їuy.m;a$ϯhQpڵ!lt,f1"U?֠NVr .V *=NR #Yw +5Q.HoNvk[LH̑rVY-:/;iO$:j{Twg> m1)#Eo,F(^/1gh퓜 S^=N /\{xǭgZMZPçKoG`$ -ʫ%5wKrW#mtk$bSq27Zseʷ:4=[=-,ugk>+cGXz?kK[+@LdIcpQbpxXtIjx%kIۭ_A&r6|=MZy:?zO~ ėiBvw|Z+htm\ȼjyÞoO?]EFnzG—9/9J3byL z0U'SxBobKkt_qxEMOE쥱~?eWZztAfqs(ӧRŻWx.wNxakCw,v2M+0nyU!b;o+؈DWB墱#4]m09BSq6O?`RvFwrۃ0BFf#-zWJΆUqҳY;"zQ 09ɫqYx;Gs4B|Q2MN`H"ʝ2VD61ӁHl& n;cISco( SxY79ҾX̭)<*=s\%#8 銛FTXe{CQSA1et,M嬙ji-'J[:^u Gp= +X V^S^Ih7%8ay횷*Ha݌LbٹN9&jN,(hKK=xj͔E&%PH^[BHbBPLkRx͓? ˕r:5r-qEY =y9M2m\3USvsX$ZS3r-^YK[nH :)c<=Ň{xQLdAm7 )kFIsEi@m|B `M6S*'-,jޥh H[r:Uw$o5V'hw X3qr3ʴF! }*\+)b?t *r"ƬvBiK}7T?c kQIwmxI ?*Q qzbv*JG'AsqeuU|UUxe9ɭy(.K`nV锝Ђ9 4jnkuFp28NrNkjIo1Qd7'י$uZME 2'#<ӵn ]MMrn8tRBB!EO7*' P$2ȢCV-8z<(ۜUA!0 }KMEhkjn&OScڣ %͉(pucIȸIEErs4mWv S\'q=jc T r񁚂m9 ŽDrD` uoZ;mf)0eU$ ʍqǦi]-J}҉̤ք@6 rjȠc)nLzU[8ܰ B#+f$VU MpiJw;w8CE;cR'ޔٛ˔b>shi%0Sf.+ZH N tJ$Z1AĶM2=kbf GC1RؓWQqufy@ u?ZĹd#vXcYޗRYă(htԇY*Wٞ8[id߱g9Rvp^Ɲw)9"dɌc{+ˏ}54[`T>|ɴ Kpq9t*j6I!F;U[M߻Z5Ã#q)maSZ~\茦eQ,ozge3M´[9P灃Gt[A̋gїe 핷Fyf4`s٢.Q]2+1IuCK+\JEmݸpZxp3$f͜sT;QwLӮ\&=lTy68 Zv GfM>UZ7BufJ}nTmg9;5b }jpꮤ]3ԕAUlTWZBF%xI6p j3<-tѩ嶞z u]6Vhf yk?E3|jrZGE?0'%֝[f{W!}->E o47rF+6") TGG4)6k& Ċ9ҟV[y#+b21RYInW{Ȟi8F}fwrT{uYIk)YgB8"U;Sv~S\nV4r\"QY- \q4F !:n-xoYw梶|Eߊi啌HB).40m3Cz䢬^MKĒSQS>-lVw/ȾYbsWvҜ杭mkkuN %)yt{iW+f@# ̑15L$˲robN1VȤʭOwyGu5ZC7$cS[5!fQu6e.[Xˍs3G֟h<J{n21Ս-c39itf<Cфuہ)hKDUr6d^/ل_mPbۣx~4B1Ri+COQod44{|PA93]5wVKuSbi\3i=c{qijv'KG^\Vv"kpÁuii%y UyoXm{'b7(2MY}Vvnf?Z|k 3@޻^urEf% RG'N &nJlĆ? hfG QCv#*sꔱOPQw9BRv8O\G%`VitE暀I>+v =2YarVɴ.D;pXCUKM},z}+f MB Pw. $O;Y;#kvױ)ӎjox p02?Oen{'h*-16j5ʥ#v.Búյ?gdEXJ֠[ߤn:EE }۽IRkt ul%Cͭ\ēZ:l7ˍBU8;WPouԃYYWњTˡEi :wg^J7 L&xiGM;^\QomNS͂܍+ѹnzU3\;y! t دh 3n;)ڣ8{&Df>*;"սTmN6S{&2gZ1xoL&F~ȪuRVh!qv廑XpkV/%]Fx!̎kymaLg5uD(0355"kJ($0dn-oʼp=CW5FZݸ`0GiBhQJzt؉x+]:Gѩ[d5~8fYڶW0*V8]GT刂@uEeiu4 AG@d{,y5%a}V<8tdC{HՊR6+EI1W9t$\8. -gdCʱ.I!^.'@!9$85?8!Qj%7Ė;m͎K=knٌVdV{9lz׫Cl\KvF6~4 GpEzuԞy.;,#^^pQ0ƶQfpj8]"o:㚎/ LM'I\sҴmmsb-v^:c#<;k I@&aҒaL%jֱ$؞h9&CN<6miL t1kQ@Y%$rc:r*\iT.H[8 &z^]ے?:5I6꫟U{+?9kn:U#kq7/r9Kk|0x5M<34lb&&+[$U$ypN:*=F{8ړXY 6tHsiKڀ8}ٮZ,VNGF݃S}6zU Z:zlM:OwWw1Bu\^_OnB]zE }#5ìl][8 2SY/M'XZ $U 'zVMnc4唌OY74S\?39ieEf/>̡@])#^EB6p'pF%Tmr 4VQ;f.m#Pd"+SjZdj20n6^_jvMJ|N:57sD_{=n%uq}X|Q(ek/E,"tw^ قD"čNr:ωk]J1(X9'6G*w5=2gH Q"9:ڵjN1Դ0nnחr[p{'2î=_K̰I*}K2 !qrRTuLG;95ǰ'#5L{ipm1–Ƨ7ZlmX)YCYil86;\s=R1J*(u=0DUu#?z4w6Ls+=r̄7bb0'Q72M{~ ymo".7bs5Ox,0q`1X-I.i?giwD~ Vl4˭6[h̛;_?|DH:+s{<,. UKRaC i=/P>*g^jZYΟ{,MUQdm4?nn4ͤ[[K;VF_B]gFdž el0+ү&ɡ'&45?A[w92Hj\;=f(.i[m>Yx%?+7Z|Q[ ۂi'jŧ^4_xf܌Tav{^5nGK\vni, SO{(]QFDifl.k>vXI-_Y8Wgi1KVC6޿x?'E[+ķuisWFj֦5FzΈ4>1v4& 5k)?ο2*غZzеc'4oK %pd=5wdG^o nzմd!FvI +_/Tr}5nA~C7bF`0z.w&Gr[ֶ+jY_DO!Y/dy ?,cW+%!ZC:xӹ.a7 +Mj*nt0ǖ R"Kgq qW|StxF7s2[8YcobOja9Wvsr2?2W؅<2 B[j2c L X*]z5Qt3TnT6ֹL~Ӏ9=#NJ׎ueX֎f&2:?C oZ bF )4| /'{VA/)XƊ%ӛWGMA%^ڥ6DҥczW_)S?M+4]T"9pp';Cζ4l28ڄChTvlvsxD3 Z!ʬ_'Qp!sU>rR&u˺s2 ̧3Wrܲ@\$KrM@v7̀")5VEus8=W>OM*7 tuR*vslIy$gkȥhXnZk3/ JK#J"I8wTcvh)1Z.ͨ{n:܉[0%v$N U-0I[n9mݪ̒H^6;k7_y`qBO`KЎ]NC횳g rlT'\BUbH@% f*(UyjqZR 5=G!_sPM-IVD6yOl 3U)_2o#";k} U+Yg Ӯ)ј1UeC,F:Ԣ3"aFx$M Q]DR3%F+ [̑$q^VMhW3D!U*OXF vl;و'xP qMF֥%UyǭR-b|6=\nUM- ȡ[vtf؜"I`8:mVs<`7)f2:"s,Bn??95KG03sɭkd@\AH9Y]jM&1P5iCbq] -XQl;`I9|W-^xf0H@qY>voǛ,ܦ$ C^5%߹NxkE9=.L9S)Ce Rߞ~ph#PYcWlehٮ]QN97JOUq9ϭY/V#SWW<ZsECwpƱgb/#os@N3֢fE9QNhc[?>Gr}JXv8 oNnwGޢUtmxCF:uN$hPp]5kmm@sո!+_ro:ii>_aGLV嵤jBPʱUs-hpTֳ~2&6XJ}qXBwyobߧ]HbHE0s[X"d8#xp~q62ƲnmwBtsEl[nf}LFO8̤ =Ԝ|իÅ*)V$,ZfY_uXw>j$- Q_R!FY /NsK/qs#""NϵWUqsTЄ孶(iUո*$g8nY3gq0id'CJ)e$7H犯e$ʒr T7:p3G)wXw&,U>cIAխ}]-v#tlLc}hyrmKdPu\V=sֺOA2H sbFBṾw&Խdxb*16J33ԗCdaLVc,n}=kaoSk*KhFģ:V Ť,K)8&5{ CJ+3ܱ5}cz wb+ Ph!d!|9y.gxʊʉTgiBN3izK<3|ۀ?ˆk{o7"4Eu>$B[Xڼ^[ՕAM5)˅jܭhw]Y$W16Ma67vPkLi"ҍ)9>WUIJ?8>rEΫIuFF\ ⫙"U g? { }&,LԜU4"؎ǸlT%gt)vijϓ*!'ݪk$wi:"-I&F9뺴0疗,kU{Wnr}UpiuZ@cXMGt7j"5+LЙM!+#F@5Y&9+ZZG3:*`H98mt7aee@y40L]?*9͹'yq8*zՇ֧ xaaa jz|_,sz(p}%jmF=+|I$w;"6w7Gv#R*;l=$0}MXm:ٔCc F;Or٩o^O9luL[Z;DwVz\H܈9D0ҼI#p{U+vܭۂi JOIfX$گmQ_!lN5˪jlY$V <Du,҇nJroO#iltT:Wܘ烚dծyXt;\R s#sEsNheܦ\ޣqr`1+? S)f֭%ҵSI:OR)&;nt2i|gՉ/$n$V*&<=VM't`;k[ad"; y1R9B>RwQGi3g,TAق+L+H-JXN:T7PzPkصoklX 'sZ1AFUzɷV HVlVm+5#?sLhN6PビPZj1pyZ}ؕ3ޭSnS0)}q'mdo᫄HnmlfHG$*%wfTH+b8Trm[ˡZ^$5$ r(Yv9艏2W3E,^Kۃn߮ Eyq \l3/Va~uiX2#O]Y꼕Ajƛ./w;FXVe3/e$(%xȥ.K̛( &E$ t0ymO4릗?ڋ1B:rMi7/8 ǚ{ƒ +}zH$&hwKmpr+ѭZc'TFx{RwUm܌OUhMeVp:_ i2\9+tt}fxe9Ԩ{9oZ!=*{ 5R/FN׽uU )<[E(eI8JV~!rNaWT~\X5΄v]l eӲW4EcBM9b)c>O;!QY猙Q dekB -9t:HeTrŜ2kpE0xQM6͎$Wd_Rm͓[fm+F8-a,:k8 B$pԗՊtb z~ۮXB֪x+5ZSO'u_BsN|7= qz;s^8H"5,)o㌞ujl1kSӼ74lWSZCmQ@@/\jJɏҰu}*um$ٹDM(D| qWԴ2!V~Z8Y)'m5Yv`CqN=єT9X-8@]̋;t+5eh٤]ЭffMZTk:R'a-SYWWcR,uiz-ȈI`S56!vXub+GaOMs׏XSj3\7EleSZn}pX.?qR'Dg*y968 ELTzx?⹹EU/vFSYۅ #˥970TRynkU3*:k\HL{4F{֩hcXT75!"@nBsJ;1{m!kf^=KF@5oz}ٚXBI-&LocYȹbu@źQJs2IIsguee{qv´尖яk+9+}"69qIqj)DosJ9rM-1=V d֦iRFbq@k5[I9GG]!WRܬ*55nzMHYlWJK l zk]f+gPCP#{^3E`PqwD1Y\夼*U7.st;JdnMf}zj)TtηݟԞ*ޡ{ ^(5ega!Xs8jbP,.b96"*M&DuJ$+b-,&JAzյ{-Q;-&ܯ"1a jZ4Np5\ ȮPAq*nt?$)bơҮ,l3bTi\;(õ4eG?0*މsMB;BX|v'GA4`Q㶔S{+3XӔ^Fj$zJu7 ?ֻFpjg yD\u#̥E<@Kƥuht[Y{}FWM<?Wh}Sc*5k顭,<Mk;gLEk/]6뇔3]Qܐ͟VG_E=kvzI4U#74*͹hDIO77l FWmX!33`WU8BQݜu%.Fg3qѓJՆ$Tix?Mp"h_]V}uK;ZSdPJ3pt6~1LvEmiH5v?0=4rH,kODb+Q=Op0j>*ME]jB'V6@1Vtˉݝ^ kWV͖K'Ҭ\rUyQ1ҫF[- O][,N+ ҮD9>Fl.dۣӡ[&sASKı0N' E3dp[\p|oi/,:s7sZǃTh i` = X:sNwlSXn'm( ^3W5Z #̘-Ǔ={hrum+D}먓KXjK]&٣FfoM-DNG'[k_ؼ}<K"@~}:8>-OHXrx5kgQN18qNլDŌ3g5i+!t]ҡm c$&g-nk\6VsbPHxy}0\JT8[($ɒ{cֳr7+RWBJgOҪfKl{+09 bZI2aAP\mIZV㑟k[5$UC]tudJX4 mDvJ7Yk-<(}_*6Ei[xijBm*R7u[Z[h @ZPWy|Aq8wXO.l8;"Kbʆ&`:3Uu)x;X]MݣB1exk\Z˴,J+ *wڥXSZ( JT9sT8.g4s*0P+R]7f7pV#Ӽ[彬xsՅh}ƸL ldVnIr#c󑊻 iI$aYڭyG)Qh,3~E 'r6`SĉH㢞+ZեbO̪NȩZh@q%*O1[:MW>\W^,f^qĦf yw}Jr1#NIYޣlA汧[[u󾭮xOsA|er?xUWSq UY{ӱUqr)GVW5'`R!cǗ OƼz\eٔwM> u5ArsEb&,-b{ؐ-rZGYkS< bƅZ(LnZyzڜk]ϦKN|x*ʷ}ib``E{pRDz72J:$ p_Jwr<\M[PMj77qcnK }8CGts"-kUO}GO|cWӒ7Nj>Qdn+1Z~Lj[HI^6iUkֱN@d֍P1=?,d“Ԛp 7ЁЯԧg"I2r/|MUlY;^H o*$(NQ@aIh[inXj9S&&O%mI76|ៜ4  * c<y&:u2GڭNWVC%SnąP9wjvb:p55<˼pzg%;6ӂ9wq스:-c LT={>Fidf؊Cc*ENZB,D&,CWO<&O9M u3WH pis'ޗ[U軌F9Ȯz@F*Zs{ ,coZ *V9XqzZC- 6v?6BX; 䚵DAq*av`䍟.ridqw4+宱*R$`xXMeƢ"qEWfX`nTփܝ j**v`ެG4k&p MSBCI]l7b:0 `ZkKUIAq2D15J/c6v2D\ujw0o,*9X{Ӿ8<5ZrƦFPT㊔B"Ƿp1Q*Yddحh(U,gYx52ZSi#T"*9&ȋ12.@ rF?18UGgʠZVkk%x>V +0w;kVQoRkyp{\\j3El\jk ,%I p}WU;&/$1[o I6ƹZԳ5YTn.ҳ-ÐrJjJXHMRKrHi"D(),Lk<3(BqIpANs@[zf$p(JPCP9*wJ7r;Ed٢)wpQs}lsuZ+[PFDҭokfb.OYOyTé$fO1'h鷆?{s]s#qںM#JU^_f>.* [P{`Wm@Hkt.C&0}MY};oeާ\#yզn+<ެ X)a?tGj嶵9 U&%I}n,Q,E}GHukY ڶ qKBK&ݬid,vVmƓ/"Rzd WTXb#XG.;KpTވ,mceTG=kQ8{]@XsjsՍrNVjyێHZlԑX5t4պ6CE~ j^ŵfi4JÃ[5)ͬ1Ύުk|xԶcLi6[ZH;f驽<:Q" cEIm9\ ۳wV'cTLV*0#56Ыkۅ07ZObB ]F]5&0A`x޵r]H]6ŴP%}u<BZfy[lgky zi9E(N+Wo.岰ꦪEdeSC*xR@۫&D;e(V5]Zt Į5 ̷vehR~EiF{7z Vktnav\Z[Uw|k9I]OSt_ Aia =EjOh뻭w.yj)y1e=2&խ1&֭ȞӢė_l*D;[ÝV^`"1UN*dνKrŤt@ԒRBD2uܱ΍ @ض+Ҵ2L9ۼ*FЅ㤛~gc jY Iy=RgݼWנE>y\8#4i(b3jb A<;Sw98IZ+MmvWW@jXԕKlt”mݖ瞕ZI;<&9 ;RtYBR弑)9jJI=km$Fk^wFrJM.kqӭyo4YViZ :PI-$aŗ+nWXtl{Pܮ&F :8^OOMu3B"4 ^% Onʒ ͏]ԙ7ݜn=?Én TK ۏH(Np)VN ‰-ex\`sR? 3Rqtd{ %ly1X^:7$ݻ Pm6X* ɔckfOMrh $[|qfmhMq2FR>e7W"9fOeN1rݝY]V6=aΤ?xVK,7wϒYQ[ޑKUiBԻ'/YFE^Ry5^<1= bjRJR4U*r*kٗt0R|恺5i$.mW渄]u=7PF_,+rr MNI;mN5aVhk6BGtZش49! zͶB9.IK}l@YC{olm]^ Gų猑;+/MEFyW5wGs&Ss$=|sp۹U-8tnrˊJ,k6XIXM"hgJsi1>\Vw\Vt;1ڦNr 5!D@Y Zh|uT95/kTzҶ-2zӳMy+37't57q147Wu 췳!nv0+&kN*5m~Ϻ"UKQ7WjmۣFNtPh r|jm:rA6Į?f-p qhT>h WڃȞȦQY5"7#+F3,Hji: vrjm&ܬ]Ȫi=nU'ʬ:}2kxA*Xg>jkkDrGˊ" !܌PȠU#r95+٨Y%/m {QzÚkj7LglлOܺ{=V@R59FsV] AZ(=nd8F+'"}^2nGV`Eyׅf\K4/~Ag`oQjF^TJ:Yd3u޲bTp}J|u:vs!_ ͥvˢuְQqfY1_1PIؘ*&OeKdqkVi=Y3N]")H٪} Wiшgyf5_.-6gX/VkνD*?V4?L]V'үYh\j9'$柵{^tG(YVEZ/ IrGJBGTlW<嬥_-8d?QK<(PݽΜ.5Q+/Q۹Y/( tw19rGJ՚+hUL29i*G\fn "Rm9֞+X7EjGe_{`{EzQV31{ HE׉x$*զL-_k ᳚7;k``QJ"':0IIВh1G&vv0wjHvBh!qZ{6;PdsV}V;s)isFCk Dk;Ȧ]VqYNjǚmE6aO§,HY 3TQ-jO^[k rLr^> l BXxR@æ[[(칷+SUsx/`v.x*̒ݥU~&۹+$SH1"Oy65(T9(ҭ֝~$2n=N9a(<]ht0VÎ;-m9C)$RfLz( [u\cnMsdR~T$`"` 'On,^- N- 87]ci>gk7x.AJA8W/. y=8Ͷ֕KCks̈?S-wM]OCVjo$zX\/ՇiТ.$#9*0F+t{FKq^dr7Rrޠڒ{n$:Vtݱ5#Oh:FG ь_&Dܗ B|1MWFhn%Mބ渹i E,HGtT'L0XYX5'5H^JH=f:L¾ʱC&A;+z=KNy|S\xA`YSr蒧5f.%ij?٭,Bf 2cta,+Gi_55x䶒=,p~P iK #jY_jIFIq[(ڌ'%u' OYܴ//sq֞/蚈i; MAz3UN 1jv_6֧omsK5^ZZHZf1T|IZO(*/#5yң*RW:y?ꐅ֮C1]֪O*RnҲ< VC.!HWE/Py+'R3U$j߉_Oi\M <uzȢ=+N5TJJtψu':H#_~+?ڳO4 &[E&&$pyUzM-)סNq>ִ}׳ps:xZN+T7k[Hnlm.bLpW|Zm.9'TfU5vKtbd++Sb*㸮8˙Hv đE.95MguwнĊ =Z&ȝN6=5F=А0#hzI8$+\&.hHqZB iKE`8k"I1Ɲ+މu7qI "$u{J?+qm[㠫bs)4PI%H|N*F%gxWkmx'!js{G_ERI4l]H۸x:qQ‘CCMTq-CNwIB+^^XlQqW&p>`w<[Crv㸢 dU\w$'7':qUmqvf3q2P*1?O9P0=qQN7J gZo fR$>X]izt㹅wF~lՏ-հp۲HpiO9dD :gT>P)N0VOSoz[qm7Bt[c޵`cUr1BQ^&JQԡ#m.0|#/QYDVA*T tcTN-#h-S=_L$PG[΄UM=JK#q z86S:Z;x$0oMɻ+W=ɷE0Gfr<OqwwuTԬFqiEv)˛D77)T0`RZRƄiQZFWf:j}:Z0 YM6a|o$*w Vb vНAPYrjْwkO U2sL%xPLwWM̮B!"BڜnF<ӎ# <)֨N`QvǢ%+CTɿ?5;P0Lb5NʱBK|ǟjv\oe-x[8_Aҝo`` M*!@jۅYIh>Nm M%nQֵ!YYZ(!PIU쏰'8Ushe=is;]THԆ/%݊_LQE{9;瑀L fpl℗QM&t~e$eӞ4C&$2OSYSEC.޵%/T=}*oCX d*p*6H;>jll mT5Ov}-%G@ jЙ$!=*]AB&KmϷW$BufTQH H5cD/JGϦ+.r0x_by]=̯3Z~'NEIPs,[bwhr;"Mpy%'9롷FHٟei9^Zs\$SiXAfC ڬ*c"oeG b5Fa0j.EBƽyLӵPf Y`yVcl,I~ɸas=iӓ.N)h;ILז=yiP^vb+n0sj.}E[mcNkSKiIn%edN-cXP휖sʱU&[Þ޴E7FŝV$sqP]A^Vs#Da]ZӗAsj` V'\Obʀ 2f3 +A-6 4l0 mCVFp1гW.GQY8ylִCPՋ`d>tݐ}vI.ź:VnjTFƁ,>c9_C*yּ0O2XI n!NKcɎ榱4.< ᳺ8.sm9@a AVȷnfvMiR&#OAi[6B<ȡPsTvw^"Ьd4~4dz"@+XlCu+ǭuE -Xk=#aS+)/chNڥsЙ#\yKj1L: rv=O#1! Dx&>R~^ᇿQ:qE9Ƨ02m[9$oeZoxqqm4*&tV4,r:{5R +{2[$[%ֆZ5cԼ9[d'*GU'* 6rդ]_elS)%b] ^mI!#!W\wʜdGUXM+lb*&@e$6sBUQ咿14;F$drv4"lHjav6#n2hԋO8R1Xm/ycdS|i Ejm22i[t qԞM\Eq nnܙrrrzyPf)%5*Ll t3͎IiknR|,jt9hӒm7cAy3;F!7\vD@Tl"vk6J 7QSΥ2\'T"[^NN-kg}2פk170|ck+״MLjd2?J:w*o#a񶺗y|97 Gcnw,ʊG]ee_U~bCYb*\6KF#1oQJN槐;r]w:*w6+H##Ǵ?ax[ˤeKs S.̫J:Ʊ%KxT sakMIF#}kF}}=9,r:N-me9+ZOs 5)7Qi_-_#S[|kM:Rg91p숫9w:%gj/=uaZI*c UR~Gbf1SRvP1T3u.Y_g_ѣGqcp3+[nϳ]NkEFLu=nx{Gd)T5618`ER$krrGeht돝]XP 0QtQe\ФwG M$O\ &}amX}0sZIԘEoQO"IVU$1 ʛ[QhTV+j׀Z"+{NhDu0A~d$}RD⹑06mr /~A cT-~B@Fz9!{G-f *U,3s"cs05aiYuK˴W'"mn$-s{vB3Z)SSVFEX#SVt+pc\x~BػIP7VicU-+)Z9=9WEkfyziלPZ䎯fuse6`[VZI6qKߏN*K=NKZĖ0s+nc%WiI̹z[i#S\ld:~rް*J۹8^Q^]XGUy#M{?ʩC~&6}#9L6FnWsu<;}4ߺկA<~5yr`s*HuO!؁jL}͙rHҜS\aok9@[˜05Er|\>gNzWam8 /r VSsI -G\at$j{-VѷyvP*3N2;G,r}._?%u0;Zj e}pci2HcjE*4F\tnd Y]GFkOSXWy{'٦t?K{Dl,s^ss"ik*@ݐs<:Jܓ=ZRnCd"I`-m$wɯ?ҵa,@S4V'ʋ:rɦ[_,GKc"E-axQ7PܖB_}jyGLdn0LcixXZuwMƥX5KqZ꺨 -"175h7$K |Vl twA O;Tjrw|V:q$3]#$4̋Ш1tJ@^s!T;ZOAP{YT;c@|+#4NEf_X_(3Z }~0T= Q'fXųr&s?wmGt&*E8bZ[˵˕0X7_õtZj18%/l hv6?;kzuo+X%.3 z?-G̮x¥A npg{]ߥ>[G|c 'L2{]SVV̩Z}]CFĽpX蚀I"CV::2LU&Hҡ;ZוNǷejv#kI,uxI}zxz8K$V\z*]QzYJjJD~ԿM:8(5.2"-OYc[U`dY^%Vi ^69!ݚSv* 3>iaȮYIj0(^H揬k0:N2cJ쭡{$}x?T Jqؒyj$Jb\ter6.g%r8k.:>: mNV\dM&ybʰDžutUfz%qֿK[}*"srq]4&;~ku8ƕ?hܯJR{;+kFI-od3僑]shY: Y_jwi qZJ}\RV|9Z *euT~x7ųhP3\ `nO?S?4ky q\)VuoCBzy8n Vms SJт8zo:cam3VOt []0+|k:[%*A"[c,@\kIG#~yi/<4ta/}غ67Bs!QISySiI 5Q6imc_ a[YZ3P@b8I񞯧2^xvH5GSYt 'n( ~tTïrBzv^?ڵ\]FSeZZ\s4kyo (⑻,:/՜6Qa{$$}jYß-Ozl~g#J~{ZJC<[jc[pZſoͧ=YP1Rx^y1"Q^xclPܪ*[sk{ou+I \ֲukM6}Ep+ ɢ`xO_/Aծ&U)F._3Qs^Y#DhqߟƩ]% OGb3[lEp*QcЮbBMIlKU|ciR5 tb7GYQ2 fj~3;my*m#@!YS uOI#`j>- {A% #(oβR2mTmŮo~(EcdlH+ WG7Ǚ և&ْ+kKM^!Z~Wwh:_k0#zWJBUpRi? }M6I #q~O0$c jV=ީO%ۙd/uV:[*uRQחa}z_Kw}lw 湅iHl'̻}QڰHMrz2ZJrY yUv(=j4}`A䚩SvkGte@Os9Tm<d @X6zs3)[wbԄь|4׺K! 08JĽGl2Ż0GRfԛ {U&IV^s]Fq +Pw%gc7V4sYS%ޥvpZBnH|c9Xi˜@5ND);'*ElXޕ`e_{h^'7Oj6Ѷm Žǭ#_G'$sFzVv*?/N`䢝ѭM hvS<~5R6|=2iK}Ԯ&i'sKNgg;=E>TR Ԝm@!>cCg#smQ%>RJ"qXMVxĀ(*@䚷,!p.<syTfI+ح6BY0IPmeAb'9n<: Sqpm6$U9yef|U[+xxy[Gm Ժ@۶7 'oQ"0$U-]ފjH@K1q_ݱiCU6F $R1֪j "[# 4H'sVk OLV4Xĥ[b&om!RZp%ǩYI2 ۺ+gndxD~JҹWP+RS59nk6W3r6~MTwl҉`wn':?,3CDS,d^+# 70*D-P2F2E$`0l}j՚؆͒'nμd#*#v*U֎)5-)Ě2LeIn:Sdn8b6hŋ)Jp`ގ `Vm9#We*bb;`Uǹ2عvXD7)9̬O@N8$*6h]ĂM9=gƱݔI|hB1\g=OkM9'ٙlYCnH;&܊235zIEHfIWpdi#۸@m /WAg 1XɻlLbBsG÷[q&d`-kMvˑY:}6΢-^,&ܲsk9;rfa2nj5NLuC^ZIYJeE?tWIOo;h&iI"cvAh%rn®N_;F SZ)nfe`}+ K! >sY9H53b坎s߭GceT񚻨D1]WNx4ڹ+<TDfKQ[F<DŽ0KR3|c>FImö(,5^"l,a9]qzZmI{}t@f 9TSC5Pamëwʭj38~+)&r[V8W^a[i's;[v#ޚ˶B V卢 țج}B-He$KW*z?=7%zmLƤdEh $rLOcx'*̺~VM,yJ@*bOT tW4s5tٛBsT&DkpNrFq]Nd]q\Ts~e dwr*b!A 1+'=9q&r6=*d[;ϯzk]Ol<ZI԰$XVEp}zV$NBSBʐ3]/tx-<,npV.J5T`157+uU]{Ox*ƌA4o8IW_΅yQ1[o!5iOH۩NY@yy.;Yr:fyzd k(-3KFxU )XT,\iM cTsIdF08%+SZ$2ƅsXVPI/uXVn_rgsfg &1[IC7'f)n`W WWE@ _=x57uPm$Rj dR}w56RwjkqiB`_1NDrdp mv#sfGs$bG~L~UoСKe w=ރ5B) WO&⢻#beGn0!Q!>I@#":q[e۠p`4DF?D $}+V=Oa%^ՙ~B5G@\GnyϨri6.֭U\#f,?%9^ d۩Fv:mRCGF8*qqR 3vZK:Zl?q֫KuUHmtec͊ϩC.HTpsKf#gjf"v![!>Qϙ)SWEQ~c2Bf{TfSfAsPmel:毼i{m+k R֑9ײ5~ϘD iuU1|Xn?-->qՉ75XAcRzTAΟM=P!&eIxuvmyڼ2u"Nۼ`moCi%{.p{J;{8XO\H63I~qTx˫Iez).] ot% æW Hyr\R>I1w-%aPmcck\%(q_0b&qtR89'*^u}&[)OLɫ5{ |WzQz{k;l3dl^^bؑcxڇI$u^\1|}k9^ӉTW.ƹhK` L̛[I *5hFOP=wν^+uA+ӑTr=3 miG̘[+D-ԦkB mVv*)qYS;#F#E]$p8 gkk\]{F;W!VиPoݗ.gоUCK@|͸/sd5 :]1NZ(qyy2"Yl[Q$ѶCYlvpj5Ȓ7%C-i~JI4JG+f`uwiNMv9̫zVI5u7}IykBOܖ@X*S>^A_iPڴ-Ԓ#Mch ^g/?Zsx~G~qn8YUFQ\2hYs/d~qNtM*Ϸ["y~ˤ3՝?Y?t$+`h;ԭ+?`UQ!" _:. W:K{ǫVBǜ :qN6DeS _+߹WuY" 0*tV]k:bDE@x>軉";*xWD]+xՊ9M[s3}I_8uGdgiڸs.xEu^5Pt 7ҽJ'&V[[Y#'XOKyo<-e]6E`98*ۉ〯XZt&޷6*cYIZL%JJ^Fmf_5c̈́Xן&({3V5|%-'Q5nb*'{,m>A5(PƪAa7-&uC=1LV-^+5su$d*{Zv HI7wEqn0HJjZ#Zӊm};~iDb ܼQsc}9̗ c$kCm#f^ĵc:R4%W{`k^+2EtwWz\*wP 5_ fylb6sԪJ3Ivejxq*J?##ӣR}ޘ3toQXE<$s*Sڹ^.M<9p?>kKi#ljRY9.9t_%Wfq}kyҜiB:9W֑pKJV1ƸU͊85h8Ab9Ԫ_=&w%$m>.us#Y%KKXFy&ԬSoCT 1I1-RD0Z͒.=fR *ޟ[[.:9k # N*=+[W1pfªW2XԝCpүx{T㘁s-aeP?5%40K5mͩKM$txJc ݎ?:QR$m5[m>Ռ2XIMj'Pi,<\4cUk\9E*dQG#+m%o,:qb7O$(Sz9ԝE~To[MlsLzҵt$=Jמl"_01"*𧆴_(,Z~g\ ֍TY5ͿԒ[yɅP+2^k44n㚋I!|å^,^&0rrSoa~uړ U zu&ۀ^C\[_ N?uA{N!k~1?fad(k9amQ`7-5cgQ{Ǝ,u;a撫Kr!iEex/-/T[k*ӒNQm/՜%GL+7JabGGɭ9l˫OT74I;FN^`ԩhk'N.ܘI"5 zNxznJ.&t6"-y @\oAY3]^s|TVh,mmԨKpe7USVqpM0n&lYZ.#j n>$m b2?wV-Z D{sTN+?$]Jﳺ=U4Ij{G(m@YS5斾+<;05i^9bi 3sP4J2aYI?=ej߼er|+/$֦{is -vH8˖c}*u:j2! ZÚmf9d?QZJiYs9Vc]fC+&SqvhCcsW-Azlwj(ATm-?:AV:~V)*55-=v]d+]NNF91qcި> 4P$Hӭom{G~@?a9JveÒ ͙)d d;ΩjKL4qyat>{D`wΠUL i'c_q(^܊: pTs MPFB9Zwk1[y-Xʷ<&k8%e DM%@fŢoE>*r6F~Z<圲ƷJђR]v2)72~'TѮ'=֛qxG f'mVPLȿ,p?Pyc~'+|JYAVrS8u~YGQ׶m 6Ar-K=ՅN( )i<|ׯzHߩ:/x^.(f!lH8\^+սfm'*tb{N̔%R[K($M4y"&? Nm< BOm)o^VTvට Wtk)`Vï]:n|M|@~=VS-cbGߨu_^H~5Ȉm!llʝ )I^Kuʮ&֏r,>ߪܗP8Qs+_T{{ US U0ڣp銊7vJ%(cgú"I/鼊}9f{j'vѢ Hj rG5("@l8`o I8*]^ՋnPCc֝c;W ;2A>2׭ #cn-z@r7uTLUYUo/ kv*Q2-G ex9zVAgG2f9dbZP#v9jlo7x;xm@8˖lMM{Zdo*V"{ͱp]I9 Q]$89 s@V)ư2)/v3rEnG8$T~5X]]*C'>fؚ\ U,|+zң٣rB2=+'U"o6m 85biw i@\WZƭ&PiGqs"Mh*y/bBJv5$" EacEߗj5nQ 2jv$C,9@#dYfyRO5qV2Lx;B&=h'w?1=I ;s֥鹠_LN~[H Ky2!QNsYuf@zSKM j'~|;۴qMkn1ڪzԂO%Llr1v5U- bg5vE8.X>RcgTޤ4V.ùa=yc)LT+)\tɩ$|Ͷff]턺af2dde&3S.d U}=zȞYש..]Z½k [ΑsSY#BVXi"s3n2m87lxE5I#i-g/3ypۙeY@Nk-4h\`q+yM;Xqrin5& hEpH@^+̓GydձW}l͛5T ݌3MIĄʊ>,pZ*RHGz cʁYȸs5bt̮T{E-0Fŏf'wZnJm/=qPˡ\Rn[d$o.2@= RF=Qzz%=Os8,?ƲQ6-xF(A8'+oڄirP |@GzͅiLVFPzzS>,[MAn6w͙J(%2T&DƔw2 >B*pC0qTeuse.NF8°ob>AȮFd["mr$ҵ*-- L7nzѓh0Biވc5;*~$ ֧TnO&ElŐ +׮ :<j#vS|9ÇbFxZnY\ޓ^Lݔ*[A)CȪV&|cE,`Y#X*wF`K*Ӎٕ_iGnMʬAmܜU GDmpfxR_*G& )$4n6 :3ʃ=8-)+#X;Ieq '};W)t{b0)9kd H6akRk_0j>aPFs]~&`:Lq>m0HG}E*q{#'QNWq7k![-+],HUͶ$ *lv52%\Dya}K\ωsۆxˤ* Ů. J4@Mo?rۘIή X[ET/X]mٖ r&@":1[ SU&h^v$յ?p9u2^v wtKBGa$]2QH6oKR)| qrO\~5i^n.+X75.BpOq?*jfw-Urح>=k/ңG2 U")$x72e:VPw1OSR0ki rvElh m@0 0k45U3 p^1n#ʹ+IDT:ն _G^(l Ski8T\7(V"ۏ-u]ee-qcm"2:ʵ$%JzM>U!\EYh>u9bcPFPW]#[CkUk>^z]nZ߻1c\k-nf+TiάMչQ-cb8"Mcͣ4ضRys2?"e`!V"#tާ/dGe\pRV|)C(1H z^.XH9$+Z+PcNpqs?k Nz6qִ>Opַ5@HCҖ-V H=T#_ i񪲙<QWM[rǫ V֢IS\V +O=*gVfiN7"lAT.S/beu,N=w5Zyb~M:u%}LkNRBC)lN]=d9–ֺ ⺋ txBҴ枌0VGvv"HO| < bz "Ƀi+gQKtcؘܴ"%u*[8S#c5KͧI=ꝕ֗dSNJƪrc2<;K;vzV\:6O5KS[ Qݞ]ҹ%z* GUn}x{yU K\$͜{_.UHe9RQw`ܥ>6)24qcm {a2H:nQ_OI5<"Yh60U:I&pW݇})5!?cn:q [+>]Wؙ;5Jšr$/'5M-ȃdm-'1@}Izf~%yS OOƻ"s6Lh021#b'BrG5j' y=vs%d Dy_y+r6k_37mTܛ-ίa̯1cinN110"t?Jmd* a'G ¬ FQcm4ņ,­'Vzyݥ\;ĸlhcS]ӺWhXvy˽@FGPTҬx4EwF㝪0k.L@PT5KIFIjYRFm~x U i;C'R6MxFH\2e85w^;7:uE=\^мE :Vg:vr9(TN0ON;Rwy';UBƷ!XlT~!S̓_O#Eg3qtthŃjȮMӭ * 0^Lז^K]1\F𳜚MiCg{&G-[b)b#8OSlk'w7~@Tw95iz7(iu]BKf$W'5Egt)[7U.1kpNdoo^w _j1U$)x^xɘjws~zu8CjZZmKHb6i֑w!:6䓊d{}Rf!D%ZJ_i.o(hM0vxAӬ>l,ָ _V1mD>M|_MuqXF5iY<4%YÔB1|ƥDB'o3S= n"}oHp5Mˏa jՎ>zކͧlܩ{GuR캭ř=|?63[Qyɮ JIٸT:0X`CMwμlkȃND\ V5kr tw=Yv Ԕ#UXS[qSF߈ bl ci6Z|9P6cf"(HҔ%%g'cĚW2`UxC <]-IR@y56l豒NNVU록zh#>[ ps@ONd3=ςff <7%UNi.,ֳG_Y}E!UYHT[~V2AN+| o ods[ɠ q'y1mo449xgb rI5|)nSҶnN@GJb3+ղ?k$̋Ŕt?RA.y?WŮTe+JO#S9cW:'qJIh]p2I)W +Ҏ+;d@(_Fs8u6֚|$b9!aLu0wHE?:/#JSq11B8Q}/RQ>R D 栰mccK n[5q-)FiG?sI-E5,XaGܟ)ӌz;^`~bsƪwA16O?"MZti\!J20"NEZu ;4pǔq]\7rE/"cqT5+׌QYT cY'`IZ}WJON#,R z+ '|:7y2/j.KNÂ@檪]UFѿey Nzlbb-ΰF;yI2k_jچv$V$\9ü|I>: FV7qC/)*ӂ*ZftTR-;\_i"O^p矯z}x\YI!~6,W$ptMuAcuh-Y䗮$ d#,I0+yh$(iyWt$r4띏n+cH&+nܩ 6d-yjd g)u< @sOʶ%t9Pv"Ź"?@ #t.Lq`ח~yU nCO< kޓGeLdyUun )NʮYkfbP%<纕+G Ok:v}LgWRs;IAZ|NG$eR +'?|eo:-4Ӻѷ&rU7e1Ln%IcՔmt9KOĶ1]NwRo2,w)VΫD泆;f0+t!v1u4$ηB.~ͩ\GOx5LNy]ST6F>x}ѶyXaכdzoEwjr V V'S ˺%݆]du1=;>iֱɸ1\̣qKB^>i(Ƅ2*bjh EsWjuujX uKrx0C%Y=NmEegϙ*kn`ˀSʸm?"oƴ;+E%rSkW|#Śv!?ZM91H?ZoQyp6(IxݳR9J7f8.e+#{2Ƨw`Kjo@Y^GoL,e2jV|}nbW"oXTTsMrvq7Gcq?n$׭cco+?Za͞Xc7㝷 º`KME6 ԟVpcs'J%y>C~<FOmηv$Vhy9ks']0֯odrI>dM ewD{7s`AZzeQjF}H%jtїRH;OjGJ㯯֡]29vi-pcm#*Li6#=M nyJ2b [E#ɗ9՟j#V{5gqZM3'^P"0<7ζNԖJ1@Xm0z55`lU;[R"%FV$pj:IBv;J]4CkrsVMm e+3JSr 2qYwv "sZ7*-s)t]e?vv֨E/\`lqU5 $ҪGon% ¬X5 4 4&A}.eX d3X̋<*9ý/9#`fuQ݊,|1WR`TPU&HVY#8A~[N)IXc ivqR\܈if\dV F'Y%fxfEGK&3ܻd,d=[ʧ0z&Pskv C\bi莚HŘFi>1=ysU6J ޵(iL`t5f-V5ُ85KQsZ+t:sZh#M͒j_*؅lԚfqiFNA*q/>q)$$AxL/ck+iym8zINOzk6%Vc1ڪ-SY"BB8U;9gU\cb`@/&3y1-SN nY U71|ʹ~Z8iam<@-ž٦̥sM1,82ڻRcRT澕]eRݚh6 Xymq]FUqk9ԓ89=RLS}[Afdrαueo<*O5ewyG՝ʲdXv)+]A$6#=NGSQ2yՋF榕RKrUN{t%;7BiGeuQ @Ḥ.ι>e܀x=-*۹9윆XjǼ$ZCJěR|U9ibs!qO1F3ְ wtҴ$=URnq,/RڹўrXi"Uds~06o.#[$2٫75:j󟈲/ ;T.GJ‹iVpwf6E̦=qg:4a2a=*Χog,~`: F<`k&S.𨼜kwTn J{3mqPfy}*χB-14q}j;")6\AjJ6нIO:|Z,R3lޕޝ @GxW]fY^[GAXTSGDf.\:`IDnk˾8_ZKV%s^ Rs{9"GR xoT{aGlVjO0ĸFznAM=ѕ,1Mj]J4 19>GdT^0SzL&@ %<3^ft-*^cF*Ѩ%j6O1^1W~rTp:nn.S<[0h}c}RMP򭴑a8=MsJb.56d9K6jDn=4}e-[ 7{}@rD8=E6Ï^l-%8dG^;<$G)>S lrqwL2I?W@T&J污h=CF y8 z[#0*;;6)B` =2P:>% 57Ytq2^u}n7k8kCnouO֜N1m9\tۜ4<$0VxCO0(*̵FQ5*( qPVl۔_ i1rtKi0Adkz\ Xw1żȧV/VnA(+&6H͎˹7"ȅSXZ`3xoiӊ %楬Ho(Md2ZL^)~ٮsW\b axf%Ū)j mkxPծQ:x+w]qwr VnO*,`iXjSVJ(Ij.E̘-ri#F0AQgpGZܺի]>Q MS"yzEvI5ʞZ24Lmd48ZM/$p[lzDeR8OO֊عPy <0 0:#8XrE_%AI *zUyl"dܚ#۸d=ogErLR3 `3Qiqԅ3]քI4b=XxQkV8 / kf[H7]h !#-gaK,F U4:Ѻs^_9KQ ﵋b'L@^唌@iKs 8Mj[EC2Ei*Z"+NKN6TY gJ+dO]&L pBkMjY$XwUrFQbOGs:g^cvL^yRsv:K̄Z0q`3R^=M3VR8Έ޾$-ψ0cY7sIK'q5Ri#ٵ-B(rH1XZiG͑moW4K)a5i'} :)AZqhsfk >s}X1cR/*~5躏1qFSJ4_$JR[/ x{~!0:#:2HIo{#%R>[0LIeojCI4I Y#ʦn *vTZכmcf lche`:\,$[@siL lCo[d(qG瓙ʶ3+ϮYŴnjϊdw!LTMSg%i/-ui Ap%Dk n84h[6 H?'*T,1#T7W]W q.?0@|?h۷*͝ΧEsluabA\߂ȫy,HSjPځ2gspjP@釅O;cYG ˘[sSkoj;eBsyuC~d*z2+EqUB8CEmzƣ* $Cos\_|}8GuF*?vķ@Co ?dR8U*'(iWFM/n.[";hY8@Rb{$T6^&` ܔ O |f ٹ m좾(ܭ+-䚄JH@\Z gnt=*k}W\U'Э-fzc5QdWnc&:=dggVEψ&-HF14{#o29*w(s97P[ɾ8B1&]~| (jXcsUAQc?gkR%dZ.ؿw.a#G=Z3{=:bbAzE(sY}^е?vz,Ffr ,"KO0j㴋{;YC $\t9myF9ftsmNϿ|]? UXs]WM&y_Dދ)ZuFR3*C5-97RN/i$ Gi?jG>Ѥ^\0+nKKIeU$#"X8Eb8SR{}F :/Y4s&G9ӤitSQ99YjZxJTa2y0 `{'Zi2ͧh0rISxdFߡUeXa&'@cL oOA\2۳8$>'5zu#zԬ ukUBN~f|Z/yծe[mgqv3\Sr5VN[SפlG.Q\/0͸pqc{PV15kz\)|I5JoG jsw9SQ?9aӒ:zS v+B=y m`Bla[ӚKLs\jZejl/ǩ=EMh@v6yʤ56 Y y;jPq)\~5ewR<@u'?x\6C_-lh7;ՐZH4ՏF6qSw>_ x_^sy})Z!\k4mTѿ|/1IpNtיJ\ֱZgjV[8u΍^[)E[ BQON3YI0Rpz"JPtA6u4Q>HVh\(qVm(M@]r}Jl֮ixEq;TKWcPVW_qR^Yd"tZe rIoukjEN2r?* ;JM߲;tx4{Dl:gXZv;" wܝ+Qn(U s??>܂jv ^rtl)FMRki ؘƚmB5xK]RpH6+ ,"KpQ⺧՝DV-ikϫ:R|Ck%n(PZx.pk@R{x$`w杤OTk c!ssrg{+oo(=7_!r_Rc\Wu°^'VI?c5|Xu@GR7XUbϡBw'ctgN܍JHFrm䙄z* =/T_>XhlgN6fvsU >b.]UG\ZlwBx"l~T K[TIcV*ÞyH!TB;$ GshdsX'x[ZY&[sOyڬp"cp"GcIrr񗺋vۚ:hG/DJ 7c|6:9:wvm /n8}.3zzUym;5ݢ\4΄cB?,U/EweXGVi<򋱖 }f5 5pcg]Md@̜Z3^jX*5ŚrUsGm<(]]Hլ5_h!PEk*,T_Sp'g}J8퉄Ҋ"'Udۻo..O;jHٝʾq >յ[ۋ_1XCӸ5-h1)n2zy}FD! _O(/fVSdi[s:p~Pr*e{D/@6}.[@pMiq)sVJnڛ2-OpCH*jH;WdpҞVeU,VLGJjcJ๷ ! Ɲicr)q+afD56v*7ss)8[[o^1'O0U7&UӯaYxV]e8h9O9U^T@Q,l*j"۳OP2\}IqEQN(4TH py5oIlu p)v讂d(G/.I6)eVSm1f KmĒǑbN撕$dC#q*HWs fP`޳'{m=vfXVFs[HX܅~$χXy:ҕK"á s chz:Ưj:z*Uˊ" k+kg0F9T\[=r5ʨa_+«&N6DM\hd1IA뷎ԱڼNȧ~x(Fْ{[K H"9 w'5"gnT7~R7wOq6L ,`YLk%HǜwA/^3OA c$6&QZEp?u4rFlޢ|aަ{w{ B;4gr$A^r-m~nK&{sҝ+,90l<X72$*ә#'/k ýRPz5l I#&@9b{2Ŵ k6&PCd]P˜K$jdT7`)GWbѪǾ@j]R7m0#l0=D: )Sҥ]GsWF7)UA݃Zڲ9!gs"/<[+Dkz~U^gOQ\ H9bz6Hm6NG^CXG Y7tm~5RCCXL(% q޺-;-j\pR?o<=x'YZ TT%ŌU$wW\ٕPD&1VRB`cA1= ^۲DšN5XmqXw$6fFL װӍISI3&[8%˩vn3MXS5gr;X 95FvwU[1]s`1'z sگƦO|+*z%3rq0LU{UlSKH>eq[q n'rDQVǭCt[l&IJZBX댊;ܵCck B'n xN@nFFrj(3oŸr%Zmx7ڼ\#*ݐp Ì`/Ҝ5.xT%]&j7os q#9$V߇fkv8DGnOSqn*5:AQGsHH#޽^ԼIhH38azW+]"8 ,=Ҝu3)7khd^< 25;) eSֲonF,6{ V:Cij|+ٻW: ci#$V5Ȗ.$hqi>1Jc7n{ŝeƵyۘJ"GW5w]G 9=sgFڃfD K )+|{XLaw!\P˕[~.jLpk -żAz;t&kZ5U$ߡ{ =E pjfL)$\Ztirj.z֬rk ̕V,8%u}yZ^to5#ʷLZ".J}N!= +$s1VoW. "Y3[ɒ[)zd$/lʆ<*-GW3ѻck:ez{w4%b1W2%b(LcZBr[*;D d ͩNqY9<ۘ\akyߥKmNsڜ(J,TQ}gd wbzަ|0gv9` r9.ʳkO!bo#ܑaFBl 0iŮ'v^HOn-#1(^24{fjSfKzaكOJqϾ-QGxeqyoDXBa$ֻ=yA. {SfO$jf rT]hB6ެKY9/WB\ J7?'8?v>:=wL*H,ܪc8MfRoAyR0 BF-0;KohY2YLç?Z}ٱo9#9_^+FgCiQ+[&5"ެt%mʩh^xOD2M(#HNop@* <ۘL{V1S``wT'ӵkZ<=GDi-.n'[v|MGS۝R#),:n sZEhVND5[JL,F[5bJѲV==9~]8IDi|zLU5`)@kjopUPPti}6 N2GJYE)FrMse04w-HO yvmƾU6&aWLCNmV2Q|ѹ#O3t\!?uW2yaz*Y-2Gtӑ MivJ7ºXdԶ3 kZ8*Ԍ5֤ P{Wh<.Jjx|:ԋCNw3![*Vw2gGq+AmƥFe&5K"_j.Tֺ 7R,%s&VAJZs|ƉDˑu&6ӡ'^+num&83! :5᥻?5]r.ǥ7P˪}qmݧa8#&v[q(+TDpE8JzdڬW5ZJ74[Y3Ѷ]ćiʇnX"C`3b_FuhrvGϨfNzda?$.ǁW_JmǕ|I2^[@]HuDA䴟ΑI<"".w[fX$=PX ִVGώ:RNӐH[z.ڇx$b>+BOqkubVXƵmiXFmsKxTK[O`ͼ<~Ui72gPdV#又$i8g]/[H M`R?' o.k"Z]n[IQ괫Kplj%Q|oC OimqVjY > Z6̃ͱv_R.t&]&B{ 8Mٕ)w91ECVZ ٬kJG3ۜuYuyZĶvo+I΂Mr+\( kov&KʶfԻ?i.غ15\Mof6K2UWZ-K8r/ {jG> akwdy%M:5u+I4@씿+ׇyˁ\X*ncd-T l '%BEoF̧'$$IȊ)n>["|wq(k@<4OZ tpi Y:e9Ί'̯2^ETf"xixdSǦ[9-s,dTSZnE~;kS yJ- k S85.cy2 oPƲC]3|>%'ՏMb6T5=[O ~h(b1(KS_ijty'2ctX5 CrFx(rJK)$PWW %# *yӖ|BH\G*#f;QqA'pt M!#PdTҗ"0ĒHl&EۦG^F+kɢGJ`0m^+ ъkڜ*wz]΢|NCTr+n'x"sWe~95Uxb0UɮyMir^u23@vp& n)oNֻHNk.yvmSd"S::)lBb[F@8/iֱl[=5>?^lDt"B?uusH֌tGՙ1\u-zçY8]1ʾkf/'׳ц6-k[n춰>qY?UnWwB:VΩ<,B,|=eU^iQ(=Vh!-@֬~>T lR,%9v+Nڠ]NQ= 5gQZ3źKWs]Nդ;ZРxĿb9.{yl`9iuF%:'J2 }9'#֭d/ٗWēTϢnYT8'kD.:NmhnYJr-V%rXb#"+M9ukgܶ h􌈓84]!g9:Eg ՛f@yE?f"& )=Q, {iۏҳu{Oyp׌I"h[wG*m[VQyL5S7e̩´/?vex|1_Cy-Dž~\͎?¨[:ۥEyw'jrpJJ3nקxF4Aa6pPkKk0 8Ey8$W4M%g#mvYN2R]j~ݖF5j>))܂G?zMFݢaUtBbIkJueKC(ɥO#:Fc6cO9 ֧Q#0^܄)1W,l@f'Vt:Pg}k;ԏ%y ~UjσJݢXX$ӇDG8u}.tRpZ#d 9W^еeBaZ,U}4bYlZBZ3sXgbMM/|C i7L׽ťyԚrC@?:(э;u< i>6oBQ寕yLq^YZimӮWD*{-,ZY K ߕZzUżt؎kag<[^ZJǫ0P)o3wiqWzWjdgN|9v#3},M%=NN]}!lr ۆ7VqWh|˷l`* "$@^!7ği4mEy||Ln/0q}iSCY5s:ʲߒI]\6睲s:QNӼ7j& G>S6zݦ-uCqnx8[+PSMFw(@ҵq֛Nx֌]+/'xZRЀ/R0 [mo(󬯭&ycd.;JٯQ}g~#5f M*ҝ ӆ!~zA%֮4ˇf aĞ.I5 3Ġ)ko;:ĚQYbq '% XG\[i##޴xJPW{!_7Mu= ~0@gt9n__.I^cn{bs+;6Z+c~Reg]Zp=鑙P*t6; gּ(dn[;yc- \$L(GJa[?z~5,00nV>W%cƐ0>GK2|29X(Uҡ ĬR=TؤG^򎊼d 1HFI8\boIrxSg8`W;3[m1Q^ie3){Ԛs-Y|;8E9`{ݞCo#R[))ku ]B$O}Q:\ Ɯ^fdkEb HW9=X+cTZPŎfOqz E3{T&`-i|{0iC`d`jv;e{lm-=`3ՋdH M}v!2ieXď2Wvh})`qhD6UUc*EthHdgTqWV5±]DӶb3)8ٞA7M'sp*'vѣQ=^e e\w,Tn[2#a_,[{ҦZ9kL'Ӛ޲y,O,-u)]( G%-^b2`s&qI䲣;@=vcbŹ&QoReS/n{sF{o*F0ǜUH9a1PԯcFbÎjdj {NbFzIRd@ǟio;ef8N4H;kKd(i[LJy=~|pp1ip cpeD+A~6T sZpoiq)6fiJɩ-JZm?+&[eP< +"Y<2[vZȱ}Fa2"lwEܙ^NzD T,ϡ,}lw5PܽymuRe$RVJ‹+}`MsCP§#HPjifSwRX~c?X[BI^F+R%A(=OPs#n\(D#ojaWfd#ܬvV+VFl&إmǂAr4gw9V\+* Rq6mV<$Je=jVHẙ-Y7sVܖK{fmS` yeOz*\~=M%I1qک=,nbywĻTO֠o~ɨ,y`5$[&*zp*űq/!J J,>N\uf{[i11 \tx.8]ҡB9v u9N$F.g1=@G'xJT8T,M]hm@YG\m3 ڱw2F߼W3杬z5Hբ󳟼bNq䁂o~9]KP.d:6ڱ;JdQz Q,\cER׭E* qZZ;s&u$ElZJgnC*+u",0:Z-$/ f]mDƾ?JqzխWȊ(7m;vyVFTR+WU7%Ma(um"EM,ػpsU2{)A"gիy[:BU2e"͆\qSRy6HEVGuʹ9 \*eó1<- ] %emwzYڣAaBH`kv"$McT9d[y#7J$3SA: ӌR64:KIcE"EKg6 =E%ԛ6ɮ̉ j>UEhKtRpE in:qm<X]9L`>a'=*\/T9gc_K@YT.:tQ(C\gVUW*H,Q]֧G[^48/ *Ԍk>^kwS,Z,Rd ˱\\V:dE$J"|c& Z#xSƕ}۴7ɵ1\+^uOpҬfRH5FVh940e@xZSy#u{rXFo{s]^Y'tVfGvlVGm2GՉJ^M[mvg.ٯ#]8K`N- 늳d\VJGԣ .sk%{[nmQrGaTkw`d[=6(4?9~3t-6y Sy{sctsV8LBu5 d47X숥JW*ZR=?ݓmƅEtMjZuV[q(:q$Ί/^%a =zV`zmms*3dӜFQeY}%+;KKak÷y< 辵w[_Bpk)MyckhXpH` ۱SQiιïJBf ֜e)hG//֦^v\F~±vqUo::K]:RH\K35ƟN\۴@5ZVq,רP?[SSȂUn XD]ɗQx?JמϕT:$؏?t89t2_ۗ -Yˡ^Ie0mvNqԕ^_{b b՛+l295hsToMWKwy~)Iqsi4]XQv2%ksmw<-z|Ě1/k[)!gi3:dx' $n^oBt4ՄAaF?RJP*ΛwO:W)h@5uO)4a] Z8GoeꚥdQ1$u,;0E֓ir/%sҺqZJDXkĩw|춲?8`9Ի^Vk"ۑu=OX~#49rfacɬ{I$:-O 9?h>k8F+yh*꬟&nY Z:oƲ|A[Yf5Z7ny|>HXsuUaXyI]+^\F+K:xJ.LMnbNՍQ3 Ty]Vut6|/⤎yug+ 5 w8o4*ĬZ?oZ<=&w)̰lb*JjV-/#.!,:y5[0\0L8zKM*pMvzpEC3Y*zosG.o'k~$M.;bD8!a~9733vʑZZk%|Y+q**QS)NiHo楹Gg;J [kM\ߌI&FVAr+zJ4ߡC\>4["al o9cD𾸰%faO2&^cx?z,f^݁XTi.~'BH_Z[L!g^5 T1W΋Q8+5_"&Sm8+j1sW+Zwfx[}*U^-+4w5i3칰w~j[aᑸQ)N2Ch{7iv4:MkK~ x\Oެ0 Y񄋈csҕ:{֝z\g0 )P ؚ[ HU^qCQ(i/bL(M19w U86s>Xf?j8$c&OW-56ʬDʄr"]BWKy}kI~ir*jti*1Tӹ>K͗S&>UX]LZlZ\,W3i>9QZO5(6ٍ FgdvrY(#q ݔP? 6k/o@qTʒ^#HJ Ug= Ά:VO)kb+;^63X_;1r (l:_F*2HM$jY4o 8%Mf꺯mnRf*K4"Ew j$aܪʒjUٶCL֮Izgqrd^ fhFan0Ք5Q,W6Q\K"#vjM&.<ёIS: e9cV^;/-H`o;ߥsuXF:US{zߩ ͤP'W47Kxz$Y'YAMqYH\~O[iUdeuRtݛSUKC+Vf{0VoyMmb`X̐ݹ1ҝ額yĖ[Xz8/2$=ƍJ3i"(dzg'KϓK_#:q{̗U9 !iCId;E8 %u=mefո\ͨ0C=t%467t*n$+syi#Z0<iElKEH.s^qiaC0"$F$"RJU3++v2HN/ ρ sM9)&~xkQו5)nx飳ywio'5*kTSd בX+c C'\j/yu1/ rLimMs/,5Y(K"k\B@:PktK x<_Lh_Ju%d޲Zq64nvy";p+%ܒ%ݮw?XЗT;IQH0u}: qG=EzR2uVv:=VBQW<ʗoo#&6QǷPRIu\X2{f;sWXWJYg}jl|Bv3a9{$RӔ-m|JvΠ`鴁Qk^2Hಊ?bw\,:1VZ5냚r7yj(Wro":\28 I|wͺވ z#KL o/'-1r+IQh{?rhӡ60N|+*,(pj'FaҜ%iQw27tH$sTYQ 3s xl:KۃVV*Әu GY^ʢw7v(ن? &K;Wx{H.WAi ,6,c9lnϡioΗ44Ij-rk~C0'pC^Y^fkxrr?n#DJO3U 4= b*zNT57F qz҉JFኩw єգ+z~r-cN|tS$-&[ 5t kÚeVB?hz٠YߞLQgssI9F &a&#[VWhH'mppٟ2-&ʜT 2L8]> 92~ ͺ84#<]q.2@! 1:5sypĩ#r?_j-V$S8| ֞,7FZW#+utgc2Q-̺x:}t;^c]%$4,jw3^5|@.[dSnO=B[: E:{&VX-f$~TӋ{Ϗ`x漡Gf1?6G_Ʋn2Fz.xcım:msIw]bnS_zԖ֙l~c'^h=Ӱ%^g dhxZFOrl) Kw`AEG*\Z,u㝮yk 8\L 㚭sm)dqDUݝF FTӹ?_i?2:|x*M;taښcB? #A#9f}\iyۊɾZ\1(n7(¾>tx9"m7P7fyJ_"hcDU"&x Uzl5³,;-SKFx, 5wobCpzWDk]{;4EѾ fޡLWzt7NAsı[{?& aO}ukPig\ɭh@W&|ZZ[6e Y8(M.Li$.FFT@@́Pk8^27yTj#ҵ[[Kuy2!!g4igʀd~uzdžn4x[tM|ZΗxeyb9"@1Qw^xJ+K?G㗎|e[Z(lOp-,snnsW4 ;ב9k'{wSMFеC<`bd%2B0p8N:ZH|B|hDc&d}kҹ^rZ-o]-3a3YL1E(b),AV'33BX:Vu*ڔ-ċt]wbrȮr5k5f=ShcU'Jq=0zΚ9a@Irj)0nN:rmFmί[X{Sa@-ڈXI8# 54kp%|UK˓mwd7ub>cdK ܱ' qo 5:=3j9rP㚿Ek~D K6+x{mijdw'9AraR<.1۴5KPٻz2`:ŗQL;qzpWA'}"fW GSP51GzU_).!|H#For{TҬj&ǿ&o݂yJ&4KB è 6M@jGld*pW #Aic Y9J ;Vgcmtg!pqֵf!s 5T,liJ+*0܅sv fY$ijD,P+I,s6#U$Ktsޚ$6KqY]}8Mcj Tu4PеqvbwQqGz)vn4-7J jDZMҟ5&Wz"$q뎵_LqҴQC٦<9p cܐ娌8u +"uUo6 MbOr)nơlP٥UZ3N^]y=p .ΞyLLŘsYF)Xc}%[sYʣ##0>Z҂Mvr(.Z@8r;Uݢ%$5dXB}oͷ6P}+.ubF:fpI>K5-qG@kaF)k9O]ZkiȨxhұq+W}|3c&DLO]h.T:c - n#?h TidZ WewrgTqUcX^w:e8G3Z }.s2In,1EsW9&xBm9,>69l.#^yaDZLՙ|ip[y~4"X=K@`sqy!\k61^72ʎ&n$,WavjO ;\i{U@RGk]L#^_2ݘ$ʾɮ/"| lr9g;ŐÖx\0OLf%w+JSt[c׊P4SRqQW$\[ڝSEqO]J$"JIu \FT2=^4ujݷH& iP9%36NIۼj^q(ho? ۍs+ǓA1a^nC]c̻!hܛ>G^ U1صRE`T3˞+M"B7zRtὍUG=/sӴffruknt#^o- ntʹH(։i冂IG9 8tfVfI5L|qOHY&VfMElS9Tad,ze){YI#t¡jIտ>,,7c&0pVNYj-Jҷ׏ܴrsFaP&[IXLu+V79TR*͎ Wiy{gnS@FOXvO972V+̈́ {t(+6o^FS_FdZgNfP汔bfuuc7.s =𧈺c6z7Ų pŪg @/y-ܺd$*WApxH#urg4PQGjPgՍXHKY1I?K|=ii]zl/oưsjv4%w'D+hvۧ8q~#5˹ ocҙ",;Y*>F>V9:ʥz̳:drFƮiLn841nٴg咶4R6h8r+YI\4v5IV"HjvI>Yoi~@ImGS4^fatAHOID\˖hi5.]DR[{|y8g]_imVU6_I0wH*(nRuXIud S? y#Ձ[VШ76aS5l:~2O[?.a wI).E@ qp=\%$.MDH`9Z}՜A,dZжKYXnvEsw~4 ]>79_V4O~2UxJ*5օer0Un|j#&!:'4_j(d#J׼G#Ŧdt ҥx]9.H'm%ĊG|^%<%b"E٩^W!cJI|6WAʻ|#c1\4{HBы<Opq%dwH5UA+&wچhGNFyf$ >6cuՔ0_—{/2ҫVR=Եkj\" ^FM8@" o_*}+H-P /nXI ]*'rֽbs.?ҏ \G9K''1*W^EaՔ1I5Sfc'˪.kVt Bv q^-yoP:Weakp;/LVD>{ (h-,Pi[ /R)+;9g"]CNDa01[j$,rO/VU|(wE-oYxx7̓CqpROe]xHy[ xպ]g -c95/ 9k[ sVM6?=9Հ80uiicQ+E޳3eZ+ax'W!vֱޮWaoYL$K$NuXGGѕ(|t:NG|A┟lr[%n(] TڜVD)gn %0[۶9*++^KoLpA*kǺj Ym%avA7ˈȌIF2?ƹ.gyu,]8.Zze}c.[zLkP9 W_CpIOJ͚H@8C |ױvp?lmN]|HmIԚH.0ҳQV-r:u SVq$Ҷkx ];NԠ+cKRvVcv;Lc}"x=.v>,|maYhd<\,N8WSM";n^qWJOB#=|] Xz=Ö^_i] 8nwfw pA;(sJT݊cGXrzpw0@՛Bkd*f:wYqNey SԿK F݃֩]i0,Cz7ZK^uT<}?cDm?$`dJ s֮ڥSj!bPjp@P:Uz$cM"?+XbTi:TI d8z#D\̣cu= {WV71,wpmnW"Kee9\ΒcL8ꋆ+e-Q-HXp6֢k|VLw5w96HJg浖'< zVV[\ܧUkBK Ia*GpČ'(,ESe#6Ob}b\1sf{sϟ.F&2拕-6,dp$X5v!bJsjxQH~EQS.{ݕ]5ɩi3{nS;+p1\~{1YkH!<ВƱJ/Wsu+['B^ڲI/];ؑ+Nq\1YN*;i cuVH+F"mDF,u(JXH4aWWLST"l\ծu7rո,%R[i"?oeL\Lᄎ-;KXH7#+mSXǷK"A@!Һ=$=R򪶺'ѷss+K7OlID|+SzrV&[I,WR`;/jkw͉w>'.q&Ʊ~P+UXc곌SIT\rv9]/WeII'u~*>Fs\泭K&RDHPUR8#jtѫγX4[Y66; q[[9R ְ#? =jJӃ-MQykמb|k^;o[PY@I ޝ*+QVº;mA䈾Gȼuq)3T ϼ̫0٨$tN+R6=0CaZT3M4pd|VDYsJ|5age TVߺΗR,; H4"W(**?x{V6\#Ұ;8]~˪H$nEhut ` 6CY'8+!Zfb3Ҩ 돂MIjf\ %d:,Wk||=f 6TFK?YR1 ݜ@kgnyU  D`3ZZoQ勢AsKk&Z~Aon<^k5ۢ=Ct;FK8T$z)ɻIܯ>uy<|>[:'r" JOATe)cJMKY,2ir`[CobέYpifHppȮYDO a :Vg$41LJi8U*4qna#SZsx&%d{Iks—gF$#'K GKbV/#$ UtVxayc#Y!Tj"]7V^e-ьTyŤ.ֵtgF(PUX*)$L˜k3GVm'i-Kve]MPdBOUN*>J$wj2F۽}͒$UH61[*DU/>$)&[jΛ!q==OVEF:\ KG Qa6΢}u$$c7D,lf۽(#j8*KnmVL^rɹ7!V ]΍^Ŵ)㙡HZ H 8UVE$ Q䞻mcfd\UfF;1xsLX1We!M+ 闩Mr5%4gڷ. 2s=k/ɻv&GRy'ȍZF ef 8ҬG} ۑ{U#yFqwj>f06:})џe89},vЙ'u5spl8 J[1[kwtYhjH|̫4cHz%{ хKZpO j9Y,.q+pw7sǭ L.TYYU_+Ԟ+ya"}ϵ]0Bh%%sڧ0;F^? &ՌH<~4laW8g$ڹQm OjI5XC۪2Zִ""Uz[B/FozhtF\imU@j#Q:.S[768?{$6n6G?DDj9Y>$n\bS`utk*k 5&%cm,C;19p4dp 8RYc ܫ:8ӌZw{9%tN೐t6%vV;1e%13^,SP2| /\6C椖("}BX z|\^dduk Dv'>;κKИGW=k,m@=!ZΪغYXmhWV oaCq4b4NqZ&'USb}+Kmnk 3ٛgs"VT%{K#J$ ­kEo0@x)mKF2_1lVYVB+RzXb{AR4*Nbi_,&zD x>kojvv\yj!Uo[Iab,p1ҳ挾3hsGHSKdd`(ïj_hñN0({_`Y,62]ȣ_~W^ Gɟʻo=uzs]qJ Xu-c:Xm¨):f.ݡ+U^ss\5#17@Oz;i&gzԕSz,kP7GPz{W .pecd;)8ڼmkeyk ]PZ;k{s cG;\w5YBPEih4FrώP]sH5IE5[ZMP=֋iv>Z84kU`Ѥm!>dͥhKKؑiI5M [( AITIQͥZ.('>ֱe 2G42([^-(Jd;ZTHҖ}L*XWv,Fu#5\_`*/@Qoc–흄*49q5&2Χ@PM,rrўG+DWfi0nn_Gg4Q[jU&eXqXTly2nڐǂ:(>fx#qLA/,xXZj޿?U$*s[NU^&ƃ@V'qbONk֬Pң=e=$]'Fj(&0/u} V$eILX7cfT]䛖9۞uKޓ+bkUF}dc'K"("#MgHGȿ_,^ $eGj͖#Q"B1K[h*YѶ^b^e6:WW,ŔgT|`U{dX7w3M{Bx"+6>P^2Ïʹ/_#鈃kǦu]rŏEA1ePArÜEt8{HGs)j{&7e1Ֆ:j*ͨ Á^ Ƀ%T 5+=Oyif|GdHY%~@?PCeXոR4'ht8䍠}'I -EIK[g'56#lsտY0-Hld5 u`x·'+]pn-Q nG}jESgCө=_tjkw_#4-V-Oe 9~%)@5ͯm ?in^! /J槅QwU3W$yq8Y*,kY%Zf2 DyXG5 1LRS_j~#F'eb" ᦵoC̭;Xxro$\Zi*b?Ͻz֏cǨ氵he 5TUҭ9ݳdRYUl5 _ `-dF^EG$eO:VYڎ{ia Wm4mv<dGՓ9SxHXmuD~"ٸd<0U-tb=n١`qVwMEI`=Ny8Pe&\NOvַ&iq3 sXڍjӤB}b'`UIF(~1s[^MjV҂8 k2()fU˶ҝߺXgٙ1Em(# j8O|uזs^[Ϙ~Fׯb,w H-uf/;I١QÚEdCi"n7UYkFXtZ$8v5KWu,0p8+@pxnN:4%HNkG, "Vd.?ӶX];$ z4āUuŲI<M#HF8*tp[ Fx"Ʒ2d}1"BىItyeg (u*+3UnAZ4«8W$_$.'=>D&g}Qխxͮ}֭ewuR>Ů5[QЦ+#TwUQbSZ¶FһgCoN 6\#;?qbBeB9YsnVP6rqXZΨ~p!Bmj!şDYi0y)n3p;m؆᳓[Mn+j9[S tM*xx]6ӔZIiNB'e :/xrJGpCfVd}Ҽ&=~|l;ki`UJyt꺷-2&1sq\Z;un8H;Mi[ܴVc3EhqVdʢ݋ڵ/'3XeC~/Zl\]e'n_*+Divo @8¾j}1_Ci/|o|jG+q’s3U"A?Z͖Q>BX繤ԸKfhșty=`\y 8{R-=6ZH$&์K[7SRe "/o:ȣVWКX6ַ<2aRKm=d=$ZvZBӕĈO4 <ްGٴ=kV϶}Q,WVK޸JJn0 QX@v=*$t^Amئ:Z.HPM^@dm稫6P«=+)]-MKv hĀIl5MK0*tF9$ mkw٥rjױmixY@U9%Oo)MͼaG qYڴ 3&:c'-ۜҺMiGSK-$FKysAPHx^+=Z̖?tv82RFrFiNdWd֣4Փ=$wqg$ #Q+޴,%_ߌ1%f)奌}NE. qVk'UOw]"[!$c7l NwSkeAv*^ !#3RF0YGQZvt#Ux)E_ME/SS_jQtX{[1pz0ʙc{С{ֳ1/l9$bPpn`{tn#@+p:UZ3NkSGIm0X ,ׁ\6K|pAC[7Dŏ>R] ÓhuSY- 9'{WIkI2in-MlS ݣe"7p܊3\„88J]YW,:hq<8 Mrfy%3 \OA[I 7^0LSYHk>VJ1Q1KcK5sGe$dmuW,13W<>'-*دgR> B#Is(IJCk[w,b%pv tGN|=jɱ=7|jʠڟ[aϷ5NxGqj$N;0c%\8r"SX"7#w:=b~$^1ӒXKcTt`$9ZԖO3^6^U@qj/'VPt2uH۽d!pbu`W㊚ Y0H1[ltMFWcF +%O#ni~ԗF@"-Vyr[,?*fT0+*.)3Z4Yg#5n^@$aЕZ;+٤>n+9TQѣjYt7Ve|IaS;Tڴ 1e۰$c*k:69*zSCy|sm?RY^6wc5-Z  5%`?C?g4qr8 (&3 KFgS/5O6$9Pk=1H+#=V\u|VZXZnJlmMH 4+hzTEsJH s,Unvux/v\.WF2ܮ U-IuzNE }TV {yֵ=BՅYr&Xzϋ5I -4 gabyhe<\][|HO\qNgVN akȴ?i٘ ҽYUIy.2Rʤ 6KLu=2< 'A-*49F:<#86_i *2axyW,[%rF3xNo<Ζ@VI$L0Ed|;}G.FG 2ïqVMx⾚nFf/(aJņlk}NGq ڸ3,/,s^їI sWEi >VJ:zCRǗSߥy"2)8cuz3(O‚&6K{rM,rt-ҮxVYuVE6ʿḰ EtZime̡fOiRyT^m77r/:@`Zf$y={S4XZ6V1nF:]ejam>mfZswmΣs==.șKʃʖ . #kjZ#IPp POFl SN+D̤oBvϹqaVU(ܰһK hhvP0^C~0=GSOzQfZ$LRF;gm*J`tV֣O3ڲ [T# ۛ8+YM7o[mA |-F]5Kol4oS!|V<3XRV UsV|6ֲ6Ce<YWJqFT8zӴRBޭGAEkb ʍ3L@ uN0zW ®1ne(jq ?wrx 8*~aֺ*hwTs)9r&8˙JI#66K3ŇD]NzXg֮mJ:=^dIr9U}qPJƯvq5^Q rO"V3Mݏa u?JzITT'.b1W~xN`cĬ;QI=;g)XQyC[j2O#`l犠I֟ )jPSU1rkMvbI^(pTH9&i6-q@2j9zEۣio!dA)ͨN)=HŻ }ur' sڹ@4xkdr;U d 7c82Pr0X,G  Evw\㡬j?(CqUmHl5FDp4PdI$zn6m G|JK%43Etu{$UMqӚY6sSjݐ/$ubs m䓌ְQqji*yr\/w)6L&u8߰g6R.DddbZsuvvrK#`g7-Qd2} Ƃ.:7Vht ⫕Vbg؊Y̴gW6+fln' z]Fޕq ;#a늡w<ΰ1ڇvɊ5 D7f}IJ$Q@ko$=XyhX6=UHwP# wR"C#q8<՛GVw##mҪVԙMV)! Gv$Y:&47\zuUjzl촛|^7+j:S][~}V]՚ W&1#jhRg?ˆ.IJ1i9Jǒ+ı?OU.QW 2]Np{\,*Ȑ TFOWgOHmvCTs  e@5ZCe7ևviem ^eeްne,A>UF9^Ȓ+qy1G5)̝K k(1E~|-*1YoW,x ;̶%K3' T KB=;mv /*=OY\;֬QDᔨ^F+ŨYsu&"hpcxb"kQK]RI]'e3@jR95.Q^lB)ݱ1j}lлyd'C[ Y Zl asXgU6W%Ϧ:U-} `WE(7y7zRm 9dv*xPƒAQr9'u~#94ʉ(fZ% 8lݜOiM{٦Y9O݋) 泞 %g=Uwv$k~[{MNW;;jy෸;Gj´h{0#ZDs5]+ٖF.;݃Sd/SYL$V,̅`s,>UkĖȲQjh4K$ݪv&Qqq%ҵr:ASE2莖寱2lH : On,ǟ1Ok"y!XBxlvm Ԯi5oFI>3`Yff9>bWk},F@ VF5$orV^{T: 9='Vt>wx\;B#s-ûWd$vVhK>Wl jFWcVmW,>t-uR;XF<ǝ} 5ktQx3z5xĚ g5˓VNc7H٬-vF_ :{}3cMfޢBO,Fkh7kIŻ+R@P7&{vL;zc޲&hĬ2p+,Ep_k.FEE6753 (ՍxsЊ|Hq8 2s7jy>Y^!SE:ǞBݸER ,B;ǽhڸS41e è[6. 3cƹ1MAvXU:ӭ"նȓ8Tچm ^)_}jeZRM'&˷7>T(~e*#qNѫ`o$E<WںGaLLk2G# 隥idqZA[E8EnHsΣzO]In7Jx>\^R4Iad Q'0,@i !gyM6:HxRwgpP0:drn کkw!`(>ZS\2ug~Zks2M6ۄ뛫9]C CaO2.eҫ׳*j|khWSYF$X~nU|Sӥþ7݉V]>!h;׽q3u]5$uIԩ}=Ψ\O#(y_GFu L>F RKQ<~%"!,RLd7coL^xVUi%KT>Vr-K"JJϚWt:U5{g46|3ZoJ qaj5XIOJI4p{VWzu!-;{ƆD:fconI;K$B#imBܰPzT&ޤ2 CG)sA.-Hճ5++EG4@׭}sƩYhn[YqxW[׸q[q j=HYr 'N֨zRj)^rU{U8W4խciE8\jϘ[4s)c= ܱz)g"G~*1Yvb]v=F8II# /q(Ԃ<[>q lZ]>ڙx]Ɵy!q֡ }彪e͹EatT5#(9xYr :ߌִPg{{k5xYZG^~&=kГC+}l~Aow۹I'V=ZХBv9 [懜~UeYyk9 qV)HxiN{Y{;xlՒ="F>T s=ǝ,xuqnJF)Ƨdµ6%Z8q,ʛdz*ZO.9.vguMgP{mQ}LBK׿ڌZw6~ufYs(' [iRLdc1iYF+is z2f-|IDRkHvzdcZm0LIje"Y?wqqVmGfb@b/4br.Mm2j[jnKLfBё,+^;jMu5A>38gf#reSQZCAS6 Z4;}a4+2HZ6:^$2t;F`Kv`5+V%tnuM4J$r:ρmQAUYc ի̕(hr7)H O[m#>O&R. t2yc-lh*}=z%jW\b]K+ V;-XkTqZ"RXZٵnS2ثj-%V uoj~{Xaa _r=ͧvl @֛)I{95ҀBGRI\D|gV# =>DجYh >ؔDN:~th%NM45Q9U;>9?c# mp00B3~:dkUi+F}m dXВg}V{NƓ殞4kޛIQoru_;gF8IRه`1zfa4k]Ibɭ.q U}2;ʭ{;9 D$baFjΗj܀1MF%{&[˦LqS;M>KZگ MMTzw%ҞBt`h̙19Y-άX]6Gޣm.>dvݍi[{t%'ɜ1c+uE2ē6U-nç"R8^F+)ɝ4iUD)bY6vנ5Х\1|ROK\9Vܻ-4qXq[,sFp =k[K6v\@Q)&akH-FR_KccE>lwDX2`U6{d\r9j[挘`<ivwL'BW9 [<@ oEk\ە`Oz޵*E\zױE14JoӣNr=1Ra}AK:y+(m/_?sY:uךQOR/vgROT`Ǧpv1Ly>$ =6|AR=cIPXI.qV۷%ʎ"!붴pP:2+sQY4مI'7%1-{=A* :1 ?EqFm9vw(0+6Lօٍ9I-6+vR?3` /+=SU*G,?LּS /Q/Ec*k蟳w:xGU?tU2}X#-#['JRBJ9b$g 0EWu_揠,'9Xi~j3O1ap"b99ɯnuiSw9sIiwۣם9j)/D oDWo#ݘ hg 5d˝c3u7YG?zǭjQaLvH5NWmrcآ t1 q"^\qzVTL^8qAxWbKohO Z{+GsĈx+SOt_LmL|+T)r'$߄#:Y[d"ZsEh*9QOt@_n\*?ؙ2(P\QEpFN C^xF p=F}r:cQ0kfIdڱ`Fp/m ȐQYd+%jY- c f 8Y`O#c2(Uv݌2fU1KpːAj'f:Տ,4Y0QԾס_$5R_.pyޔb"DsU5j8;7,XvwPw?QN<5'&"rȦ5`ZG$VC[`Rp aNnu ՛WRIM:VuM6e =h%iw #m̬Y=5){P"S51xa\=: #+ "ﱛN:2}. c$\A,7OhpX0X O[)GHKhdӳ560=Z5\A 7Fy5ݛƣKC}LIGf^)FGJ{{qI,ϘԾ87r#9\y_9ː؎Z̨v;39nFS-Sv%utId U@usSWM {( O]HZ\s*9]).YTz4PrY[zشD{c6[1#w?Jl&0$-OAUm";t^ɧs)5)J$eYǖcq/l ǩ'V{523+xjԏ٤E )dH4lO('fOy#Az egbKqUkx5$ 5pnڽG+nf0Ǘ<65+(\hl$, ʕ$Uoc>[ep0cک^F,/QO=iԹwZé0ْuci<Q+; ]?Wa9^QCz @mbnVnxϒzϓӶ 'e3~qٿmŀѹ*[me;GnrK`JrvHc+]}  ]`wԀ=Nzo>?.r1ެ-Ǚ=n8uKVZt#InlgE$`lǮ-pz6)jhcOʬZކX8kӝ]lF$Sֳ䬢k.UՍ4忒~bsT?ʳ;O&լfR?0D1֭3M9Y$ k}k;j ,$.Xv?iim4{qW<9Zy"g VL+GfvJWn6ymSi睹ip@[v,smucUi^%U!AVἒ"e $tֹ*+[S/o5^Fbz . ~@SzQTMd;9&o9FQE`.rH>!<k?gֱ-kmPv,}+p܈0+ ѫkUsGg趺Uh|!1Z֠d#dֻ[@|ר.s$6hͽ#RPF\枉*5kO3!ލ%'KREU-!V߈tvq: }k:òj14!9JQ{qjZĘiXձHi%+ϮV!HR"? h3mQ((TvEM2hVV"}D6r6Ll%@kHMhxoQJ+Ec:td2*<QDzU+/$w?{6އ BUHEY?ė_J;rWQMa*D Aϫֵy=jg6q?M}Z;)j$9[HϸyYH>FoA{Ez:7F:HdqJcmYOa:dq5wqo,hfNkN:V 7Uk[XܘLaMi:1WefV'Ozf݄VHݵ\:Mێ4Jmr[ԥ'w.nG[H˚ז)hctk=Fr%t*L<҇NwbUVWoڽ{3rlYDӕv0/#^qNZG@+F旞[BIvKFnJ},H|(DTfWk%aONVݷE@@z1JL79iFn1xQ|E `;jQ 7>z@YOf9WwR-yQr,|w$Qck?y4ׯG L<ԗYm 鷶*^";(KIH dַNZ#+^?7ZQ'7?IVQNhةB<7qPV ]4X|Q' cYC$<|DjJr^2f-7N*thSunCza0\ߛ=)t^C{V0UmrҼ-dr 60&C웫H?Ƽ=m+"WIpL(:QUZ%=2.bITIЫ^U&{mg穓5Zᕯ<֊4t^2vt>?h4۲˭d?geud_Wz|W5eKO;٥9XpuFHh/jK+oPkJR=Bs+;Ul̀5cPp\Nۏ5is"7Oc4 oT2Qrk~O\Y@88C_3 .r6/%r\掷UoGBʻbv$uR+|Yvfl:7ayIGȩ5[6F`z(qRJ|zC=V#Rk_;) s6Eyڄ٣c`wZ"=Ġ F2&w sFyn}pk֖ $xḑ?0s^Q e'ObkRzԹߪŃ_jipVV5fK}j^ t/\%f'޼kWQGP~1<)l&G{sRkSנ"cNdU 嘳a62xfN$f5U'+?n-4w,jҖz9앙k6kteX,YFFp[ׂ[A+ <;yH{Y`\gcp)-? ~x?LHqfzX]FQi~NBBj6%_H?nS?JյCڋǸtVҸ7*k0r;[T G=Q\&a1w7svXd;WMg}iZ+2d~b-C\tsSBWKxue܉g?SY0rF~g ֺ{\sAGj^k5d۔Z1 tHuM|-!us5[뺴D1$[3jj0z*Qj.U7ކf`LDXBԼbrGn;#8=ߋt7fV=rfjj˩,vvr@50}xy^_-1e#W1~{6nMnSkFsMD=?GKBc&G9~⭶M?M&Lc{4T)!=P74ѩAY ֜֞F׍5˛0gM;:Up0{W&oE*|kKm1{ ϪFa'TBЗ>Tut\̓$ "TNEmsL:uIHFGCZ˦yF[i(kyбSG2G.6|Nli"2ʁ݇HY\J oٍe:wٗNEM/x=]>ZO'# 0Kulj;m jI砣]ywH1#IsϟkqU}H]ː8Dɉ>+-J]7u> ye0ΥNGy!Qor o=RK3\[vaGry?gw*CtڼGw AYNKFtA-lIoYJi?y G׊\y{{:uGO$-0H>Te)gN4\G&ѣ-^ .i- 67Sh;J}OEa ɥZ۠=*41\Uf+;ݛ*V,{Xu{mi#qWG7f)Ej3rQ_:}5)>kc eHPn!MsկSIZ'oeH5S%1Le dO(IExTu}XqRe;$y8<wٲqVϥ.A-fX񆋨鱶#:] BV.r0xV߈!5ҵ֧Es;mm0RuvˍʵG963hGwE$f9Ia7FݵTv4QEHcrx$0muK0r9Zfh1늩_h2{!~}LdڇQ3pnk'eV6,2?ZF,EvVFM%3V\gUeC&*+*W|;)[I2-z3Qi,FUںVR|C鞵;S*V3ӌsL5ҜC)feGCXLYr@ymV޴Zd)GZ5˚d04*'ֳu;(Kۜb $֦0VԷ&&ZH{WK ,%id ghhW"+Q5ucNi_yR1޴b@+-a;" r)2fɑ&ܙTBv<}jZnvm59[_K(ɯt{aC'78L<x!EhI,K0YudΕ=uP+RK kFBHqON+MIpW (Q$b;X] !]AՐ;xg[,va(%05^ ^6ŅWێyojiʑ'62NJt#szVV 3ɮ5E,|9a,[SJe%d.~,xLk[uwN6O?Jd }kϯjlNQ7p{W5e瓓Ӵ-}NiT=44e"K&eX?Ɋ͚Ο"NVe$E빡E53YZΠrsɒIJ# w,~%å\!rixZ  bF)9=v A4ngx8;t6rm"suQϊ.E@;Rݠ UEC%ϥJJԱ:eydcC@?/L܉YW=)B*guS R|f夒;s:VU U~ PqE&m6;^Fpk:"]oۛ,oLWu4Ff#*[t~ ciT )i'*K'[ٝWmF'Zdd"sjv.FWՉXuP$WhXP>b8>lG/dVlڦMI#-bG#)<ɱG˴㚷L"Vc)g cHlܫ*SKqlX,p6Z6 @Sit!KA29gVS$uHc0Þ*y&_#9Jm43l}DY\iIe!S9Nq sXĥH,Gҝ{+v6w5U;ZѵɈ(|۫ߢ"3*~TYƓ3gp9(N\bX3IIIs(Xl#P[;<-\ үʞݕS|"孃]ف U{j֗<O9ZB+JԬlAAo ߿0~\9~v5JѺ˻) /*A::xR  ̱Yę<[Cm'ynt\yr*9̩`8P8sK9iCv`皇 lnRb/qc-a2`2?ZD #jNvS*]̭k)0TBNGfLG@nZ\J̷8eǧz PsroKBHzY$09{EO])|a< $ 5t&wZ1A#nzܼmXd ]_dyq ?c^ilҳ(^Tx/z`hWT4HH \wa:&ں4HфVFSޝq8$y{A#ұἈ!I`1VÆCTI5{h*yϽ_v6rvՂ[{UKoBSҔ^餢6w$Kw=y҃vKx|zP%k$ڿ2:f]OnVJ/K=慕͙U>4R W̙"bsڹy&8u$;[BVmM[ =;՘u;LjqW!+ HPqknDe޹h&HRB~^B8(¶9YУ¥Gh&W2Rwdk$|Sl.Z_%%sߎk 7{}A<6屛%hڿ1ZHeB ިZ|O,8Vk+&sX٫7e SV829B-Kgn}kUl }[+Kd˔e6,K&ߛgY:f<䞢%Y2cܚ+g-V)3a"5&+݆\?JxѼAUJ5]W4um+X Y3՘:mʕbA5ZO#[1cRfbŁ;KvU$D椬{cQJvr.yjLмlJ3V,R0DrH\cUuq%kn}婙Z!Agr{Vu*%dEkj /Uc# ,lhe.frVRi2(e5w>OU~e/ٝ!M84F-6i/Q&Ԝ.up00s:hTEcT>60u #?Z_ƞM&fmN5sRmn5 輭ܯTgjZ[Ko3\+ JFnLԮ6284\+It^\c'6vZAfѭ` nulfELbbtI:ޠIѮ IA[]GF;ԗ馬Chw od`(-ݤ+'TR8'Z6s`t,9OuDCqZ*vI+]c}#_G2=!\n+AT&ҀMTWBRI|>ɖ&Sޫ˦NoU$QFqY0Ԥ}* c>*8_FuFԤ|#袪j>mRy/Zm=w-Z3xgJǎQU\{4Y9&Rm{u_Z^#y~v%pjjFwe+N<:)Sh6-S' .5Ipmd$̵IHFA灊ֽn^GHlA~*9n41 yZkrN.ݙZ麋M?.[S3=5v+rF:nVGa/rMr^|imlF1:+OY&r[-m ̣ Im1|Uf𾵜;[Ex~\[Ms(Tj|/U' !=ݫ%X`V1| vB=`ڛ2--D(wiUCoűֵ+MJ$Vm~6?B+teĥn |mXfbnub?*ۏ-eÎpsQOö˲Ϙ G 'xZZGKN85ΙAheAj}͖Q V1$kNpۖ;%RRVFί}LM9C\ڥOt3&%y\PT.qݏZjNܬn1N$ :|j5^WfBcO`*q'q*峓 U$a7OR#èc«q)xcBp'A*n֌B8rf8鷭TV][ ٳ"9`u}$MO?(\eЙF6t[KHхoCDAmt},҃ jTyuG>L]d{BkὍuN+Tyڏm\Jx칫nC&K68qVA3 `ÎLJoI5t Y!p w~u55=.s:ge~UX鷊 Y3&֨Zm"rii ᤏ(FmVj9t#IeC)-Ыv~mu7Q%,2`^d(-6LEըs4Ҏ^9쐟s-s:*-(Mv`7lpjdo-? lw+RLCnN2NkC[ң,Q̯خjVSz?# @1l@^3UD73n1 s?W!'yG5NvVB:[T\x HǙ̫@s]J/a5&mb\TcmZUi=pH,͝1k^K8nk6mF8I[{X\-kOܥ{j%aH^k i $rsg5{Nbpc;9mXG#wUdPABi~f`Ln#)2[VvxMA% qn6ǿh<)AE^Rw JL6.?cR$zPorj퍜ZVk`kKJv. r0jSG|f3&<9<Ӷ'8鶃c!Le_zD e#rlHcRR˃ ش)e]G`)Ԕ"Y QjKcD6Z&1S~Hih0SS/ِҴԴf_.$ҩAqpSMM-ɜ(׎V̟zVqĻ~3Žk>Rٹd 3Z]cv*͔~eOsPĢ#ozv8y3VYcLx "F$4n7v A>'k隡㜮+JIA&HbU٘Z?"@օ,aBX$gj"vN\B=!d*\ZJ \鷚\2v#Vvp3J2\6,MZϧ>|J=Z'wrsROnl*mJ.R9Bp]zRJtvs|yz r.'z+1o3"C11A'#rQXGÎC3 ˩Y@(Rf5+Aj麍VTэVRh}I\(NY%ЏV a%ɐF)cyrjq8hܞ3Ov ]AXakD(շԞCe︷sf~i^bOT>"hc\js;)%̄V:j%v+2s9wiZ$ݰ2&ij&|UZk1qJ皯+B8gSTgK\dnռ-[0Syg|SkQDEt;.:W6+Fup#m~S^\\EriO}jn~Q b>(cf>gf_]* \Fۈfc6[ Fu_^?Z)tXs':jsu 2ō6▌ʫ\ 5V B֓OXO*ٵ𵇘1…=[mC}-yyu_:XgHѯ"y̙5aMwFy$qtH",q|~5|yV0Nvmǩȥeg\iRQcӭt6z_x\ ihVfʣ#q*qYʮd߼819o*_ʘfbvVGtHkc!.za2MיZHE[je*ǎkӵ@ ~JZd=4aLsEm/ιegF 9;/ʲևج T?FP+fK;؉!=5%oE TkkBjҷs-܈m]LU][w۫,]P p@#lYzUW1\Z>wȫB8K/2PuɧS' " H. 8.ŹdѵR x(Z}yVj͏nV`֒Ɠ_׺<Wi1Ȓl Ժ,Fzb붗N}yzqUo-BS[\d2ʴuVfr0tR[<6HkVYU+ y@rzDLPtO֭~ɴaÉq5[tk\Ԏ k6fB7_­Ox 02 \Σੵ 7Jʳ5k|жt"CƜֲ)W/v$Ѡoj%wj;C7kɸc$W Q7S!,uwޡn$$G̸dP:~E9ҍEMŰ}K{&c/"ά!iSd΢OK֣z|!DN7DܚU-ՋӫetQo\ 6ס!O?Z>4XQ"zᘊߋI;^Έ9 :u.?]?*?foCxI,nOG/lȲOV#:U]OO#38:`)ԪTI6); L[]~bЛ]->X OB%.-eRȯ-oۛgwO*K8hH;K)9KKTyi8*M7>($k8 MVbp2C]Q r@\Z9U`pþU20nOJ߸5Ug ֡X3*lw\~ϗDY²( ѹ8HMרU@%͉ڥp mܫuA x5R.J$#75`o3WnN=&\TScmxLQN rSU)Z[Yv#*JԚ}{$0`lhQ`xb1[NVi4y֥2}AM!XpR̵| ʹ+N\eizP"VQ@V18F*kxo9"֥4pɶ݈sQCs#+lrAn!JJCw :g~ 2QqnYSg8IL {Hxl$jp1w M*iǔNBx gwA $x9$`ՄK7gkrр EmFeǛ 84Kz穪P?X>ZZYMswZmDRs-RD)/u[Nx|zsJa0@䜴4ZjdkͲ1CՆ +,$vjo#kdc<ֵqQ~eM*TĖW*A+B_<յ0{9ZWa jiiw784mB.~c׬p]]E-±黩7Mc֥㹗,M"1t Ehyߖ(Cy8pTJmNWh|78ugS iD:ykMv5FlHB>ELOLh,U($`&̖?~^ա9늪TPOZ`~RsXKTu);Xns:旣C2FNqX.:J=էke tS-I1%"`/׌UF6bb4V唷 BJF1{^Xتz݀0R[}E bNJڳG+KmBwDo\U!)W_'֝`$PsUȭs!@WB2,o@H[w,F+Q%][ odՔjV45Ĥ1{?f+r-* =zy ʩg_4\jOH"T^'2/++5U$V۹8Oٶ'ڸ诨GT<\g"l0CsRYZIq1%,zvmbKQ*zaos5?t?kTD2uCoj}+9mjW>̩ v:i %ĄtJeCgKy'8]iR d|sӔwڅ6-&xc5QSd`Ć%som-Ո*̓\3iuw6V<7c8V;#A$s1RTd8ɫVFRnjg7kjm^t 2p޵4iD:ESsA] mQ$ >LzU/k#X'٤R;MPB8>kM"E5h*6$؃zW6!@֨Ѽ}su؂O.v܆8Y+c 8f;>ODŒQ'C,r q:GSI8aI^lY2Т1IV)V{pTMfy]AFM4WVoR 3YWøݻzӒ[uG8ihypK8̠"\jM9l2%Љ$CP j׈bڪ^;!x=iF)ٲDա"O圎mi_FB|Ì/5jH3_'&z%vRv "]䛟\vjʘitz~TǾ#ihæ_G!p *fבTHOV6s\GK%g}ıU_4:UDJ;[)^g큊-ZBc ]묘=+:~~!+ޒ"xbΪu$S^)e5GKW=ć2JUO K8]A#5 c8ը#Ѯ[ G#*ֺBɕ5*MdZxZЩoB lt w3j{hWA $ŭ&aƲ+ҸojȺbֶJV{Flt~ 6.C*\akRҮ%khqn&+9XOJĞB[S"b CV@3Q1MmF-N:+IvvxMgůx|qKmb8k۾wyg~G(:I){MxX IOr[r='#twyZvs3jK> ~ ]ki6HFfpzΩ{w#w"Dc5mh&>ûo> +DDzB4 m݁=0X.%:qO|";Ve>;,z7 j6W~%@Jkľ7hʾRݬq<y5 i˸r9kH[0O.5C "񦢟v8#\VsPͤ$a =i8)u)JxW0qQEAs=Ō;{ދXܶ0M J&-7o&rP08ZPHT&}]!J+rR{m*嶩p TօnB[WBi7'ee"X/ݸɷVb|Ú[[RTþ=jؤ 5i_@s|6\jw$E$~rOkt#s۔li\k7)rO f*Fۋ,l>X#U/  4_ޗ*ekmF6R̘Qtz_Pk)^ML@:/ipK4aݤkΣ%0I;gN7s`!U;_NhO񥅤{m4ϙ}G,:Ж7쮵YղtUyֆ/f9.Ojs9[ ?v-O`ئ)9;4Alu %IAYCۋK;k mG3$m6nZ8WtĕZ=B1y?ΕFh\xJZpQ-*{:^}d5 o.omT #JD"ՔM1Z y]ې1Vm 6݌z8[4jM՛)8oE/kg;BasPA}NٿsSX] uL oc-Eiei+)Kᕑ.eLvzVzUg?lZWV|R[(mrcbYZCXum?ԱiZM%QO%&gܰh#c TVޱp_IeF*dmT$[fOӤCD+ɠ}9@6;Zho<ƸsSsiѴu5 \cL5s-U!Wb$.4VO[+m} #Z*}QI{7Yu [M&P.EawX# <?@sֱE'O9>ѣgoS֮GxA6<8VktJ&O',p)9ֆE2'-a)-nO<3HAhO\TXըۻ`mn~d$Vq. ؼ#T$[\[c5=2-"\$8q&%ϗ=NkYZ00{W0Mjy pqKSkDeol0Z8ՓsZvwufQaCQecr'Tu9E w%'sZYb$W3[^_QKvf=ޫy=ZNVESnJSMSm~n{iR@LsWIn{K}۫ nv3R ݳ)y`FJ2Ozt]:EIni`йm4kRF!? /q%'aI뚂[DW9/Jwk)*U!){͛enѤqxv91E`\jsq S lՆj$1fᙡ0\[Z_ļ#LH"i$?LbIQ^qJrڭ̄;ZaRc#ܞ2/v Ё47uICbQIikq Xݹ\Wni^ mT+5y;AhDíX |+nXI21n֯jZ ݾFܚM :d*rɟϏXIy{x5yY4lw9 W7iOSY ۼ]؜Juh]9! CG:zHFکiK im_}3T3w D8N V7\U mcҗ"l$>nj֗AɔԒMo5Aair2GVv#E/5 &1A|WW7|c!\T^\ ][89G$i]1WRKChVSV$0wI՛]S fP?Cs{YL6*1~eǗv5VNnbrj ͥ :EPiTr?A7 eoG,'AC]ԵˋRb^}QdmXgzJ4OZ\0 0[oX]m„cq\qIt*ZZ5kiwijҧ(H@|ոjyw0`STNvLkH]3у֍ܖPiI$Byj2: 5!mV_5pd<Ňn:Vfu";Lr @?q&RЇ2,\Cj+nrJkԦ{d{{Nqkohwwcϵ #Wѥ,J1SYJr9+ӭ]5f QsZ<[Il 5:>%;B\T#R(klW ϵQ6v!u>~qhnu;]%4[L5ӋeXǦMU̒i_k-:v:~6@EZuvg#ֵr&9gLT1m@{(qSRwKN*zr~5iTO*.#Ӥ[\_)1E[,y0 PrzNagHGT1H_ҩxT8`Xs{2]-(Ɋ|jZL 1bZm<#9S< 7V{fh⻫4CwP_7PFqVtDp3ZX:ŧcά۝],05ibzWHӨl8cs]HPrv75jZgvBQ6(Jjbh|J g#ޭ$\PCn2(8+L_Cb\ @=qXsZl;#c'?(7f\3Vr8[oqe聥XG 31i~;@`FFv r%b=+`}}[yk&5)k=$ oNN֑JqoV§ )<! #+v'pﺟssz\F{ZJ"49Q'yf՟i:;Q`(֔p Y gGj\ѩ<~m&J$J݇줖Cه5|US ,LYs#iEFj/fM$2vնԚSef~]>]Uq*)a'^7q]7>o7uj.]n"Au~qLKOˢ];ȷiFӏ:U۸R%9]{ gYre }+&CsL#IjK(񌞕F{?3s*|>FՑ2]Ĥv)Nqlmt}pOb,-|ʹl_5[wS}BD|Q隡{QzgJV=(Rjo}M [,uJW23\At9qH%SeV2:\Vnd<qQҐ" 8=*\$M H7K,m7sU>dL=J fR Yrr{鵸質X:Wd;SIuotZV_1-H?t7ɮwFP$kfw I|ՕHթ[SRIoFͻ]Ĩʼn#~}j-(Q5vG=ZWW9KX ^ư&X9'#ҵu] J;PNlݑ|?n4ޒ`ĥC?xW\职7t\ZsLRzNJMEUSR(u*9sQޗC!<[eۻ=U&l!Y{e5FA5i LK'cMevh éJrjwk2vyv>8UHe_zbŒ+-YEU5]G ؆љ7ǓEI-ʹ胥nhYBmاTڞXF ygwֺ}.W%@棆EI2ڳ-fQY )&rVWj62Cα7,_]kN 2U/;X'cZi61gJI܂fYWx1 ֆH^2^hѷBݭ>p[(xdE %GcY>'iEӝVdq:U!M3BI  / 8qu.Kq:Vn)\ztscqNkiw\wRSv۹$[XɷvKrG47.-CujpW;H֗]Dupqj``1oWi8SJTII-FԖ'Fu#fC0NzֆeqQ9]\ڎo#9@a`p9z64`FqZ%v5eIrꊷ4-zd$R6ߎI5M^wr9ݾU4.+5t9[QRٍ'$`c.5gҵ`8)ǣ#$KʜjyS/x,|`QiʥP*#A%s$\ e&,NOj :ؽ"1Xwn&%rO*c+g;[rk;+˶u8f8_+٭&O I*ȑ'Ȁ\Vln%ȧPBH*K2͕ҧ%Y<jhܱQܕw3R @ON:Vv \ KxNIt=JxEƝtkfÕ88sYW񅍉-=n_isٲ|VUʒq4jFO:u=aʕnk V((*`d3aȳ+6V38(Ju( n,sX۬dnpU6gl=-T(6ڰa TT7YiDO8'VuSjɖLtb<ʆXa-Jtn -4td7˟j_\\n- ;c6Io0v~Q;[5$oY%r= M*8|ZIt"Z;KXu~Ypr6юcvCK*RFiO-Β$6)DnHZůL@~ڰ~!j\krAk, pHHTތKnchĈ1f锻kOKyS}˃ֺ TnjUܱQHWZ;Lpҹag[JK.25Ed{c簕c-]}+Fӻ]ŗMG:~Bu3hC9k t-vU6#G)K[T\1y(vZx-xk#S$*q"fvdc(/igxz;e'͘*j/vFi@ s!Xثڄ6a2zW mJ;d#}$Z joor~m&:cEb\j(gr&\&t֕5h:,!FMkGL䏌̃kAgrG\T3qE_hNɟ7hȷК8tiQHxUe 黵Y|Q<8^qКIZFQ<=!ZxA:U8B?ܾ1Q̊4d}=ȸM`^\.uB6-.&G[®4ٝLKK[yg0+zre \#,3vc?5Vg:\ qLw QZdĺER} .0\Kvc3`|O9.獰U>ȅ)'vtw 0oRqTo㼐ެY\Jg:٤=0šd-Q%[ߞ41#g9XKn8Gn1kb' .ch7 L!<9F_瞡NXTgV=>lj_iko@٬ IR"S믇ZҶ-N9mu%&lRţxL "q_+]c^)tTfL2.Gs@後N}fjeq0L <گ$֜lQ%[ieh('ئ'O=K4l3u"E$~:+-J#gdlU+pL0*'8>n.Mwոba儜 %Px+:?>GHIq $$si<%O3ڥ0O9ZHDl85;isP+cZ#;mj%+ڢAPv]`; nb ke5&ڱz| V:sSCi4ngJVݍ5|$6p?mXz*皊Ó櫕>ʥH5-( ¸n Ҳ-XB_ Xc5<7x4qޒGuqvF6O_ ڌ1 v UM7ځ[3V^<+][f4uLY3QuS Xc1\Cd-5i^"V!G]hz^*)`2iT٫m+̇N$G#PިAFA l|9jȳ| mi֫ڰC)XHoc<Ö/c!?v:l: cWYdH_.7Z(|wp*%VۇqW[{֔:]԰lx:ӵQmnV?!%{Ukq/r1Mԥ'J)n<;4(uqU[÷I?t >"hDNd#@XUxI:lLv,.kwjU[)7c W'p=eNjIu̿5uN1Ƌ9JHH<,_\ocAa[z4_1T-ipќgW+^|YİIPEC4Y I#/ 'Ƴ3veZ{!^U5iL Ç+ f[ѡ$= j Bۗq TVzU[s+U;32ʜlyGN7n#Xo@kF=<QOBݪu7sk] $)ƣPա) "aB̠_DdzV"|'σz޴]W[6W8潎?$8<sM8& ֨1EC)FΊe5/_ b+v@>uSJʭpS۷FP\`OS9GMVi{z|d`nsV"cH7'gih7 P[xL 5M~n+>kmtF3oG׋,u.; }fCث8!1⦏@`#sqWpn"KSpi4fM@@>j[89xַo,%trDV?k3?Z49괚Y 5)sjm+<^)#Ѵ>d4>7zae.LZ=UhTkr<7HӕoZӔƨH3O\Vx"sB9<'?h' S% dGhJ]0y属븞>*+*HԠ6,`~yf JO.&X#3UO{=*c֯RZcÿh-q$cF|-;w&% K0kLw]^Zt&V~)j :_ZnjZ6<#vb*j6XFyrc˴h4I^(# i]χLi18qT`%YOwNKOU1Iwاh듩i@CeI^LTʖiR7iu'Q60!,90?O-GٌuK[K;>W8yU[qmirmm#WԢj&?e=u Ȳ0[ߋ{ 8+h &[ВE@ <J5b]u5vգl#N1YaLᔓ/J' ZI9i3ҽfPBX%e6gV Ixk*x ĪzN9]*Uo{$kZ,/RDmc޲n3* `I Pd ȩg,/q%ebb\i4mH3?JVY؆8uP@bVm1=bgX .O[{5vX-NqIj6Iq#y徳 ͜rKYnGB9WKݖ>@@clE3ݒG"V;ܱɊ۾b5qRwRK]=CAבwkF] kq[[| $Ӗ;UL+8Zv|F-2+G rnC 'q7LdC >Oi fT ;U,j@ \C3h9*;SxQ uݖ to`1-gO~ǡZSW[r댔/CS}S:t oi/Oݰ-N)D:u_(èvu/DOE;+8'vY[6^-B9{f>jqs99ֹMFK] NJGEyt ϼJ$H AƈW{ĺƃ-4 }ÓmǑ!6E!2IBt8y:s\&D[9ddG|M;P-,0O^㏈Z_cg%cXo5YSQ:i]>lG5oIp2kJ LbsX^!#lέ#9#ֹw% ;zg4oX;؀T?7^´n'SQnֻTHM m kRTg|wsZNL`}xy;#IhjeW\`rk*POu-0KU2.Y(֯i(*Z?kuzd!aڨ[ؘܖ~cҴ ƨ~m@#WNWrXogCpEq8n.C86O$paxN/Xۤa{s91I cEO8$WW42nv'VVfPzVa]ddvIlRP06H^2N'wb΄`#k&6ƭeIULz~fC9#Ю+3BZ8lrܬ,5b#S=G`XFGRzqY&v~݃+VJ噤}H/'@.#f/l5R+G&FpOt}=5RQZH)5!|6+MqL6Wy@[ZKnqW֪.DD=`T ܃09gmmK_b*S]lSQ ÒG4(Y6kmŷ6ry [@oSXѮ@Zo9=S6C'KH#VVgldqC#d>ƛ JzwGH̊f'l2ȸ`NNkj3 )sO"j휕ɔzc=+ir1=j!P;U-Inק5-5kt$w 5n5xB]ck/xǭbhqr,Չ]^pemm,h5xiZ\̅"P]BsKKX@*9->HĒNMU^Gw<}*c&ծihܽ5Ó˅/-g]N[yܤ{T1]2܅An ]n`s,3P\j#y/+%iHV3* KřEgvvDAHܼ/na#-UV_(q./d71ƊB\4XF\c|e0I+:I7;lMuf>ɫ2fpیզx@s\,DbnI4HIc\Lw]f ?xzmb6;UW0+m3IRkN~V ބM%zI[P;)ֳm@)Rle}j۳`2n]ɍ;PfOF2Mi۴1M RH$j]=wsTԗ*%o"6)즛5ԭ"yj6ZL-80u֧K\tl[szVKd[Eg320VZϙDW&vE :6&lE-A*tܘݫ$dź`G'bɅsZwV+@hEx[%|0Vr:-|ԊwvZʤ5z,<2 cbbXdt$+XS5 u1V`(axIZv.SjƢ79fMY\nQixQOҮDmN[ѓGQu/Uq:!aA9'mm4%U d 5CISOrxw/8ҳ᳆<WMb7]_zd ô39QPx* v+ء3a#.k7\u6!K&w -X 彍a;Qy`{+1}AE@ Ҹԣi(ٮW) ט6e_J-VQC|Exd4߼ #5B8}cꨭ$[ҠY8[pd QnUt\ٽ [H%VP:ô9RcLb0~\Uψs΢X34hۇghS[ƞT-^!&Tn̺W!lJº2M3s#̱LV-q <(rMY3.G?ċq*b,1.5ȺUrHʻ_$1ˏ+\R-CFFJ\}Ve&;xC{oSK*ꖲDF`2: >'KO\f8'sQ'yr:k5cXNz *9y +. l7֖dIIo5+JKdvzpSNzC3OvKg2F09Q,+r~?Jm1߹^Ҵt-^/E¹t3+}QslFb8 r(DUCI*$;d,d9\sMIv܃ .2 {bOȶb>dґbǩN:mG]F ɍ'LN2m!wU4d$ΧF=LBcZ?;I?n::ڻJvTԞHhw1+Y u jZԞh;H=3Xk{P7Bx<(R*,q05>=*hÜ'I> C [8ʹŌIdR3Z3fKp?T2F{B̤m$RG,wLz8d/ >£oR wІ'aRQ?)&4Ma֧M$ٞ@&N{p.zWW>?Rќ! 5Ifr^Jsֆ$^m =j. j}uj+y$tb0*UAy!JCSJ#iXC) fZM#֌)y<9j-Bq#rgȹY[1<]‘pᢹu=]y [B%z)lLOLAad ׿zH0^.ve"&޳T (QO- d#a]$q5Vv%4e'+ljֲƇu+f%H?4Sr͟\JJ;CZ'*o"#U:RŚٮoj&0F|h2[ɑsTu';A]n8ZvF60y>S q\rzT88ʅKh[2q '48<Ęo̯5aÖ g9רGOhڝʑk^V+y7%r\w9_UC?::=60 ؞$~+ٔsIK0=w Q5-͇woHTYRY F@GJ|ڐ_ʇ&=V,<[tbT ` JxUJH{Щn -K=Ryiq?{{ z&h-BMn)Z|ӗf)KLq!F0:t4ǵ 1f# kӌ'$?5oe'}by>KVL03#ν!xn Z_yX%c 9 $9k1q4PJ؋öv'[X՜ WV)9I[Ub7dcICn:ܴsELO}g=2-sCL-kB-*H-+ac.k>V.ԤN[:tq!||Ȫ.g)L. [x 2pƒ&3,"լC(&O,84E5+ȕ8 q6y?=,:Nrӹ6ouXcNz.r+FҸ/x$`kƠc*'XthT$-x}.,#UH\jz4p0QBr8zK6^v,OjFm+<'ԕꚵsɦlJLα`܌1};/Ҷ8Kb&Cx`Qq"(޵)wtE=B#E8G=]SReȑP$Q7m٤#{RK۫zEܹ'H^A'ҲE-2mP1Y_Vm|y}*8ATݰ[ؠ\Λ {y@$}9чEF1N:řJ|뒦Ի EtvA56Ui<Za"`E8tb+R uyZ}>1!SVx̳fc&*=D64Q?6?JSt:N+eJv絑rwиfZ=+iy+H-]_ f`QZyg,FkOf%(CXaWxVI\ ֧4&L$ ].B wh$)==eV {ƱՒ\C?\3PuקIM>8;qW4K8ks#`hnE睙.{{Wνkj\e-~lVEF=v]n4+XS+-Z4+WaװȫHRD)6k5 lU#'ev2zzRܺx[JY<Vq`򸦣mQ'#zn B/C ry[ K`Ag,SI]lfţ٢0=rj q}ptہU,.UQO3;Gi7±lT8V9j)E\ޛ;٦ܹ*MѾi! nh>%9VSOoHD!97jr񟛚|^us?&WtQ85[?h;;kF){3ݜUOH,Fvc5qk[&Q~m&- E&{3~U4 8?]]3Nm;ƺ cY[0jm=q Z+w<-H|++lʹMr FV52riJ nQNEV h5 c#7GnZ1 5i 5є91V>cڥ9FF5&fIOSvb;NZAUdXz6sBj }ΔZ8i\'Z1o3_ʌ,7^1&زޡ+#O/|9G.8vX~UiDT\4j1 7))*][}?ݮA'wi^G::Ӟ"+RKOh}/#eZE֕psG>ٺ9Z=6t u$-6kӋDkNb LmqTJʳ=sba޲MQ)F?+qJRVLRr׍j g\>q[l'IwKZH^`z";WZ|h#U΄܌dO\;* ?o,F&5iZh*t2wcJ:̩VS֍K"i^$d&zɯOԼɭLV{|mc|R<6RRID?*ڔiNQ5jU%Nzߦ_xqcspӝ}kʼuRh)8?Z{__xk\P6mUW =3fbs\F֊h`1J˲;گ^f,sO^?.0sַ,lb}ͫVb5xPG, _IzX\zbؘU ~LtF!\] sּjUR\f[+J5b8JTvY[tF6 wi8c)lr[=0r1ڷb/Pt9 vv2ɔjzr̡nO;.5Z*)Q#rHnNi23Z(aSp=KfЂ3av1<֕4BMW3U*`Kfm[zTte}Bͽ rB`I;6.yj;hAf>fEdE|bm0n՛4`ss]؇sJb,EP8 Zӕe8{.8p 1ZM{rlqY6hp>91;Q;=S5am'|g9pm;rX[LnsֹąHzi+2ӗL/B*y-QI8.i;t{6bvrqX: 29aZѥϖiqo-aa +[yLxwX#O- 'bx Js|f2S̹X!bkqd \W;6PI8tCG$ q'5}VĈ0zDoZͽ%Î9k}7*Ual]$c 9RɲD},Ok5a⯫֩ZL,nYvLYe*'<@Ʌ䊊 M?U{sJB tnV{IcSÚ)trH k{HE!۽pN4N.uq#33gh@F]vBNJ(a{Σ ף W8gI^&Kmz`H {WCWilZɛM9f@'gk>VƬ0He$gU+eFsf[WS 9&s.Q'$zV|[$6%!VN 3 w(L,H+^:U1)Va-wgs*1]l3cq)|*9SMZL{Xs C ", "W42B֛&)f1q''QZ_U~Ԛv28l N/flHTZa%׭FrR}UE2c5* jk>H  -B% .s%` 9'Ӡ9ا$KPBHW;Jc,ؒ9[B,iaBvQM@z [9(Y1SmAk܃ڶDK:"ުZܣm ƄY`c#~}jճn1S䰬uHi8S\][ PoUq+DEBv՟fp3Q8>UԚi$$N=(/ysnT`M8Բ4ZQ;,#ILbex\r)M;uU+of[EI<+qԵiEQ0@5F卡n>R%.ٕTh@7"AL=iAsb=f OӵWX I4ۍm*r,קwOqVtY`Cji\]#1 ߭u7TveH$\)NqPm]P "IHI;(D[֯$XpQt늉VKT$gGP|r[Q :Au3*Zf5t^};Ȑss |znCj؃RU0A,$c9i $sTK8&l(vާ6pӷjʪl};F l|eXgˤ)Rlci=d(?Vg؈e/>Q+o\\G&yP}pMTTvSME}v8*q[Z>l$*O TZql8d\[֭<%'5&(ܪ$kczz> %Iޱu6«s2C/3P&nwqw+"LI+1!|0ekhi,$*zC[ge3uXg#b11aS*=;֤0v;q&g1SdЕ=ngj,2d754Y *ߑ'RC3$%%;rJW"q/ٌO3UQ;tzۼ@s,cEq[q2qwFQ~MLZLR2~{=&I֢V5d\5&Ad9+;y;YzJA}'֪ZHTܚҲٿ;bx桩'{jSGv{8*fHfBA=*Zi՘/9wd_VUh#Jc?Z K.6ྵa_sjoCR]FICQ]81#xwl$7SMp/1D c8ɬnpsƲ5]"<x+?\2j6ѧ3\̡ 3mZM?x[Ҽtp <ҺvZIku2FzUls VӖ2|7HH@'+TnKōՊ9衭n9 }gJFѝ^m $e%\M#8/5]-p =q[Zb\'; +8kjǝrGr3]W~W K gzqn"BHl57.E|_.r}]xhg];}V饺O lsW^P,)BV-m+GXM '櫩rK<\9 3YPȶ2\ĀX9;rحlȔlѰHUWoW|k3`dےWWx4spܑA=n%Y&i7әc"rR\2!9g5 du393;V%y䅆ϗtT,|ktV-xqR;*=1akiJ1VZ*m D%,k/U;qnvĬ6K_*$r=z,à ֦r喻N<ѷSբf7w31$kPܶPڴ.t?/<SUP`A9c^ܴv,98^P\DA!>j;`خ[iI4ΏG:fvyCA'훰]<4y;ڋh>pLaXq4BN>67!Qx'+2W{$WBrc y=F|<.J~eZ!_䌺7z{F68*]ƹL~s')UMOw'cRes) W*Uy<9B})n&k6ddp䆨10 5>Ze1;y +69(@$C>dEntַ C*vpQԜmm^zԮBǶ+NNb(N@.BƸIc֩P&:^3Zs"ytZcdyʊH듊Kq#2*MdT'uXIU:ɞ2OQk8i$ޖ$ :ల\ѼIbM;jRV3-bcȳƄ.&@ˆQѬvtEyymCM{f54Zzxo.ڸh %èZM{9UKjOZTխ TQ^cmu;p<ݏ!RuH#'_!R\xp8 o 8RM 4R[uldv瞵NX!PH1Vg7x^+-GRi kk +sI 7 peb#̫jzb0or(֕n U[hdF;y@Y=jziI)ƒŨF~'4jJ SNޙΕK+D&xEdj6:!-!OBʈKfd{l'FNnVc1L5M6-3dzWi.j-Ѷ9ZD2W)&z*#{o";ڿпW '$ilXHZ1KH܅!5<>b_,UJkIkr6!_.rT7#A&b8shZ(_"Ǣ4fYjxŠCGռqpmaC-]#gHK096sxj Ku 3T IH2&;l:1 v֏m0TsU[ib{`{;KMRXUDi쏏AW$C\cY643c!jv*ۮ-܁иZ񥢮>qXڷmb ؈XwκY]FCv6MI>X)"J^ey>tr\btmcNᣕ|zZڐ󰓹}BEr1Gzudwd߻6~ oݑմJ9 b#2K\ǎzT8'7U+G[&m20S OAmn?xm0yoj1 ^*94odNR|>dAZtK%WlwoveVmGqesT8cjn-V@,%y d5lh <pHay ʥKQ?l4iP$ylG %ׂChorٮ49ep|OO¯ANAR`b*\62ocͮ'HCj~-nY~s517`3+נq|4M,O g*LԚpK6kjB|.e !YЙ>ԃDL \;)YH)|Ba~1PĹWAkҤyweIJtvir.Xywsk4(=*Ķqce1^e6"矸*9G JX"IFvRaU2'}468ڞ[qZ9eӔ햭FLv"ff$BBئI{4dyT@sV"([̧&I1 Qۺhl5KexWL}\6XFVz;n[^&VSqVl-"goV-6jVg}FV'bRݑ SZ >\Y -56Fִl> 9=z 874XUKOe'*GOƪj>,q^dzT5ÝqU nyB@nXlN.yrG-yat<k~[;=*^CދͣE%oCjYH }f_ls֭Yנ{Lz vJz:ϝ^/'DfExZCe{ $U+e+7ݥ_#e-cPV+a*crus:LY+Th&`It}Ni6:wmQ-3GN z4 F~p*IQd2=[WSK+y~$0JGb idq#^q\)T5-Rħ>Oh*Wco nf3X+I$=ڛQ`#d]6 C!ãShڱң;de4kXwb94h|ء3ճz 3U;AS$٬R~HӒivf`{iw1'ȷ Ӂqiq.$k7)%.̵u9+N6#m6\ⴴ%H~QqMI7iaSRK2mu+OAZIYGHW>έ~^+bw_f/7 3|xVqQ$!фet]?J-1Ch]$W;O#p∤޸$$8>[=MZrȓOCԶIuUb+M[f {`̏+sT3TҖF&M1A+bpq!c0W!_JO=}m{z7NMmwiuǸULVsd)w`) !"1P V`Q 3C7IzSΔꯎjj+ev/$gfvգLHݷ$Km$6fݧbqOxCWٹԥUIһ r=6LLrKEVwN}m#cxT]N,J7#5{XGJ\gl5;h^o,Dgߥ[ItZ[A3rZ?\!&mݜVd4[T;lznkD8Y CuVʅkM~m!{ruU 5(Kk"KƜ.yb8 ^[뗱4hg IoW+2WyaaY/(UP5惧@ (?ʲ:˙Bg,M>=NZ;{Sh_y7`J Mo?-Zݤ.*{-z򥙓;Yu+:42߃5aE{u;w%N3i@[HqRC FSb;P<8юh?Jfp@im>[ͶmVuf+1=\]+G-;ɢB% $jǟR3U<5v/`KΕIBJΟܣsFJYd G,^v: `JUeЭ7i YVX|=J2gmrcX#jGq$,K?jA5ks5zcVlAZ9aֹSNOY&UM6v[W%ݤHJٹ!gh@U=>Rb&q.]=LjOkMi5L, s*zeX:gE0GR]Zfi~%F-۲K=ݫ pY5tDzZw>.8Җ= @*H:K+.-%լ ^wr}f{ҝḐn6/Wjo h̨*TJU)Ueckoq&:JhPpIa_~*3j3>rrI=}=~,Ol^`qO*Be- sQ)*%k*YޥHCCYV*OJйu" xrY4̧V \T Հ}P g"icN17ڒv|sɬ:nzq޺o\abzcw9ϙwS6hLm=s\B5kpZ3 +*4)A8TN)1*1Ջ"ʈ=jE@Vʄ*դ[j q?)THk構0ee>RUJCHp>LѴ3uWi!XR;,.8,0Ip ej@zZ"vg\`5w*sCD>]P֥"dB 5`۳A)5421A۴m M2HV#5!y\#)W'.wNEzt)ޫKַ-ĂEaRћaW{UPvXSnaN@=LY.>:Vyk&`YQH$= e^ Oq'VryY/ jVwS1 iۋpX֒osdAɾfMd!8?5bEw=MCW x;T*.[FmȁR@85~f08fYLFI؎^R\f f.kSqr*)|-?n銯c{!5i+0NGJ̪ w%ܙXp;~5NHxy䐠Agyc-v5P M-B"9NiBaI%!3U-j  ʮjޭ+rϏ\M@ j5V#_ze`c+HI4M3*є'*C>\ާWPĎS$πu%˱S_xp+ =d`:׿YTmAU9n`\0[Q&؆p3Ycջd0l .ќb)Hsǭ*]mݵ-ך[c޵J9V{bbZ6&.O_ΪBp`Tb')ޮ]7ɂ1s Ħ97o#8QP֥ ) V A-W,R}C[/w桭ˑ~ںmfA'"Bтz溣{Zp|UM+PEs!a3֨\" "{zm֮$xF8攡CNR& [4RԞ[yKyr9RH0M7'&:[T;KG51rFMM}`Pwlt- t8Ӽl d:k\ YC r}+^;a7T[ɒAz4&0l9+@W5xE+ nafxLKkFq҅?rn.6,gQYdɻ[8F,OV^k[hH7wjvB6+ E'M({Kno6`ku\#o$!)MtBȧ$JR'8JXru(-Qf-SݸQm>K!|xqQ\B6  edl!'rzֱI"[XյrzZ|35v#ިƉGB `9Dt8s=HC)a ;y#jq )\s֛BnbNq[4wJqPw&GK[j[1 zT~ ؍]?4R5b5SzbOK0B>URNB.J>n`xЬ@8Zj\|)eѭDis`\V8ɮ;>H5(OCտ*WwhGSUATrS`l;[4Gһҍ}=1%0X1]t4; F;OװBܱ31}h]4#X 4洍TyFU~pY>{Y%bc 05nɱ6^,I呧2Wqb[Os&qVKSM?LtJSqj֛n)&է_3$0-j~_­܎*ſeoj.n>nzxM[ԥ+($rPٛǴ;OfOrC ?Zd8?g}IZ'rl#櫶ݪ Hزu`BH)%bƠWUҥ(0[5#^[e]ޔ^rՑw7dԬ5IT 9Wn'PqZJ>C8nÔZ;]9A8麰ṱ4DRGғe$`=>1ӆ#XS&ZNwn 1WN:#]WKzYQ['i/ݶxU]pZ-z4HV, >;Uv^iO w>e̗.z!7oBKbä`8V4! spGBW5f3H =Z\FcN0lܓ!*x'ԭ`ۡkyj;j)NGmրYsWEجqּB20W:"=8T&"g$0jƿ.mXcz-KWo\4HՍ029Ud֚D۴8MC۲k#kΖٛ`R qU죽I{j 3?o6>4JS 009$Vich4upn'ի]ZḼ <6F3I~5FGg~]1ᚔbX0UnXJ c[p^=/qhxMu4hy8eKv]{y1nV+ Zcz23[&3 1r2ohc8#4۝!&q5~Qci8ʞw @0;9osHNKbmJHr^ݕ!Z$e#쬭de : ,v0'Vg^?WL4 xs\>2 Hn*iC"ՔS6;~Ʃ6b4GN"y\[זŇrǮBHm4:\8vZ]u-i/[I*ԑ!CkĚ|kn9\ Ͻ5%8IFSA%g" WzÚHَ!PrS}UyC'$TƔԹWzd7pBs)bJ51ȌN~k A60>nr3X٧ʖ>%Rܣ0=Ԍ ss|zjV.?:dhLJ? ZKkcLW6 I/]XK+<ŭErZu{-U#W;G2OdK+xvVWtKUJbHY-18:K;m^%Bcsy%6b@GXəX|=cU}Y/mtj'zq'ԚbFE(DERyv,`vQ7p͞xɭ}8h$\r?*pݙtX_ Hjʹ% K仴mx+pjUԮhdmZęvLag[]:,xsY1> fmp21?J֒+V:9qGv$"qӂ b[I]*D6kF.N!#MveRcb5C_1kX6\j2|yq* @+*)ٳ9({$z*/Uqa=H+&˭+$H1ⳓ*)<"p=ޮZ[J`G8&D 3V!'m5+I:dd{ŞA e$c !UHS2ߎII7c't}ZYwѩ8U푾WDd1OtԷW yX""Ժ5)߉qtgY9;F?pNb&bt=9;T5ա'w%cv]6/6yVum68\ۂ@ʆZF+u+ij^oqW] uZ曱Z;ښ#(*&o9JxPhdFMt:^r-b nM?̗)^O‡dzΟ@ǯsS[v>Ae;nц6a>Q52(jU)]FyotD) CasV+N)GRu=0*I# @?@EMJW5q~&=9ִ!_NhUgf1U=WSiUO \ɫCCʜVd6+}&R,/ M~ɸy1[pOu]t[NWgJV_3{"q\Lq^^ŧۙQ$* +Yɍ6~3pOcti3uxk_hR|%`En0  ը73}wFrUP Hj5Oj{9n?~b%u3_yZ| I4ҏF=+U1du&"ݜ-qWai\UURuT%cMGVXl1sT%V[GTMf4sm>^*irQvr9aZZZg{u~O=l4(uA?x.ERv2gΦfj9htiz~y{bjwyl㑆{\Lq1H:>w{24qnpJBcs VRJ)e0 %̱ѭIĒrn-؞|[}>k}@pKk:Mɝ R-68ml|//V-}cIyUAoS4iSYzn̲̒ߔ~˨Qf^խjv죥`]o7+Xw ~cj@s*:-Lކ-kwE I:U!K>΃E!*1TޅFړꮑDb)\Vlg+0{Z6Fk2VtCVie76[V]tO*$qzUFVGϵ_@W I%gs8; VD #[:;CmndA^ TE,dkUÈH#aOL8lk,LF-WFc)[#S gzBXL .~u+oAJ\˙gw9FfywQ.3qZڵ،$ݸ؊kVe9ՊH v}*<ڪEUb[Q⦭KS_ho%1`w3ZdRF8VG QOޒDЛN! 5~J񒌻 ģ\ebN8ĻF8ӭhLTc)+"Aml*Zv8؝J&XzT'Sm=ܹ O&@LZFO7l <`+Y1)jLcjmF=j,CāRYͧ6dW(ۓ[ mJaZ[d)kO(VX\ҧr1+(7 O5~g2Oz۟LshQk'B3E~<̪#}QZ7etg)Y410myeep&~XaCn9]jM9eukIιՌcʑx*x?+F᥹@NKe݌2<*W;U];z G/ta)s(#ȀW4o乕ՎyӲρW 4OK9Ԝ-ΗlnXSTb19?d˅8n2irk($ePdǎ0jWk#_! 9[(S9o 2BɴӇzWZB쯀2jIaU η4m*T2JR:t,3ogu^Gf#G(i* 5ʖn }ꇍ4[68UQ (XRSkUvuHQ֕[ |Ӓ1V,WR"Muws!'V]LmI,o!A_-m&￉5 RiB y4it!&\F3׵^EswvR22UNe- WnI=2jŵnQƠq~RRnn#aǵL4w0?3DZDQ"(I58 -"թmHɆsWg!  J{,KbӊZ IxWI#lAtE2كf<Qc?pR zmNx ݶ/$F'<ڂ-IL=)#czGvTi~eprFn۲GG5§V 9E?X3smQ6=i]Zc$Emj@ 9[+-t[hFYA\m!Dr[9f:Y#δTb>iܞk녉^y^k0#=3LЯe =@c)5 %)rK;KeȪyYϛ3^g;1n w&xGM]~sGZ<lWiEJAڳʛ][Iyc+Tg9dĞ\@rGzy,6cp?[4J¶b%70,4D}bÜYaqH>Q{ tqPJMuH,4X*5 媉B׫PL;if(RsO)n C(vWF3!8L=ʒ6cS/ln?`+r6~NU8qXw<|ᮾ-2aSPNy8tu0jNs-Dm4ݎ*>^X=oE#Qmhڃ&g<_,lĎxdҭڷuUJX&[5/7&km:kQe'5.'wP*ŎImc55f"ѮT* [tWQ zX6[7-F֒vi' k-2w-hjY _O YHGɮ,nc&0Q8e{S=8]<u7tQ#}2*եI߽c*7PV'HE9-⪿̬iwgM6n܁H늕ِBZ,:b<6S>oez ܝm9ңv(JRIưcţ+`8W`gEmçҦKcpw(jg6洣Q64cWu$mΣxm\8i~eI ٧˰ѓ*#2ɈWcOP&+#X2I}@Zue8$ *1ZksYiθ79liph<y|N ^z>pGn?S(4Mu1x!1R"E$* Lj.WYś\֛fu9ZFOP٩^1SC3l[S]4ZWjVv}:f*?:і,i+ݫВ/`VuhJK_\<@䊿a-64yevQ?fMm;$w)M\] \]|DR$Q%};KBX DgcgBaRg/.Ș\둚!LM(8MF s]vmc"Iy^.dV@MLw+|WS/K{tF? vIBq:z&v(;֙L|#vZyQF,+ y]#uS5JVtrJ̫>gˎ8 kH&r\莍cq~~mHb}MD1V&'ﳘKhѮ;rjƻco&Fy\ d2pc"ɟE"mhBkXĵE孬3VUF`mv1 ZE0^4NnVHl7k-M"֥Y DHYpjhK˩Xr?8j#x;[ bR~ pOUr^mN=j!_83P [ %57 tp#BI!9-Ҵ^:1Pcqv1Rvi{ ߽KtI۸䴆eN2=OZ,ǍtZI]9ޘ*J֞BsЎh>BO^IB_$.Ѹg`)i +J[³smݻX7:ٜؐK9M'r+j79g@#B6*3MզWiIqڞy;2H[>⺝j+r"I1ŏU]?czq]Vpǝ:ty&_":RUst^?j~xxx ܧ\Or iN2MU%RK<3VDwwl\rdC+kK=Dtj<{I/Jզb yu=iHt8<Z3^F\`Bhm<ӥ(q.Τx@J}xH=n eJ JW<ٍwƲ*QWfגH+hId%|yܹtCig͊|:G_9%kN/y;OUA\x4e|ѱ Hۓʠ][}UKK _V]Pg7k_#]ֲJВjĶ}^cekp~tjxoIqrqҦ&ޞm%tx^Sh uwTnv 9w:g`q[ڷ1[QrN=N8Z[p\p.O 01]ꥎ z"F틋ezgqтk:6f𓃺[IHeUH_zsT.3]"X[IlU&ASƛ2_!& XSF75J'YAZ@U,&Xqίz|6@I5%ŽaprqֳXutm*syNByvC SYsAvO," zm*Ljd !B;1eS41;}%)J 5jԈ^6@~Jq5䴠OznySWѨxb$rpHUrna{ZBHYAiREjc8dt\״b$]$ cݩkfE'}>xѤf\ flqՉcMG$rOzYne[rW~zҔO_(8M{~hCupnFUb񑻨\ZKɌ9W a /RR]5  y[V!{v6He*ڻ!$cMJZi]w lYk ՗K_jX"*ޔgt[Wv14/>C N":֙G͸r3O/LB6vڮU>Ѵsi׭I`VSp+6bmv/-w3G2bNÀ*e戡oHXe)v\VrmCVOޕVnu= 9,^ϖ*t˩;UIQV 16kJ]*k|zUh1CҚqk@WOB{;U $gG#}j!˝8RְvDRZAv˞TdQZ4ؕGEi80տʭ>fF+M-MұE%Q j=F+2bWWI)bGn݅7\i.QTQަUՙe$&AT㊣y:P 9naMVcg^X|ª?ޔjH,̶LrOqT+w(6=FM6yby*ǁ6rbNITW4p|Xٌpw`m*k2 'OqI 0Z2XɟΦ sںݣfưvn{2Áۇ-j GQZ28=jcZVdSZeqZ^z$i +Y3rj-/e+d'{50hX<|خEKAd46-=e_s 9\>U(kM87f2 +.ڈyGAH Ԫ jݵU"۷޲y6w.U%Ӡ jNnBSe[j\Nw AI 13tr4M[/ .T8TӑPc8p9]ӵĄD\K[fVɌ9u(I/$J0pނfݻ'fWb6=)7e%s%7yW!S[VC$*1\?mh +au*^:ӏs66G㊖,.O#;ݳ}j,ЌgiE*Wj~dISiۻ4d2/2\inBJ RwU9_C5(x9֜$х,89Uu)LFثQ$y[aS9]Zq>e5CȷeAbsXa#E#H5kZ"RoBքNW` -WɁ _[}3&[ZMWcYRꦧL i=ʙb;2HhjtIJ s5i.9na4;:I44nՊH@ާV )ݣ*kשJI>ڲG,Y$nmJkMkY&y-Ɨ%.k+ W#gZ i`}bi v5)Y- *j6­j|#ս"{i4}CO.69V*xak,zC3 u$}Jcc_;F!DA^Z|Vv26FHS|zu?nxHUvڳSwq>!O]g2v\ߎ.൵o:0=)GfOEUQҴWqZL AZq,;KnsQ"OWcon\Τw5UCD\(*H+ӛBhJȓJ# 7്ƣfUNƺt?Kj \e;7݅Imy[BFn۞47a3_J-Y,%׭X]pWX+t+IURץ,Gv5T]3WM%[Xc*:Jd UCdsFۑJ!CkuAG, 1WslLc =s~g#VIt6lmi"rΕfĶsP5}vt"K7S]m; bB+g)6¬!Jdp_+d+[MSlq7cHnR(LS%KȈ 5ѽ 7N*9jmb]C aDaf?֢ѵl*&Oֺbd Q ٹZʷ@0ݎZERH5}G@$h;&j&+G{ | a nkx jܻ1hoF] w+9Px5vա0y&H 1E }g7n䐙 '3R15qZD%V]}*>_Udyho 񯹫 ~i>4CPjՔ3jڶa86f Ќ"t ۳ɑjIzF6dz)KW8R9vCZ6IOPb9uɤWX^mpzY5Uٜ`޶V^I| CWK,*!m.>^O;9"9b{cܠ*Qyenr8!4Ǎ[|i0( ;fGDy B}9VY,1D!P2"?j?k7:[{ɪmhZ܀ c5Z_{ $`¢n8qvCfD>vermCVmo4m|H44ں1FhMʨ㨪N B [,nHQ0+>}J BsB Q~+`d֍qt&-l2 $7 )„ǭXh-)TЩxSK6gVW%/F_),j5ELk +\0˱`j֔uړJHuIVm?m.pkm$<eHГЗ$1E2G^HZeFL W~u= l-#%5Lm9>*0Wy!FmiUըppx V*]GЭAT FMK! RFxFBFsLN?Us+%cZv #)z`<pOJ,Uu.G\J G}2ƽ[iKB7/5b`X:483X&2H*̢XHUb?,p6 qkxR0m{D8y"gT itV0.9kF'LD'(E a\zJn.K@9kb+Q72/\(]>VdYΝFeʤ6AS,.:р}5z7f7r+r6T-o ey:9so wYjv )!ߓ^ugad enYZQ@C˚[:XAܱڭpwkI6v3ŌTԤME?#\y3z\,G{޲.,M䌎ܑTGERNֵ($㞭IY+1wXgjs[[ͧa.t!e1M?7R˽/ZqN:  "ϑc IewG4m8+q&#:Dvx4;%Rv˰?c=j+0{([48Lr*z$*v'r^:zr_5bՁ̋jK-\ >15iYJy/ƚ(2 bz|*UT֥Y+r=+N{l**RWP4LVV ܌ՐÀ]7t w*Tm1h;)s;^NDQwz`fQT6Wo l i\P *ijÌ5ح+OFa">8&AmtE)|\VFXcⱤ[i_#3U;4SĤ$T|N6vm59u?f_,cQsK˶5[lt(Tway=eg;+&kcOk8Gsq&ᏟYṀoVB3jɩ$ fl `[$?)9k- Ci@7LqK+qGJ&j:T36b]uی?,p#ŞzsEkQNeW6 = ~QAI̎TUŪsoٴVhXg"BLDwW[z єW7_SiDUf#NAq:F+WQmC De#5 Q|vu )-N-]CZ 98֤ ̦08XFQqsvk m?MQ#%x:Zgd屎:(tY:ʒ(G|x4x! LոX#c5Z}@)Hu5%)t*ʮfD ,Ƚ-x@858b"|d+7pc.u{ɛ6QF\sU>N V=lj1 kĽǘCZ:lQue:Rξ% '26E?.ub8oD(pO9Wz/q [PaW uRŶNCsi#'ĺE0xszg}<=+r]%KNVM*hsi1ۓNjZ):*};Yhk!"pi&5ԯkz#JBé upcIlKÏW4k{E;IMꚕd$fA37R2?) <#%VE+8v=9Pt*Ztw}3B_Hv|+4W.385*-僕"Է*iڥx*~VP3lxzhK$zFm>DbsI'iC[or ug-%ҩҌyb R Ȩi̷OJݚtf)$*y!j⌤4YW=GYӠԫk@t@wQZV%@RGlg֣I;xyKu9)J8ޜ^i>\()pQ`5[F-qX>(ͨ;RNrѿέxS\-ukZL0k縂%(+Ss}\8Rvi%` RgLΫ|Wnh>'#Nb]=} YoZ rGR ^~nA UN v{k?xV3҃Rиc+UNZeZR% MtkedܫUwQ>gU40 nH^jNؚu1Y\еJTrG5, glx[x:+VE@\~uSV6(ڤ{؈$gY&kv}B'KLlKFMixsǚR7P-eS(WFi{f FV6no`8W=2D0CR-Pu֏QbolUdjCS<Z?T]ӕn#Q5sy86.\thгy+0㹐jo<̿c;x5Hh2Jo x?wn^0=fz_Z(aQj~!0ɛW\|\<2+<R0sc^{{lER3v ]^! . /3~6Ρk,w7&U`jBz}zŚfn D 3τu^#ZU#pv=<:>湫m8҄Gl$\ FIb$ \͌s[KjK,n՟D*?{l籬c'{1:iCi#A3=֖"AC5}l^@3[.`gVVq]ܚ2j{-՛0n:t5ᅁ`i6.\ ̼-9p]e/Nz~ɸ-s7ckQ7YB\r+1Ɓ0s]\%My7ơ|@xָfsV[8Kӥg\*Zvo(=hXբ@0?z'4DF)WQvBVa.l)QZ페#a>й#j&eIԲe^Yyl8|܀M3O[; ,zم-9 s]KWUuU|sV[aXfUY /P2Njě OWvbjbP q:+DږWuWs淥4֤rRX34ʸZfoq|,j330FRx)A6>U: bDld4F&.O*U+`'511L|bJI*&EDqi/L6À6[|c&uRFy[KM3Uk$ߌ+VU>[{[$%Inۣ'Apk:|OVf ҹ-mcUMlj*~`=*iFp"3Yz;rЅ~;H2z}+9%kF7E[cH  g[qȱ#q>]+p[w<1阺5[g,Vu"u%.P\hr@|ݤ)&"Isε8 3H,N*UddC~б]#pj3~]nqq*&ݧZ}RZV@@n21ZV1y5V;\P䁜)'ie2qXڌ.AS5wO}4a ԥJqz\4ZjĀzcx1B.x54E[J+&|UHZ4LJJXjetoq鲩Ҁg9`U XHWk21\= άs+fM^IG#g'hxb4(|$bmic34|BRKGD0K^bܪ$U!MU9ţF"cqTycgxcޝwl-L zQet(Zh`+Nz]L k&݋ QƊ6Z0ݔm?:!&2b8a(7˞ f^RQT^*HJXf;bjX_,s޵W3m)Y .SexW(cSGQ3w:[I#ăMY݊t+$qKnZDPpB?SєBXɍ<͒+ݳ3j/o.V]_+\mc 1dRPk~UN0̣&ݎLi.G ,;Ai18c}kDe3"e,ڏUlr4=)$m%B(a0")' v]FdEaI$*5bg1 8ȭUURjF.e<`7տ*9ArY9H\:4$gk)-t x1c2ƣa(EyRwlds(=jm BȨIu撒ZI웼 2NlDoR5+gUaW.7kKR[K{T0NcOLNF"]Q䦞cskŅ$w4aMq$\1i zzVF-ݾbnUlW&6+ 6~| f٤]]x`vhیՋkȍBdVbTg\hv)BŶ?Ɯ{66oA⣒+Yk{3tMFD|WkskOhȪHiS!D(NOSV~ ][k>#Kmv0:ҒiZHޏRK q*:zUѹzlLPsgOZYm2nʎƻ M;΂1ʥO* #OgȴծYm|{կ{6O;6"'?hh:XdL qY,ԯy~jC WZŸ)vXn7#31# W6tHY“שKwY#ݸ _zQՓ4̟$N.$Tg\N<5O, a\ˬJw!n]12&NKCn(|Î}#HKm1nb%%oS\+huHY6(3^ד ʹJ\z'wZrFf}PO\!a{Th"X9*Z15mfkxN0T˒uVdXC3NDi]ȩtJC`>^ɭYSVQkn$*ij3I5 KD~ˑ6+9A͡(ɀHK;ԒƬ$V\Sn` 9sŸ#J\DRQj-]B?yot\Qu6ܼYk @ u]'G-X{3.5F'k-Ey-%r}+I|G % [yR{4s5Q~ȀAqXԃ_m5F}BIepO\ NyֶF ֆښL3\˞Lxd3Fk(?c⥱&PHYm̱5L'< qOB+~~$tPUx֑R8=v9I;6:\⁤&hqN9QJʝP9TI+`%]$gAasZ9,.T"sE7Pܲ~'4'/]Y{w#ay5[$R?2.3sIkkİNC ꜩ1uMc!adNZ3:Wzl4ȹe:kX;LvZ-ԍk<򵕢&9^#dcԱjR`}5ӯ/BXՈ둊kQn1Wʺ-g>{"Gz|Jmuk=P`pqֵ,Q l2S\2<'"ܑVmk܏S^.av6 +vWQ/qޤ5V󸵋~q;W>uwij䎕fG9Tni&쑃Q\VŽ D2`S].*!+8jȅ-ԋ|+U'sY(-jrk vToؖ WikB9bZJ;q۵2.׹5QۜpW{˩W[kH'>[>ڴbU ^.G`*[ {+J8UBX9浮%EhC"tM[rCvw&Rw\>fBF,YUX$ƨp z ^M@ilg/f&LJӍ=AUT֠°D^U -^_k3[yZ,{?θcrv95pz,R-{d߄"ʉAɪ Ko$OO-yEs,pL0!14B>wUyb]& X=,38ZiSgf`%fNj<=@uTO!V*pyӢuǞ}s]&i˴N܂k ,]YD5ω̱2Z[F;w4{ZPzS*IDQZTR2VKk㋳ o>Xs[G]@G(+yx!W!#yzUЩ9/xޝz'MaⶸR⬦j˵@>m@۪^Bi]T.'\4)({dk>rUF7DKSijvmgUwLlcYھ aO$6*xr6isRGK ]#/ESM;JUڵ3B rwⶴjzThʑK8w85ZtҌkКő>k猩9 [K8[iQ(ӣGCW4ȕ0GJa=(_gL$Z"Ѯ#lC@Mh) ~W4uGDcem̕QIJyM n>Ox/5(cʻGƜ/= N+I!0[Phl Fd$kw#qZ ݑiHUdVze[+q$=f\Q*IƦ"#N>dȫ둑LӤ-N^?V; rz՝7Jq{•Y7QщKBFD$ƹ=+€CKe;;kcr4yzg/ uPLXԻ~7Vm8¯MyBAʥb_+7xFrugm21ǡ9Q/"K%#+I)$ TAQ:D_ڣjm*N.1e-U/5&ltb%c!~KYc7p1II98rFrEqiW6˹5g}#Nx8T+"F6##ӥlk7:nVi8r5:}d05"F%P{AW7wj/+yzBG5OZ6d`\Y'-#ꥋ& .tMr4 ^ܚsnh$k4#.r1kR[:<&2涥C zzJDíiXMcdf^3F##a&s(W<Z{ =*͜ ܮ3m`G, 1ou5+XE Y!FܑޑV$'*4A3UI9Y RoFjbr(1y"yʸGqUK& ЃNj-v6ұ#k=\Ԛm%.q=jͬrisK1#Hs*Eh@;y<.Kɀw`q43uJV46`R$ jА21eE򞝫U)Z4ӿ2dKpv\J}vyugyLǯYrxGo/p{x8ԧͤ4FUjk!`eR)2jTtn IxʃtEJIp u $o3'=M_=? EMҴ̕E'b_Ԭ٤ #,; ȉHz[QNK '%lY85E3IaRH\o.B3wڜ$k՘;.IWt+|#[Gt`䊷UEh`fy|mcwۼG5Vz] 4-Ԩ6AXTk?B^^QB0SW,3:ɨ>PVIhG!>E8x:F<ˇ(;2~Qh,Lg ҟZ@͜sNƙo4JnzU] ѣ/@懊sx`O)vphZK#0Ga^`Xg(V;֗)7VҒ0CBmٔ9n}W[՜޷? 1 "0*HHs\X{s8Jrɳʮ\7␃*5cL{HcqHi6z|m߷^+>šLY+"}jN_3ҦeDT  vŤp2rVz,X[y$R9Ik? buR֛HP((ѳHs[ve^1I8IX]"0 59ntʌUw*K鱝 Gވ%N&ǧJ{ 1.uuU~aξ#TlvrVͶK=k.hq3GЕ^+ͯ󤴅_"9@2M7W%[=8k~E 2(x6y% }lڕ?9iS]$ıKC,`„s+ 5ikt`? ոCo4+"vp$ ֲJiz5 F$&+Ök~e:4qbXzԍ?u?ڥ'4-ZdZ78eroOTQ)y$+޲tkQ;b̠ݚF̚OtY >j˃w:+B ;CM,Hf\ :˦ Y_Zjab:жhMm7j]O_Gny?8ȁBש6H.Y8,3WUY_AJ8WEKwb@sz~dA FVBC*7VSLԍ>tIlO$*`+*TgV5]p´gO K*|Q6pR+)9t~6{{y!;AzugmHv2|}s]զ[Cf\mRRRBQ|?4~$ʿ-uklĄt.?eE}E)p;[1s3W!kUkn+jա9Ik塥zPQkEޫ0]k=|? )d_Z݅ڌݘ>ǥ~be9ɑV_-E-ٛ(èEի4G~ [E$P7~|=͹O eo@=2kzRⅢ>B?咕j^J&}lOVi(60&|[5O_.6+o.[3Wp5椰$9ԚҴR{jukeGo4g(3Zq(Iџߴw|<Wt+拀 _|TӬ!؛V! ׄ?+@νQ hIcO?u/[)MVg[q*ZޭФ3 :?κnG,%V _~stsM«k=u)N)oө|]Zog;:u8 Iwh\ ?56E2oC0LIM% ୊7UiSvK MTeB[uU=k>9a 3;9$Ω7||GG"9 {T;$?mZ&ڬjş3'5 CVMIO1# ͚o%&%$*(|31988iAZWry澪e_NMtlp$5>4A 3%Ɨvǂ>`D5.:ժ#δ1f`0bp9gt5GJ8Uqs2ĪO8jUB;>Ưy`!@|~#*isrdygeLF:LI8%Î|H©=aeVodB/82=k%Ü]R8mY*.## 'vr9jwJ:2'֨"T,YOJғ1,5 JŔ8[6j6n`ca纏' ̤N}jDlX<(_ =5yUY7`-hFp2Fi `gӷr f#8BF1VXj lb,l閭};AUfzލ>}a;[Zgz瓟Z-Q`w$YňJlYeڱ3EmPm,q]ExG#W隣Pdo)?JrnI"%k(@]AKnnjG[uĊN~c}5+ҹ[zl; ƬiwIDp1 e[^yb|V&&bT;;V):Aj>ۖR>FP7 t "=y- /tN6-ėˀmAYPjWډK;t"5j_HcK._5+3o SG_Ʃ%^J&blZG$\[JԞ!r/֬pD- N>ESU5ͨ03펫/}2ۺTXxQ<29 :/5 f̅9tC3i*rk-̼S!p?(jKf\*8O ̗2TnUR$zJr8{KL1G&7]/.fCSWx7( D'o:׹xwKo |7$uF$t5R0?%EEgrOK6g {W,|Qwٗ>^`k($5܀b] <#J1ѶkiOX;#BOڢUx3rovWhb1Q%wQ?7Vћ:*X"f830>Z']4'= /#9aMO@A@Yr;[A2cv=M6,0\;p{֌:e۝=;[iǥJ8(9hpjZh_HߟzK"9l{xu{)'N)btIМv s.*$u7NTվt*)Uv Pa`~VƟE$㩨?S#8[66ӆj}:GfCՌe\ۣq5r'?$fb9Cz)OY#ghQ+&Nuet;9 B]Y卛ΐ&j[~GC~lS涗65R&hivc6ýePz֣b%_ہMcUe']k90˞h=Wβ֮V #Ukn=)錚5.7cn5ǎp9=;ll1X· n=SDŽn9̌ԛ^I_,N5 : 1DWt h8gA4]V NDfSU]P*kx.."֮sqeqK n+ơƨ"tmJ;Oh70>^7z|\9,EWTiJ,DXJ[4E 0>sl m#cˊ +n`O|VZFCv⹧}a4.Jk|Ü^N#]؛aXZφhu#YJUݛh1Q_235YRY󮁼+vr!qh8GFyR-#q#'TmJ-Hۧ5Yw>)+vBIq꧉um[$9FqRh>,yn[n N|;q=UoXqMӊ^박Wz76ƻwt95rmwKyt?Z'IuuT@OY1;zWњΫ徟2xMEлThb=j-a[3AqWgQO Ԥ1([,Cy2& N?*,4OX Eq62V&̫,JG-ls\~&@^b1Ik'qֵb"@4) 837fkeo(FubBUepFjSz=F+R `c+: 3R~n>5?;ekM Rg3wj8ʉjT tg?2[B ,;KH2i..7J8ʝL<b$ GY~ vEz 3>`C#k83<‘(H; J+,tie&YzrǤm 7x6:nܠ6xl{v2;TZYˋp"*~,5AD%أ _0ėzfmTtңq{hkiG OCy*}*厓!HZ .ÈԞ=d”RF0#-g KءCԊ1*esVeT )Km,lQLliXɭ;_<-WLJ'lKDˎ,/0{@XW #>y_i7D=+;V*z-B~h$vFoo,#<5s]H-.d:xaT"&|SL>Wk~!XylpxM}* fb2J5+)Sԫ)+96hُ$ⷴR2-j'`gijrƸԃ'4+:zEU:&*ȭ=cL5mA8\JԊ=Nh˽j'*<$9 r'lJ/z'iVՍŚc1LCVmRKW#BZ O4xR~'+{U/`vI#tv:b!GD#R覬dDdF>!-9^j.o1K,NNUԣǾ{Þg79_ 0ՠ4Ƕ+o `׈A㊏Nqt\-ӓc(tTq* `BXeG %O?ݨ5?Zy>`ݞp{RR%aNs>x10 αr֤ o?1?δ4?1P3i5B+[XƜ1>BI!aХBڤ_20Pq(lLQGzv rPʩNsT ]l5}0n$jؓ!X^o , UمIbZ_xLu4||5_q=ĹIȮ8]M o6cqJKgb}HɉgGefO5؞+-m8^`;"Ek(Жv<;9Eo+3w Et>I-aSGT^Yې6EAo}yqs>7KH`Ȩ:hќ-!k?pv~fk1p% ^hZLqJc5@iPeke^2_] yFgb#֩?7j ֛yT5[iAu0R7piYsS+cۃm5H?rc5]TB8,ZHJ@Fk'VVV+ؤkN/ʗ@1V߹#pG9n3[`_2D뷑RAtr+y*pqPk6QZLa nx~fC336$b&nBY#5~VlmAo.OK߬ Gi]!g25j[|t! f/u:9ZuMeԊvQ;YDU-tt_ljcUA^GY6Ek)#rmk#9'ԹԬ-y@tjbϦj%~S)*AmOx>g$1,zԱk$ \)^. jQE:JEka,wSǏHA;kX(,\{{bLډt~d#wJokk΁ Go*L,Su qU.|M2TFs +}+۸~\12!Yg.g<`)l;)dؖ+GY=CyAը&av F88?H!dqІQ7Vk48߽E EeURњބ{]M8Whw*y)ntOܽ;OԢZrV"cK?NW·|7h> DFy>LnPz-_VdS+7R w".wiLddC5e\:1!Zdmx7)WmW@XxE3 bi $ЃNmuX2E4LXsEɀUc'8ކOM+76>iHD3Z%{H-a+sj8Ch}B'uwMXM-z3Elc![Mg)x|mw^I B͙11GWu6m"pj8tm!5PImL Cή_ ddz YQOJxEAWQD6`buqʹpC≮kŠ=hŝ7@d?&4Z Bp *%KTܖ9+{}x)rѢq5~T5mMtMWUcmD)+F"T4n3<343fm"+/o2(˟8'XţFВH:a5j4-I1l+L&rF{ֵdFAjمyw1/j'%Ž?MMٲMGCVA)դ5^îeLs$~ni2Ƴ%C 1Jlt}NMںskgy #Ҹm29}.&ѵ;+Qv:%GK͵͸U 0珻 TOrJ:\:mj`xtpeT5{<a]W=TWN1Z,NW^ylj%.GnN1T',P^Pr0C:Oǚ8 u6%ÒQF:C/JKW<^wB붱rUq{[I xh3ȘX~X_0+0<'c#7:Wg?x>k6jTvy7l+o6N;mt+5ׄR-VLXμ/zB\Sic+iU*еaS>ksº6a[׎Oٖ&ѓˑTCP4ͪc5,kōXd0}ev O㓃W[UR}X@H;j71xC"2 ih0#I+sIjg V9 rѴ=⭲#ʒ*mXʢ^:S2Bʃޡ6fI H9ǭdihv8jy^WFYAe#F;*-6jex }3P|ꛎ{Rk2yh~CA Tu9^fz =W9A&Kȧ ~'t$\~5cm0sYrI=k3kltʢ{Q01Uu?l@1k^}ta pyn%@g{B"l" NJg5;Ď{]_42:p};tBhRe]99hU+1T]n$~5,Nmb5.L9]*7fNVLmn#pqng HݐzS4c٭TOaQ:̻5ěp8f2 9&KQ+L 2e2 Gy{j%뱴=̙ݻx=O"QF4L@?++6vK$pֵgnkbFwo8g'&p*ݱW5`ݟ$sǻ$dִ➌KL㨫KlG!sUo?+fdݜEf''^h$ ǥiK,ҫH]G5!$3ںShL"޵9$Т֬nʑl2sږ4-Оޕ#ِ $جMf3 dBՑif N nVlNSJE,`NrM-9 QEIh\`_fUn@9` [0ؒE[V[k\#w{h"j#ď4ezv pG9o"^֫JI\Bφ.mj=cMY-ֽERBAo02+KՏTU"\ٜ}ߙM"g`4̖|y'W`ʍ ^Ux`M+T㡌=Rfam>dHܚbԤ䪨LKmg".3N޸Ip0MI9,cEV oJkF% ==6VS%RSЈcl$" /NEVzjXhi c%د/,^+nminLBwo[D`o$Eu,k+"eB{VIg`YJ|M5DE>HvglxwD]bϥZѮ&hpdx!q@xpp~NL$Ohk^t!ձ޷t]T[gE^EXГױp?8=8Jr\Ͽbb)zi,19*G4Nj+ UTqޛV᱌JץxP1Ҵ<(Wn 3ںZ\IȄ' 'ָ֡Q`+Z);²sxDn'֡T}UbX0R8ȪV.} 01[23@k+X/4f8&iqnH䜒{IEԊh ma"EnW@s>3&0\4Zc#rђP˲ gp9DړM"pz B"԰ ڨxlnUոh۵ݦ qo ɭ =`J?0CODvÓ=?8 R 1CMPN[ktnPQ1FQyh{md3H %P"I\wؤ;U(:j%%5\o3cSmmf9e"7qYՑ)$9u[yaA8<7BP`F8$e4r66*!nIF<*7iu-V@@seqOm.;N_ŎEGm}V+VkOmI$s]Fūxbɷǹן\[R\P"GNiw3'8pFGzi׭au"*,dj1^zp[gNQH,sGzЬGzg"lmч,:6*b}kcc7*1Z\eG)gF2洎ev>: , *:;LwF8k[JQvG0Q<+>H?<ƯGtV1oxrpS"' 26vt VY߯/N-ˣRKdmk6^! :Pu67)-1Ʊ+ e8Am닀^Ryƍ%e*.N t5fZrs/l57nA)#x3Hd֎+|DBW \v[Wij(Q#:1twM4mkBeXvZ046,P(qs!< kZGuɺ@ګG] #$wSG44p$VN&m*ER y2Em׾y bt YJɚl^3GPg>EYCE!j_Zg8/ʩ̙G#T>dƹ'dr:Ã"{C?>{VF\r=ZM_EjOiX LhAFZ@q]VG.ርGCZӏ32S٫VAq;ScdH漦T֠fh0x,oIpT*0SGu=d&*֋QVB{75.ywcÒi  -בQ?~6eKM{{}o= Sź\[1P=s] R$&qC/iA8Jp6!?g]F:d ~#ha TG݇l]8wX썲`aӋR̠F +&FSڵ=D3=V2xtΘCVfN_3"nM::;@bǣVa |gRvITJ1 o|*Ԛ>mGPa 8aoQM3jY3!m|bSG`߃ZW&-Rmi?B(nb)Dnɳ.3׭_䢖b8;ԒBۺ="'[l "_\ӧْxn*@1 9Y8jZ %\|;xTלV$ڥĄC_P^ hW25Q2*Vn)GTmii+i&n$GޱIaU>dr 1޹Ui(uOYmUI55 ;+Hh2}iӥ`7^·~9jMZ2kYbSX՝뉙G`N {QfNpŁf3M,G8删Xmۚ+=X 6Y6_GE+/p*h0Z׿Mu,xU[ ZՊ |& M&?+v㸦\*(kT-d qY¬[c? @Z6:jU(U4B`6 kM؛>L3$ⰝI8ѥ<:H mP8!@Ҵm6ԷY\N[hl7z&GBA.fϲ0Z|:$V/fFX&bA V&O+HƥOF$hu燹[_R/Sj^H!RNѼ)~!J95GZlZtRj~YTn &p;գgMjeͭQ;#45.y$¸V2,)kDg ۂЂj&`m*ѱW TdHJ+֥ԩn=dŧ\/xORIZVicrjJe(E;4Ae۩yk[hT2@8p_jK3 rGLԹ*0R%h *ȇ ‡=Z ePPJ lT{椸N[=xJHVMЅ=:З2.If9j%NEV(#jIr{.uKxeV{|<$햮!7FJE BRDr#U’qIyq</@i{+fM,»#*;\[ɗbQ`9-Qc>]#њt$'浭|I; Oftqf^doJ:pjG.Y\=uoi<絻mxViԣ4de{]fdFVoW,*c5B,ˏC#k9"_qQW#JV.4ś20cUVhm݊5:C: \}g ݅MUH&WM~$nY~=j-x5ounfBN:WUYVlbQrJm J7[rP|A.S{֜4&r< %JZFx 1s)IC-KtrD ?m~&lȎۖtIA>oX/O9;EcR%D鄪Eh]๏6^m`GZ:G<``3[eKrC-d r+/uh͢KXߑ^ܲ;r&d2;vQC!V(rH]𾙰=O*VD}J߼yB2#ʸp$T[jFO׭áiqDn?ShlN8gOdl56$%K98ǕɮnMoojq(}ao} oA`;$Ms2x2I?v|RM{ԒF- +z$Qڹ5O#-t+)Y^SuSRWS^Zb~Wգ':KUr];F0~QɬMKߤMgEVYGluhxy/(=ΘXt3KY'I@:e KRVnE.T'ynqjNgy30卒'ڴ!pԠ DFY!F2Ɏj՚Z6Y^Ivw/;r`k6+^L#[sYH'ފmRtTd)鍣P+q7c;V\ַ1REE喬q/3C䞵V&'XUG֨bDĴ{g07lrP_yjyC' hhcp*ZmlNkJ܌5WW.-ϼ`dII=1Y&m[ e'WR|p_-A1 Ƭb`鹤w8Hʰ4iw޼3d;?Zm%M7);اi4ysޕtoZ#WNHcxV}kZweJ-BXRۓZDVcYam?ޔ=dUZl8g:El7D!sPiw"0!~cӊVGR1kf9)=,.6}|}jթ"b Gia4 giQFW(4+s* + *{ =kq% V;u&`NZ,jʝ/WU99I?՛<]fa;LZOc*1NNkRu7.^T\٤ ;pZwy Ptw.B}W\ m j"n ҍyFC߇5' Un>Ov]Ϗvb*ydw~a&1kxR|-x Ҽe c`)&?=3Om+U[Io/UH@jQ9_PYSmR-'/JQ{cͱ|W|B֧mA^OCt!Y39aq̽>I3wz*]"<һ.zc1Zj_Cd1?k]NhƯU5:huMı(vP 'zhWҞ&3\ٍ! H[.܀:sTg7Wq8޴kW~brk*.K35lpYxF2V.TX[NkT#$]<:Vwe,9rch^t -Dž$cҸ:ֻ bpT u]$HrҟU L9Zfe[YbvpySr5RQ!_6-W\ w*ڻm#E*?fSNyҍD(8Y[*. sz}* F{yC6eTr'=b^:m&y֕gv* =OznoG5t *j\I6w,$L>0BMF裮[=c q5 lW @Io08995vU6n淚q$f5,!g;>^lʈ'Fۊg5Ya@h;{RnhӂJOSchE"pMy-Tlr}廒Kan]%+C)wWvJ-JRkpɞzף7䶳RG) ر]۟#޺#MUzӯW";{(M"j2J>Bkd<+[IŃ'}uF'iSjM8E)Yr#8!.|:_e1 =:ƹdLceMS{#{*W?"v; 8M`=kжEIf.:gޝ_Ld+*܇77Lj>m!c:hBMpRk?Ls4HU]KXF"R׽afcdZN `B\ 6Xbj+@ARkԞX qzTiI#GiD3Ijmڼ/ln]{?홖GJqQ{R5-Kz)UCN{U4?99Un}p{U8 rOnִiAXIjxN[?Y^巙d+;WG/T]OP "1; hd3mܱ܎3I! sV#/M6!SDX3K+~/Ha)+YbFƿ/ S*],n#>0ޥc1Sv/!fYs=j7N Z4!:s"ז؛V 1Y:>Ӆ˱PckVpȽsEx8ąN0j ?ǝ:oo"e .q E{3zs!{kzw@Z WasHЂj`+3N0@=EVF,Ls!%9PIn¬ʴЋJ0qެZ]kë[jsp.݋ԃZ"H|L2?ZNIyTs} |#Է1h2}kB5NJ9{>+j0?޺yf;oίKFT߶*%YÒV|AlqU#rTGtdKd>^Kʞxv k(e1`DEFW;9);=B_]Kpdb#gqN)oЯbp )i lYՄU\=:Sm584{~A)$2[w nN 5U_MUȱE::@yԝɌTSSBYg$?v>6㹬[f V_Q'\#3Qdesy瀵VY-C,MiW>*Vu(ۡ4t=S"Qmcss|݇\##56g5yu9RdERV@p7 ո!7,C dsrChrN. E|؉Yib+6IQ ^zSĶX-q=+W/msNrfcMZCļ9mC,Kc=i_HkD*Gg*/O.$or]Ucz= õ>qZ>ӏOPt 1P%$C ÏZ+FtKzSܑPN^5 v5-vɦn9u_Ls1#9l6Hz]B,¡xRzpM-#a{)7vr6:0Τէb?suu6*Y|;TO]Qo dQo-_\x^5cCR-]g5:}){Ha_@K H\W:  !qH;Kr7v+Ζ;GF?ʢi늩^y*(`3Uk֕II8fTe KRi֨s Wמ>TyQ)4I&h,5 + ƴ$ΊQViWiXy>հ9qMHn~y!_Oݾ?Zr^鿵uvKq JF+|bRةQL񮩴2c4ؕ 7+^Sq皝͒$esX`J|W[_e3Y *8Br:Qq˥܎sUDdܮ+7Eh# MJFV׺0^뵑uѶҭqTakr+Ȉ>G=sw= "7F(!&( 8TbLV>wFn&1Qi%ԘT#KRz WUAEĒ@O]VtTb&=ZFW%J cYմ.:e[{` '9|aNz-]km>~K9#:ZKH((?jOm> Ssi5jiHyӼqEUŪ9_2ZHmk;RsVu G΋3Hޔ߳tu\hۻԵx!I[Z|G}}dlCTDڰA6ņvbN6"}_(%lŽ#MujխՈ ֛{q8Ү%dx4dVr 1@F@#/+kF\脁&:/~8 #<|8SZ4ȝJU%b!uP늉apD'Qng 37na+Kڂ &2z6UDnp Ldf ] \%Xf:{Ơ>'ozOD9:Rg>/ "Sfu=:fX"o}Ձn=qCt\)O2j|rElEִ$R8 s:U>M]fmKJulG$m vQ1֫?u YE=ҕ;(y45g"kZ1.KHNꭷoe-|O YKNÜZXN9ISm*x/`2MkzI` u#ɲaFFz 3q0\4;5:oG-.d,k`h1$j3e#*za6+AW+ж;ֳtmj𦒍33cjf~ϤST˘cY?իM>lSupyÏqWhwe<ԹOhɲ+tiiOqQk(!psEjَ[L OOc:Tޢn4-A@Y3[Kp|$bS]{={O9zGή4}xE]ů t$CFĴF>'y݅YŗϜThX/ 9+z"0"짰jy['j듑^{k]ť>M_W`B:ƴ(.ںm^D}c8>)~*çks8;-&k{W%qRAWx 0dR}O˥iv-^t:E n<1S98\4Uy5+rkc RrYja 69yu{)e;W{hNϜjNmUXtwҭNZާ^eW8p7\_큓Y/ cK^G6'Ajbb95%rƃwNpPҪ\ߴTlN9\]ÍAEcIZ~;皻Eg3洑ݤ靌y9ɪq);= Eg]i0HӚVcTuLҵ05é!,Z.VRуfh 5Czuoy&v8-\6jPqc`:UEm }4즽RiKGiD}Ei[0*jx^]c^ơYZAɼ!U8b3YOfk)zݶ!{KRFxh#3CEe:D:Ws:e3J~ 4$:,?lUN:#5Mu=Fmس5OB5&GL0KTgGI] xS+9*\­vN^ȓC2ŴN<֠69Ƽ:35FT<]hcE-os.?/Z;7S# ֏YbuRpj8',0d#xJ[-#+۽@Q86>n 7zOesumrP=j<1\d⤂Fwqߥ\[Z➤q,%KOZ]+թ*XK㯭Mc9 ڧqZL.Ч*Ut'p5 i<}j} |Erp2q4R"L:x|`֤! nRfjt,O˽ sO SaC" f2I-4d<"$UbJ&1ާQŔ{8+n8$K,$xgv~pqINێ?ȏf"l>fQ*v>v3-0g$5_j5/!=oqܙ6@[ۨ&CRFh:b&nG#*aT=}ka ݼ3юMhi7k"lctf4>y) Ś1kH\ ePsBr z^ե5*B"Q-QPbaں;MA~eı[#R(e= QpWǷ G-+^Q(t\zYQkXԞMY{p*%9: kԟ݆ Yi @,%.c=C6" PsUQѝmfaN $l-b<EhOfa%YrέxM^ZҢKBa\DWVKHʅ_oC'&n>UQkUN.;(ͷvǔPOBTp PE*p*p[ReϹc+Z4qP5Ҭ7rF>ʽSkp v{YD*<Ȓ3='IH\zYUU:.Ԗ  I+%;M=JoZk`yb(9ȭ#QY L'%8PSR%tۇtéiqh\W60e'_ї<]nid|=kN6P1k(O죡I|Cm.=.h3-ͼCr+ޖ2.J3ƥJx:v_3q (H#JXш$/i+nŃD9UmOXyx-Ҕ*EN:Kpm*PHD 6&¥#B ,V567|,;!rנ3}C^Sk#Gfq;;hR;{zּF;DC'奕X/N%ƴoE}6NhPK<[}@'2Et=+2w?ֲy$/a466kQ5-aќ¹}_KH!F`UV2#JQX!踥g7oGx.dq]mksus5˵OJI"۬ӗ-v&׺eVl|]tcֱ.nSO5\,Ƥ SVEҟf'Pn8ܽ1Flpmإ@n HBY5y]t;Uf@JGx*-JE9#)ˎ_:%h6ԊF.Yr+B0cJQ*79̸@\;MQLOi* _MBs9]tK+\$O$u5 韾|b5Acy=A*y*xúU␨AҰ.Tͽ%h|S%YRO@*eM8Is$t>3EJH>Xvs: ,{V;;[3)tC[Ylo*BeIZӇfrr;RXe*y8]Qrۺ{חoxfmؐ>hZU[sjql)t+T˚īsuj(,x#ޱ]2kŻ/#=b4ӤTӔmYu&};6nݢe@I'aκqZ5knwKDOz 7My-k?vwd7,*ަh&Gk:F)n J!ģHF!;< ƿY 7v1cZqX鰫ʆE]1JgkÙEzIu(i|y[y$(b\ڨi7ڭqSzMpv.Ft S3Wksť}oeW3ҳ-%63UBX'Vy709⻩Ӵlr98wE6Q&~c 3lU1[;B!UBz g*~~PzV4Ir''gc4QFȱX fc F~ }io$5hMQ؜I9EEՒ: nVn1QR9Md>70RvGiٙ@\`TFMq5ei%2[}W5jCyh}pkZ FؔMi8SzMα"<|ڹwM:G*xxWGr,vy37%˜-ڳ(6UIB3Qnn)dq5_I/q}\~sYzokPdgkZ^Zd6w:iLb<&c86蚘.xs]nE"EMr\@=>9i۲*x+x,\LDG=̣=ςc B9@˷=kF)`$ gJ[/ڹe2z֫FXö)7\0*ĞR0YfUޯV~jNۄ[2Wrba* %p$|9i$xi謒o-GB.USzְQdMVY,PB.ՕGZa}6U,0UOzǽ[{UN5e&54ZybedqYuDmb* a;L\ $ebqCm ;5"-gե`qd1{>")62fqN$4c1T8/KSGt=,AQ9jqi2:lخBV  d5oD#/こY\i6qvzZW #j#ɱ"Fqk{n^YKBSĬ=+ZX6^*&y©TfrI kk+ۥ_H,A }jٞw]w^*Ķm$ۂG79x9hެqo~+6mBaK+}U%봌ԓݘd[%`?"N^,5#WfZ]~A&cV-%s %\$ EkpZM'7YDkYFkQ|e饃Y4מX?`Z.ћt }Pu_--.lͳT)%f9哨QsE\PबqtZ.A56Umqް[$C`cTDso'V|kCRғ5`6h挷jdt@+ HkfNn#lw [(|,ưܕ \U㧢ۍ_Z5-3NsR$p;g~- JZ*`W1M*Ms~i)jlb[$cźFTSV䵱%v}Ӧ|{[]1IrENGY- Ov,YoX. PI c@8֪[GI?o.u+>ڨ$Ea~ky*;;exH揫ꌖ*סmxO(|;VKxbUYawkkn>卙+>;IXZ՛섀gީtFo֩M%٘Z TBVv؏JM1%ZyZ(8Q/XjZh7In1|ڍmlW&4Ja#9PMq܎+G߽? Wrm#,r/_Z|2۞Fn=kҴ2&rV.7F5S6R.*\ZmF# ŹPɶK;WԼմ`nFS:DʞcV Wd)Sj,䔪ј7X[?u^UnvXt m"b\rvg]tiRzES_CӽAVj^9BcijCxҰuG\BJPmXӽxwǷ W27 uzGa =^?em_oM*\J󥄊z1mM?<AG}^_j0= _HfkKOݷsP&:xbbe䃀Ұu J29#C%;dx^anfT0sQDIuk Fm2Hji^DLBMDwTZI{Zeoyy5Wd'kǎ0qT؈݈4C Q|,׋V:#BelfY۪a^\k%|HF{*xUtR^lNc}K@&{2VLV>lBe(qk 2#~b9Bщ$iJ5=QIo)ktI754Ml 蜐'+G\i7^n*#3ckE{RSoMn{t2+H>A,5w#óC F 3?ޯtɣ,FxnMVǵ䧚p8b6 Ʀk{M+Rح+%ܩ5 60۳8g[hx5<1Z{JoV"n-:p0a^k޹翵mi7xg꧄g'a_"94IfS|=+)0fxQxVpS# C-:ⷡddSԜד*`f\7L\psYktZfmOL3V,ʕxN;W IdpGԉHʓsYT%ރ,-Aӛ?#;?ky1^ pGnU2B H"v /2Epqs[Y8Myjlhr1k+N''՜߻5c5]2+ɋ/o3j+27dݻՋN$X8sZhɤY>Zn$8"їkv=R(k,^wuQ+.88%'k(BwZve{r⹫L*жkV/xm5fu^hzi =eTof {/kX s[V%Ԕ!@TyRWΨקzGqr|Rsjf299ɪaG"Ub}x!xFw瓩i+CXƝExJԩu GwEdEXHۮ XY5kFt2z:5_*vdUJԭCnWE, ydZV ȭ}1! `vcU<*ZCNwJC;0X5I.n|b/e֩-oQ,q>YGX#јĞ`=Z \kIT(ݳA'"HZsC+S&3—!׽nb!t" @O0sjxrJ*)t#5.%r:ҽPж VHPR3檆*+fg<#ݣ-u#pz#m(Zfg}Aۭ>]\UJd-$Ch6EZ+x3 6k ?kWSX{L-?8?^ǜӟXY,E,ZL.{FxYgp!vLB qϹ:l'>_]kHe95IvCף^ߪu%tJ?8Dp1ŠV|=BqIUe[hovynAeE+jgn`sVόi68VݳFZrk[#UOE&uϪ_"E+¸?Ҵs8 aX-U<+5)NoRÓP@f&L9]AQτ^HjV+ ̝[?VE<նX{U+IaI?xXOYFSTjN2zq -{~jJU<8_98zӣxJo厩ܳ2ŷ槀zw]xvF+;QAV÷2xΦi>brGzWjFe[b3|Qp1qQ<5E5&ß梙n!#Ya uwmPcSD%b4&>a9AU/Dx"Fr N E8Eplzf~TY!CC̷0:=֌r3o ~zӒSDG953[gƤLО.Ty5d'['IE$xnL֩,0dlS,T^m̧pi,u*v&msSKoC5RT%ꗚܯٍA9[8Ogje`OA#'=4Mلvn݌j#&Q[QYB51D-oecC!o4n#q,^.3#.81! wD.H+J総qLIJ!]m&H\,G5bb\Tu+`DZKԞZ_Ċ]BTI#~FqsL1a5`GEv꫎pk;tff |JkimB8̮֎,0f;x$jaGg` wrhC73;YoqDqRz־B|sf $8ԅ=BlW9'YFRb1=EU]JʍWqNknS!PJXy֣-T1D6rs Q>Nzv 'U!zsv$!@ pq[P[b;Ւ"cx|qˣLnV {椆P vN@rVqQ|uilGvG=gsy*e3x-4TK֔kl*sjխ0 Z`6힕vmcpd:[=Jհmh{i" 8cT҉^ sC¿fgΈȘ6:sZBTMuO6ea 8ULq$NfIuq {{VV͸0BUFoTO=[_Jg'r/0%9sj;(8v~yj\JOc^Ђt.2B>umRƌVzѨ6*5t7I]#E8F t%Yڜ>}Abx2|AtNg6#+B7 g#R JFSt(zEkysi+^tJ+MOB33 sk#s} eskVݷ3yC; ֖iB&znN ,!sB^6eqm=Įlv knCTso.En0c*EĞ{&,ִwaJϕ>FJ68KyĬtsAXV+\52+|~\ݹ#N^TYp׊#2,r+ M)ȭ;y+GQ޹Q2\F=Jː|kcD1$2+SGCϐbUyd+]Zڜ߆j / G5[M6"l5V֨nD`\Vu x 1uTIM1/-c0e, $dH$ݲKF=T⫗ZLSiG졗`Px5n][tr%XTqֲk+)7*DbHlg\'y!li`E:rQDJ q?'&H?hZ6ܞ{K9iF6KEo,:uGcFܛT>i'eiON+q2ʽz2ͲXRtҜt3s:W'*ޮd+F9Zς[dWv [4X-$i|J]hP\4Fa]$S6jX޵c5%2I= [/yGJ>o"@ƍOMpWoJɭ I$ШWr|wvF`T*s4(ҲqVfy ewz IA^3s;ѐ<w5qEi^TnidvtmCTI|RvWDXi;Yܣ}1K `>&+v4`ӊj3fMú+ZVɕL5Mt>Hz8]g\ 3(Nel+$ 75]E%x1rkY,}k{MR[uŻJUF0@wSP]UAMVuVOrD)ukY^Ƨ'*;Fj7P\бtT2wWBӀkY`K_^),08W5[uh$g^yb+kMon$rq[ƍ*k2J4/k"鶻ć-j~ZjKs*Iv:w%e$u&BٖpئEҭs++YKGp X:.Gj˕囨eR?R:+>f7e_x* 9t %\2ʌX2jav9/1dǁrSg;82;)>1]V2]OR e,N".O0\2s@pN,r8\@ ::W^sA wdj7I_یy 㑜ӵ-moom4"|zRRʃz֋%Z}~G_xd,N|wo,` `<㊞/ dU<ɿ\c!ҼCguV Wpt\Eb71l$8>JKXJxjE'cc>HN)_Bi".3ҹ˷$(|9A^#&-дK ^١S֐F;jo<;rNjW);X0z#\+AEWs;JeFEGǨiۂø*Ŭ"o83Wlf ǮF*K`o;E% 5OȦBDvāAԚbGLixnxe$;g2kO-Xwav2TcMʉR:Tfy猵|P="F6s^vn; ϼ4V$KU9ޜ[;zi#T2g5џ&vs#(jWVJz%LK,pjz7$H=AjXFn[ RxkDad4{K'FPè@$E>AM .e lx cTEBJNZix|ohh:#ex}+Yf>K,gޙkW"K9i&rq.{Z8=LUzmc5dc-E[kMJ)|% [b$mU%VxXr4SA0DUlbqW%X)(%FO9 SI ȁw@\ٷ$R'UdM/TwFuL(keD-fh m֬5G7{m%v$ˉ沔sY*qi2p*KxÓܞxVу$'>0c=f稖LXg# n5T'nO.Ɯ^=<c׭g󻷖#m9!~6j7t4K4ÎUgRy+Ì)5z4Lx6*W+^)e 2ҧ<˟Suq,cicmsY"K:񂽏5e@ 5k qnQ(Hk97tF6;tMeV>X ոe*|⧻g1(d^9O9!>Wwf;J1ӊdx3]_*2j]P[ɮX㫇rZ3dmsj?k,1NN+y;ID!ҷXor7]}UmEHpï\~uY"100ԿjG+/NTpъI3}xFR A$־4^4C\ҥR TsO[QPzu1FBXZxv)PWId$a42C|Y}rS|sv>^:4$n^.u9[)RC13M\fZ,`0^]5Fќ:1 zUVRb&zM>h5<$ 2;U vz}ð#qJTԘM-b_ISvSm#QuqDlG ,r+9ak83bF*k2H2GiyT;kqOK ƒr~XP!'ENՎ&V'up=vMZ3IktM֥X9ۃN}jP &qɦ*" JM+ɭ =ӈquF} X18Yَס5\nqc z B %g| ܎xk&2Cxw9qԮ?*~/ڢ(m>[Dc }}S^ z{.REnWWZƒ=R cR>8FQ $]lm*1^n-Y|V4l&vp%*Y #e=a݋|5-,8OVԯi'҂X|@:u}b,9] :MVjQN5qvWz"(L j;N;t5Z+qZS35@T>å@٩,[ b:Z2qg#hYrZwv{P+} Xb8+7VE8U7oMZ fQ:)Mrږ$V)X׶0nRB gONs]+)FZ$d||5nlSE;!ԤVv ^m `#k_ *Os=ʫ)Vj#2+ͭCqo0 @}}0qV5a wasұ^uV&ҲJVV 3`i@ ~cU^9~h# -2[akJVYЫkХ^@lf8mΤ9=/1#!aenԲ#s9ⰍiGu!h 1"H1pOE^1rjE$(w϶[HCʁ֦2ܳ<^[X L6m9iIۛs;"Ϛ{W[Ȯ 4zTmo'jQp3]txUWYWR3ǜ;#zW?tpcVDŽ$x&tsOgY&(=NRvjMM2zcl9S`1Eq9%%R"R*U.#!XcCna/!)jDTM{+3Giڑ 1L=\j1.!=+_Uy[V-: rL'Wk/l}jJbä鞆O4ɍUcҦSQ(+7i4sG#u,q#6ڳRN2t4 *q\*]t+ ǓBel`kT[Z`YV:r9# +>m.0 f>pK(%sf/"%ld)srv-1\zV:HKsu-6 `QAu'޴=`Or5Ҽ#uvjZ[;h1S?{ki YQg"Bɹ\P**ϷRITrCmVnhIV|Um>9V,EBFq3Ӝ!ToUlZBJ<2$\px3r95> 'DS>.QF>}fQ? z>7AjwsrU*AzHH4pF޵3lswzl`ijo38=k)m kZ8/56 Vo-"gX؜v?Zٳgp1ci bL)8ZVi+1RcB~H'=*?ȦycqN>ma"*٘G#ɑT\.]ui&% YH+YA aぎ"&H8˳\ޡosyPo暫}$.XXk1ao7OY-;]# n +y蓶3޽7j(N1USV"ߙΪn:zͶ| nP)5^ S"xv;ˉNcDmCUt(Iʞ 8kbKD7.gcHS(zZrζZYvmS^2qIn) ZPO:L<'Ul0~Xp;x'kvV^ddekGIJoxV n]cZg=M`xye$|!W]Y2{J5kN/VyΦ[jLDv:qD׆Ҩ"c3]x d3C'pCץmҗQFWn7._<2&o yZ ţ>2{ԞѢY6HzdtJk:*Z:ޥgE(8&h\Y H֡N9 =:5mfk{cgΖ/5y'ExAftD<\‘[1kV(r.:5I 벝7L4a!߀ vB4G;@j鼳nT10U!7 ^ r;ZKAZb/˃W<#4P*\uv', }*o'knppqCsKcT{=!xv ~`{wpˌ8Zq.,҆v-U{qY Ǝ{[ނx~i4C&MWH>L0j-YtׅΤr?]a[97VjWǕKbƯ0&Q.=+[L)l>o+F@pP<vh6̠"InFgjb+LY^/ Voѥ|3 ϸY|Iy!O%o VToN R8uՋJZEبҍ :~T={='k m5xtsZV-orX= JZ G€t;4+SǶ8=\U#JxhY]Zujw]e"PT-&{d-YI#9&0Q5pkyHc''c H>,m-,`l`е5<[h0\JS#9WoSGQ~dح9 }^A.O=HWat8R֥aʏ'Sʥ*ٴqW-VqF.Ґzpq^ggB1̺|(@(iMd<2m̴&Ѝ҂?f퐘9c޻;,l au *MՓ"!-"7E%Vtܲa ~e4`vX)nSHb'g%:$U䲳2y$ =a?",X&Hػjɚ)S^*˸-erWQc;[>c *ԧhꆧ)nXC:cŶ2rWLXWi?Q5J`ӊi::]Ԅ:G>X4K/K]wM/gsEbIԌbft"K$Zkؐd=>X~X{zTw]khKc'RkHkpGOI=;F#ˌzoA 9**{5hz$d՝]wBGYZNz77S!ݖ,9;N8Y1urXقA9Qm')w+NԦ'ly֢R~]LƝDc"l8)hzd,w1\ #\} 5m/c yGkWz1I6~hعE;"p:d>/ M,:팊G'mg lqZ8M{*t{(acRoj1իhe?D*H|$֗hk 휷!i]`OJ JQfn|=0lĮUi6=4F?`iW #i=6rcR0 =\29fhdz اC$m J 6rs2rkbNP. ՛Dh3<2*)m (n~TB݉nne- E l-! P;g%i#[X)YX=+RMr%$Sm(eh U[bFw:].vov;d֝ny@wvwVSuEGDqϦڻ-؟\S$GSB>lXXY$B(8 xXy>VxkUԔ)0k:x ջkHUT +xR'|!U?zEkY>IB*[vn"i;q;`d Op*=ɑ5.|}qSbAUݢSRDgD{Vvewo[eFS^Q88Dk4"(]:6A*;Wʑ6j x|7SRY 9ϭFd!Űx=N8Tnv&˸1ʣ!KHןEe1ltd"W}'R2QGC,AW廹?6<⣽ϟ wcp0@Pi<92#\Vch4ҷT)} '-av/JH`gUid6U>WY"1Hfi+ c;;}1U.lFrj5{|,AK$PAjn+2VٕeYA8ϵI,^SUe˗ ji<Ϡ厬&[XBYU*׻"5c<|by&`g>/Kw5$3 wg*b:3Kyo j ǥgΪm5n)\DgHŽXo+!/YI 2+v&yیMN.,)A{7ñd5v7J>{kpşRj aP*eZ@ޤc8I%²yp>%^}2qshs{[v#) +;kox6CsB i2 ;'vQm=:lis@1FhPRT}5Fk*!=i3n;RմɧIl&h6k[MGw)TMFsW vҟ =Y pđYeRb?>\ky]b; 9m!.Kw r zt1mS<=h8#~uqi9s8P= \cO;_"EEWG}Όzk*;Ji\/jqٍJ-۔Z@CuVLC$/qnlyU(dyaN9f6Jndr0#Lwjkxt7Otfx؏+|#]qiV{=c"z)C5^W1yH˖XE\vu;VՎWQ%G]* kG_IfduǸt7-eUX߃UDͥ Ȟ+ݷgTs̐#c9\tWxdV뱳Q&ۙhVoT˫N)ѻ3|%)Ʌh@բai8 UzIZgYLHH9 %մye \ ɧKXOй6`椆u8y[5ZCHq.9C⧞½oiG=;VM$Cc z*ܦOjzj9IY/q(MʦOCs?fVm#W4hP+(=iEI;qJLWVwdž{+ <6Br͌w;㧆4|:ybp?Q^]5SFxզ>%?a;*^k8+4;:7-jW繟OB k!xŅ(lWV6aQ˫"iq YZG~Jpvnn氵H}&n݀}k;M^lAb' >bI)ŇoZ93h[\ "L3\摫HiZ410KRTӍ'ıųJ$:Hk֛]`VFw-ʅfkvTJ.\-1z{WjZ j.hVcֽ Ldc ;XڦF(WRMr\"Ab{*}\G3p\T%>,Es,OWeuIyY# k*PH,Ž늝VtT-v:[",|0PÏam߼;2D$gi<8ja p;Q˗[)b"M۽ dB--lxJmXln9+.=Yx^QQi7c%Mm$Vڐs!!1ջv4rrqXk"Y4WH㊚v@~5V.wQM "R9ϭi[Dxbb*K ,mju,xٔ㯼s30^"Ob{nggg_ǵu0mUL$qК Ǧq]k5RM%pDq^-¸8Ph-{Ö Lm##E}ݑydk/(EEVK(Jq1Vu 7ȻUsI=SR[ԩ6+Hf'$} M;דIpjΠ$ x1}O rz-TcVvn\w(W&_0-{%] &Ց,eޖ *ʘ #.Ctqވ/뗔d$|jYnԚ6L#ORHfv˞8-&qơT+ű*[k>,{XdbNȧyP3Npk d{yn>@B$ \cօfccҭVQzʔt{A&=XaCYMdh!JRF6Tgf`)ƆȻ$- *WpyoiMA8\RNɝ[ǕۈP 'tB0YH^[̱֠fU-#)r r!- WqZ Z ^IWti5 0:M?M`|\0,P7ZibOB"KeC05VcgnnCZ)f\ YwC2dVynv.~d)o{30 ƥ#R\;+JҹSE\O GRqw+rb0j3C"βղtbޭ$WV鑾ĠCҰ4l=V2Yk{}h⇦q^GBm#c:f87sf&鳸T>= {VE"_z'`kpT$wga%( J7)%mZ37H 4gpի{]ND.oi#7[+]RUG! dA az]gv;cjrXW7圽[iofgDuPsCU5m,fOZ}Ŀؓ0}CVҒiXKHk¬^Wa 5\V2Mw֧ 4`T~,QfpM)Y(d;- ĚoؤzIi! &cjE}Q$Q: HnX%Gzc-Ͳ\`{S~\;JeBy>k;HeB TYYbcۅ[Cyl݇<ДTn.o+$/>̱yLnwy,1(_-V[Ģ6UWo" DnɐRlv*5U84uqޠ"v!Pec45D6;v⨫ 4~G#51اUJ){=Mi}y>lz qP5wOcr1ҕ4ǖAenzk.@>Ձ4'̸;2ǥZC7$nLPccbT4ڳ2'o73.Iir6ȊsR0l8 _j Ku-I-oLpz!U_c\ϖ̣v4ɍɤ>dwuPGaHw=jX=Uk-٭ qsNkp:υ&͓QT$Ta-I$D9R0pyNk-+'RV,}= 54>nrL+ֻ͓IAAv{g?bm2`5?AcbOlZi̫1ѭbA4Һ{AjsUZХK]Hwem"F+Bð4߽F9y#H`jčqj˞rܴ{UȹԬ#eP8-by<,l vV ܮlF+EcY5!CMK# 3ǖM>#@=v&ժG&t䗻Ne87;XY)OZ[{V>jH"@ڟݻTj1Z2NN#J;{L5ǔzk^& o#kKôO+dkJj ^='O(oc u`U.lic&ӵɲuvc/tVƔjW=M7I.4$rze|9pycqޭAVn;CKk= 4M #& QfLsɤS( A<krg{R۫b8$$t9Ơ8lsZ6mpz{eߵg7BC6=V 01ݪ_KԎ8,VL`rwuqi24V&v̭A":|V 6UU5ڝ/Mcm_zsi2.<1p]0Zjc[ $? 'qQU-A]x_J!#|uYtwܓoV9]Xƚ WuqV_?ȑJȭn%N's e\jصUd,Ҵ$ '84jq.x|qֲ"Hc!ʂ}+NH POqXhSLx9ȨM!B*d#l 9RW<$jUqe*ș[j)N ²cpIC27w*%g!dBD@1U@YJn1FHѝzIjO\9tyc6:0_1>Bha0=x $Zkm29VB\dV 4J,e{#մb]e;6Jٴ\aJޛ*q֫= Vr壒W;;[(pkOwڠ!x/(}H*VưROs$H sUq GnSlxXzMC ㎂Z\u0 KGo(rw`SH,BV֯IѬ]ʫ} |;fWhBKVQ`a#p,[}4/܌+‘sG3IӠga4$\$rjhؓ;}6c4g=rN+^Ź$~= SSrN1\v5X;-Lo e'[TT2799b7{M#IB1^*\.Wpzeq?֭gaz5cvqz֐g4Zr'jxeǥuKqBGhTKe[߉4 -raNt`sUn,wZ[V!%Wq[ bs0AVޖO$L]팑t+GFZ,TG-*?fJu;2xxynUdFrMct,g9f`b\Nx?kXd8=$7չ5@k_]ZW!qUCC+ŭ{F*. DQ1#dQr*wK`wFבê];s֭)3tаbozl#IT[# Ҹo57۾.#sZG -9TQw:>YH 0Q^g{(qZLeGU'M#RmɮJ֯G5,h995Zm6)9=u#n78MBY\D xz+|6mb?.s%DwU:SѠedzEҾƍƷ0ёg*"&%Bhu]D̂9iVQGB;wEGsNvW,=%eJɜUIͥ\b;x=6ֱA{חfQI{ǘ}AnF%U*eth R+$IHqY/XBvS7.1AJ6j\BSWтi&ռp#?Uh2F*#\dVuΩ0̐ȸ`V!wDF k (6z5>bi)#n").A sW["K9;ӑjD37J}Lr:b4X:|{.a֑-uS^TncVw*zpަN{ ~5)ҖFHn0RV7Tؾ1LV|9(I@BwZ7 j+בYXtՠl0A$ۡɬۨLZ̬ٛ[$;FҶa*H6e'bޣ֯Ġǜ:n7sXF\y 2:*H"{՞H x\`TgOq,?,3qPp$+!\WGtҨ> c!%2…M*6b (#=+VHsURfxFsJXKhB:I=3HM2(۬qzqMU? )ov${\@#WBj*&^092_1G_h)aW d-S襮)z%դ;Xg}G-.a]އ\ĊO{%T d&&Im.}aϷjeCW."HqcCY$ ePjN׹g9I)Q@H[5[xpwq80Nӎk'FwW@j}Nn\mviYǀ[rZʹļR.EMi8*]GZue 1\imSjQTlFo t'/Z@:sռ%=1\i5~ ghoR͢8 G("OMe 8J ܰ+rېC'-Ihr-8$*|}#i/ WiѮ- է/9^]Nm Q,v0s_ckgm X6:+yJ?|s"n6 2{ț J#5c*$}NF&Gz֑k% W5[f|:e"r5Z G}('f̐$TFq#E^K}~#BTrͻY .6ur c W&LzWGr $`O OrۛE]sV\=hHV\KiH s(rRWDvgN\7!dhzdH2 kR|1=cqƎwVfY s8梉淺SaaD'37_K,BfBhKv]9iaL=do$(lx&q xEyx|R:xn7Z O,y~lS\o-mv^JPSU{[ѤȤzOx:4ԢYY0hS30995k+-U>堙c氍%eciT:<)tZ܅erV w.2 I=s\I0{ViO92F+HÔU.H"k"3>y(8[࿞%Lo'Gigy ]̠ R㠥 9H|9wujҢyq*l<ڹ=_Su?y:nYB2ZMI7`. *Ww:V}кpmI3Mo$')^CZ.1"=iB2[yz&@?1#1uْ%]O{U9Y0+%>Ʋ|E>J37/J`EEHדܚ5o##rm(I2ռR^HKMW]>6Мs֖Q]4ֈI^r~\ty%kk4[Xt .ATQ%M]xA0HN=84}3RN򔦢H<ڣy`ڬkw۬ }Eaj?ǽ[|V182(e$4.$Y7 "2:bkZd(L`eiWsv^FJAۃZQW:!EKX6QA 8vbEw77\m,ˊ\&A8M9ZˤJMݔ灏l԰*\?2/d9+bÅf;֮m AH[OiY$#%7h戉`Of(^nf=.k;IYs\Y$qU?8T5}^Db>f*YҌ_* wF~f8@+1? a d&^>\~n{ƪv낋VLG ZZeȆsyaڢӤ]998#Q$W3NwJ)KZzhn$0Ga3TݵLt=kKQ[h>Y<z|E?b5>V"'}^O)UǓGz޻CJLq$C,󭵻H̄ k+o2 ,z1\axvR&2E\5kdmQU v}|oth$α.jʒfT{& !`;>n!#iځ^JO޵o&nD+n- Ҳeg{Uj{299'q o,w=:V ZW{"( lL>_ Hy_*.zZX[lM^m'.qJf6hQGoHcIm\OARk$NVd4 3?JQKKU7FʡnGLWyyW'h`YCl;y<3wu9IؖcsM]oDKr07\Xb,}r2I`PHl*gheuRNN=is7+Pqt籂V5cKuKfSAR+28Գƛ\ \y>\C9JF^پXzi%J @ˑl!9MB+ "nsY{NtK>_z'rUܣ ٬uW- _bYNM?2B=AqV)\zSM cjP=k+i=eoR}+xTƜt&ܙEhAibH2)"\9hlFj-*~WGZ.-UqM%M31ʆ3d5ǐ F[v;.gajngȫ[:m) xU¶ӜQZF:3OXo&R{RkJpr:mqYa9ju8g[=Mn)/Kf\(6 YϪ7w\NG"KyœjAn0*6.q(e9aUe]s z8( >NoH8*_簦qyrG#qH2fn\kzbRS tHSUllU <:9`[>E>$hA=vTʳ. n:gٶA18gAr ;;s\SZ%vg9:ՆIU=3J ȉ>Wd+cGi UfD=*V73&-tXZ19I\R=jzz ɘq{$g՟wk"!ZH˼[:AmrީiXy5JSL3:mu LڪJmƬx1j嵍I޵Z?ZCy05S޼Å8%gcamJi[>h)5N{ ,$FYh =2Z.٪ tݑ m[LV>c p7.H޹^2)}o%rLg)[Ojm=DW:.4TiHHvHqNKMoW:V9Y Es&8& A:\J/wa{Vto;I5[PpR4xCbj@8EZIdDMtk~`ֵm5Gּܷ٭uX~oHH8;immXHI^:OT[%F+RT|ּvX3[8Jҵס\¨왭ZPEF0s^OkK@ǭjxP9Г\¶ʬt #L QZqpYVd@2dJZ\SnI,*?5Ͷx c |0::ȘVJ[9=*+t )k lY0 QBV= riw`*;b9Z מZ<*e=(uEFz=BqךeJYi{+ ޤW#`xTsݰk{wfb>LnIMܫ؎}k'}id3o,draZD Д'PF@$'|Rw8g-\#09BLg,:5߻1"\*-h&5CuT9}+#ӭG pN ki+G|vzkq΍Ki喑!VU]b'`*Y"?Zx=P.t-:+MheF`cSխ?UgjV!ϵO:_,205eom$y?NhDQZP >fg8Q]^jHOuFʹu"WE8mjxe:-Yq'+n x\ocV=wm:HO& Tq=.jO8y,{qZ1%e <.o"au-Mm*l 3IJɧԒM0:j1 L!%mo1գ,wU88ҕXι`Y*J]wfEH%E~yDikDqHT%1QlGrE:ݬ\,xZXH5GKU+}Sפ0 8xه_qzU%gX> CA- T$F5g٘ ki`UP u(`zTlPf#MIcEzU9ཐWE- v+u)'82{J͞;=x,}3P^R"~zi PI]IS5Y^H"~@`54t~F2x'&K}a=evz*JAYA޵L{UycN 5p7+U6rbbD~X=zUf{h;3{I ۫=OJꐕdE7,yOe`nK25\{`qj<$Vv jl'x.ojt; lU ;^O嫑F>XONn]0I ٪S\G+Fjgx0=jX6$qo4@\ #UhG'*)hpG<ҏP=sSy( "I+H@|WrXn(xV'm:SSу-zCSyT 5ݥ˶ JaWpZ̗[qY7.\J{L[{TG+Cr(z\2*Ïʘz"F$¡N[ӊW~VlqEۍ ͌\^JnwխL7 GLnMx3U65GQwXtfζ&WfXׇԌ$8,縴%L>`_5c7'j1KX+;W zWABrL`UCSfAZF l՞o8/@V'-+'T)fV2zw[2g[<1^񀳰$z Ι7Zu k>3q26gi[~tEiB*3%2/vc]hk;85^m~L=smM+%oݳѾԎ{񭧸E'WA[-$koCԤ;nɔ:bZ:ެ&] P2)S޲ybcYUj]M&ьj!9lSX5~`;V~lau{M&yP8S/jÓ-CX&qN[Y^'f%q[sˤZg$U;M)Ic+FmZxHIsA>\y[׮ZIt)A>iI9*͆%nURzVyhb:5)ѽz(5zqL5'Q.x5~Zp  Y`9qMLRTݓgaKXv,:WG,f36^Iq5Fc2zm =O>|Ę^_n +,f+4'לW[e}b&*3YiU,Csk?ePj^؟DGm/;^խ `=2zkoS }k+Ay^ JcJgjnh|%eBjpVeRc _ZuʤW,sKVXў|/axeXP28 \W#`qp+Mt#T85WNB3* 걲Ó^ed`|b0*{&*pwV @@ FmjΡRS&=$j%ێk M$=3I,ʹ@kjBJ+sy$w!א8I%e"J0o3KV`01(-l]d2 DE ^G/#zW7:ܼ]Cۏz{'Ԍ \b-I1,\4oL<+VXc(kFX񟚱5hȜ54mɬfe&+/O8axK,^Qw :G▒VXKvKX@^I=]j/Fs2=,`9[=+ 楎Hr ٿLgS8i򵡽|o,\2-tMqA n7xd Ʊi%Ǖ<*5 TZі<@2ȷ@[ӟJBlc>O\*qV'ذ8X t嶆*m-FXānKCbJԸ.4.eZ嘔o¹=%ƴ"b6:)G%$R[t")u|uCR.XQ{)}S-´DaNE9Y=N*DjRp3 9WuVKe䓒kZ&@-X&njnCQMٜkRyFDWo!k 9)ۑ>,\pkpT29*+Drx6Nd2Lm'k[F07QN$2GՋb~s[_ gEʠOsʤy*4|u!2#9 k%ĦLh <溿VD9DZ{X9sMY6sM"т3[zd;%ӟ¬Z_2wq xLCs1])jZDP߱Eo/b2 x̅ä @0GAgX!p0rz m"K q(M7ڹRJiF쩠*o3HpONXt`U%S?W+][K$\Զ&$ jƥmKO&_tl`R/[ Uv*AYZM7R{6{-nIZJ.pztݰ]x=2iu&kH!2flJ˴>i#̏sixbZ&3#*%0ij587c\b]@"\qq\֓X6Ҽ)uRn$ō#nɡ!vnR9 V =9SWs)SOfs_eVAztsԑw>s_*tkxKn?QA7;b ^Slz{$sb1Xu7Fn+ xX-h;`#瘌*[:W?=^:N=]*Zweyuk%)zOqopսg62u&ck&Z-oo) #'ԚH}N86k i#HaqIros[SAn฻i\I#qWs.[>V@GU?vcbE wlj\O8A]7GǦ+JQ܁Fs&'$>EH$S1b՗CH֦[w}.TK#:`\e`&}r-=Ƨe""&,YٹqPjԻp;Kg[HU"5.?h֖I{˴*>'skIŭFIrr}T7jLW7Lbz]ZlrrGt̡M*$͞4 Nc[6PYWa*劵K>4"B@ V7PI>衷=6!"HuBVDb86<SΉrɐU7dt{Y@ Z6:Q@G52] cA=ÍB^0MM,qR`5BW'fT}Qȿ,4hWUCZfslK;zbo6GZY̽RЂqM)DkQWU O`kRE'kT5ovpG.(S4m凮+avm!vW/?*+6+7I)lp^1d Hq18&Kxg=9r(T;\Ykc+hP&=3O*$P˝EP㸨NpREfj+VRh\ؿ"REiQS=9 ty[[k)"k#Z2~$PE gQMke#?U7g͔52ip(H?V_X7+c6ŕl[6gQ9&L\~'46,0k9U# Ti2JUX(%l rE\khTluc+i%IiFx=NL V ː:5Y;OUb#[B4౎*TuUơf̠cU{f͌pxW'!Hz@y|Uͷ4ڌ4p9нZ%TQ5#Eq]L:!岒w }jSٲeEGK.Ɯ簬{yA)s涅6YHo~*`\*7fMg3QF[2r u+XsH# P;It= n-e@8vRWƱFwRM qUH))BW:m:r',P#3!a#r[R31⫄j7Lh|;S;EUI¨V& E) /^kJ+5vSwZ劒{QoFFϜ "AJ\n&11b`GutQ0k3OsQָ]C#jV2rMJ$_)l֣KEF569/c-u>nߣu=6J{y=ɭp7S?\_\knGj?H=dh0,{0eRi#B94eq#¯[闐<=kYB7TseΥgZFj.{=~FTL71{u4x;.h[]:nLfN8 %D%E𲝶c%™%G5~ =V&Lg5[T?6̏O@0UKTںH&J;Qo>a`1cX]t' "q*HYd]8r6SI/*x5)v[nb#r .]è5%0\a^;LbܶS=QU-Ax!f+ZQ0Ji:#r)=MI$l;qY$`N{Ҩ:m2Um];UY1ҹ*eep12?Z0qe.o۝sj|Wbj0[\ l /Mj%5y#dPz !EYn3Qu[z:B+ڽhU, 2=r++q/mIJ+.:j¦66Yor1SC)XޫHp35D,@)Ani/y٢0_OXJJ`ոcTa&ݠl1IԛʟV3g)2ZŎ}nTcj QJJ>FnWdR+Uh4dMO&ԜGɢE ~=k:t/ uM٫w01HB#>jZj0sCǚ}kntGb4&sIaun%֕r#1{5KD_;՚>y ƳMFɫ ld %n8CG !%bs5ZyT9/dɨmeQ.;0j_2/Auw5< mUbdzW0[E.f>+^tv;d9ZLq\050Xz 1*N ef$oN-:I v*=j.{yI_ZZnl{bUU@;$7RA#ڪ27 nΧ'"NؒŜm-KugY #lrU&cnÌVVXNM&XGiI$Ejɿ6QPsY2Qn yi]FF~떧Ur4#n6Pnv|*;+2cvo7[(ޣ4Y=:iyDbʤLռ);*8K,U=U<$^sc V;0PՖ!-]5f+dLEc%Ra٠aLjp+8ٺѭZ|E:$H.PJZV!ʵFٍmwraP=RskDl'X3|Ɏ0ƵVa199>,XgLjߡЦmF3t_*s&dWua̅(-RT d rinb$%#cdvi./ARp1m>nT-+FUcݛi hFhILɒ15ݵ33Zeq#H1m}AOщ_Ay\I$Vq"X j6VFO٨E/5_f$bm[B z՟3\ QNj_s`+nPKwU9W3g>28zӦ yrQR QMiTYAyavHz+:xsy:5?#UE"Y˕%sQpɒ+Zr lG7ǻ ;Uqg*W{ g~I $*[Vե'k5iݜt5, ̭»%rH犥W_tҕ<30WRq%wlFnuP?2'])iji$srd})ؑS?(X vSWƼͫvv3.ۋX?Z|7K V=6yleÆ>OA2/YӠ!}?0P‘C-tN4L&~*Wf^]&=2e[vtshtBiЁbg%k~3Z>|ȥp:6FkJTu TBFP\@i#] w X'Yc%fz-R#)Oĺ!zLdvb11"aܤoR5*qrZ<_ >ҭ"r {X?^KؗzŔWԖȑu ;k^`# s.3?>'ց5~0gfLm]u]ޢkŋw`v4l13[FV.bk8;8`84"'V¢QlX!y@LD婽4)j4SUA'QTFKomJ%URLcVegFy4$*nbx`7RÚ\Z˵i5OSq5CwVfT޲t~EQflTjV5thAv/F*^3q4jvO#2^kQxĀApOy sv|+ui"W'u|el#i>h}hmտcvd7ޑ TgxF^#"կ+#$+<̜&×'rjIVhjmrrѓgsr課%ͼRD-4嵸mrXVՔVY*qLհl`#j#^#NMs1{AlۉRk"Y<fm8'&Yc'98M(hK|HX[UX1=[r^5T<IBskqJw1jo ۃҬ}/_, BFXϰ?ETZ7+t9}K' !A}jtCMZCk58Z:ib;#K`cwF0jhY+JX3ʥ vQZ\otc,bcAu}mҡmHdrcdC|e+Z9ԜqDN o"$CzVGI H=tyCΜΙ{9%ɹě"Pg͟ZKJ|,1]љYڤpT0; cs]߽#@AB$F WMjW{-̭, $v+1aDvr>܄NN u2^R_0ָlIq]߄ }?z:yk DtI.uMϵGX{VD]ZIRuelk 0d?)=5I?eSCU#e43|`$'/;ʤ4LIS j@eL=(B㓚iS|Ggasl.u˂O Um6IQK~fjVe@!i_i9^(ƒj۵uHXFgkj$P4Kx5xifSYЅǵu:}呰Rvf]|郣B2 ?z{b2T{W mqhU.=rR6p3ORw$CZԘh7&TH®юָkYt>gbc"YGz6?+3` 瞄Z6m9-a|QJv[ZKibTKY"<c'gKKZ1p{VPHڜefYʥ7igHQԌ6H&yNʖJBI-w@_oznZ&'x$e\ u-zL2 p(]j.&{u>9*h.dz5G'aV:WdJpW#'h29QșYj$x`iq9TUmh]F1Dw7*xW[e,,L*EI qjrjRp@GQo[里 :gZ-4B|(lp1V=ަrG]!k 0HִtF1.85$1S9;3hoB!(^6\ /!ny 4[[犵i32ۭs:{h͝:6Iխ F,vs-l1ffEH%sd _ar $@bX!㭌gvk{v cRTqCmԱW]FG f8k5Xb`OBGO*̇5bޘ&Ieb}HzeczCzջr0^=&ANq\9^7Zhs&PkBM@F1OZx{<>XeߞhzKPZ#Bqmk uWLRqpkמ1":_3ћ+[A8-LG`Bⵢ8"yִ+4l!ROs4˫YqH56۸f^P ]86շ1ole@LU t0#{儠 C~F8s]9FIcᤶ.; A|`׭ {$[d~nJcbʖ hL" [/&Ibn)dsT^ecNpM]2%}VgZDJ\ 1 2!^wvp yv #l7JsmW%/AuM@U =`iwoskŬ@+l8^-I&q,3dv8RVrk>c4ټE`VLqz"H`MV\홷7=IF1vVTH035H SZC^ىaX^ 6BkGДز!$u'ZM{Ѹe }\":0OOqW#\.1Tk we`ѿ.d66,AXZB".:cҲ$(q9 owI]6ɧ(eҢ/qF@R{\z"䞽EMR`~8ΎDɶ| aG4Gsﯦ*RDp7m=FURd^Yڰ#dgqkhA3]ӌQ8dqǥm f3=2;SLjd=y8Sw{2d}EuB%RJMaîy5W"$/4ibrFsXYzc:WDzr0|ܜ rqe#%: c3WKqӭ*{;LX5(UC޹{/µt `x,xIJ'D znR~L/5Hi71㏖Z^ńr1֜`NWvԁpe~;R4ڴi1}Kрv9*o ѤCӘǖPQ&K9xe.H+e8ddvjsjZKگnUrmI7QM5L {1L 3QGRd S(SqJu"ͭ#"ukDm'"x|3콪:Ͳ2qЁֹ%COtu"u/VNAbׯdt!09-Sj3'3YJNƩ۝\ ӽ$)I,{.kB|=MF.uF78Wa1<ҠyOcٮKHnWԡ渏>SS잣u,/fAhJIG׊ke5zS·R8 K s4 lAMfW wde횫[y;snk#IMy1͏$6Aҵ{YPT0㓌֠`˕*qiu9'VMUTc՘o-$\oV =HR`j"ч[ՙua~\Y=9+nk4m^)I-b"5YD?`sS},vdװHٚX:SIab?'[97o;y˨*֙ I)\n6LQDd\)J:e;Dp2+K=ǝMab\cwG!O\(ҋiQxn=Vrܶ<|VF' znKIH%Y2zZQAGnNJqj7a8q3[N9GҦVnh_\5@zV'nX8]{ G5*TzXK};R¡;NA=+d}EU$~`V}̝ÖYc2ӝY}*J+`--w`qMM`LzfX`$N3=+HB3]k{c<pCyɆf8~+!ȮH0M01vRvѬiҪ{3|:j!q-EչwR[® 9f|ğSCS:9ycڴ!r9d_C8ت B:;%8&ymCnF3ɮFFQAV[+ÌgNP%Ջ#nys=U͹{ֲ<cj fQ8'Y#5-nWYt霓]MOqB4X~G_4kV|pGEHMۖ\|\Q5Jc&br*_oX*U\qvd :5 v뺢D [k.N3oTA9T`tҪ,,z*H0|J9i̬is"ۯ#bx5' Y:jg7c;}W%AҲ7y5$rOjъky##=s\=-qnjTQ%Gٴ3oލUtO"60v^$P8ysHJMZp!H9$R9]*yU+( .Ƶi-$x{*ET\c64I-^xvxxp1ӯtդtjp8TR&]kJnOݚ2_4ݗ]XxݧfJTZ9 zuxA+=|'kӑL@{Rֺ#v%A{.# TRԲ U˜cKcԚqd_qtR3QN5b]\sZ h]zU'JiޜWPUi"m.zޫl6REgVTȬ@+bѤBERMf:dOi#_JHQ[8'jLOCQ*#-%+z&֑H#ߞЯ?Z) Uyn0>]Ny-gjJ62iF{8CϨ4Ci*k7Z)u3(\~jvL!l$tɫ} .ƪ1{o|6 r9+Vmb+?2gUZ_0whčiIJKۻ nhj2 s:VAuJZ޶0mN٧YC?#jo饖0ys]4!wMpo#8&4[xQn&bP@Vlr*Ezt'/PFP.G4wF  x$FV 3*徉{tᵞE5'ʮa:n*cAq$P{_ P~xL&[3dw>2 \yш W٧e[!nm l=/Jd>{KWJN%AOɐK qsU|A8WcHKky-PѸڮA"<ב,=JU9g8;MN n,y ,JdgWX)ȖQ^FWіwaV@"n|[v kkZ\ o<^ӑkN)r䵌_*:kZ!QI[zlly>#t6zd9ؙRlY4ﴰ"WBdQ"S;(ȳ/̽u+bޤ&pBU޺]n^!RX7H.p4RW[);-y'W iTHʃӞWJ8q<5isDERov$H㍔V%!AWbݘNV NW9N)#pS=}P@'%IҴˋf]/k!PZW .ħx2#(B<آca.κkClI.Fj&A@9ZoǜTkӕ5 &\ 6i1)|ʥ%V#jQ7oy5˳+yq+#[6ڹ'\){w${{R"Mn\?3Ʌkğchg zFk*e02 @X9uA\8hlE7"U%5 c A'izҧRU[(`N@S&ɴQʪ+8WȹAnN44˕l@zSSi)*3J])CWM 1-)CM6ͪNrVxr R\GG91 {][H~R"Vac0bQxzI8Z$TMt]W^67\xԺ-њ]ޭIg5ք>sj9U2*ْk?,3Z:jMk)g`mqsY[\ps޴q++I[CSv:29c%Mj\K h6N޹.eGFL=k.TL|x'q:WU\$]!&d9&X/~e_6Ň!5utyInˣ9B9ɮ6ɍāgNQe-cpAI{``fFTxuwY< W4e-Juudnl]c9йXɦ_\D4lr~nBdEZZjZ5fMEYm<.K:fydS RCgy <] %C_Ʊ%)$~2]|}`xx$P-Da Dy +Tԭ1I`=;/x^/a#ƪL3nUcE[gJn.qYmsL=R+tv\G$zi)'4jtq81&|10kQD4P7a\̷|Z k^Fql66ZwQI-B;)lCY]=>k"Csճ$zeAH=JEuB]ɓ=c;B 4ɼ\HHݏkxIb3Y[Ioelo?95ZMt~T%.I_|U[k:cZR7Qd |6S\JS^s[ZX%qᣌ֭؉w.FOp1A!e6`Cf F~S\*8)-(yO.EP m4g"iP[:u)?u9s8o ;Q\ -뚧UZdwJ&| 8$hN;M#=zTr+n)`keQ.`7\tEY#~Q<}z-BKT34h.žb m޸I@d횉T\4r3o1$uK%ҵM $S!մ'2`1օ(5~B=O"fZɪ)AdivAqv'>خg[])W/3DY~HL# 1y.5N_$\ҋu`q2UFld⢴eڻg@J{lւZK U8x覗Sѳ`WD`0S [_gm¦Aj]t3ޗ9 ,ɗwҦGYq]%Őqgjt]&pcaC̀{V̠XsKwiÄ )'g"MZLk9e^15jq+ɷZxBN<^%&H'"T5Nc֧X탌T.2:+ݦe'z(2 է4ڳ&Qt$$n .t۳V^*}G?*'֥BhFrkG89"RG4KتpEF5F?4N{Zt_%EԷ F1 1GVv\sҴ"+RޕNlE:>sbX;]]1ܼ 5/'xh"Hyýa:qzD+5SmNy!U9@Ƀ}IftZۗBX`Vw%?~8HԤтr/ZK]QMtvqdt&n@]O5 jNVV9 e#>iڰu [#CUs(b ]Y9HbΝzZݶ| >g$qƆK8={mYT1ֹj Վud jUx1#9Ƞ7A\ܱz&W;iLS`o=iq|qs-PzO4Z 8>f^{Z|i[ Re{D +pz:ddz>%)o~ +VoTS"˞}juu Ww+![n}jνlVWF.Z 9/ӆExc62IԼ$nMw #_0z"j'(TfT'e+@v9"8ʌW>L}֤q) 5OtjfOGlt"d$7W4Ϻ ӡǯj)%{Dk>$B9e'.G\_Wt@ܿnYhZ&|b jɐAˀD@v;{x5"j S3}3O1iVfn?R_lRj&Msf "ebSVKsfj-nC1#Vi#9+"h @BץHCqְV =oY߉3Zhb CrHws/ֹkW8 1jrMkT5un_C)؁5,+nJtbjS13uLGsRːISW4zHb aɪiN;}x 6OZ8MrJXK%%^Lw[^ʹp{{>h0'ҭi],x~hzxzN L728a *{,y9=+V n9&NĚF\N -bD5}op3屍?n kOiJW6냊[xx7lva JQO}FHF 3JT%/"dnH`YDvqڞ)RJ7#=kN,m/:5ͼ1KКҔ~4ts mApR=p)ϭ]/ܵ;dH=)a Fe^%Y_k0;E=pMu&-KcOF"ײj܄R7qs{xKbk)f4f63&DVa]pTN;I֞)Gc*w, ROJjmoXXr Mrv1*NMRW鷑2VM)"0k'H^jd,@ (ֆ_Xdi`O8"ȌadR}57PA ztAswѬm _*ʲulgqOm(E>[L1)ܜyUMbXCƵ]0#9muY$@W| AGSOa 4Jpj4Jѻ eVc؊vQ[#V.X@8PGor#gQFMv3lmYk"xVT>0ZK(UܠTzu*Eś/L5r8dz4nF( qEa[EFOC') e$5 6+R˷%G=Ua]]þ'9:]lե l$㝫޼Fѷ>-ͳ:t9u JCо01PkbEI.Gz,i6`VpSZj\R^s'OxlKF@xiEv ZՔ ޏP$gB;h-V~U%FbfIX֘w`2==*bmZ$"$#\xu6̫:tN[F3ܯEkI+]e?4g ]m*zn*, =j͐V)wP柳{\hoǥ­xwniMB dz$8S.VsI[\H彻}z N^A麪Guw nsakJo tז 1^SwWJpS 룷sUSMGaOZ~rwwgJQ\\В4=.kU>T D }?*tWM `5u+$ bz󊽮yjs{ #Vt5j%KE #_L$:g3M*|QR!eu 0\!>u+(a˖U C0HDYp8k4]읙_4o\pxe>֊Ob*2""vf9V 0jdr?4A1Xٌc &<'p:Ώ}o&mɌR1V3cr"K햢Fsl'Ni\҉.rv}V,0om, 6ΦSlNb~! @YMڃ;qS8l!!v=)wX9*䊭G?tMs>6r0rzkKTHmIzzUuay|{l @IqH3ҥN }Z(ш/ֳuSMPJ/{d/eɤ%a" 5tj>XǚLu=gd3%Iǭx[$yi$3Y~`$U57xҕ8GކRnjJ }5Z$V#"qn+mCuaMU̱8RRtՠͽgc{KjVb8zלj0ԓTdC O xk[ӱdc&jQi)2N3֪jtT%#5lt=)˒A- Ȯ. )f 0x'=i. l!'ezJtZIt-?cl'>I|}CW6Ƥ6 o$;ϰ'BF%t|$Jn$)C*{Ek~)<'u MENxjgox讍jwBUAX 8pBWfE'$ 9]=-gcW?'Ѫ?m0-, pOOʹ{5i-CnYS9۠׌9c'>{"0SKdbYٔFR<`8tk*dI!Jײ/Z ! +ln!UV]ߞxu%-VtSj +xTڳ|"nW`Qp$F'c5n] \t6Hj*.7t j@I ο&q5PI=s K/I%QF u#w$7塑H?ezwߍnd\K\ƅsz=uiZ7͌) tl?x۵qD8TP9ZmHĠ9q]4JZWGRI~z>ǝC\8' 61r+F։ " s158$U[ٳu!IUC)*1Y%YGZVpˀrOZEycarkcv_;Ϋ]m95 \(IG6mdH7=Mz淋OPvg/|V5*(٣|Pr35wwbycJ5Yk$Ēp? ْdVHUΒ搜g'd̯Đb$̰ܼDnktfN3=sXrG1ƱNKz4Aꍽ(c:y@r(0 Z~p9ƧҞPdU.Il˂i<(*EVXRDž0}kiŒ\:[2[?GsXrKtj/3r].<79wA}1" 5t_f{KI$vŎ?*7b%ukأQ$գ+&dX*4B>9ޮ 2wjF`Lz}9 Ew(f̌WO=kP=85z+o!*㏭KrC;M*6$w5robׄHl6 e2Dԕ7Kmv![O36Q1m%_SEHFXΫl@&q[4R1|0;tKHr1zKǁCƈN1=p88BΚGJӕHJIYnnLM;L>*&XfAttV;lE8F{ԖԕڣjSQfn3޺SqA?Zi xnߍei^2Cv>$nD< o$$+j֔v,NJ)[2QC[˫g`h&RCq]4*X4 $܏Z Y %$L~7=}i **Λj:V﵊ 瓞ROC6{[E}J8Hi84v% &ӴF9%p0\)nJ)b7QC.Xh·HzPrvj׃RLY+Ңt`:R]8iڢB/zpjmZաzs^=Iyjj>ϲF`!*8C;[m1,w+*aC.7V ,<,Z)+cxXTÁՏFVGGVlpWL Rk"H}UHRR \VV:>6o~;OsU٦Zm"67 zeg |Àr)S3#4'ʑwwƀ,J{K}N\b)a3 >?)]$k08Efz&e/{.t dT0$eWRSCiLs")N+ ʵɳsuIiWn\n^v_2g zُVȪMÌͨcqJkԖ@ynpjciP[Zx4ho&q${ Sh||REqm#EҒ|+E~2 m8x1(hmL *iCR HѩMJJlˏ5 {V}Wr$zZj̃x'ӽ>[{8cPgrA'kK榴ZVOb=VNtDt_Z'jX&9JM"ÊІ`b5_ \{'#8qQk8 ̾mLiF*#9EkgB:5;]ލzG9EZ3C͚gj.ʶ=7$1miYj<0=8T%湖ؘ38xlda7.0r*8ӜRF95t)S֚vZF'Q&v2n]Z*F? i -¯ 4JOKFs.Yk3Csֹ95]f. ͎r2sT Jʔgs@oTԪDY p}P$t|ARڑ%M虧rB9 sҪ@IVc!lK2[IZfVp0zo#ڿ17Zu$ڭ E oUqhhy=v z 6R 2NY$EVgiETfa[6WVgk GJY מӖxNw18Ռx\+sѭ?u=׽U} `; @:akD)M[Xgs!z{:S:#w,zf[^+f0w+:RA 8[4e%mizpr; ;¼sHEŽEŎ=j;Ts],X3UMݢQ,YzVM###,$zՉRMiIu \Ts]#yKX Q4nL^ OP #uP>nh D2tsYZ#;WrJb&6pO>ի6$OųvЀ#U+P3ZO(uɤ:.'vC +=RAE^Z4gSJ,7\T~^sל[6" Ya\V5PS2ky6a*ż!Wl յ&Y=jUtՖw Ρ@ W {Fn=ִZ)C6 "fPfqӚJLkva7Ǯ:cdِB{Ej% 3Q\iJsw3{ؚmlg,2ϏV",m$Ӕ }sTg^Cj+&b܇QBH8"rsW9*^T;!m<ktΈVb28N14pQDm޹=*ۚI@.=Ҫ6wҥx/ g8$;Xw( 9}͸K^[D@!H5=s=j,˹*00Orvye(j rcm)}{?zpHu#ðg?vziJs0^qZz$<9I#Iq!I?Jgr}LӲӝK}NMh $E#T qTtd$j.0+Ӯ ̨;b,Ps1#( /ͫ "; ܺnEbI=s9;1zT֓DRZ (zd q zc8K 9vzX w}˕.k$\ao^ݕasu:AWni. @!aW]l$IO㧸h۴TXn֦o$9"#s+8IZE3ZHps# yFW\Ec@iJJ&Њ]4GY:r#] $y=Nj\GGLժve*WO3$Oww'+tRU>ҜnJJAr3)25t9xfi1eyY3Bݺ+kFVyW:Ӯ $uKNបiinY 7m >V\<ԫ%9=An,SZ"qSq|mW-&|O8"JвHu+1VZ FjK`^8}sQ*aX 6OzޣJ*OXݩ39UFm8h<#Z܊+0{|k**x㚗ZoԿgO}ZR,m=ѽjՖa#ф$|X`w"sڴ7-quφ`5Q2Z-6['y!g$i vZsYV|ԧ$za{R&pOzQZ̕wOD l~.=ݑ!OcNcIJ㸮m6`:5MϒcZFjFd8{qFN^:E>=[ErI#'hV#1YKH M: X.-W/\ǖc(um#ZR0CThkn0YhۭRZZv,x ) lQ-l5 GnKoÅ݌UMJT J[ּXuiGcSX TvXD߼CiXˑیMw" p\$`J58-y2I8i{9[WVD]r"kXR)OiGSֱlc"js')=9Z=ڹ1aEFjRKwvP\8sP`i-]rFZTwNȊ:Gϙe&Q;`gb+q.pF95 RX혗?.{i:^mnԤAȫisJ~Lcy28J*pSmbccUk"d~Ir%:Ok-3c7H,ej!-00ISTf׸GrpPHաx|C–ziv:o AZ)>%mQ҅9Y \|M~И똹mZT4(R:~#R-.t\3N]ڻrYq|ݴy/Vd"5z.!`#_@[vw3¯%֋?k7Λq-Q[иUeh߹GK} -f @kNMU n> 1\_߱J/g/zVNz͇4_\sErhQ2Rj͋ȥR(7a֬in5y k#¿ z]XxcO3!o5~注 ,p#Һcգ{ hI95cػVa}+ĺ:tV >c{W) 7D$2vJU1ƺ=6HDHQ=1ڸTRo,jTfu휩5aB7pkwxG>+7%pW+ h돕[GWwKZ͢q+Lq^'JJ>hy~'iqF.ǯJ\w\qدM4[~: mكv#yj)NGQ^:PQc#jm-ݢQ{yncRS=ePsN֒B 85v%$r>'=4\ D%;5Yc/JYy,,G)*iǧ$#rWJԈv])$1x>%.㑸"83:桳Ի?ʡF.ټ(͸'Hh7u}jШ|3`W5q[?˝YZu5E𸨖Wf+|AGZ-Oj@w ]oNkvBEiԿ؂i(r+iPj*ڜ-W"2Jİd!5 9'֟2bC SHtqרem{YԳ>++  T !qw(U$΃BE4`<뼠IXeAۥqt)ɭ~Q^ _#]>4]1p5ypdMZ(JܲZvmH% ci]/]8K pKsφn3\Ed=pzYM*)\-~d;rIk2|&FlOQwM @\LUd&IsSJz7:hm$ N6zԶXYqjfWW-(]9ScU"'WZ9A ;\#7 Ҥ"߸„^{; Zi ^J㛞0MsSFx%9,,u(-(~]>kHH`v5K7cX+sN8Hrlz FF dp}k-n-5m~2 Z5KY%xX˻)s:Mݛ:[Bp:VqsL9V- 4y:tK6ͦ4}o -տ.Hp-)p4mW0ۡ}"#5wކ'G{;\#z /)x^2kƴڭ + „SU YgTR=[ֲtyhDݽnf|6O"HLn(鞙NNMr2:+WOٿ:n:q:(6VT 0՘cR(v⾒XccB";H0YU…;V-mߴ{gE, juMEj:YHqTR|f cG)"bqEXک#9J{Zixk]x+>#aE桓%X~7Э{JJte?4-92Lij0&חO2kQgmjvWsZe zj]}捽C6cPw=JRٓoCk lWsLHbv]%bDi׈1$SҨhNvukmzѾ \L:K(&}3]aZ2 kA$вԵHV:a5h@[;kڤ`gcuFK[[ Q&^`K%F$͡y5Љ80F0{U+GLv`+Rm3m-V`u5Twq!Z)XĞo3dݸ-& `s:fiAgH0F*7PI口N44.r7>!m}ҟmw8nw#^ʌsoVZL.So@zx'19Kqk &X?2#iho܅R]a֞1s%,pBӿ*ɖ pȟq)UC.:IPkX z=WGc $&Yn\/ԍaVeе4VQ>qtlKeo˅YbF>aS\xzBJxq iی7LR_j[9i{՛&$O9kჼ:dUuE-Tj7A* }=+S߃:UKrH +n?1,c m)Y@Dv)6`&t4zTowȖ^RMn?CЊ.S=z`v_A'h̷SC旎|:V72mx7۞W`h6guzҽ-Vu֚P=nf#[t1=Q*6[d   hԆ /O*iv2:֞)d߼8x\6G4񯫨#{L}"3<2QFh/[y 9aV0J%9.`:T4ʓcѬPx`ߍ]ؠ 0`karVWR{hk3AGwΝ٤T$Nm0r$t)ٺixB ^ lFbcOO/lˣ2oU)4;erROڝ0:ҳ$&܏cc0jYw~Uf즶He%Ė{,\qsLܶPҧH,qEvAUc'b87:VB<9Jӆg1y{)E8lfjS}S1UW_yr oKH͞έS gԨ5"tujo_ܫ6OV#Ar9CD8SWu4dץ)SKFӔ .ߊs?Pkz)qd2OZT1ӦkjUhJ9/aO֮Zc6zzBO&U :f&1tV/lč_rί<ص A랂9HuFf2nNx&ThfXkNlIּ `n{֕a?(e:ҝ^m7k \r:f :C^u ֮$v6\õzC~Y.B fjp6<֊8]WjbsJ]0ib*eqcpCIŮ[O@WUı1N d9>i" X jvlϕ5_(RW! WGTw4Cҵ ٙM #,֚tČ:q^kt5!pxR#O>]O4k[5e$T:uTc-B@sPLQˀ mqMhNl䣵DIVct@"bBENmPpF([XyXt#[ e{ t3X 2ި=#[ "clZզ@1O0jo7Y\8DK0銷 x* sChV]i42GǵH3!{ S,D }kJ]h('lygLe];]s(9":Bxަ8J=nrzSYI<퍌KH,T;U]\iV(Eb/ sң`Ϊ|S qgi?f;3^*pE6;by#ӌ%r(N$< =+U:-Y2-V/EIl-jĐH!ƳU*Cmy#wtW+JtaNR:?Ri.19srΕ9Vy[w`2M]E/XqJY?NC}jQZ(|^&6aq֥g\REcC!SjŋHc+8qe XJ\0~vr#)%%bna9=ꎡ ݞԐT݁f1[BPO1/#F5̧b \'z&a1nU"9jSv8i̙ly85fӼۭ穩"Lb[tZ(Kco1)H*Ƒ#gַűDP ҮdJm(ȍ; {AU?\rj1ruqa,)z|BNkIԯw5zX8-H3U"cE3YԣN➐;rX07_GyN͇Tm %dys6ʯ@-B |Kck}BA?ZoNZHTEztvSH[p21M44Rx{dV TYNL)Uͼr" EW=[E?# dp5S5A]~Y>n8-I$u9Ym$`4 V ZkRV ni#*wSjPPuc+=BAqaUnBPؼ1E- Ud8 "On=|YZf03G7Yʊv0n+!> J2_ NFG2;h t`_,b34`y%Bi{e$+КYPNcBq4nq犟wfSLd`sRAr¹iB6*,8.q[1ٽʰ$3,!Cv jH0޵#wbi91&v1٤E$F"s۽gȖmGc ޥT‹q_lɸ4Ot8S͝Ǘ:pM><TFQoF0m5[ں ໶ 2T+$g#cVH'= !&ժ#f+xqvñ_JH6iK: 趴Zru\uJ@aִtFI#V>O-'g#͝5*0aNrVmX;~"-#5w:>u=RŘ5ۘn<ħwvœdf1՜qifxu0TEOwBJFZt.ZO,I7 f7Ǫk.Hǣ`V|wqQZI?zWk]2 ,=*b%ٲFQxc2ې-cRr>=/;rм/ q} O Ѵyq^ o. [,ZŽe 6a&]XZ_1'NP1ovNC<7> ׮,Ԥ~jÌYENtʝ.^^[X&wZM0Owwfuuxk<Ǡʋ%ǖ q_gLb);7UVN_PmR: . oF9a^8ɧ1<^o[XUp2k~խăaF =}kSj}G g'$z]bB?ר#84m88}E_WhӦȌ^0֝,-7%85ZM15 oFI,ckJIP1#5xM,HY>F,nu;@>?½ rnJ 뮦NNu"WgyjvA|t+^;܋V`W)wO߈ui[fu!71X=~,_Nx:M_D`Ȍ^qe7EKn U:~ǛxVtY!SҺCBl4ϑ̸8\ҘWM7xz]2%gEMXTȻِ>fy't|Ci rFGְt]Xj 7jŝ7^ )FT|K缑IOH\V>FIKಞeG9kݞsԯ7zƛ$d]]k&YbOz^{B[*wk~J [qOOIl'ԙ<Հd~u-Lj,|ؽW ooy!X9`tlb-wk}lFWwڴ(ےEK7C^Ucx:+>5Vxz,+2X'c&MdTz:7m,0$W[d%]y94LnmOKDn3mJ=TXB3Gr{.}5nq޾QSVD4VV,SxC˶,#h\ٌ=3] ŵWZ1\c>gnV6P[֟#)b3/SO*rE5f\ 3ڹ nӕղqH_jfx:v68N~jtoF;st{;E]@Y.Uǭute-1TJ-.#:E$\urk0;zάlu҃i6ǐFj9-XamKV8՗,OCͤm p޸*M;D]0#-fjJlf;hcTUTV߼?WU6*@nf&~%܈A*!Y/KeӦq<~ye<J|Ldd5Uv޽gjvĒj>Y䞧jr *s}j C×&%rM`SZ/^us?嶀m,O|\Tgxzӕݵ%h9"N2A%'m ^N( c9᯦l4K5Gٱ*25 l25`#Z1ki{Gc-pSl񎕥gy1խQdBgjuX9Jܶxl,cGgjsK]cxeƺ(bq3&4ww $#8׏.H݂sXTZ30Rݚʄ3`MtªP*9> ̆\3븹=OMEhZv@+Ǜ1Ώ,nB ]xfA gRGjK1zV^[gԹYrAk. W-tJBۧYԭOJW%LwZDR -Y.$`yNG\i܆lbt$M՘%$Zӫ;jsԣmL7[!AϨLƀ]ۛI&[j=f& ٔac=NSΏVvGV%*z*|iT$FAGj(G5FY.3c @$xpvЕ&jxziakdȳGPkSHF+bTCJܦ1,ܓ%l]+Fvq5reU(xef~i)j6щr[(v\-F%XDxخ6o@Gb:;w5.b\i7]mh6|k>h[v<נg n(Q簦)nD7!cJRo'μwzBq'>Ծ\8Ȏ5ϰ_dA?NDI?,tPb+xp @ }bTqPp{|ʼn3x<')mكϐ!%BVrTwYّy뙑 `{U#7FS:\d GJY_DG"ICVk*x;r[bl+I> [[:a6,~MBHf$vRi=P:s|5#\SEW7Q8k]VI[F,C|I|9 }z \e:޳nOfkʗBxVPhݢ !\z$upfs9-e=|> Ytki8"wSjj2.WcC?hKRF=6qe'&WJru˸E꨼?jnbqט'R zU~~kP꨸h ډ,bVh>YmJ[l/j†R1fxT' eLRV/ڹ%mY);tkYe?=Aj}%$zYrq*z#M{NuN:I8cGl0>GLyqbxKS[\8[c@+My-IY\aUuZEbSi.NQxuvQ?Щ>jI:F'#SИ&7ЧuIjdɡb(sjDžm%`nR{mBw$2xyBT-mPMHt`e85TXL2kwE{l) $Iw6Jb8GƼImuIIWcMaHެg(jT)^].1r#R!<ֺ:kndp p)&f9oI-T([]ZeJxuor"˧7߳ԍjsWҵrn9ʭQ0Xl$5 |i#@XtAcj$tHؘZV܌GZrZ2}_NUAR!zR+@WԿg˭̱cL{\w=MtY.:sVRJc-vXGNMtw-2,͌}j_pp9:T)&ej8Kf_-q"8p1+cOېֱNFvAP +{ V5 ӫXt]tz1 l#5Ū U?;c=[rk/뙶;OndWsCXN(-\C1v"fÙ`w4A} $JZ+![vKFPM bL1Ucn @IEgJBOcT) vFZ 8\d8 >޵pEj;`1}jnAqkU\: kZI`9~/\qmkުr#8d`qU;  NR1Neҩ7`{Ve{UnO)Ҝ 냊ֈEAB@ث%~Ymj .oL v<=֗"7WҘGR;jupVV>mYP9'y8{ջgv#|\X6  O>* ^2KU{bɤQ^e,DsMc=:մǥr)j caA3#֢XJwOSt9*v>k2yccڲ\27Ze0dҶTTla9aSL*7 uiyo0>W2mo6!i Ԫ0ǞZV[ᷩ *Na9CML=)i3a:d u93jJ1 Eo1=k?jcv1]An5Lɭv|M:*>\מ2ċJKfP[c2@ kTDU."<lRSI,^>l l2w5JтR9w;s#R>Ԫ2;lae^٩$dqwQ&z6+RXwdt5ΛЄM'r)G'dgtigSUN30kos>-o΀V8Z*ɸ`qnU^z75c4HwWpܿ^#V"%UA'wl[sL O , Z&>_=c"xYAc*EK!鍵Z)[<1uf܎^*^*Or1W%H$sO0gD]49#ܷN8xh!GggXZMˍM:'Zag ێR(mi UL䓎=b$Y]hZ͉@A-*qr>We\KyYA )oH_Z<{T_Keǯja 7I XHŲj$fwmLzHG*8`@BD\g֦T3E%H32|iG5~pZ< `ǁOخxc@}j2viWsD@;Jե:mJik`3:݄#8zy7`z{RiG#qɌ'dj^F :# Rzr3$RAT"Y61Ja7N.4'$)SquXV<F`Ϩ*uf.QJ6d (?9&O7 >]/X#<~4aTc /F51S\K3 îڒpӽc]\yY:I8N"9SҞ"3S\ WັYXJ#h (Vt oҲ._0p{VnVہ&KFh-q 8#H<=Yv#tD(N2)|,#V Ip)y§Eć^UH`wvㅮTֱ P3Yƍݭʯ}bǂCaRɋsPH1WTTjxo3JuX`ǹWGԅd j# 潓z䢠ܰ⢆J%dkREK P=p ^76}@O5WNPgz!Z޾4uSL^u>()xǨ& =R˹PA:1sX1 haQPzWiڝyr OkpSו\ٸ;B\rgkIdH\3q=+3|Ӊ%851:\n4p=w8O:]Ů:N0+!+B5s0f4rǏº>{s`ץ9EEsu:3:Z~ڄ2 T擧Dj^hc zj, dCg**ST7CZe[HΣ~#P#\wvi.{5]V=sYO)W=ӫgRsJ FBaVR2\Mq[k8 uōF V#x1Q>d)崏ut}ԛ;0G ןñ'+ޱ|A>rr1W4W(f9 ?(N/|KZ6#i~>)>i4$^kVk51ʕG FjpVTOImĈBHzrkuĒJz{i4.E=?*:m#xm+#5Bq𭧕ynp9"!<J '#Wbw~KRyJ߶wg o45Xz_SemYr2i柲rz2 ~7Z5C1t[:$^E3 MB(]$ jhgi Bwzg5Ҭat@ Pt4?t6k[ۀYq<L5|MkJY#s;Ua챓@$TZfK4++;K[n*[vּI֙DJ uY)mJ5'qFcJk[LDN3s^k-3UԡS]ɵdUI`[`d?w׾$!,d.k,ztLl#2x۞Kɥ\T'?z0䗒%ŶQ1}<5ͦ]&x<v?ŽMāGUVהxSZdk&z==+Y}꺆]^1y5kƳoVZ\oՉ^oZՄ#]\uYMi>0.幐ow×1K.2uBD _桖Yrn8Ifc KJ.B"f%9T:M9"$52pO8D"wBB*݅-ֱݏ,Vat?`u'$b#+z3_Keܞ3Zڴpi^KW9ὑf Q]'t{Kd5,5p9kB00i=hvU6ΥjS\k [*mdGzVj4)i'd[^0Gj|;Te巑 `씍ԺV=ap{V'k%gl]Э. :OܪUʶ= , |֣ݘl-,tbX,6;W$J4=Nwdi®Vz¥Tm~aE4ܒzU?QImZ´t#Kl^Y"*kt7LQ[J`?ZU[›Pn5BpQJ1V4#B7']CQ,x"I==+MmB1(W1Dz~|ȝ]}Ӄں 'MI&F0I\nrlf3L56ܩ6rKmQ<&\ȋs>~f'gA<Av<. sVj:0kAs?u=W{}t9~VŶ.a`;Nz{XЕHRZLgi^٭:Q-MxI{o#vGA\Z[R9efji{Ҭ8htm>I!hUӄUe*[cIQݼa~$Q5e |+gMK7]hjKm`@S޷C*$+t8 Z!+O- ʹ!sW)j_g$%9{gY\w!vݹsMYou!wuc^)ǓmQ1qq՛p V#pհeP嘶xvYan]; 0+:ӘE,;2%1< <zeKT615NSrFyA'8$Hrˡt s_CerfpsO]ۉ"_+! :4m86$W Nw,HMBFw]ۗkGKdVD%j͡E:\ ]D?$(|@9QWeFwV_ّJu"$ѝ7iϧjhcvVvv%$،Ƴ prw|W{ؙ;|3-2[@a$j׎#us 4ۛp6k_mzzx(٦bKs!#;cKz1+L+1Wl|[l i=?" `qXir[[t@a-%8u؇nG08 \Hz9eGe,xؘZ}܎>BAΚƉz+1aɓӞ+ZPGq 1Rxf%0A7t7䍰uudh2RpJ;]R[vJZKMf8m)YWdqʔ3-$ʎqy{bV_|Vjpr靵jGi+f3we8RCi|LR+(=*mEG#I#/ 8(Rul}.Q8؟dֲf(Zd]*PTڶ`Qr>X4*AϹ"ZlmE- EjHSm= W lrB U$G+kFe[| :ո纑N#Nq߳qc>1z0:u>[S/͸UB=ivX j# U `hۓT%DMIjR;2zc5n9'`>v k|0jtКuz6tq6_&G%>g5y$+'Uݚ&똞 JCG.i\mĞ֨]Y˕2׃\k` &\C"+z(3wc]NZ\M ;;PHFVFoq#Z#Z^䕀ćj#h_bj 3ԖϜwrt8ɭhQcЊ+pmg̗o8fM4g -e-dI KFQ5a匁:Mۃ=(Ub1͑+*>bRH p?xVӔ&U _f BOv5˰iP\ IӧuEOx O'ޡc]ӢA91xm "5qD UFUxP;ΉC}[˥c~>\3շ5aq&ga$g5$6ny[q\~ ]:NAJ^4(Q7QɢZ6g,ZU!)jjpaiRԵm -"TNJyT۹=DjOc.mq M%7}Nj{cH鞔Gj3?c܏( `bEC!zR$N>N;U.u+rFh%~e)&^@#MwH}`~&`Ɖ@4XXF*ycXSԌI0T6(K)8om*3V$wJqjGTmeIJm .?ߖ@8˞ ^ʏ\4-V3LvCmI j]N][83ZV'waY'cW7fiz|z%9o,lfON*#WLrRvWEw(ۦI4GЌp|VXe%UՓGUtY:u:sM :1*vS-Z8#J9|Q*z'i$Y}F)-?=2kU//g8U6w`ҧ m.$tZu{q]T݆1,\>(:hB\Vùxl-:!jUlUE'RWqK7E\q>sЌb{UĞF M.m]8+c" fzӕ\uu옓뚯-N`1WG֞"x5jdأn͘oLׯ!eup*10C*39sS:TE Fu[`њc^E<pH2@Iq 8M:^谖&,9EgOMUAlU l`L[olm늯kZ?+ ib0˴NVml 85҂#v+R1 \:jCKݍzԑ\: 4EO\`ICYwVw) ;Kdz\bL$SZQK cen8kH*?6u9):)HhҲgkj#c'ƴm,@r iM}+t:!4եԤy+9*Q}FxP{TS WI)Gy[G֙Ԩ>5e`VS*MɡFLdN)jOnn94Y'޲nnɢryT#QCljpuӭ=u 7C"bR|5afu#iG.5[՝T[ԑA`W%9-?!@uCUZgfuUuҫ=-:z) uC:|wsG&N-bP9J2:ކwNW8 ZWRF-}u-rnΛN9;}}iڄL>qäˌ6G^Bq`+'E_@&z l)#Tk\8HXFitMSL9l6qU2FڧBt沍øMI{ȡ}(?w {J==WhI$[eƶuٜh2#$`8-U= m6j֝7[\o'1nY hɧ۫|^FNjKtiX{F5Rn':Vtӳ@]FkT)gl[U-LEp@Wgv?rkf8<1G^:V+(= $/ ?*Ƒ˘n#-֧ }RՔ'ж5 ";vZfWXH$߽+@EA$NSWfi}sW&UnMg' Ҫ9i61DZT0g1R𫱤qjy5 Fx)> ץK2=jŵ>X##EG=9L<@_A}.5G*6:gu\H!21T+zUZ.ǁ&Cp|Ҳج&NBqV`r3i:Jd*p7d?gTS≀_ Ll٭\7i0^ԖQ{VENBjdo.-.6)`)C/8ޯ8v =rR[kwtD9cen/Fhj+H(F\t[Ń&G&t?;A3ZBNp^.OB zՍ7Y02:jE/qq:ƻ(1WVVAMzjB OFB2'k/rt-b'+KAGK|;qȟ#ʶ+2vY&g@2Esd'i&g4Xuzh=.ad2{O7\|Kɑ׭VK㔕TnqU;3V]%d^Xɻ(8?CC(2RztiC,ScVM'R\z2c"qs$w+!PmaJeƩTpVr̆p{kZhI$MƺlM,Q> }Ꙓߗ,Gaeb(lPP "4{KgLvIkBY8N1SBU [ W+*}SM&%ר*kmacnßʳiOUa &KMFa)s\@,?x[u4of3Zz«-AgAc^kN_rs5fX50䳑 t_l#m's:43[OKȸɷyF3+Nqke{\Bv1b{5-Χ,gd7+|.zGKPIPMzou>˂(Q#\W:#.IzԡUA Ǻs ҰgE|(%zW[11W` cA\GK#85Ԝ"UTt1aF2+BH\ t -Fpq\XdIv=`l&m2|BZ5)+XdS1M: ےIo x<HR:+A֒Zw8h֖P?R%%ak wjNTW~4%+,=ZuH'B-i5zx;{[ 󗷸|2=xU6)@G\*%ёMIN mVP˜)-0wnZմ̚TG5bY_Npy 2rBaۯ*2{ע&Ŕyb~AXd~|so/zª];)t䚉'[\HEUҸg}\iGhڢg*q匮33ѳn/ݙ*=k7R$\}ns)X Ajb5y7Z›uѮ"4W,'D T-޺X$qDZF)&a,©8[65'xxDƭ&ַ'_&u Ok-c=IaJ{#fZ҅Vݑ6o+txE`ו nVHS'qZ6ntP>JܺAj\ D66(pU<\#D.2O p872\plyRO{~3O&߱Vlj,9$ԏӯ =j/z,$hjޫJqW/E%o/\êteKd3;Uӯ,;*tƍsk.[a1UIH=*̞.0P8,:ƹ]cI)c Y6:=DY5Ruҝ&35`I煵'XY~4h]Fg=o xgOP Leቧ )+Ift@;vv!\IZ<+l03 Fkׂ^V ɚ2r5yղ~z&ʐhm%<ԶJ?:? [ Ȯ @[m1S$hhƗJY#,L&**jǑΥG8sn<7jV~[㪚MK(~tizg4Be=qBSZ*Zo঱6gɢ׌1i]$s|;^mF3%Ţ3*M݉u~$t,V_uNMiY=Эbko+d$oU ҴY-XRfr͔6œX15m<=i>-FVҝ`ګӞ^:Ồ9ȏ\x6&9A6mR.Jvg? N'W5Xk 51ֶu \oU\+_tLW kJ0r˗Cڣ)ZiU,T[0: q`*^5ៈW{-z"9k?|BPGݸ׽yn$"jVGE|4z\bo/ .[e&HՈ~LxHmԀLʸE2+^+,L`]- ([EHh{+?\׌ekv-"5R+x^#IE8a> $SxLWmF7v lϊ_f4&HW*_>/kə%:sĞ*Z4ci;#'ƾ,ҼtqUNIo?u fieI_/#|͎Nk®^96˚Ѷ16z2zqJp#CU=~ 4i,4:t 0<5]Ԗ< G͖.T1X?g/0-x<J©P.U5G4=oOi1#b[]JBL$RwڹO`L}.& v:g ٭F Kr?TCkj7X+ #OyeoF1\{ftOG-OZ ܴ7/e&< sW|9r .fL.k߼gfUM;=+9;ݳu ,apz1Lӭʈv߅pO8*Yݓ{E<7d|DZG 5m6Jjn)u9\]E+ q>ZkvHsڷ?e60x Ζʎzd…#0䚽ef t-ڰ+T2Mj 3Q{Ir=ͥxad'55֤&5 we"X 9E]NΦ W*߇ k[z͍۳lqA9"s1Qhbo#y&8QYwӗr)'Vi ,{{Ӭiy7ȧ'FoCJcf;\/Z:"2Jǖ+w`r|́9d3At5a)cӦt,$ ǵm翃 ¸>C0+^dO۝&1y;E666XMebX Ҧ2*[JYڒ~@80ί,yl ֪|.V;iS5tPZv?W*rz|9AL@KÌ\߈-$exx7IP2sz#/K v5?5\zW]jݍR7ccuC,Gs1z}jtj;A>g@ @Ι F'G*Hi=5g+0O ߎz\0A]5*}4Վk`@A>.˜< OAVЫTE52]FRHtnRsמatOf<u9"єzqT9qG,>iĥ-n[rHuhUBON՜DA ßSlFx5+ \"i5ABEץ3')!$Un-5vZJ6cv)u$zr_gF\'4f P)vuyBEY}B'm>瑨\bjmu"^Ǝ[FWZ mZ6wJǮ p^$(֤7hmjn(j;C(aOLzW V]\yY\irJf2ofHdUărq~R+}Jd#rjՎĪcoZms7%wO,6clu5jѶ9?jI4E# FT:.bd֠a(afAvF7Ĭs}JId܎7ARE$8F=kz[52JGlcSў]:f[)_ 1[!06 ScӮ"QJnZ2hLAk1 5bi!\ ֈAqֳkM#}Gcq 9zطd}le*LR̍5h%Z{H-IA>T~JNZ塼`*"c*# ѳaRd#>RM)TˎF*ݞ6695~-^ɔ˚=Hy*Kh3kyLƐ)P9,2kR[W$f^HE&GQUt{H=mǙFO\TZJؤrv|ݚgquo1XYA$JZf)joXDZ'*cWʀHY֒1I3WNn֙v3HѰb:O\S }P8&ܦ⪇'5h#Vvڕ mu5m_+GCW"{aq'J_ehߺ$9+AQѧ-9փZQdSS%>k'TV䒻k[iPp͌5{5s#+ZUSeR-GSxYI#kNyk2wc[)t1UeK&f*>h-]Lzy!L$~QS :ig=Or31QҜusw'yG^WI^:F2;/z))w_jCZ~X?o@Z:WD}L*Jg4h":!p -DI;dz3Z :{֞Q=3ҹn%_z|U&SWRCv$8b>Tx"66j,:]7^{80o^a.>h[1\\$H#$wji.T>цaRm̝){WFPZ9S&^Gud7Zʏ敃cviSe]i}(ډ#cǓΐ2*'ZάZZKFQvfwZ6mg'l՘^z8V`@phci1ıq֑aP)ּ2*B)`Ϧ*շ)-F)>2V͎XKd8)m{vrkTFW/tZi"zi:c ztH bgb$iO85Rh-lhZ4ۍ2iX IqM@c屁ڢ1t{6j5` :bm0d഑zֺ W<ӱZ(y?I#iZǺ>f#Nۨ@RԆM%4m@x{ o`=3֫JPoRF(J5dQңF2h,$ҫ޶o"օ[U3+ ,N^KTT ~I&"UfV`q֟}q-;k_}ɴc-[D[hP_=Dρک]$ \dqG&8/gtVmB -i'DQic'Ȗak'Ws+K#1ZM-i%n% ͉@sU5pxA?zkF{lͨyhQU=.(B?q |IJ:XV7ffd}E)B*go!fvdH1֭ˋ RKFO,xHN( sּ٣+ws%Fojh14ң|\5[YsU}9#M\u&Z6#NMucEy#)8ߐ(`G&xmYE6kf ynvV=BEZ2FM9_{vJ!˵{S*K\I,@+FF@ʉc?.954b&II] m>F+Ě?c6\ImD֬uO늗\kT[|K_\TWw9oq:z.c 늎D)=͘+du֯]͜)q{ %9SJbFXqꍹݬ]Ry9+IxV.c<1RG'=kcL9]=Mc.dw:4h] Yt qO76MkãjWqajzZœ/y؉>We۹Cv)dA힘Bd]6r8uwH*傝˅X^iX(Kyc$NYl̍=OEAY $Uu77䄰84F%.fi"K T1:m6pc^q?flGe߆8|ؒXtQp3S|ѵEFjvEeh\%`SLѬ$ >+6/McY ݕqPe|t tqR|v_s V:Kp?vp>묁8+UP 2>Y$~vE k]1ݏ x"'D qfUīՍLƺC/:Q#%'So?Cѕ:NU$z7sRH'vNg,֯bLm@J9Qs(9iWPc>ef5ZǕK:vo%NH9 w5O<;(f\򪃩RLFǯuI6`=}뢥Ib%yFaՔdbw<Jlu3ed.0M4W!/NG5Y5e+IǕ2]EA#<} %,sfnbyOto I)Kkd_ K*3/3rmjsgS,:ⲵfGouk|oq$~J 3$t?C){J󗾙nXfax?m\BhI Hp[+Wb+e=e6QvKS>&ΐcÀ8'^%[Ǒo]YZ4E98G/'s>3G™z?&YS+i4[pjz9yOZ]H嗥M9.%Qs3`FO_zNQszj5}x[=Ev\7*{Y ]1ֽŬ-]ӓ޹_gKt \'Si/]zN*qI.@߂zSJo8lW@n`vp0ա=<o s!7df֡ )cͮl,(X94[DH`sLn팼vn8ȣdDvܫJOC)ѴSe]2KiڻO\whK#Uj/1X?`X}_r[v^;Up)6'XXV4:oo`8.{֎Ii9]EXG=n9%b%u|'*^Gu%~m۶Z]B) RQu]R͛u S2Vĩo*Hu]e8xsZŃ1Z7^*7(YGrj\Ƥ߻'.iz U;,ʮ,2v[kLch?uަܯhf$/!tNGjdXN{~{yAkiUvs҄u [4Y0pY\صnI)H'OBWlm u~{\Y`5,`ҏ ^MjG$T%-d#ZVzV"VN}A4\l)kjt"gnw*1ޑPʾ-/s01 W-_ݴVv2"p:㈧Urwu)Z\ɣ֏i ^Uq]־AMaȭ*}3IVV$ŠUǖqԺux-4ڝ*pȭ ek%sq,<|*ٝ)+v ik͖+ Ehبb*lsmk{{a@ Ui}8TG6IQ:ֱuKy6~hʆr$bA$OC.=KTG<&&9oEWjzg5+ 1%ֿR{U.ouBH`A,pz7ûO k]wwڱcJx<GVV&w]>K)/]o ,JM~ H?3 .j>#4xa ph61Oϕe+5AyR]~AT@yCK!ͭەGڠuNs3ϹT+дqmr@#s\y7I%BW}GNϞ|M\l}&Ōbe8}3ZnOK..&S8SeH$Fd &Ю8F v^\2䃔ٓ-Ƨ,hm8 ctҶI=pk3xSBKg峴 ʌ3cR1- ]>Vg1]zGc 0* No7kF6_j(>Qr= YnuBHr>I$[_)F]䖶WIU>r̆Vwq͂#_j ocI=dIVbmx&U>]$ УIti$=Y6n-T^-m2_M2UYU k" 2-Vךcvm0j6msպQLIi-ѐS[$L]_q}lx$$\յFj;r&ti;½Wus\6RHHU=w׷b'w%DTɭHu`%RT!{G>nT[1#?TSII"FH%0խ{uzܝ7Le܌ Գ'ϴ Ydb%.]7LWK%v*qkRԴ1,eU~2j⫈NbI ڲ 9 H#SKs&jo<frcZ҉.9 rk2I!1lb0''mMvZI'mIjJm|uU,GݸcI>Bo'&W!~mA˻>^3(#$zحЕ28$`*#;FZy#*CjÀg5&F:"#8Յ@G1WRF>:rHSҚxB݃C@1sL*v4'w}Y d#t"iDHvVmYG ³X&!rA vw!ht\=+d**8W93ʠHV7*SX+ѵ8:bBV׺5Y<2bXFe5Xª2Ƶ5sM.kc  n[5,j #7+ܭh Mub VͺvRuI-$ݮ`>JVT[ rsң xh8\ r?/5Dqϵ7ȹpe/=*nѤ57k=*MqL5m8* #4"&dv;}[Odđ6rROLi&,T׽m5#k"Eڂ۰ zo>:䞇v*3hpʤڎy[VGF%Tg叨[0xbDtn,ڡV[MQ)-:|n[9ި岍̲1թ%.T3چޜ oBIk۳6%S[ɉFODdgT>oS P2#ı?1i A*X\|Gj 5RVMo4d J³R>[2!^r~w0ǽ2Hm%OOm+Ǧi'- iEyk9>Ol $VHm"d!Pz5 )0ɐ<;U[d#BEC-u Zޮ20JӴHS^Zn4nC[4L-6lԒ4ʄ,jd"aJ[]]#z3A= .=.KI{hr#sg˼ W;}X5ezG旴 $*16AdVV^BҺUH9[q93 ێMX_ֵ[L`[GEBmfڸQZ2@ HnZ!<Xb$cڹZݚSRJ1yeT}kGx*wP:3HQڛkG]\=F<@+E՛3T]8bYT9޵lCq:WH<x:U[6 kR hJWG/؎Z:efYku~YpRS ~Mb Ɗ76H6.X;b>u@ĞRzQQHe>36Hkp@ ӓާJ<k\To%ѫt fF dy2ߝHmz?iQ5)U*Fs%>_ӧK<Ӗĺ֒6bY0sF95:&G,Oz-¤xNkd*̪xzViҖh¦GRH_kr9 r16iJzs֚2Fi!x"-IV|[\!xWhAtsZ0{ִyZ.;zʨ7l\VG1.qWlGNj4E§2CH0}+iǽ@R4aUNZ U,v;GM#lsaȮP4i,:%g]>$BV}ư鏒b:+= D\_l:=VJ6'-1\ QiwF8kkLtw1U5sXzRPWy͗v^BS]0Ev7ȯ'P-ԗ5ZJfgw幧R0c4і c/~f[TZ"K=&ҰUXجV s+8DmjuYCI1]qV7fuƬ[j6D<ېXu.DHI6Yh"<}d6iosWFfHc8ɥn$|@8O<3~uY 0;p[5^Ko6mnlOKudT}fUݼ^My ZZh&RMX1OerV'k$,w r@sDN`ȩQ}?i'tE+o_oMY {W3c zrk^kր- sҝ+4i \ⶐnt)|dv륙snhۜLn?-sʖdfξinTTVS%mZ$iJ6~1)u#M6RGN:A$"5 .W#5GFM2Ij=q 2wZO(p YpFBa=kСNsu=WZlip>\»xBXǥ|~sqUo2MsXbsm3䇼>?mAfFh=Ukv%B0"=m2kCy3ISSd{_@Wm|K(PU=O97e>y0ez>|3ky)wrr$1GeׇIuu)PҐ9Ũ˜H1Pf[8K@ԒeIu}~r;Tg}QQit%ˏr+v-Dg} :r,JHU`WtC\ __"˜_CIrIf5+'婨*.ڟ cպqz4Z4:]>{k|KRMֺOݵR3kt+.Rfut'ΰԈJx$)ՠE%{۸̲;/j-,@r\omw9WI&$ڼEuMKGZiv,+]Z~hm%ՊD$8W};@9 xhiv7\S+&qq e{ԥ*rKIN 4ZQnQ,BW yܱP^d4K;MyZq}.MK '.sxdqNW2 =͆[I {J~JJrvҍ:K)gQ}sMa `qԚ4epjI!:JzҒzH\՝=5 iV2ǝ̾b[% ^m\ev Rd.n.3W5uOĬc8iu)WrzUJ>3oWG,$ڕe6:͒n@1\y9R9z ]#qnSԯtx$<J22IUVg"ݽyX`SGT}{]ȅU@8VދmB+YfͦlbܣEqQCXο2p2cyy(7goJ_1$c"9o*SzIjn6k~Otrn+ߊ,b!9ɯ<(`IIF)bu ,y Nz{m;Po#pj[_sy~&n};GYkJP1-!a֟Z}֌^r;^@i֪̓0Wχs-ByqkC^Fpd n\&Ӡ-OԵM.abAM; .<[ḌXoaA%*>5tyD;(f5ygZ٢tCmm0Sj}[V+o<8e6U8›שmQ 橨mzk|3 /bK J9 ||rqu/-[ZƖGQ 4%lUGRuo%ܔyXAZ]CEIL׭TС,l KKÑZ?kseL`pATl&hGG1LsֽúY0:~>]o S3 Zi:|IX*wTjseQ,l4\߁D z]ӴMX$mGKqsyri W_&G-κ90ZtVjO 8>W")xdDXKcna&lWО[\j6?iHoz&%]>FFǙ$yWğ.-Čd{~ubKq8w+.;{աTXkԧ{;8/6=:-,Z6ƹ_.L=6j2s2HQa"D͹AX dӡK gpI?ZjJ\/ݓLuǰ$ա텇=t2W+qZZƤS=WڝRze'h p)}Mc:RNƾ/mtnrcAOq$L=+$vNTi6 Ns}m R9yu[YZ Ht$/SɘǫcRn:TG{ף$ɵ=9pT18٬g/emb3ך(8uR*k%5{-Uu XzԚ}֡U+$DJM唘 $)alr;Y`S/5+h6Jv*IQDsִl\rrXcϴ-9qW%kY **qm+*4v 8&(Ժ팍+?#'^|9asY)"_)YY,{ ASH/PR*"`4i"pb[ޭ&d`曳8&wr8P6Q5_81EEsLUX`[FIh(k)fFW¹{=*Bl6{U%X̬>NKwhw" dU st1p3=x8] ].Mm,޵ ƔQGrb# EQW4[3F$m'VRzxI="@N2WZ69;"kW"2%cZ gTOUc`$*B*1\je"II/V@kP37UQ#6ʭ`:>_]dX e$ 1NF 2 |dZOMrH׫|0]U)ըi: 1Z;Uq-55F 8-SJb@mTfRL̊è?: KnrMW 9VXepCI`@jK{f%eHF$RK"@u7N=j =+OSJX"g+ԞzkC@ݍI_j~AIsYTg}M.62 _#ko7Al ,ܶOJhP\0vݓZ$ݜw]O<)ٞ2IuہY,Ry`D"{GH9*Wqh4%'$JȀ;zĀhYZC)ϓ*tsUwBlnj]-0@?޺MaI "|Ti5Xƭ[IN#> h$v .XwwL0 ~< 2N$w\e3,ۂqڮ\߽cڸ8mc(PŽ+2)S[xl7o+%"Goeq4&+(͉cRmN1jU^Jѝɥ#h#6p+lϜ0R֐\)|1T'*i^ LE7$Q ^Ia-8D5,hA8ZKl0ȢZ]ZVecHS hv;}Z! )ݓ(-P4g1 lFSZ4W>W{WIUKlS[-ܨڕt #h[ve>jx&O75}6FCh`}jC]1*#٩<<ϰVٜؼ''$A!$ d'8 58fX\0xv7PsU^zRs6iTo25 Uxq\^Rwd##q9YIjCvcn5!w!98gCtFR/ )#iFG}խIz?Z/B}z#BT{k6suEs xrs.Nj[ⷓOѺeތ̨3D\$&ܻK/sMI {mp%F*Q~S޴Qv Fkc. ӊCdsӚvJ5jKmb-Ń!;5-T~[zlnLg>JI,֗3E;VmauҼ C*)#Kk-zWƆknm¤TQi|=2y"iᢐ{+N%8cmf^ŋ V[x$j5ѫu&V #ͲnNjD7U` ?Lˀ=9Mc=]S`T:خC7c9/5ԓ]ydDG(O+lqs7&~FuܦEt-c#TԬy]D 2jEq\rkLIiacWm f8ǜ:䊚E};H:1֒1I Zw]NZH.yͿPPNkNy"):sSAs P}3Uͧ4)C1c4? pw=oݖb=j7k~E==i3wxI'V`n?q38R#$qүYKǀY(^.+TGiaE榝Б%/㩭{ u#_A#]_/v_ *$2e{V 9 w| iyVϠU?jH -Yo#|J݉\ICu_lF";uޤX6]T/6H)ʣ,O>kq}#mScp}Q$#MTcҵ nگ[Gہ==m>ŷ{+0`0 7+0Ĥ4mg\޵qJC1Ǵ:h.T{xkq̹H-CDd>:Pl8QWʣP[d8e)ZS"UؓBBgj}TL<X;bDqI,~1=keؗM4zl=ju?#ísh W>]Fpc֥{\#Z662HVou Q1r7sDRI$V='{3u⮞e<_DKȁbfLcՕz}[f/ Rij]QngܝQxrIUٻa|}xFu=sPA0c#_J;}dϔUs)W3G2^>8`ZGֶK 0]T9\mqeS5BVIC?D2Wp & -d||fM-%p绍{%ג@=j u'DoGnmkǠKwq+V$z|:Ā3F Kkb$uj$icR:em2Y.0x:ZA' #0THVF9a I6v 8muf9i#adѶ͛1nUv\ݰėGa T֖q$dw&kdܞYƇTk G,]sWӬ"o.3HTn}ԇ)F򫐢3khWya=KsS[ivyDv0k@hA∧ٶƤaJ-Pw1D ֪^]];:UHЀOZ3O%+ǰPr~O<ԁSVզ* uL75,:ݴERNROڦY{" 8[}6Q]❡^hG6yT+eE)ː3ֲؐ5};TˢY?Z-kdAVl$Y]IqDkӵ&i(v[˺Smƭ}F m Nqֺ %P=z[-XWp sW)ԫ+߻G>i1Ƅj iۣrKH\(ΘNtWf}b0vxT Tz4nY6ySDp؟ht;&}8 *:KحbRR{Ҹe.ay"A(Io ʬo&GEְ:(X1߳0O@ UKչG[(hxr[E3nyũ# nw*JqwFJߡ: ZEC$Tկ\W*?3^ϴKU!U(-t=7Tt6Z <-i7C}WIIyIj`FVRuyJ\[&o%䎷~ ѯnFp.WKxWÚEizכ^IlYk kʷfsXʽi;WnY73l:UBW(ם.}ᚷtu(ɎI$<}+7X7T2#Z:T'vfDlhmw*?UVIӑA m[HBjzFG+?ExNM=w j@»+OHYZ]etVAqg6^&s,><&T%]Anye4MƪM5ݻDx{ƞ%,$7S<;-@A)c'~N|Ӵ &p OkebHq=M^NdZ/3F~YMoG[nko|ӖrkµYfW<+OKbu3Jn2MoS=Ƨ|QֹFӕb ѴnzdsRx~_ͷ GzrŨK)$xJXL\.Ujze%1Z~̭#6q^io@et?Zg\Mw0y+iSQ6RkHCV)<džX1Ԛ+c^[\X2y5R0F3>+ڍN\O5ZjV9V`3zOƍSu/ "'a\^ա#;$#קKF.M_^۲aa0.p@xL~c&'tzTZ/.omQYqǽlQڴ%֞vn{KxUB?]X|->SmXγ[ X:ӥX. UXcζ|?srQIWiyDqK<QѦqԭ>Kǒ7is,[.હfI]5͍Φ_kddbu]J\ѹ涍$h:'# &N3sY0BƵ&Y&EݏsJZ]J¶]ZOG,uF-G'mμacFOX֥#nGZ2tַ5+˜ lm-](,rɀY^O&HHIqSjpqYκpMٞ[.¤x]&1JKGQJJX$3aN? 'w2cu*9sʠxM+"Ƀ9zעjŵ-z }ֻndnIE:M G:OFQWzg",\qXZ-kYp3Iˆ*7Pt3Ztx9f]D ю+t-\h$6ѓw2x$OqVV/MCBi\t08,هgW"kNȧ+ ^7[55ԣ _RPtv"u0G;jaf]t$Pb9o\r#R9%\d1<7!i %F ٭܀[ʱwk7xV(Tiѕo[ l㵒6*4%x X6¤1ss]N;zTչmBO~:g*J35iXq&x#}:) ݦ\ܑ+v~QV*z>oR?k'QuI9 7G`dۘCixWh;S])XKk~[|ݳ1^^حH!3;`$jx^ִ1^Qq]\onS[98GVʜ$|Z~'0x `;G^+{Uki<.AW#]y/'yf~N1]ږ)$0*atmڍ>.v sV-x\tK}k~ `2k++~g/a;__[;z (Zr(sXm~A1xc8Qi뜷Kds'5WS5.ʼvYW6pU[wZk2zt`^6So|2q {8Fo܁s]iy15Xh[̭kŽŸMt/=Gf뛩v` 6@|f0dgW-eoboy4x=dCt<-6l^( ^x➿Dw-H_pjf8⁾BAWJkgׄx=_/sR!֧Amol_iehld)lzm:|CLC٢mI%+үIч?5jm?Tioxᗁ5-_So3c~3~ҿ?Gy݌)!/u#?/t9mVr+˰qN;K>γ$Cs0YO @$o]x2pfh`WEm{Jo5ռ/^zӣe 1M 3,`GLWk;\3|R^ܴANr5^vI5x\ #/"]V"yC$mC2n {Ke*9\߭9(#Ҙ ߑi-)CJ1wrG; cpgXD ;ԊzsOBe5>Hi?LS$ K]A͡PU!iD*oH黽Qb'zز^c \4r;_e°gr:XU`Av1Ǡ*W;8RI!q~ar&ۥPAbꐤ3+\D{1dLaZӕ݉TqOz#q'5vNFj4ānG˚+Iiq"6qyJ6dYF H,̣nzn(1B#*jHR($UHVmdIdmG49+ л[00PkW3\D#QWm-RҳǍǵRgA+R͸bɵAxUհ@8歴h%. RE@7!I|$w9MM C9>.хctifbigA:?2=˔VT8Rn!p u>kߍgU6EF=Q]KW ll@aQڣkdX$d)*cuAӞf8FW2}j]Aݭ;t8nSҧA97(δao!sҫu8!AZƇ01O gGzGC^P!z-;QW-L@5Z3B,}ξqDk0+p&F}*[diWQmIl/J`2ƿin C=`eclvH.7 u#1{RE2!'pɥ(hmwɷcAkJ`0O|ޭAds\ӦSM;$4jnkZ0:%I=IE/%hؒ֝2dJrjh.7L@.|?5^铡AB?u8 mb$H[P&X-'Hٙ:0/Gnk5Us#a0XɭNA'l maNXs+cx 3Nky8SM8-A \sձ!.}WF|5qZ0j34'J@"(Ջk{wM:۪ _\3ӥO-ͣIun/0]9`UGgr{d0;Um2M Lw#㶴RbM@O+Lyg ,8hzɨeH7u$Sbۓn*wv2m)Smtdկɓ>֙8o':N/NؕNqw%[6剆y9jiq pj1ĤG_EA x@M[GgAZVǑJӲ՚L,n [EFDAwnF 4̀K(C5'`HsAMIuf;- *Tp*rnOz؟M`REyFGhbG+'8wp 2=*ŵx{g&Ff巏fP(VӞ,%_2,[0hc~],.^L8'*Vީ]KD1lNzlv)ߵev+ f,aޫ'֡'f涌;ׯTW!mrkZeU#+95"ی{j ]d#-W*,OSոFQmonϩ6kAGp"$jEͅbq#`j=wUɒOb?*- b⡎'QlGRH5ffIG-;TCݪrJQL183؄ 'U]XdSz0#6]Q`/_J=cS:gD|@8R x>Oե~X)̀qL/=#cLmr׀`E!]oC*/5b ,"mNSE>:-bR/h˞+j@kZ6Og=XT5͵ejsV_^j 4{WS泮?d85 IBi1cb䳳Sȫz7,yzVD>bIWfh浴 \Ǒdcˏ"VbEp_zf\ըk(b *ph"I5*r]\:+*7P ۱ɮ %JbYb@G?6 *R7pHJ)jqoKM۴Nk>b#I6zI8sQ#FߴqժQnPvGZa+!`P"2};^]觹婡j8é?/V1zÅCբz~)oq 4[H敬/R:FD-J:05Z(n}ʸ4TeuS<o!DjIv#,l #|;FysQ:V 82!#*1I,Xm.$љݽZ8.!fϛfUHFH>]iqKkiZirdzvAq1T$C'5*4䬙nEe%SnO\5́!"nCZ3hl&\IdqYظXm 3i6-2'*pJ^Y>Y&(iHQ_]HVe(Z$c5.8'X fjIq\j[cݓVdwO>׾(:LG4}uG:~նLia!UWbryäyf_[j+&֕#_R_óĪ5@F6 񿆼C,E# "q:ԕ8sr8ƴy~S$z(XkwnYO^yQlTcƹf;W(dF1Bq+Vt#R sç,DMt~#" *V^NI?VsZkԵBY>^4۷+rPbOVQ -" L1U䶹Tr{l -867TEJr8bz[ϱ,[K닻UO!I4]_0bh嗭{!vшMq#>ٮW[6.mSV9r=ͦҴ(Y,EH9:7 ȯgJִyX \x'm.Ytcꘊ853, JNUATr]E9A*f^0MKdj2 &d,0B7u9'`U1Q<79%ӹbU񑓊5I^q1 v2YMlr^ <qUY̒<+54jԋV&(YJ{F>,[)'V$֓NT `rk1\##G QS]ΘUj2ўc DSecP2?*M4 y\3[e[_\K`y- *xrC6ny qU/9eQX}&շ5gTƥ;]*rk!uy18\qQjKo®3%:P26:[vJɣQU׵l-r<㦎۽l0U?Zwj4ÖjcSsqOFRzơi]_\h'W%g˷n1T2oW>!!=ɩ=FmJT4mq)^tWI_(݂3\xWFjCo1,M.dq֩TT(5'ZF}!T?δHI};ӂRK_#\,au8?U *p@y|a]ѥxV#&!fe> ">pԬIjiSJIV-U\7/&.G3? OKt6^yk~ f֮Qs63\x0}b?{iBc0}k yo4_+Ŏ) AJkC;?YGV-}iO^<ԺpOF-u=Oq;ܚ,Ys3۞#&C ,yv}kzy-8K͝[2_mmx1֧ûIr] ֳ੮\0k_kT+֒%gtbq>.Îs]]g.gY/Q-,`Ӹǵq9aέ&(z?m1 崍A|WɅ3]9|uCaC[lr{;Ӓ*TpkҾ!Im̸ODܫR0~u?BHݶSEҋR~{9Zi=XJ>O/5i=,[}7e湨c4,Wi_ X#~J_P<176V!a >~}__ oc?mECM4y {kO뷺2VC[?3Z|H7H-zz⼑%LGzx Rkޖ6x\2HȀ֝.M?3pI돭s4gˍ2XdsFesq|Pqi=6E)Z-/$f65];nܜ]EHk.?x/Ymt `p A*+vrjRRm[B~85)-ܩq=\5bg& JOzZTe#g$$_\fHpJqֽztzNȖb =J6'##=iA*ť 8Թ38 2\%xnf7U+=MV F20hZ+=BƠ 'qS-3?.-bl2FNj+vIL`(#֓ЫY S}3RבU_*ͪu)1n21=*nYAOzѸ+#\09_bl`,z *á*B+(5Xȥ߀q؛8]^R!75%d\4zwFx+6b1_9ȫjI6&8ckodm1( +Wm nìbdD;Oj/gݷ8]8ma[۩Ո[S5ʕh:b#{m͝-Yؕ?mtevChY_ Sjk<23֙uҠTs~(i= ,^h$ FF ҳ@m̮GA޺l"43"Yɥj<ɻ2qzmwQ@jXRbJ֣- nA*ۡIߵeB-i}jXq+3JILNvSdPbQ#RzƯ#Xy$bs޳;<]\*uKj.~SW@6W8]!Ú"I/'nA9a'W=H֛vk[xᐍ 4 1-z "$TEإQUÙ+ j4'>cBH h Hа#5!.? f?:ϻ5Rɦ-x2jx\;E(ʷN{UaI0(i*nH[=I԰ڻCvnj*֓e.gvqLrHZOkڄcҫc{T*8RGr8cI$ [sLFyGzM/H͞k9SSWC$$>rwĚiPcONlCY;;vS%stm̥* IF+C^Bm-$ZD%Õuaw8Q2ҭI[ww%f(8^3Xv%C9Lݕ)+I :R7F9mX5v g Sp8U: %4 ר,<'4(UB:W{V!^v2kA+{e<$mWzFs($QnAVðp2G:]r+6h#3ʁ$YW؏$z~Ւvd-PCL{}@ An(Y+UYmu)60M.KP4Wp H:4ad#iʝI9]#yܵbxQ,|=,3kjrMF~a[%WW[m'N}pX|N:* jQxJ |:i>ߓK{m̧Z@lRt㊹1fqCTj;2Dx姕3Aҭџ1J]Iz fkַ")c>,늡l=ݛx]p=wdMFI]-\!bM&QtImR>Шn[3>=E1ozb!5ou8׷Hc\UΕb=ehmM,'B7`EL 1 ʨGvZ2f8e-OJ8lq4V{!Ijњ0^;`կ좱|q+ŎPZXpQka*OWc&arZQ\~' Az|AIqQS+=xxEjγ2>-2Bs\ŖIWsձN2)'VZ6g*T㻹6LjF*ʹ6pAm4<ɹB bJjOsWL{mc({TQTl Ӯ*_ V WaQUiJNڽM9#Ж#R?P,;T^:㩥tqhBź T]wZLq'Jع+qܤf㨬ۻ }jpGvC)%{UXƣ[$1Ͻb]֪<1C>I5-nuL F8SJ8>'/5.M@|_"8MW(3ЦC:'?yb{K'v˿Xm^Y|y{ )'Fl'$(+`y8,[ԝH752׍ĒaӚR_hюɞ/TRS ȯ?,]vJ3,;JUwnK[6nLThf1337΄ d.hᏥ;2 y=jI>x=lig([fgnē=i'meIяI< >b=穁i b \;ZZV]LNHnՅWmt[8\hhejrsdIr ZFnt5FHV%;G:V~侖9hRRCyVO$t*CH5*6uDNٲV(Gsޤ@( qM[FP?֫l1;UYϷJjV>PPj7hǭMmmr;wwmDzĹU 7_¥BŒ9⚶b0=Q\&O~9;ٲW7!A=*3>U^ĩ IY$o(8EfBߙFfTV]+y v㺕q+K7gLv$c7[?fٚMZ8%*y z+6p95N6ϗ )9ܯ1J E`3߃WĒ"{+}B2io鍵qoMMVclwߡbIK #` ,jkp{֪2yt8\*qUN!ౌ|슫銶 `G!z^9 ūZF[;skR[c%{Va-PҸZ>"T"6R{^wPmT4Rs>wZZv2ɭ82rx#1F܊ݸ=RIXQ[˸l5,̛V c9mWiGj7s_g>}hIQ~f.E2c SbDsUN:T`{s5dD9"#,ְ`l9y,M6bdG3sԅUI;&>5'U@Lۏ3h#ߚ,uy8CHEi:2jv:YF(f q>EsxwS?zΣ؞oM$;n=-Ǥ$o3ՉXk8 źX+ `I.TghlUqrI0GW}.LehA3J-fORU!KaQ[cQu-ڣboV3LKmWNM;O‡GjmQ1$`:W4u]\v{2ў)AJYprc- Ia914IH׵hY9@UFӁ^Tڼ&ӈenmsI⒍zӴ;q3\+qƾj:.AݓpŖVV)ojߎ~)}/np_<38#mc+aJVzq &/ح s\vf[j#kH3YwW7jU־HFP9R=RfOmr1+6;4qZQѯD짟G ,L1^#*/R A#cjZ5E 8! \A;Kag)ٰs=tgovZS=4LOGZ7)#3߽kxKXЫ -\֛5SZqI62U35{U(_i[~%/2O5w۬yI8 BK {Vl3OyƷGJ"I,qa1+Rƪ hkCk@#l ^[IWb*oZ,u )q[Z{^i鶶jp&''cH Ræɷ+zfVHvonU/&0IR.Њ8g\ޛvJ a7H'[KE!X0HTj#r.Sլv2;/՝;EV[quke17&N;q]A]sӭ);t:/zd]2(|46;6ןj̰swO685jZ-Hҥ{ͥ]Y(T] :Ի47ޡF@d4IKQMqI/rVGO&BSZϥig{* a2Y D[(rWKǓ'*ϚOMe2NLv趚\.s:7$+VVa,eqW5J%ׇ(J><;{m*"nDepHm;HЬWڊd'| -61 0I񥦢$3 6+qPnم)HSҸݷ32 8=*>AEf@8d =峎8pgR Ȥv: TLltѓի~vg'O|Vzz6Xmι>5;!%c|>\OtR)ŧOr\4G)ɮ#O.Y4.ם~q"H_1 1F7UӔ>\V^yqyF;?t诧Zs'_VGY!6|N:?"=w ҒK95 3m0Ar;ë}Ѭ6?Aڨj~z5wvSFbBvX-#JeۀXdKY.Ċ ڨN}"€M6[bsE 1=H'@FRk[ V ƨe$VS>Uڈ;~sPNVIf]7l#$#:՛^HWZ𷇦ԵH"Y@s=|evr}#9 I< 7 Ґ-,T ޹&\+@l zUQĖ L+=&f1RĒҳ;֥hIFw+[s) 1HqKl K OS݃zgntcMdpq[d_W+)XWPIP S&#}Hb)w).U%[bSj\fS=a 1uVίDM2w'f\3*=hؿk'`u8m8^Gfx07, |qp根O,|\ONt#bqk$BrQVxNtA%&H# Z"%`В?Z_ *(bsP}:h{z۸[ֳnƖD9-~\%ڮZRA4!b\NޒF吶wnRW'o-qĮYI&$;ԚI@FۂOZQġ ZN:ZXdfmqcx}18TpAzI[69Us1I ^:j((8.cq/ yec\5ޓ\kKIBF\ -K-l'wf` q[ԫN4<㡱opW;Im̈́2H\eط_S\wR!B=AJ$kR5bW/-_a;F4EtG {'U)jilԶD75=rOzkѱEB}=6Q֌:]RryMk$*L | sғrl˛lɐ:T݅F8#Ef$T,&<:mfs{/CxJmZK;\*H+o$u5"~Aҫ-\x`S^G50=sRP7ۭSi9y;Dմ=(-Ҹ\`Zo cj6)J7%֣ 9f~a 1DV`xKgP6sլ]Ny EO{$.c:cSDA6*˴u龌#QuFkœ4\TgSFwQIv^~@v_c.(D^#qң*L0:dPdRjL8@0;\&[,a9,+;|k5 p'X[)fi0mGegs2&Aiç*DHEvZB=sY:G!Hw @H43d21z a4]"HOj3K$c5a @[zQm%rkHԛD(2w\SrZVEAޮvh[psg47;òj\qFzd=o^PiV>y4k` G|hvi~-˨Z Kar8=wq. ad b(XalZ m~_@\j=Y6jap%ҋӜ)(٬鮮ްAc8ǡVڔRJ6{B)hpSݶ /:R˨ƜGR1OuHOpiwI6r\cjcvɞmF_:κ;rsܚ}9h`*+u(/ʍ*g%szvϭvVMZ0q+9")=z<5MF$y505i cX2wȥ { % p0 H#y#algԡ#$չht9$j6%\Fx+W MT䝮qN~hJ`ucVuQU] )Rp}Fv)+~L4ɫqSңf F\j=f`$O:gְ,Ks#W&ۜY&4$NX\<&TRK,qF1Ru:̮$q@qc_[EW{c?7{o$ײlǥ]R:T1Ky洡&KS< -Jv cr{Ggܢ ˖Osd!k$7 d܄>m[HCsTM,Q9#R N VNPp E^UPϟAֲ@H7ZeoHPwpj]-Dݓe;yfn#vX뻡 B}^6afӍ.jC+gJT,W隭mew6Jh4RmC*][)NT0Z]ЭXУV -$ee"o'{xpS!m܌e8ڇ on5ao٣ǔ8۶SNewO4RfXrs< 9$r㜞i'5&vM6X8AHX ;QTBzuD`wry9)nʕXFK *s$jổ^y\K3#Z*H aÕH9ni"5jm@ΊðrKa)NBIIy_JX44ܢVgZuIlҢ6[wU]5B-k1G¨Č98Wk]zUk4B}IH4ōPKc90wyz?5sjΧЄUw=Z*;|a<4itPvN0E^=i`V]fZ3jH\62A暺 #< kp~:c?$2?e^Dk9cV5F\.&]c7w$Bgf&F^mskKǨN^bE : n5 *6l~jZjkwS¨!sYqEGx{Gkn`5\CxU6 J\$ZZ0^aNrũK&xG2~z^CjH@ǩs>{[ZxgTXQ1"dά5ҋvGkXCuqhN0$Wqfm6ؤvF!Q1^Z4*@X#v^e |X\<|[4zcZ| d X:Wx!2ċm3 \u0iI#1ڵVeԪR-x3.}okf(x G\ JRHJ趺- Q%2kˮ|=ZYyR냃^uh*r8;t:1u q(\*dr^?9ק&Y]E VჍyW궲iUo'W|3-;Dž`د*XzM]w=89hm{/ovCI^suZX<<ǭGS/5D88.^ƕ]їxL.>ͩ@`?Xg<'wl]e۟,7_jkk5FPG96dAA5F|*^wuera?!KA0F2zUVH'vqzLhpsEWAI'9qEKt]#סsᮛ[ %<_Z b$یnKRX@}* Vga"}/ Z/0 ?;va9Ti:gLiժZihΈ5s^}RcO]}mY )Y}J%&sgj;pNkRĂNdMȄ85608 yEhVQ=E$w"RH}vNaK `bj_;("[y񕤛˄J=)= ͔2ϲ nE1S aZP~o`|7JfRsFy'FxVyyf.p1_61xw>_B v\^0}E~{~Ӿ4|k\f2V?GiZեHw=+|BL=ڧRMkTii26SpŒV=ےw>݉mh`]ʀq5`b{lKi ]z]fy # FZ 48RkO\Lproj]nKGZ#Ȕ,kz/2n@ Vr+gB[ǰe v(hxR.A ЂybkXa=8❫10(^$ӖvGmNzz% >h GJC䩦Z)mTd =ZrtD ӜʪpH˞Ժ8S}Hw]Ȋ͓sXVg0~=+oM`!irdEњ;-%d PFHixFcx?5$:?w~ - L~j%b3[¿"!xZuAjBձֽMP NC>J"X}V%&#T祛 BҺG(w$q@]Ðz eGk[K$i\cmĞ+.0V.pLc))Iؒ;&Ax ֶ=pT탌Z|A'3a=,\w?imG0!W4p$kR-F ?ơD4S;RA}Vf^A]">IX*eXnImIrZ.`0lHy"GރcK&?t8^yq!81UB$##<Ack܋Ral!<QE o68I,Mǚ7kekX]1?/֯Z?Ȥh-we IեM`0wߠ.P5gsuR_j]}6Pzdl&f EKr|+O%J]&QXn5hrR~SěL_aQSXs8S"׏ c/s\C]e3sWf<8PzM)4=j ~{b,NЖ KpRgn{3_I>teFQGc*"<\L>"yojSҢtuƘ%}MC% 1'5ɼ&2ߝRɳŽpOz8JB@ ֩Vvk,j=v r,尻Zзvq()P0p"Ƨq8%GsŎ=+)Ai؊}}͟n^-3;e9dԾ`:VqQ[i3Nb/ʽzՄI\NGn+H>bwl2+VkKJG)o:CҬE3]kOs{Sޗv؄dzҢS۾zӎ,X)Jo=iG2luec(m\t5b EDpxG;LW\K"g-6;u||VJ11l3[ihs;mb޿/n6Uڳ46Vr>Fbk׈FR@y K&'-&{IʏsA%yycsRf-15' +;I]̱k1S2紜=V* [)k@c2\`hebB)v<Ց8~MhliܐAүq~6aC2J/cVuviHǘ&Ctդ'Z,Ùf<9#bKe\ApOԖSܜLa^Q3Zic!RzK-Cwfq8@sյwaN*FXÅEݟ=NyI?ZT#KSVW{f<~_ʻ3LT:S$*98I K6&c9pV}i7qTA|WovGvY|I& j8vQ_Ol)?+)a;dLmj̧)b-Cķ>Wl$ݎ 5v] 6r1>jNF=5BocK$FHipBLSL#[Uy7|C銗$eSMu$EWk=tc3[p+40 £p=Y.Y5OQ׭BL죚[= ^KdqQm;ɣXßkr޳Q~#]Z oOZ T߻+{$ќ*;-N~b3r -;Ψ<7|Vݎl$Y'Rr`5IyQ.3lr1 Y#@n1(\"8sC3(Triz% v "̮u=3]mEܮ: "@sRP֦1dL+fi \[i{Jet`4T6Xf'Baٗm_L7K"¾sK&8l@NrX$ ۥ0Z,UrM85 += +=6(e95,Ѫ(0uhb3UUd2H隨E%tdd21)'zVޙ'J0[ʇgk[N@<]Ԥ4fNpASZpč;#RN85jZ}+ FvK^TQYΊ 6Xe l>JU݌pejSO6RԻoֲD*%7lVbW]LF8چCgFe˷mEP9b0hgmѭ*Yne'z{j=0VRFd}-&̹eɮr&c}KUȞuvQ{i! 229ǥY$` n$ݫkMw?*UȒz|& H qUZAY}(@cOVŻ=Eֳ Sзi_y*<&Zk8Qn\imM4Ws^Ka+@æk"XHTnGs(*]"vV uz%͸oּ2 x{rYwRA겾#7\GUJXAfPz ẞ 殽Q ҵ:R\:IJcV+ؼvr~cTltAwjIx4% fwSswH𖔘%֍b|xKh1We2%JVgf$) }jwQOnqjv3!׭>KWsܴtbY#VO%Fk[Hȭո_Tld<'4}DCJe{8d}i?ș4s+825VMԥ%јRiPbuH$cca1j"ԓ1<%2H~ҫ_wXFmb{|[`I+'Xh=k>TU%ς>y_c+-$U#\[Hzd̷2POsj[Fuy=(Vnvʽᗅ%a=DnzU7>mk"bulc<$*lB;WxcGg7`; ߆}Q*<# p^u: pqɮn危!Hv*57coi4ggelw,3ɩ֬O>pFs?=d׊8Nxh7'+u P5BI<|ϚrimnDİNIºJ^&îXEBH;rݼ2##lRD˻u>hSc/ncPjO4rכOuGs+ծ#t<'"NŬ!h8g pjp*݊i6g9k7fi Bza)֨#Eۿ6AWT/z"1h<s2$2XӭZ˒AFӿOO52۳ֻ46?JְF g5'+3r aFN)r^ZX|mHdsOWr0PnjKs+\[H{)z˟T֎DU'tiWG1}2A=+3` [F[):ٸ=}kSlu&c~$n@`N9 ζol<})d#]"ΊF+I,A-#`//)d̗`#ԓ^SHQJVm+ 1SJfmI{\]S[A,@r:L`u&qԄy͋ma<T.#!3QXM?U^Mre/-61SԩR掇 ⇂ ̏Š/LT72C6TŜ1}O;KH]jb!IzR(hSOozt--E5~!Lc}PC\R̐#*!޳~/k6^g@a ΤΆNumS0K4ďSqWet%a’jlj:wIbk `V5:4JFQ$RꭊFʓCՖ\׈p͸kH!'#'},z4 ^M1$``*ƣ 6~NT9n+ӪGɭ.0 ;1wxe;1UJWGZ~o> ş>i}ꖳ}Jt]24R3,z }+RC+ZWKh^i%v]pY=uG<6zc=Iun ǏZᰊvۖ)F(s845U5{~&Ҍ])$i..%pv$n/;2x,u1 \Fܩ-g\qQr&fcHun3L6WbV۟J_ xJieV'qoRM-7=STT0Wl]+NOc@eq_Kˍ޼>t'*b&gxy_vAp$XX{ %DҦyTEfbq0\~4]g;NI# ݤKXJH 4spd9B9M[d[g$Zpk妑]u5+CJ2͌V ciQʞ!seFyL^v8V+[pii]GZJtϾi#nr2@Cڻ4* џAV sMFFA=);^ȋ/P\8n)IRsޢAݜⒸ&@ +Wni/ӵ;w.zqP٦HnKI5I] oyss,]NJO7%i+D[j1.vF*JҒO 4mh,LHҕ\g.3ȤtSwiXq͞xV!t?}" d9'',w*ǹp#9ȩYQcPzd2=jb6ʲJB})8tK(Hƭ ܺY9BwTC`j(@[+^d1(\1;+mU``ޛdv)u7JȪp{ vH-P]VH@{LGQ#S=k:)CNt:RIolŗk*,VS6c'^qmn"w0c1}NA=OSP< {TJ|HF#Ͼ I/G bE6۴g=;UtYnsˮZ+yE6''Iw.Rv(\bB*;HRy9 kIfRԗZ2#8M]9E+#4LFgFw/- otHpc\ԍ$̫V>E.[;fۭRl4BN{QpTjnQ3? ׺& ~o! {U; /?JodH@\ KzHϷjzۦV ڠ8J}/ Tjkl,zToM mB#=#i>F L˶7*]#j609"FW75vЙY+gtE!bѮ '?n72ΧZ^,zJodyO\`UƩpѢctmRzWLmm]I0o'>N\TerNӊWwc3U ƔG/lcKiJ;UFm,#W,n İnd֖EM;0cz TEHGjat᳎=+Fkܖ.2s͝4d9-|eMdqL=+糴p;\C-pٛ2<\93ީavT@&w|Z@ZFhT*ZX 1=*;7)8ev7>dIsaCt5W6-KF:gҼͪIgX7;+cyahskֿSEnOMkHt8r3W:vG ucWP'LQ{Woo@k6-$e X鱷u ǧj:ڴίc2e;eѤSӔV uc$p WϿ4l|A)HW펕)SA=:2䕎Oh*" Ux8D@?.A׳(FBͷ_^AIOމZd=MNۂc*Fdc'$+Or1*jwIV:a8֡$ ڬnb8iɜ )rxPUWn/r)#OV?Z7ԨDZS>HHkfu G25XzUW"m,b~~!|^9g7S5kt%bm)<V Hu⠺Q|nZ!5-Q'Ȫ1Ԛ|o8=Ҳ.b3U \cPV>g#XJl')`1cRZgeU%lcZ_:`Bc+7Y="+^L-4JmTmYxn3wɫ2\dhOlՋI.[%85lkua^9of-lCgܬB{⫴l^r݂ḦT0MsM앝Z\n$|p}[ŝAYpjo֟l [d g15D&Dt=Mh'vc)GTvܳ5<#fߕ TЗ2)Z6zNqqlon1V#3J2^5C_c@$RHqV#՟&/z k/$#cq &U%L]@:wB5b(nW'[ r:S3*7v=F3S _ciA/Gqk4خ7U<ۄqNȭj՘,.d$d5S(^ :t4tnHHb\*+yZ5V{G]*1Iȃ+M2im81`v 6e,sPXNd ]ʫfNZ$BM/zM:K$pyxt>O"چszƇu,z&ۮP}֊FլKF+[hћUk+dt:1P |4 Z-JisZGFRNhONKEnY=jh&fK-= ] n@%4w_3 "Bݲjfȳێibd,$+W^f1 jaR7O8,r3~!l 3I0%i#{R G(kV)GjFz ܿk&J q#&%gmȇ2*[F **ӵ~Ż=>[QܰjA#f([^!=cOřґfQiEumjzF+2O,z8oUcT2È".+>E.4@3 p2h`'ig~-C&r$Oc]($k"Fp9'qqf\;Dҵ#'9CnF#DyJM$"/ri~3jNWSnneGrh򬣌{mkhTI z VxrJξ՛m:x IW;4|F Ϸ$V#R7[$uݑ[[DvpBU6)pt5 'aj[M1RlI5%ݤ`u`s,#IZjP4tkKu-ocMjFhqx%vTm +m ķlڵqyb1n pOG8=8vՌN+4rHG\FɳcKޛqݛm}öqkeA[:Wzu>LO\(niD٭^)PaQ;?Zɞx<2H Z9t@ҁ_\̾# q%^SVmˠ,h,F8͑7S״$1<,ݥ 9T@=jԚBty!1 tȪ{u3FМ.*GBJg~op ~kOဖ^ۜg5cͷtv286;[[x#@3KuAS)+j4_= )zBy&QzWQi̲B2IYUgD\hTDK;`^ióWΞpDž+υRHY8iSVR=tVH,cc?McO-lЎWѺn$w5H䑚J,x؊գ/gZħ\l/0]Yl>nM*[2fgv-jhhEi^y*=zycejK078Mz5{KuUakn`f3DA 8_ fܓ^NeZ~քrL$hoeq߭*mk񞪲Al{NH*d$1k;Vƍ8$\TiOZo"qsJjYf-{rdWO[Ns @k'X5YRHX={dWsc$u`9yyduQi#I㜩*mfu,3+r&W$sƩ~jR3e(Lq?2+*oV*O]X NnsSC:FD&!ȣnҖS9ԊqP<ǚų]bV<.G5I\4rF}HU;Œe BdB5=],<q3&ZI8n\Zgn7' Obv*2H?ָ7PW̒&G$R0B5]*#dѕZ4")I#'í> .YC-+@9n@"A8,OZ F8'o)'d77N0`y&$ u3\–-7zq(-8+Gy#7њQ! socӻr4oZn6,~ns[Mpn'\s(JE:.Pao1n4g8+kYRqT6lO'(pqZ8RctZh,Sz&!tnK[<1 nDE2fWpwl }jp0Wݹs×Q#VFG;bpOTO˓k7PMRAMqƤܪFƝX7Er{f.C".q`U c8ϭc/’qY^Ozʼ9c} E3F"c^j\p3rs=+8.U̶T-^s )\1"9+g=+cX3JIEgOv$m#!xY$(ܩ851(`t+Mz #F.S)B+<ˍتڻzkZkccKfuYyNmnA&wCD0o9Y8z-ƭF3DH$JS!ù| Z:/ԭ]T F;kE!*+;0EڮV87O738՛UY|ƙ9`=}kRؙn&Aa%\8 =QE+:lmX;^|o6MĠ3^[ep#?/z$Heݎ vR|*5;NGl䊥 %\T,_ 7ͅgR:5KCgηHcoZy$7[ZiPM4NYI+HFMRv v5`H /&ZܼʠsAMsX7{y(v6썈$Jmaaf\5x cVwL?zFRFMԹXW;" k>mͰ[qu&C6z̼YqE9#mY %x}T 6xw$>fɜYDvoy ޡrGtV(#M`U K+HPG5a(Fsxrԝ{/0\LYGz|Gf2D(`^3*aSUX5+rdp8rz1M6܃"#$jx#]'ea0'+p/wis$&\5"9,3tcTq7M#ݐsE@15#È=|@͊ЂM`ng#aauHCgqU8r@Y+3gkhWCA"-QS nVڛ{- ̝2j&2Vԧ$SI$ujqD9$$DA#8v&oZ]pj8y`r}"e'"'d>a38隩x7~sFO5R$ jjNm4iq-S0i>֔UC5^q<6F4 SGE8Ř9F &협 c4%Zv.ӈ`ߝhWSIp6@0>>n_+T=iXE@wd} TU"7zU$HҌVU妆5,25QIP>fT-v@H#\^KO$<ҟlrZB]N—wыQ% jޥqPQf=k6BQd1$s1'ձcUfDv*䒳[? @q(o:^qP%qQܹ},1PUY#=)IZ{-@F\u~%|F6r܌ l5;X5d)py }kwyr?rpMP%~fndMi︧G]WVMFKh1qּjt!< {XmrzWr#8'\#h琢^xXhZ7fȤ!i*2*퓄ryk)mٓ \u1Im =f]du^IʤA|(cWuG|VQ䜰cSBO,á jڱS}GD 7QZ10PfQyϭY T##7V;}[Zb1ϧ1X4EC1XkF]obt5+ۛ8{JO%B ZB i -I!u'F6ت<"3B1LaW${Ռ@֢Ҥӹ esYFQm$Tp#=TtE19ǥM*xd[TM-r#(ֲ,4sn+r\P)pG g]v0k0Qɧk060t&8b)9^GgJtUe%A[ $tew2I-Iwj̦g6OLTʌz։B)bFHV3(+IQdq?>ڪx< X[,NN1KCF&bxɫ3ȢR$Ul rXT&kÝ۹٢6گHҴؒ/j b eޣ=:s*@aEy8DCBGg]DDp ]AceT6i>ldsH%XT+̩^5W)NK9݈xcZ_mܱ'n9_r$W@jv/ r3:al5VQeOֽNMi- E)XzfR-;Pe^9W%wάvk8֍wؚ^[Wu=Enn-E\rq^iIʶ{0B|6}Cp̼otWdeRpPqֽw6qԢyŮskr yR0kn="XC&gmxhPg-V,(zkb-mN5I[fi3>vSn+E2d$JKZ(+\;g\Iq#ŁG)`z[ry7ydqڣbZFz鷻3Ŕrz-m䅍8=yL,:Tv`j{.DaLJ9"00z Vu+r =ǥO*A=¬W_ñFwrL Px&4DRq:jZs=I5őаҭ@i:+"#vkp( v汹+Q޻+x1U/(xވT_dqQh~yv >1?kUsH"`3q5RubTEPnGQ֛YؐX֜i;B/ՍI @~m=KH m^.2Á5qfmn ?<ÔnFjYw'ّ6 0)x>.X Z^ѻ-X0fFMδ gU,ğWGi$1ڪȐ\^RsUt)n^R-M3ޙ$#Af?m= [y<1!]+p8wc՝w1GSx2fc䕘UmtvC#e8P*;8mM4O^*q "^2jlr#Hi)D%;(cOfIy kXCեg]A#2՚c0f PU3<=XatG&GFr25?J@XC4쉒\h Ƙ[!@#(\di1D q9I1_q`UƜBvHa[LIvFMt] Iֹ;mh/6;g[vrƫIТ֩)noXF@M^;h#{e zűl1 V}X뫻\"Rs{WF-TjZ5TdTQ 73u+ϕ#oLҠ{LWꯠ6 ㊃Om" F3M.]ךŝ2i~#؅"7\wC}6qL`=BiF|sT*}[5sWGxZT{pT~;(1+XZyi_5t-+:[!8#9'XXoݷsZ6N"tbMnTam绖b#'5l&1ojv.]rk~n܀G$umҺ9Bļ<)se2IX u5>b|kk-"A3T7v9g19Vw yqGحѷst]3 \NpiC>VPD`btYKs #ly[ v݈Ēsְu'3ZO*2C \\>񅭝N8.g-s,j;%&tE*qrֆFew?"/)l(s ֦6;*'bpկ7B9D[ 8rY VH z#S"ݺU:TLGaNEuGC 8(7i3 SI Y€G^S3 9'g987 dE3*:Ec6d GaPˬp`hm4ޫCV;o.S'ظ]cOTn;:n Vp[F2ˉ#`#elIUc g.+浧_dXhkyR2ʋ4LABX_UmLHy]Aufr 'BMjD>)bF3]?W2Xj4<ۣghNc$5C\\u^o)! v݌&E (^a4mS[E=qVZ#5K|Ǘ=_ :,X&ksF .'R9p Ax_&($b:zV޶B3.xW;7?8"wRwfγ\\$C ȪC]Hë3rIxA@4H!V;hKZ%8 u ?&:q\%v~Tni4$֌VW,][WU`Zt.krzf7B%SqzGGJlʝ<ҹ`IqTΧtёs=+NpȆ+8Xp.Tj뮧gmbE=|ɘ/15 ɏ 7+ rˎE=t˦]V4n.SxUbU 9Rk%QdzzƳцEfvWg=!ȃq*QSa?C;g BSx3^`mQ vFû3cM5kT4W]SѶ=m8\g |i$V+%Bdp+gmwH(%JקMPQԣUǘXrM}$cUjY˗Pq&ps^ie/.n%=l}J4'$U rkIRe%}'Nt9ʎOYח&eoEdV?+užI z»68y qknoE];>o R';_+SZմ_rZ Xڧ3UKI oN}LAqsx g^v"Wi'fUUI;mm ,HLl ^k̵Oj0jf;X؀z|g&ѭyFTpY%IqMIN%GYV҅y&k|#m?irdbu_)%,+]->ABz{ JoJϾDR6{(q8)lWˎgNϡT+9EPVSf\Bóbu!+35tnt[3щ<ǜR!%eth9?{_B sM+{ifRxx3D\]]&i10B3 Ig{ Wd'NU;_eU+Xm۳G'OZ1.c-H'uzW#UBJ69 lt(GMjj,!v+K@Z, w[i&`< kf,2ڔYh)Fɞp3zMVVim@bqW}9Tb\Ry~I&x(TC]x̎޻-A%%'ҼRK9-#q[V:jFp8fӊ5ۋv1\!ܮIc׌($OAZ~#I(sҰ+qznJ3"7;aeuh\˵aZӻZjVzלvkm(a8渝CPMbq5X]<| N9ɦֆʞOPqs ԖiKvj-bl Q#k;#jd)p'73nG#\)#f,DZ8x}]k"Eq"uw 8bkuh%ykS'k,TA.ެNx"Iad8cXZ&y-dPuT滩 \Z\X]n`G֧G4yT"tZtm[@#}Ey=y!^KK[[ 2wȯ0I{U*\շ!J;dh`rHէ{Iʪ QO[U8ϠW qӋ:ԍ[;{i>cc#xXd(bψrgvx-,4T12tRTYZg1>YI#~]JRzw6 #ְ$:a8^XN.H̕DEIV֯4;`bxWC 1^:]䕌ɥdM ]J8K$ےV=Jp[oUŇˈkV(E_= 9'V[Y帔c5SDKj2ۈ_JBkިeȥ+n_$l)z̿Y @=cZM'{Vk|<+}AB+X E*H#ޥYک#`w]^łBU[*8%}~IjFqpvЂ:㡩9@c#'5L!, c'랕4l>@;,'K<6vެx:U"0< cz`1ϥAx03RFoH$/PS10O#֞I19ughlpx!X# Inu{Tm<ޕ1ƣ3JbU G=_Yw#1,梻pr@轨S"՚8Tlm㩬y,wpbcil|zsDao1+w*Hm8ǥBK13׃ӻh\ (J=IuDϘM:ov*@e@ܜ14 TVM+Y[Bkrae@p ɭ2Z+O=2yzf,:p -tAȄw9Rd}8jG{˸lx4<튧@6I*;I+\uєlLGVYt9%K"j#Nh =R cS pz`F8j͘ T6448bp$c|\<+87Ϗ/=l( &ֱ7KC2ܜ ,qIɭ+'{+V%Ame1e%Y7ҝc=58g؊mzw!DgINV0KGj+ԐA*Mb3Lv >SVԾit;o買9f$^^.-4Fw78'$0=)3Eo$#qps\xunv0&WkrMzM;?*9{ޚ-_cS{:؂w6cdO'YvvM՗,ogE8*F5c:KSlN򲾇hGx/X\.萟7Z˝;2ys)H,תxJ~rǚ66!.'Sk9EE&ufr('Y4K|+lHYjq@0p Xk8̊zTFʄw'Z#-;!*)VGqRM|"z=y٭ NJr{O'k+#|xQA a5#P*GQeDF̭BX59nM92KcQH[eqYv#Zm$Vw{* ӎؤ;FJ$*6@T 2J28*M fΊ7P@:KBK~5\jNS~ebtƗvḙjp, eEvZ/Ų('Lxs쫓i;nYVKsyS\b͝uhU#nd\Fr^%ϽjΡ8oZH_ 4rcSEYfNzAm%ܨ=kFQLeʴ44iB K\id!J~@mV<[|DYQkҕаj8}H\dq%c(5ZȔ@*֬"X+H=$m\ƹ[$mRJ^sd-ޛmY6sJnc)Ù(D&O~E9n+XѤYN+Od^Gt8ǎqQv+vSJDY1-lr:sK꽵5Ż[ۉ[;cjp@ PqT(dVZv&p*tY @}r3ri(cS﵌@MnSmcS:zT>" ǵVҮDI*OL6qY?Ң5((?zF燮_&KUzVzqO3(NOYk3*vLV  8+m@9Uz[6rR}$hot8'Uf'_+CZ}1 1KaHpHs\4*, @ zd 5,zDQ0I<#Rn:h8M4bBzsK#m Ϟ>X'Y1L$x[gvM^8adnlu?Qկf/EtLH+ֳH–d^8vɽn>mj'vmD9pG\T..f\p)8ܷRV״ԍedu-K9&Xr a6ZB{f("FRks4QW77gLHiY?j7+ :,iOښ?7-}3闭A#p-V[ FCt ;5*^K^1# CoyۮbPw6⣶u\CA:cP'4A#}VI"!'gCj o&;ˋHU;uq۲|waP> Hva9rԛ7dB;j|dڄ&:]jIe{`f`H~ lgv+˕I*C̳\KLGo,hX0E~Ke㑚V+CUqԷZeqq!3ƺk,1ǁZjcktݹ Yri ng`GVrǎĵz 򂇰\ΫKpUyRzJNV<#iNÜv27NqLմ[(#/'ɮom>E/esIt7 QlVwVK¶\w7K(Dj)E8߉]$" `uDڝQ\_*1#7VKEt<nj}aH ^ӯiQ -N[ɤL%&H=kzxJOcxy2zfqǭvvLJ:qNҴ-,އvE[c\>b ڱM$QjڼXF13.g֬^Y٪D;tKuē9~N ko~#Ƒzcy oR4uF}>l>-ַّ yW1[V=5F"3Uh&#֩K@o4PZ{gEF5ϴ;5HnM1͉e6?/}f_E1wgV!rιdgB*.e8y*Hz(=4k@*̈93_Q[_$տhz^ky`[FTq=>lNx%ϋ!M?d3;ku3x?~dCׂX8ytrc'wHUjQO1:ԵZ|H!.7O1L's"/C3. 1eJ] 摳=Pdy75.KT+3[mp$W?܂k]F6/[Ɲ⅚0.R6=j:tF^Siz4yV:K)%{N;g;/m\"ZA53Cs6_iJFfRn2ќȸ%$ N=pksA/bѻ9 ֻ]SÖCX#B%EEo jVwMlwF֮=Az|?!n `Ҽur$uuKrd< Tezo4W(\xȁI#qu~Tgl^}E_}suq7亓XTG5xR;<10Ggnf5G)Un9sTg,jo& I]4)^\nǚ[,:?Jw8NC,N{9$=iv, +Ia%ﲣ_Ȳ [zu(l{U XI:b("=j-fuؒ+)vGuz7-(hSb*У#qԊ 6ot-h;dKm"; E46}JTwCD5k=EwY؍3Xi*Vg/гtiick+Qas S#V zU4@Xlߚӯћ6tJAKU\dSh:qWKy3e@Mq #OukB$jl[[=6^sn'I8dj> ˑbWkИz^xezסi# 6& 3Y #ة_| ַ|I(7QF954Zvl5[Ga_~ ݠ_:

R'iX_񦩤jV"h7!'_uƑZ>ѕ0ʸ)ԍ[I9US+)aSW&,^6:8]-IVL0@$T5^* +iE&2k"8c11WԵ7Ui鵸,ėrٕ1O Uo<z5gL­:kZv<}.DJdv;XgHl7\90%(P[:&VD8i^["5P 6lcv'{+i1ԁ nF .rco*u A8TQm;LB8Lwp+64?*HBzW( '"v ,niٖPL65:ךxF8[~+J5rGA[GeV>aּs欱189 jcK^IljzjA_^Yn|;P2zUje_,ֽ+eKvoެ5y}/g50JnqdaMVlaH-/A^\*9NhTޛdc8mkD|c5y(T iWT:Onc!kJ5[?$x؀cbM)ٷ8"z +9'JU8S4N@E?`NܦʒpNjjWZlzf5i"$rvK1J#$894.΀riv1 qۊɻ=Je ɜwHW=:fmmCX4# ZJYG=j$DەjDًM22.~lG_,pj2w #(z-٧ 9:t"4 ӕ*nEaM`͐x訕I9FHs}K ЎS<Fy/&w3UMDd N1i%}ȖiwA52.bP Jl+C5Kyc2#= Bw5?JNsvpyϵ+z GZIN"# 24pRV;@I?,rMOrG5֤d*z:y¨ 3vSԚ81rcdZE#8!!`qR1sQ2ffJ[ w7'S-€H>g,w\qU7sL Wf;j>`Z\{@eRbf J TZ.YmRX`UJ%ٌu#G`Sa\O'q Wur#n[{H|G* >%J.e1-)>`c0r+:]6SkċnJa'ҼZE2mMg(G QĞ |8na1Prq1bF3zBD*7nqI;1!It*y2jLp is&BiQI-h*c5+ 7OJ|ؓz=[Jbi"\jRϼ$E{pLIráklܸ=Gt}嵵.jĂN:)@w#aF5n̓ <{$M[f ;hX<GNj7 ʥF`㨪tdriXmY )P eiUW,B MiĻ %3Z~`܃U2O8NK9P{E69Y#J#N0q\pƫ\ X@uX]\Bji̙6џt vR0cKE,K#Ȁ; ӳ`pA}kvլ.YhRx73\m в9\W]lV[)%:!T BFQzm嘲H7` u4qܞp;ֿV=X6VխKq>S+X=pAUcnrSK JL^)PY>r\\\HGu#mTw-f5{Q{Y4%}%.7}驦K!qڱ**UTk_ٙ0%'=M2)rG2EF@&ⱄ6g*8/"#OAbZZC؉u`OOjUzJ*^% $ϒ0pjWn}k5*{K XcҰۉӇoMx<ȅ֍Vj5ov ⼖u:Dly@Wt8 @p$pqҧinwA\0*3zq=sϳiv9QТ=N޵2e,::ܚ|%Z{Fʍ܆{vz#PXmj:X=kOMfxVuQ; %ڭ-ñӿjȻԣ퇜ƥw*r*&gE$Ƅ$#RPEԭ%_=?!Q#Ī~ccFYSMA(]';Bv/;өEzبWR`U|K}VզE漯[."xG8=Etӣ jOYRҳ Ap9W[)ʳkRPn瞽EEwkm]r;=V&2CqU/ʓ&;jimy$'ǝ=v2V1$C1$zb4I,:GIf<ޔ 'vOJ.F>%mUq56.{W5-}kCLP|GPY-b%mP"P}?%dpj%fiFwc|SGlp@[z]^TܿC[ZI,¯.`1yNG5qzTq.%xVvּV;ɩTPg֬Sڬ vYhtMʪ\95̩4xqMkS)% #Ўkz]n&STU 8]hTj UUdns%=0qXwr$YW0O~GLӖ!F(Q ;U~Tg u eH@['<,笍bc4xZ.\q]0yg{@{RUbxu!շЭ2JQȱ֋XRM-0z֭1UhR#Ƚ"JNߗ5>sY3\$kc޳5aؑ"K֧JD+̀Ҩjzy.}{ bFsu< QG3]KLӿ^{'ݠݘYYy*"oZOK<1H,z#ZſqijX% ~)8dC3VC.V&#[sJdP ;֍~k+ X x普8B)~y+(ba]\<̛G\^Hv'#i"ՈPA޼J 2Qo5dkZ60nZޝ*єBz^I~b'5;]f0g@>fJk"ԑ@VKY;%id'tF eJfηyˈ+Mxjl{(*7)i4| ]^V9vDG'lSi?"ikN;O&,;`}jdTO*}roX(2\3*z48 w8Fؤ*FkRJ 8*7>}ɉ]j)4+x%FzV y\o[³)Z7ͫ99VBh!H;MZTWtn6q93JT3Hi$ rЎ$8+e\*8Cp[uK6^0dF#J3G9,y{1[Ly_6б[O H8hEi7+hb Fwtת  t[UޮTs}(UF9az/NYDdQnqMޥgt n+Go*dI4-`$>:UHodAɬ 9fv ѥHkN1\^I-wts>֬я9Aσ`> | ,M)PP@J\)¢QW6Ÿf92 lr9z<|[umXOv+ h՝=lB-ޕy$3L`)eVR5iZR,W&CmR-U3ɬx R8aKt? sHKۜ\Uq۔-;(&bkjɒ;2{Pf\gSHvFNmwsU/:LzcFfX_k."XJR58kFm]薦O_Ƴ.[T$. ==ξY\M[\vsO]WWVeC(ɩah3@g]=֭Vm$4E6vJ҈tӊǾ1#Kn dwRZy1 ׊ba& r8$܁##+s cT,8Ra+{#AO1ZEi23bݕUԭs[ͷk_U/ nXƪɥT{T6ۨ،xYn]'$OW(<z>^@28adzfڙ~],p"B8ҶtXqwB8Q~\\|Ao SYѢNmŐ7Ec,9mSVGeK/@A5~8u'C,T &v ZNRL6SԵ^H?5w&w) =r?V nnHc$hpK+EBK98zddUfԓMzO|mz\/$V'7{ "JN1cpu^v(WHx{dפZmJJ6.){*ii| [CtzvIt,xag܌|m.%i1=kӅJn)|RE9'yƟ"$=8Z77@XYڷn" *rrsQiSiZ2 z)2yV-66Sck#*%sG݋1Ռf^1ֺ}/]?A!V5,q|;HU11h>XY:;%]G"{6r2o)>V$@]}˜S}dg:N)K#+b}kJc6' :Tfuچ8@LkFqʐܠXh5Ž7\!3*-| , (5-;7B!U(̦f֕3ĖP0ZBrE`%Ƴ!#?;U멥+Ia^S uD3+ICP tZf_1Z0?G魎6Wwc98Vn%N><}.qr"_ƥM"y$+Ԭָ7R eLpue4an_|Ĵnde$[bsY/.p[HO8MZC=zս.r/thCIp1Bp'rUYXAmmoR*7e#ҵ4}1&&fVHd `⫖ 2(&AmiPǪb뚜.-d[kk&h0x-Jg^Kn@F)xj OA4;(Ƭ:#v qɨ[4fnrFib`Ij1_#m0ȧӆ/b~IY&j䰝&o$&2+˽R !jݪ+ʰBkf lI56 e*zJMMd|](x21dw9 "91[ƌRfr\چeu0؜Cq ^F9ҐZ]@dySzJQ3[*:ꌥ^OH; 7f0glA*Hn.{̦0d|Ň&jܗ9?`rK(͉㗠޿ʭFt T[65zJr,$F21\xF~POU"!UFz۳B':^$>swfM20wpH<([#ԚkO%X.A>xE81jTVQ<}r$6X{+㍈8Z c&u\S )(ů ]v;r舧%4Ԟzu08/& Yf25YgL0yS<.ǞFj"5aɥi%s1P0KdTALw,:HHg9Ob6c&GEIh rH{Ѓ$w $LTWSb$_(?NUV9^iZ4뚩|oZ[K/d6c@a e tis>+ciȐ)&s%}X]ۊ㓊U.94c3:jHg,2=*mr$wA9q=2 S.2p?,I1kHRed]b\}f T|9`WR,2s⛉gr6BqV ţrqe+`Ww%=i&+Kqي 1ʒs֢qJ OJm -А8GxnPH#h#N̘ݒ:Rm,}IbX,ZrX"~dnk&ye GZJrU$SA$\`jnQҤShp!LrIKnA#6A X sVXH˟JrfFܒ %m bǒL2fP09ͧhX{V#wQ6p#7%>#rǭN`wcZ-L-p.s3]<_t> tQ֮&:(VusNmZSfwI)Unjm.ӆ7Ŕ7 MMg97VY!a=N`Hb?*m T`Mj([0al<7"5YfXnN^ e;tU9=)bB{Tv)[yUʄg+Kpr{tVy%yR}(uOG+A$gzu$+1kl4d3%y+{[ 0r;ՔS]]xlhf&@k*J w>=m,Fʾf{d<3֪r܉E;$z #WWɪYJ2[3'.é䞜U\p=9`SZYA v{+/Lc׺|'\n$"862)rW~ ѮBҹ1kKnuB/=~זWPʟ#w*Cv|Ftմ_JvhcJ|i+ |J3喌kix+.<g{_yҬhV`q޲y˽Gz2RQ{]na LI!0zײ:Uoζ糈0ČO`*[M01WNV2濸A2 \qՊzn?G^Oz,[T$\j[PZQ<0 lt9$)WG(caT:[%HNN]1=Un`(_jjڐIKj+K{7\ 5YjeO'jvH>:%+>5à ozcJצ+)3S쭋4\]4:vESךgppU|{ZMac:՛+!IjX$֤{d.)bhmH/;@YCjH-~4'a@ D>!ùF? r¥r@sM'IR[\pOC611$/ { TNWF:MTRsJvgQisevEjP+#Z흞0@=OL; &=wb{rM#^a 3N*p7o3>p~KF $0=kO C?42~Vo3acLz 1n=k2_-\P<ČKu=j\TVl8$&N^՜դņueG9#EzImP0+}OIfU.HMsv3N͈Hoaz좴qoH#s\V19ROQۘ37\ rr@t\I/N8LO2CکW{4_K?)Njѷyd8xyaFWI ƻA:/Kj:IԖo' pQ޹s%;)%u: i6+*q2 ֥yr!I-/XHCVgLîJ+CXywoRNnF;\u'5*ІcfVF BW\.UuևLM6c-U.207].K!,,2+1/SFI?:FM#5]bU{vмm<~op.3U͠ B1޷eZ)MFrz}ɬhqlu3ĉ|KG>jEq_n3!u`Vx<:YWmpLKm6xָE?+?&TG@) `^ ʬv<ʹsqRn{~^ eخ W%O TV2XYJRr+Ӈ=Oz?s{'QǏ[1#˹,,x;z7O?US,+Zu!?cPoAm%U\ԎkNWLF\Wo{kdoHU]ŏMJ<QZ >>&vAnhen5%Aa+=DJ[|CQ|Fbhqr9cp*9l49V!K#I ?w`3$[7JON?QRBW&5 \ 56 R]=NqTplg4kY˟5wJ״+UVcWn!KKi)5jOۂ~URCjqI#cM'XT6{v#çIj*1ӷJvcr'9R1 aZb6h+KdoE2kin0VāJ# t[3!c澛3j;&~F@n8sTeQj|os9DKhWw@`y5$KEMZwѕP9:f&Hu dלx_tBNA5i.oF2b"t*7Uwլ1܏jA&CZOGrS]I[i[SզR2 hFU푝j|^VFB+o|*.`o/\VM5i+]U褙ݦ^GΗW$H⼏UVebAiC2X8 pMx tw;iS$OSdI6ه#qF`*ž@H\;6s9r;Gҵ}ĊQO[#@zT(8Ң20TD%y03nlnim V"dQ֞ĀKd5*i~aɤq$ R.HG68$J݇{ [Im =k:|Op0`~ށܒ/1x)99xZٚt6G ˋ*BwNQ+)p}:fBJ*H9ⴭbHp@qrv"1VBSRyd ؤZ:Jldt S00yUWУdc`O;wӭhO<(AYOcS"0R\e[=Z8ip~R2kt'U84ҿ7T8\І[rF`A8#n۱zhKnUYrj6ÁO-|d;VOQN6̡$?VrpyIO˺sSܫ婝lD Sz.r 7g5RZ!39Vʻ(8G4˩Xs&Dءm+}=]N)k8r+F Zu=+01ZKK4i Z+LW  ֡o)~PqfmyժkQ6'coR0sڨN%:)zlY sަNIh&Pw;7O*dUTr Up/zjJX[xnX1|WcUF*[׌Pt)d`F3[cX4\ƮN g'F %P\#i4j2A1ڈ- jW7\z}*U1}ءa'mJӜSF3+R&;f'-n#1ƨ]amN 80IY4[n#rT eҠA^FzOpPtW?)PMM8(:Saz"mwʧ^0!ILU u<.0{jيw dVl8y\=OzV0U*TӵVE>r\8b iD|㡮N:W,QՇ8{1e Ixk 8W`t%Q4P3zwZ R|WpBZٰ۹NxVMM+ƴ[NOhy%TC r4; Kº /K0"z̲eT+W7Ta3^lzpӆ궀}JXI]-ҹ5Eۓ"HlT`6OcY*KVk"O's4 }^#%-si6#1:0\./ (ʛfc$+:'u^ɬxT#,dGn#5Bl˪N)("Xg[iA )<?>-2ڟ 5ҜݹDIIYCt]եѮ8NCSE$+WL(rUͱo7k[M 7pPf<ڽzS?L^+1awr 'ռ<:0X=O0YI<'hؽ 6}Ԭ|7™xX>yv}+ȴ\2sVK(7.U4mlspZo;fV[SZwZ]x~zNNrdiI>RY:tDZeأ,zjO-z;H`9=@i\63Wailm[P#@NsWZ3Ni=A8Dƙ@ XLrOSTLm=.$E@*=L&6[HlpǭVDLWYb @6Q[m`Þa+{<[ٕ5<Gv3e+>˚ϖpnr:;Gd}MqẑksW|b;ۏZ+ŏ+08eؚ̙Sx^n_+WLK@+5oL?>Ke*2zPMrKM$ilTdRWxdNKq*Ju6S՝ @~Z1Jı LAO5xRxH8^Ii[dl'(ٞ^&%KhXd}~j!+-Tϛ#^XʆU﹗_}Y&@j͌η;cXr|r;TIkW0D@6#cxIHL BG& b\cjw%9c5fOGGKo%F7Ta⢸IP2@뚧uN^WE.~-"~Ӟ{u=dtjI~Fz20FdͽFg,1\uG$i㚳e4yq˿~hiͬbwVpɼڵ,݂2Eyj/,6g9pj% gfJIh:* xmb$gӊżծF`zc<ewFN).!r 0>k:Qo+IړGGڲH2skoCu6A+I ZE"mHRH^b08T׌yt OS| 3,s)|"?h>iX&k kM{w#^xDK@0|#ӪJV],=z<"5?4HQ ٳֽVuuԿLbk}֗yMǽvJߙWZ?v^Z] 3*ҖQԊ_^݌a#}EAռ:N22k|{ਿ cpFLң*yS`:92bē# ݱ|=.'36 a 뎵vy1+}=kh)^>GF 7V<qm%c<JܔKoXzt1Ĭ󶽌5m*UoSH*:{[LF n#oR5sm_?r*-'(:v(˗sÖNoct[u׊5dCmlT1͎=YRۭSMJ\VH5ԁx-B;p\ypB'8?2;ca8N^]T"]A[N2rz8 w5rHvRv^}l#f0YmL{f1F־k׮=LZZV#3H9%Sr滹S7V>t/|?umi_[c*fV#c"4yS'=zZSM#͖e;{ؿh-.Pr1`WazddRHv\p3Zu[Gukwlzt $Yg-aFx1]1yqʟ,ڊ4>QK̝pt> [C46$q]۠T㡣X=4v8E:}:ЍV<-j[-,gP85Nzc+,ycdI>pW:t T!34?ݣr3k0ڄKU ]5_Ɲ~lتN۞cR3J1&9r99ImBSS"hOB/g.t5 upADU(3j6:63]>qjA` ՝N]*XV9# \_$zl(JW3a6KZ:6WeFPvvW~TЙ-I]Jú-mw߅cᔜ݆䁇;W&-)mzѬWs[w_Dlz1kA<wa]B$Ԅ-ԅ OoΊՌp]cyߎԬdw-*~xcDUmFE kX_hv>I4sV圯M;uX5hkw(yB;͖2yu)iMk"`(}JM#Zxc`|ىn=j'vmg۸p=K`hVSZ5^rVǞ0 `S]Lcs֣N3Zl.ah1mY{Vޡ[Nj1{u9#$zҵˋږRn:֣$|ca<1PҥRTrj.t#C]w**|eB}*[2 ~\r\Է*NNs?Bzz.f;NWaM"c~6\ކԊE%t ++.gsڅgrE$F@a 2?J^X<`z6,Ұ١hU 8$Cg9vlE4 dsWPl63^mn#銋 T8Zʖqګշdy?Bۜ W8E0mqHoR@Cu-hXHgsYLz=+@Hd攵MX;Jg|U80u{g C)jqp)vڠgFˍG绲Y:ƚI>ϗL r4d(V,R,vUy^kzrvrD*xH5 w1YdzkRpμgޜ۱(kpEW@ l9 k6e}\nQԛa+[ U~LoҺcɺf'*qQ#7HJy98.4QK '{@9Ns՛y8ᇒF8>oAi&P[ <`vkrdbZJ}ݧwc}͊fXIq/a~g_VK;efk$2=s^!q$ +*>Yf p:ם<\r.T}xpMjׯ֬Z*x[dߔ b͕g%2G.x[6$1[3Mq9[e^\Gc@aԜpjfF%fq,a$=ח*GZɻi B|\,C8si _;fSs֛M+$ZQhКvUv2j rtw&[r: j/X{i-65J;KC(ޭ}_ yHuA!Kg=j}Iz Z$R YbqS\9 9Fj2Q~q+lzzo=aX#/#r{u'+1FZnXZ 4Znˏ$JDlW-@3!S6Us08&>CHPz+0st99nxr8]\kygʡGX+^ozŶn2k-|֒ХYpWoNy?Phؐų]ƹc]ž{'%L8k2IXxH1ts47 k>xxR4kʮ12>+[[\h{ǹ'KD}VhiqҗaFY%6.P$q[ߤdqBOC*fyO/DNuKK;W3,N#+g^k[kȪkiRxhJC4+$y = fȧz=ÀQ}Zg}Z)ImNV܉#t+vE:涐DcB))`8Ec*l{o:lZAbx5M]R}@{0N2McRұJ6NIhbB5Z%HA1Z~[z{xxBO]¼ɺO->Um; 9G:ll4+V& +_WY6V*U1CTSytaye.]Q-Ăsgi[of9;O|H@ x=k0UfyiR{2g%Nw^-ݸ)fp^7HY'XmzǎeYXuxύ>tK^ W$uᱞѫǛVJ}rīaXcIɦ难vdfd;Pxwr`prN3xVЕThR*00R:Bu:#Wg}3]}dKsdp0=CIhv+{w \w<ꯅTivab#MZ3ޯ|Ge}qC|7]a5͜p9gDռzW/j*QRʃV4]l}ⅰԭG$[IqV&FGg7j 7q+Mj+T|3lⷹ8G'(hw#䔪]xbƸu+׌$j+P85 UD쥡j^)d!۠5V2L1S>uޡc{iʒyXO/qhn䮏c[>KXBմ}Q~mkFleGf9zŚEŦەI 8Vqzթei.wo?]ֽf5Q7nz T,%XqM )OJڹMZs Y>cҞ>&)ca/jMhc$ʦ!5;=_E1XHrGsy'KHIloaũ$ 6p)5_| b蚔 z?"kUilt젞[6u͜qp1fJܧDqu~̿Qk:[Eyǘ0q\+,Msjp Wsm b,95Yuj8,cТ3jMޕ'I49$͟Qɼ#gZPvy62pOZpivz牬WQƩX7t ]͐x5wwDw=&n, 'kHԫ#9ѣ}O{l͛?@+,)8i\88ow fw$V$Mi \>{c 7mn9u >64 '=kA]CC1"泺9U+$kR1UX۟'S֘ z3jդ̔VsFA @= @" %W*8{laE `ÔǁtFӟΚЖؕ',(sRPuRk g<\Q=jEg*2qKpr%eS<11&JK"02uLwCA5,Z6F:qP<޵Q4}(q*4]cqUm1Ҧe'h0xrwBXlS_<ԻNHE2AC rzS6H$O5HGj`x+5Onp0<O ; R8yT"GZ=e*y*^IMlcIARCv`z"bRz c3֠pZ t͹.*b1\iYFdUA2ITCRZ˜+Fܧ6Gnj֢@29lTIo#j^j֨{BOp?SDa1QrSQʭdKMOZ u#;p*&:Bc\:ֆKU:ƴAbJڦνA9SC3]G\,kd VbGlӰ8˼zֳ0V'<dz؎ N!@ǜs `:-ڋP^F8I' fI#1TRԙx'Kڡ+{X~̶0s8BaIS6U|SӤ#.Y6rGZoݩ$#K! K[dN1Ǚ$Zs MJ3)9Xdu$ExMX ڙ`})}ػHsYJKstݬނi!\崗ǭtS}jm^qU\~+;3ަӌNxL:j@1=)}ZKXv++ۗQOH43itr /jk;  zЗ.hE^fd2!2{TL|"Flu5f-̛HwV8f_zVia»^6X4@ ݳi*/RMtSH:0P!{T$F<0T4OkkO"J5 ]]M jŧiTA]u?*uEAPzWԥUtR4PBXZ415mqGjK$289\x1N6&zFEwPCPA=4-~&TGU蚕ίG$7ztIϩOI=9ek#Vga^oL ~xsHP0+ :#NfFOodIqI.%ӼO}o$9'7CÚ:-BL(Utn@% ]NW|qJ%222#5/"@c1Sѝ=V?oa&=JPX(1]O i$6vrbE.q V1Q_d.vEk\ɂv֬3E}j/ '2\;]<$Z=*ƪ{g}y8;zգ{Xk7-1oqV>[I@^*3Z= )\P\% g~4@}XsSr\Ź[DXoq^8e9fSZ6"zTƬ WPSEs+3*8ɀ}$XD뷚tiU[$21[Gl䃬æ)}f63Xw{X4wE (2(߅C,sk}k'Z Ai= otءngZ Ym,̬rO'pXܡgAg(E1-ʉ@Ԍֽ1-n*эA5sK RXeR2oZta'i1J (qՖ-r:gkouKl㚫:"x&7E{NnAA5>C$] ď$`T(R,ץJkNcΜ'Qc @c{TE Q  IB$8]n#M%[ d`F ˒3Q:W-j՝zuӒZBcsySG^q\s-[ԚZm.[M#͗'~u1sɬH'ӗb*Ю6w =[SIr%̠6i^tNnw6k'(Z2Or&;؂EVOQ{Y-h>_1>h`"P[Cm9s޵4y%|wH(BoV*̕"QWJR`c^o}m>cr+gSw\kбeJmEk`xM`n٭}33NsxN>κ,qKf_,RG.n.nY=)9dkGj0Y.d%Fj]4x"L ocrbmXԶ*1q4Q2Q:WFjqn11f;PXܟ(:55C?4ܠȶGc+hv!-Ipaj[wey4U(O9jIhzSn69&1Z+cj+q)⥚5]0ƴ[$].˳0J--Qj&7 YgJ&lZ!}GOq]gw╬DVZ.*ɼxK$xR;'{)W#s6GNjG^ffi+:H1 HQ)G ?- ;{U&א9V+Ư$J5;9>AW7ymw^^'ޡĚ:qu-wkc;i#YћeKx%'aj>b:2RxUjDkVI6zQUw`+:Z;Ogy;T as=6+ukPc};֌Z̩|> *vXfuuIs"8--rMHtKiЪڢ?:❙խsmud2=8>"@+–bC#[pMf:k>,,5Zd[[ci _dj+*M2Ys,Ab2zʂqO.dziXg=mPomoYTW5#l )8IB>S;e#Gs3>F}qV-{e-4B\fP} K%(6M[QbWr:+N(GS[߂FR:\崫0@)JLI ¼6M_MYEZFy֭M14{vX7Z5mǜf=#Phʌub&JGMǒBImy$x : ^XySt~&kXT2y9 M])Òn䳼+3ls^KE.6ΐrǚ?/jQy]zҬЪH_ƻpè)Y<:ĩ81$o\f#m Ì],jGh]c"u }|Nr;]*%7LF?Is]qY<0ͬ[>>FZ}!ᑌt8Hiz#`M%I沓ķDZJ.D?$qm@3M_A$xa]x&!B;T#VvOQ*}Mk|\P$)b{R!2Ebj^$7$ec>^A/դk9.xݻZ=3'QkG3uG"wssv) %cPƼT9dv'ЌֶobH.25gt7Ux܉㠣IRQ y:Ww[i ' x@nI~;FQ,?;@Fqާ|p*7w&{.<8kLNent4 ;Jr D`kVVP:xaT?WbV7S(;@Yn,=MyώmX:T6$O?1M'5t%=[@Xۯj!-{x=={tٺHf;R Ywcg9Zkڱ2\س.%D'.sc떨\/Gxyø7ǩ OU3kE\ґYl #j M&# X*GZ@sNNthQmN5-岃j\E "]-@ T*6RFmJ*bJh-h$k-Ka$[d p9RTwCʮmG{w[5)xb(mZU Gx w~fQX 'do d+4sާsq<@Ī8~yeZI8냅xm~aXz7 AY7jqNȆP7\HVMr3V{%aOMɁZ+SMlrGА HH9G0axnvͳZ2fcp:=Wխ/zf3~ym XuHd};OJsIr1{7AΣj$R{:_y%k{81sW;N WR%_ 7\DR9`JZ]?ޤUhOg[iV=z^i@`>5xEү-ow+S[tbk[Shqv='ize7^ R:)ຜbC=I2c'[& xOzrR}&dIޓi\f+v7T\W=& m+6jjגmĜs4o*+5+5-+Ҽ!wX]Gakaix'*WMgTC$7>Q9OZ_ۖͫE$9Gz<=z\Y =+'e+F4:jKlyuGPF1޹ Vcw,0Gc^;qʗt h_Aݤ#Nw8Ian.M>䷵ya+}K>d6qN2' _mP +Or;]u%-Rmw`loohƫ0M(-f~S?:ʋ\(/=6vz> #he:S^֩%5_55φ>o3r1T|AIxA0`YqȭƜfKHg !2W{^"-#D׵yZHё-q^#F2-D:֑I+ogB_3^-Y|=}F>t#+: %7$xRkUkgЃ WTp2ky86:(Nw_xv{T%=Ûi3X09\5WWET$lO;3W<M6JVUχ-e<+9; ux7Zllp q:"ʌj’N69>OYK$1lbv`Ʊx'5WRį g862N[G5V0Zb<D&y@89 E"6iQtGcFt$>J᱔i 6y^jyݤ`$*s;ʵ9$M$ӣ Sm#UĐӕےrzT{ҺR:gf额8ANiR`8Jj;kJH灊AQJ:dQHeA)Il7k\dV;3PܣFv+e/U[dq B9O]JIdt:UR0wd8:U=6+ \OYMj:%n ֚b-ܭ95MGqO VEcQ KRzjNr\sS,LnnJvKRz _wQT6A֫Ĺ8?wcJ8-]s+:]iv% 1UmM;;Hp:}jrLc{WUn&U 4銖V]Z#l[la~Sz7`T>"6'AOM1RWwXlxǥ==>: (,( 8TРrrFE9$a JBޛN‚#JB1Np0$u"xf+XP`Mp1= nUW ܜxfr`PR۶=jgѐ<35nin$*3W'e{72 ierND2] jVuVŰe= O"Kss*;Vχ4q8洯A&Yxڥ y,\c7{Zr*n5kP#ɓ$ A@%+g-OYX= D!% [@,n#9y)w"1jԾ(. ?]2BQjέqt?]b& d+d9%噌'8c Rں<ԩ'_R@"aYqJ֪z A-7/3k3gi׺%dcY.tn5ı]ݾ7$xV9渨moY26{Lcq֡jΊ(е#$ɜtc]hDwqk# >գl2nw)9n<=/YD;{EdX$I"8{74&dPnS=Xyg4^᭣iv-|FEnpBՍcenKgQ7<[s# .J_WiFW=& "@R^JJ9%ѵUܤfVΪШ2J۾0ue/yq1Kݑ,[XGYÅٜ Mz-{^27)8`ףKkS̫ZSfޣA"DFMC0qm͎@U;;9\fiY涴;x]JER֥䑣_.Bp搟sQm)1ֶbe O^:WΊ%bo6"Tnra\uehNQqYJR&ܫT+7Dn%H{ ӵLmv9V;lR _*F֥HWVǫ:{x29a䀐bynD"҅-i&h/dTsK>ߓz7J[(Ȭ}sN4kȆ~4 I[*6:U??_u7NӛD=e_LUOz44e5ɧc#t.8M#z ܌ 眛'onf4 0$m);׭\H0 Ou9'9ҾCU]ij6JD<!k}XWDjAt9J8KtPjUuɀ lOj#h#j[0=+WZ=̽ \ǹɔE֠#'$t6+",N( 5e0NZϽ"Zk}?Pq`*fwB8PC2zjh#b䓚oܶrOrfcYW:F+9b/=&N $Vƭ%׏Gy}4,3ޮ%#fIߙҨ+saszXxWP> >]9nX2[01qc9EKVz}O'XU-沓ZpQGZrеß?^׻M|qÞsK;ϒFOi!`Lqk{`l F8Gj& aTbZS״^;)ǑMWOR1#Q1ּbi&X$hWPWNWGOᤛBǑqY.rCCXf Q+%p)Z囻m !gI5b=)b\28m|XzWEdF_Ke^-լ1dzE֧qNY ]G8IXnR6ϊ-$@<+/[vjw,&8癆1Mpԣ*ÑN #gljz|"g\o Op6Hm?'4:WGrkNuy}a:4ӵ*֭!#bj:4c>IF/Xο?(u d+KG<%ϘAwM@y\jxGHbS(nb%ee5D;8\Pwt)xHvWKe6SȮ{Wq.g (qkXHIv.&pMV}bo #4d%xt1X)EW8vq[eGtQeOS )Rv9o f<+o[l 0psr=%ͼdXHjOsL)TǚWVկ|'[{or:5Kh`T?0~v ݥ )Yށ^,V:WWiq: MzUI`p:R1u=00ءwTֶMzEC?*7Xaw [[ 9@y5]62OV#R\,R5˷@1ma|%$znP6 ^Xp7g<ȎўYyx9ɘ_li:Fn.H (mbD`Sh*#5RTh3kte֞K;23o\bPuvyPVVkpM;љc}($a,o0WWj*`u'E@IU-u5[&>Q\Sߐ֖c9J_7IV2rnVeeUITڅx9'۞V!T8$mEp^{gG8_XoE?ٮ֨)C^|;0 RCsYMno&jlڳintRǸu\Eǀ3) ǿJ- ш'4骗ѦEOe}SGst][ѧmpgXI_Njݗ/eܗ =k*%NocNkIӴ NI˘mZ=7ҹQmمL'r 8I}tz\HK[4P(^Gq%,z'q]41s6.J>5-.8皒\Ąg5a?/]3+rխKmxٺ7wL#uk;zڗ1`+4h\)'7`j2攱sbU"V+ux}z0=je{+3UˣpK6|zťa'VC-Һm"^°x./Sy5-5k6IṖ+' _S֊|3_jgQ)pkqWi Qi<|i{)F8ʐ֊S}D{O.4.RLZ԰n!iRL 3ǷL qUOVL\4xµ8RQVQ*h頸͘ +< 1,ӓK\kw~bkU,2w2k*8'N&{qHkvƕ53f3xrĽ NI%\(b8&21 Ɩˈ݁O;Ս/Jw_3$֭6YP I kgY[ˎU- {3-6rJ:f b)Xץw&8==v^+[H5vv{,sZXuA#Ed EuDk䌜^*SjW_C' #ѵ цk6Z9o"smF&>m2y"?,O^d\oH]kъռF,{]HAdZ>pv .#xoTee (^LxZ0 *&WI+D+?5sӼ+Z+K+3?*ҖKiaQQ1-uQFUhuK[b*tneMݩ[[[[ hzkxMX^x5x_td WtT3u)ɫ[9ҏuv/jxVIH#zY/lnοz<73t#zÚA֔}B{]X6ɭA  gu$RysbL fOtxكV yZB1m96IFJPVCM33L)u o ^~^+"V-( 'xnu {W:ësE22i{jӈD3#]r)2k43E$ggkh5<՝,3ѼZTEH `w/Mq#JcqWEھϛy.AqjL`S5Nь*s.j-/ifY$l|^ x0+֧ݾ'V=3UИ i4+F!RҖY e7@;WGʄf'<#4L9ⵍnz LX0ۈ zcV2rY4QĜS]4Փ9s,%Su4 l#Z:Ɐf*^}ұk܅OZXѫ;vM`a eݏLsXvr3\qpq]QUiMrVC8gk3c>؍e2+Ҳ.%@ OM M6GnUq ylS׸#n$Bd gSS+ RݓS28p"|^(6NF{fcj ' '>:H椉 Ƣ\: eЎݩ!4řJ#$&#n2j6)Či<~VGfLPr=ؖ,TE7);3 Р眊Jn@gm3jSNw9OɦD6֝qTNsUh@ rʤ㱪jNKdQ W<(;jq2,#j41n,Rd;avr1ڕrGniҪI)`Xw}iSbm95fLFX c$ c֣Qc&]OBRP\8 m5>/ "8WخUm”Q%@ZHmse/@,jv8nS}@>u|xjoZ,`*G@=*խb]v"h#9tOWTbh?ZPNwة{`)8zS6\)Cv JFhSm sY+29~9$eiL#*Fy\;TcV#Kb 2'ʑ{Ձ!\EMDIa~LbF&Sqz-%.5F!6G98攤Tm2dCHPI]qZH;scNia->\Օvҫ#=sR'Uԏ.K [awOrhȕOE]jdX~A=iɢqlm?u܎Ů`g'#e.[8`**ݼlwPKYYEj8lS;,k.Z&+c8{4T%U 79VؐL {}NTR ?MƝ-.WH\z/6p2R+bX@5n8kXTIlfw&J$F8ukϩj^ v'zR4G#S+rV; /8ZW0@V-[5f]u"\vǩ5rUW+5(ݍwMvGcֳuU~,X8v–EYsW_؅ L cVTJȃJkGzׂ7u4k+%yU Jc F1VR]&5.>v\Y1zvR8k1UR{f6JE.\&Esz$ygJfU֐v_ԥ0d vtǞr}+ίJVb;UiHJA!Hs}-4(ެ{k6F# גxV}MK/ .I82q"9560!rI=k VP#\D#FP;*#4N5O4>rO/sJҴeL'Y9(2эPϞ§ :v5$y< C$?6Oҡ2ї N:s^1O]G"٢&3lE(V4e{X)p_|E2v8bx"QWF0=14s?X6 Hdk ;'P3QjK{'5q(ytU{(ߘ8@)\U.^.xWh15aʀxYjk ٞS_ƺ#9I=0A"IƥKhո\Кs2G"" 0# + NzBɓtތ 9#p~D w ;H6@ ܚiy6p IGGRZNj& H܅5{ڝ"x[ q|щN9f׷6BG8\Qt7SsRp+ԵHn$-Y;R,{c,+Rwd^Mlfj&+H#]׼xɳ9OHҡ27;dfXDUqc<nZDr8)<-7y{b1xjrzX\uHFGk3"Bh]c9h=9 ?xtFE9m[!]߁x÷˻ rG_[N͒Dp8sM2oc59~o6W2OrڽJV~E#308U<jUu895_mekZMf'& 4Kvy*O=kJRLdԲ&bГ־mMmOԾM9Ѳ=V}oꋨ;&o0 ޱtG+V P(#<֒C~Y-7x5bŞ : i.nn@W3yp.e.Y9]yʶg NsҼcm4;LsuϏ5hf^q\ kL$4QՇ{hK>؟PkҮ"e[dl5:.Uc,Óz<5㩞UWR8WKUOvNdI.H+HRoMIvf+I8?Q /\[\< Y9XI9nʌU,T\#xt2=~4e[A뎟^.-+ocu y49&7~.TlՊG ˉ{y,q DqR@+ /};r6gI.O~ЪBJ:R%(DC!@Un.!f ^wmA 38]n4y'=i1KVmTXI-vQ֪xİ\Jǐ(^VnlX9ZOye\׌UniI`:czO=_s<{Wὖwb%|ycz_R:0QKܽ̍Kk7R'voB=<] i6-ǘqW fh_bǔB&GJ[uK|j@QAlti L@մ9%g|T1)adҮkj*20~i7*ȸc5~ dHƒ֔ b ;Wtm)4Nڕ̀7 1#zwȱETQ0+V9q qԐI 51`C7@I $(`>mQC(*|l6 X9Or\sC)M=;SIdSSrjjY^+'pNHҚb [nq)ȣ9ROҚwu#M:Ͷ92ƭX*R9*'zT~auOzh+Ȍ;$hf(ELYG}[MqڦmO0dŹ cLF \#) 5Dp͸zsQF̹<)ȢRr#nTZC%{ت(mJӮa%%!8KAZRAz)nK`}ipx櫒т }ɵFFAUIC#aa5erڴ}f`օ..2@ jmRBZT2H÷5GUIMr:Jﴜ, 9TݟKkPd3+H]2c;,Kʪ{]5b.ER\1f&D@(α& mϊI5ce87seN8e-t6@]ix5ŤiKƅb# : ;sZ- zTAjq<<s7VHQ"/kFK&*3wmzf [ S5 u\qN.O2[MNP B{g{mH'[R}{Ŷr<ө8JڶDkN@#@kkO唑0acϸopffu8YTBKԛv|?#G#&cY2ѢPN.ۉITU қokɴjLy8i?0 YQue]_tܮ튾1IZ%|89 wG[ yJK7H|=Rv9Q:%<)˞ޕf2Ub[=qWBX=sV cv\Glj8X}"1x`>8orRٕGLTiڊǂ5_$dE1Xw^&!# B;E5uv[ORi8#U[h!nluj̟SW'Ҟ $5ԧM# FtP.U ¬ì3\*N~-Ubў9^-ٹq|cjbPTɷ|a|q ָT}Kːv(c En脵;wTmX\ݽض#!-#ZOrclUXKIlm**TP\̬'T.<}*\39}UM&И=M LyaQѷl:TBkCXae- CõiCp "pքw|޸^ڝ0MvVÑک0/ڬoZ|^,sU#jtUW`XvOYnFOb:Tsgb`WlhF+ }ƃm&H7b@G#]+B7gJV"϶;w n'{V-l,0YLsD׹\ 0Oc\ң}$:V56%ČjZwz1uvq6HK GOX0GJI\;c 1YWsI;ݳ޳$N(A݀i_"qkQ-$`[Š6$qkKM6Z ^dDG=њ?nXo"23]6Qs3׮ UѮXI#3k݁ZZ^Э<`Yӝd(>o,՘-X0Ҷ$c)8E_L f팛Q*ÁQP(bz=(;25yv Zd!A'g9UxŷJmli 0kNMr:'i7qug\n2{y_&.`cZy_ \3. g+ݖ+P#95jhÚѶ.rNr*;wz?;keRmy 03,1,lUx+@G |@T%15sMb3p}1X BnwQ֞A,^[)rn̛˥GS| Ypk:eMI,vb} c}LtF>"YOֵn XV!K,j@W[ mv'=y4X#su"QyfwnVV9B;?n`+dq+h?4dzU=mr{q[q6rc9 1V!!Xi㩦ZZ*)3H\UEK`j72HGbjY#.HW=ƣ+E"jx lԎ)v I']]m]&}V&.JqU&L6fJ;g8PZ$C ZF4;t:ҒhCghdI"'u;mp)M5; C0zjjuu:q\?"`2{vHfv L2^P ڊV9j;YKB<Mv:Ua?/~rwHn3͎}3F7G(}4Ikt:98{/I|cX۸\ ~Xw#f{W.g p+U^0.E̮<Woeo.[<׸x+BK+ s<@1P;b* ^_Ԛg̔~ѯHVvOcֻ[ 5t+ 6jp RdN9ZϲVB[ZjpM`R) vHpCs*#M+8Z茜zў\F yd8V5*ڜ G-o` 1gTc?d2՜ {Î"|>+  62Mmܺ!+u-c`Eg?JF ]ʧ6$xǍ·>슲\~nqű5,NG|U GҺ#ֈ{#Z%on&攸Îp2I&2"\UE$cl8.板; lVY@,ÒⳔRWLԋڲ{ q29XzZF^BN÷ZzvQOZ\B7ydj9V{yE#FmEfӞV% ⸿M&Z(xz6+#cTMHHc0#sT[O"$-mh%w˘\sAӪ8~usԋݩGou"nx!zn^r)qq]pa7ps:Ɨ l+:r$5!-7 xR$~\&/ݴ1Z-R[](Кa-?%"I\*mkvryZ=>7 :L0;fBz'nYYTq_/3 r8?xq4a.5{+.deo3Ѯ5ɠE7`3r~&ִqoqs=A${#Iu|F9 5SN.yu!KF`ԍÞ*<=jNV]h`;+f\E+]Nn/h|ɷUڍ0jdPX:1[V,Q#FXNJܲ4ZMg,K]=O&ZtR7:nRMgOщtֽIѯGڡ!pG9%4&L`W<ɉ9Z/j fH[I/˦?@=+|YRRX⸫?^Ti#eA;}kuQNq4j}yhgȏĒam g6l$9Fr]0XzH%tT.f1pqWQvV庹ÜMsԊ%9ry4Y< )G[Utuƹ>\gA>JC[iK{Yc4wuY7hqi/un.qq ɴ³x>)3`Iqa'U,=#,Q}\ /҅Wl ϟz_dZU;#hw: ă$;k %B}kJ3:"Puy_"vGaijX̑^eͩ|<1]֝ ɑҴR4imkj 0\lF)9ZN1Dg5_F:G^,:WgQW]xgV Ҁ|3ӳ#>[so&ӌm?j(2"'T* YPA͐rjVY&Nw0^sR/kSq6KEKY1UcZҬn#yF0pV\I?g*;:ZF eI\GK}?Qr[piye$7n;WG>9EyNzuSwg~ N( ƻ]7Fi?Sy柢h*\pqҶX%RwsZ:wG<]ZgO(s⶛)[Fz+,-Y,g54sP7ְ÷K9┵OSoJ*5ӒU8#9 tԀ&pU+wJYs_+*==ZJzfxem@OjZۗ8Z r{v.Q֜*"(̒^]xlÑҩKrd A-UahۘMo]|2lgn+՞g|\S6 G^C?h:Ho5縑B)9*;W=ծʅfZRc{N2Mi<ie9RxZo@ȿ6pvs\;>Rr6 mZQM99NӿynN8)) ͙q#^x^QY%s'O ` ¸N/Yٳj*PVy.Vu #K:pA+4A0>xBx\ |qTz|=lZ%m1o|?v=p+.\wFMajO;IJ Mju%9qP0^BH"Q'}snAo/~n^gCsQrM4S+׭ øLlGlYh܄԰%Nj(ks)ԍ_q||?wLEt6kbQ3W[qG5꺢A i+W,_s~QH>pRsXqN sp]bGz}sb=9=ԱFܽeͱ4hHE&آOfM<(=s+N.ґ' ukY3w ҆AAZA䰈l4FS(N3ZIR+sD L.@ʳФpGc,nҫT_0t)di:.-VЈF fciGUAr'SUjV3z;MۛRB:02`Fdm8(+#o1bUZZ3t̷g=*]ҤP Qڴm[Ԁ}N9ec6ja;Ȏ_t*x][J~|UY4h\h%Adݓ9z,qE۬yjNXT Fu#oj, ɭ?WK!uZ2g8ȫ3E%;A R_mc%a)LoAu^=l瑚#6YB.1Q%YM)Nt+UPc *sɫZvC)9tOY_ ֩4l%;sH) x;ք3.1Ǧkfa1 !&PC֠ lsWZc2y0#$kpֻ~q3rԠtmι-cwƗL1L!8RN@)JvTF89Iuk`!&kxEP1JR0\XnE:y5mrH^sQjq\lņ 4JUbՎGNi&Wo7JZ Mb2g$uGD*<ԨWx@v*JQ]MգukV1nՓ"26RO|׳]5xc㜌;v290' b"vG$b3җօm8t_rĨ~'  G,h>H#+v)E2 DU<<۱mP\\+ʡr Ub\g$;=5@r DRޘV^~['bSO0À?㹬[cqM#xm4{&rv }kD,ڹ5!H$jqYIɭ)Zl 顡' =Z֬E9MrG;Le8-^?m\vW8T_ԞYš9}j+ \|"@֞N(6no9J<4jU;USTOs-6 @x.ivw!sz/j]$F8k#]yɭ0qΧ7j 3M/y '+z"D @T :J2%d)wt6h5cԲ6\]-.Q]y_MLwVѾccf7~D+kpF :?G#]͂21zy@TvN+77W-`*99k@A"2|i Q&eFc=8=;Ux l-ƨpU+Iݏ\VnY{z6G d|qܓ\<.ϒOܰR3җ-Vګ./≣l,1\dC3qN4"%pq]0ح=/_i`G=:\6C+`Vd"Ԏj㊲)N'"ntUƻY0߭pVI2ճ$# h{E%d]t#EC^UwwtE@nU*:jsT^=]Om&VVӅ)Gn* ©'piЧ$Q\Z>fe[L-4UId _u13oa$6-b%U~^>BE[-Y0o}*]sOR^ǡ2UpܶuBGV}ڱ[i?g䔒3YNYA;W֩Fp 4 [zc֨O{OQZĘ5ӬfzWNٲJ%(,2WKG@2ug";`JЋۇ#VV=I"v5wZv;*b'LW*)b.}dGWGw[<#[lEbÎ(#S,|=,(*NGZq1OSҷ$so+89<3@dV{ڴƧ2^ؓSb욲8}wiHj[OyiQܚm/4=6S]s]TZ#'l$l~oh֮Όq\6]D!Bw@[PC/qS^|QJR{nu r6nV<7"6Gs#J뺼XBwc0Q֩q6ۏk#}`kӯъrU@kXXvMzFggOTEI܃Vփqm-'Zmqs'vWRN96Hޠs^oN[iy޵ pBAU9%mI7MrITrǙ,sNs՘nHc3*e_ОƳjs!Z˩qPFsOG?Z Hlݷj8X⥋ZöHa$ c`֏3M? z 9=1[ ?2u,@:XO?dXb\ή4m9V!7hxr~8 hPzWqiufԂ}rMjhSFFo7i -: 氤cP?gOy9;2;}αe¸f9:У>\u-i--L3OlkQf1GoWy}rbIԞ&p1'e]iI$ڭɥ veR*g&&Rj# #GR0&ןq* yeeg\XSq!mN%'RX+g9S(f'ˊFӲklM8)ߥ\T+fL(vf5c8:օHd= #.#O3HP8 Ziu<To :=j[ّn“o2WnZ&5,` cc+$4cZ.298*7zƳ 4>3P,n!S3y\qR Q1lv)J̨] _YIyfϽZM =nA'jhM>ɻYZ8Įip8T [쐖KsY7z?$)9JAw:4|rF8R]ivīA++g&YgnnVMiXiDW~sYIEYnI@Gh׉!-9#0*mcm-Un 1L^t7ɔ*FsxcjyL-fߴ3KIckP,-?JCn/شs;cҦ:vIs]?t)dFp$Btm1wDE%%hF A =< { #U<UYW|(E`˩4R<ד,9 &0?QySM$VUE aɐz+wlIqMOa% : !' [X-j'={˔@-6/i @3ڶOtGR}MeDƃN pzϋ3yHWrZ~F8œ3Jwa%)=Nii22պ׆>HT.q>$lZYֳj5KO9-ԍj-B=2u4`㟚wYՃ) 6y#s&ok-Ҡc ֟< JvszFd3tƥem RU:=4G >M/ZATn+p]jvBO5W }QH3`WV^-ӑZPUMtǍMHp9F*ÜnwTTN@*9+YF˒kϨ殏Ziܹ6ii mkQ&1^S5kEYNܚ]ܘZ9yZq3gMYQi*^4܌`aӀfT+*†\5ί[YP1*Kg Y5jL1.x&EX$m`C X֟cN<ߌ]žhGv殕:3wi]#zITSؓS.fS#4 wöbǮȵMA<f\S֓ћ:4w31-=yk "lV=9CmJTӖwQ@e u+ye;nץBsW0kLw4"í9Ӽ5TMQ0 =jػ.'NO=+nKXɊ@).JϞK*&Gs /yGc,qU'OAu OXo >XQEepj1UUxt:}5*vo8Zܵڛ%PI=<`wz} qz;fM&u:ϋ[#py犼eqHbR+|&p@`0^?u7m]ŻONP|B?"|ow++$?=j]^ee2=MRͼsҽ'VV:> yI% Ω֊x›xhb8EzA^QۼjXqx8RGN(>xЫeX`BYbG1Ͻ?V/"BaFNt6whRy 6D珟%5 }ݞx:&IV=fa1vW\(ǪQRQj&0)QOT6kY&WC$ppAMcc4أqn Z] 6FYqWD#^Dg)I;C ot\n; Jm,I< 䃟|מxO qtp# kԴB $jֲnSӰWW0? 󛔚k+[^s^ j蓮f&EpdGOZB$̾|}41~8Z> Z0]Eh_]Aj?+ҮďXyOE-Qu%?/9/ |?dIIpEw"ԢނW2+Fz֡%avu9@T)xΝ+34'^^]*Oj_Az f1Y,>nb }͸tckc< _NM>]<=_x7WRĭ$.NEztkKò9i#,XHYZwo59#[-g$k %NWp7X9]w`"IE6$>K]`??l`brkI8nZ)#m~yᎃvdnpQFS\?# 8znDRף? t[}^dޑQFAMA T(7 dt;Ҵ{Y)f]^ eSw7:ݼoikڞ5cX*IiNeslaOoUq02%G[*Ɍ\M6a(]'u:މE[m@OrRs wefT\OzekF9⦴a%gAҝe3þ),W8NgK.́9k׉!i-QБɮ;-lÖrӍ8˖-uTjei)垌c5VkZ{o6K"?[?.38ʀ1TϮXhQY˸J1wB_q䚯b:< .m58بjcXSbV8 tkh#`ܕ"*^#E5>\}+[Lҧo炘*y$CgXl Q,3kpIDMIZr4CݜrYϦACo1c-]%( 8XѼ'71c$sRUZVj[6|;=eqFNHZU$QyhuB(<,T-w[dgEu*9>}?H=jܖ:Y!I8棞[# *Ȃݕ@u6y|6l<Й5-֑.UW+'f㭘RxQ`7FC_Qw+^8?W5k+ \KI/'̖e21lx)Sҫ2c֝,3Ļ0@2 Chʟ(ڮͭ>V[Kg39~x$)-qp&h* 󜎤U+SR]rLf7Pl*j,maZ6>ưvo^);dҰqs/Kn5Hq]2 ycOkC"6F c5xԩ=J&2閻ш \αs,gGzT0EUm丸,9kd/vF*јw< FzӤج@U9$j:>3ZDz&tP\nA \AY@%S޹no s3YBbIXg;`W%GFݐzkR҆ڔXKsÔpyf˝n![$%i#riXlJ.sZ]2]$s(e<,.8z,oic=#ҹ%%tuZ,cjcq85+4[uk#F |ޕbA)s=VM[ptju F97y#!UK3zJXn ozH47Sϯ 0^ #=qސp;TU{R \Uqڞn A}ЋA]JǩVk*͞?Jd2S)p޵ E݁銖A #< gҢ$Frr;gޕhX)i7SZ` 0p4)|pq4dyy>ir3%md<}j'W1'֜GIcQ'BMvG&d|#g3QJriPwYݐN:/FqK_ϥ 9M\!+Jl=lozsTRv1K 8|g!I&Z-sڒRH9rOx6IFDrS8RE#r;QpmR7ܞ,$"sޣi5$*-֥Hg馁onk_O8YΠv5p8"I^ j=[XuKF0Vwd';bjx,80ǵ\`Ց'!T #ڸQڢ09Y}j"ɍV+j#4q)\U£6pj*fRoAF|_=h$ HӃҥ xP45%֭m&gڥP$bKȦ(5~f*ҵ(Est:82eRjݞ }-SL%!@U\pvmK6*aF; iGb-zZDJ va*w$6S6J qZ?Śͻn8I FAv9[ ʱvY>g,Q$TWPV55} 1ZNHڥIijƆkH\jtҧc8pC[~nU&'ҽ'2TGg4"٬4 }ɷfN6xҖ!S@?}3)}DcE@2 rھw}9{vnNMQF u]Q/;RMw&2F'[θgK.B֜RF^q޶m؄e9ZSK$!G/2Jy)\7m}2;sr>f 15 PYGi;XŎwj!nXzf-oVLSҺVhnoDc[$I^G݆K՘џ(nJg7G8i89RA Ÿ^sS$U82FI7jݶsσJ ɭI-6V"g\c֒[ mj1Z 8>lYLjݥ8r1CRdE8=nTG(X ZB~_oZOa8>A8v8l(IAZ<j%[KSõ3(`O[>B qQN|ͣMWi~o9+UsaK`z{.#C {GWa\r9hRF-\譯c01бB{u>5.rK}-¸xP;uG{QGAq&7B0q}+礪4ֽ%3Sg )FZ_I*£P\Uұ@v4S-&py`Jo$qTYҙU5J`=*El#.!#Cܷ5g7H[11p^SȵA#CqHnI8c73$6q(aaDڂET㩩~֫KRICJبkd$壷O@¹n*|L6Îxir8?μactsY30UOv1yҿ09"SMYe޾8XϩbkW YI9RmqQ֢FX파:i61Hèjs4#KRi+~n4\v85[nsMN[ l͛kK#z]0^lhewsZ:=Fhãw=뒴$cEVP7B2c+| 划EPסCrlO>޵09iR7DsM˸G5 ě!@quB+niQU̩8OM AMk_XiaDЖ5C۝ZCQ5"5=JʻE<{Y9w;(zo J; -(J\* kɠX2MW, sY-݋y=j݇r*MHIH 9&_\ũ=U<3ҝeSDrT5X>Bbm*k|u ճ&eiis9SzKRti 㹫+8{9dOs!#AAa3FƤZk $楓U?+H\=kJHi.6ZC&1 Vg4)GzXBHzubTʅsZ6~!E,R]JM#-t:m-bI c9Oh%7zj 2gƖ;u,-J쪶б.R-P-m5*i4X1faCH2"&K[#vjHq4+EuHE˒?1(Nfl{^RIhGy[HdWjȦE=EV<+8$rby-[^b^jK,yeC p@>N :TXC(?S֣ &=k5Q7dS$]rNÐK5nK18=Hvsq<мHpqV,q ZqNTZT (=qY^%#TjBIjl/`DR:eꖓC7.z8OwҰ,3Yms$ O^X%wNPlXKI5ck/~AĎY4 Tt<9G$1/}5Lc,X/n+n&voFNI{hC}Fi"YqxoL{B ۏOlbF4ږKh2G5 iJQC`DWhƳ5=No~1j*zTuvǙMt)S㔪UwjrhwqwzgeeZq=.5b aO.{+8¤gО"Vx^KpfNiMEtQ4o]Y<$҅e&Z=?9H'yK)l\y7S/tqL\ *ZnY,ӿb\[C!h6O8/gr TmmY9$Vdub8[R١$kܗJԙ9*yk3Ğ1AgnݺQu\¹Xp#bճ(UO݊"y<]B22MYL!KzipLrY`G&6JF=NItyҼ3§Lop^qgYDʮy&k/cI[ܟbO=M'Q<!/HtyHpk-`ivG-HׇEOa-Vm εAoielX۔0\gWRa!qzD9ᰲ+w4r=j6Lm/ȶor~֡u~X :t5-޾΁$tVe)Z<4e;܅RQlxkR ]ܝ#bj~$ҙ$y$Myr)f9\ӵC;As@kJ1vNgD32r#vyOcҼSX0~+޴}+ȶfcTo? ]]glq?Z9GmN^i pLl͏OZ_ݬ\*?{xsxA*{_ X>.Q)Rrjs864xI`}#H?wh5w:tPdi[ĽˀMe]Yj*zQHa|1kdU>x\Ү4Ґ,g.ZC50wSe,؈;qN2ZhҭXTJ5>n×,epFCXZF5pNv~~5OL27zס躕훝JO-5:iJNoMjL6ƹ+].6g=Z_y&lW q,|$&i)JOc(+Os[G'Ĝĵ䈫l=8Gtp;utFRxurA^)gUVʻ x-[<95 hD\Jp;%R|Wӥ}@#o8$Y&+ap[9?}iH7O J )[_qɈRy<&fk{ԺT6ڬ`Lߘƽtx_Ri.@sί}kv~ѥ'#i9(ɝ0J/54-?MJw\sR:+ú]2DNx:ml& '5M+ mHc9^)#>~ dm>2UH|dHbڟߨJSm4̿{kB.'-2HP0DZ5;6&ci{ &w0K·S1ݚC8\sF+kCS!i1RXh ڥ-WOVMp px8Qzo$ZF/V>eTRxrOXb ZꏈthOTV%"/cxWrrhH^OX+ euo,zݱ(>uFL3[emYJ25WT#e85iRk͌W[kk.# 8"MKl;e^1ԗBU4pZ՝ͽf#$_.?u7Fq^6fD\WxEԷ}ܰ':؎Yӷ2r*;RCd.!s3:,I[$=Tq pԢӺz1tI~, .9f-יcqJKF '"+G@?d6xY>LJD~"mCskѮ윢6ӑGEs4$1HÜ_c^O fkQoJD㫈ެ[4a8\5T0 x9ɯt(F1\Gt/!1ƶ!-/NƴgZ$ZR>ເ+Ѵ[uaIJҖՉy͏JH8aC)a{VJ)=Jsk9nBM{yVqɮ*-\';⳵&[M.8$wSQ- Tܙ$IM.9PH9X*kSu.U<@ \{9ܤ|iZ߉O>:s KE"**_jf鋴 IU>cA@5ɩwcy57xDc8kk^I!z|!$0pI-x5HnueiQjJ=JGX<w9ͭhzC;ykEyׇGn5LYnW\Oc^ÆQ>{?ʗ2K\KǏ{"RÁsZ!.E[$$fD'm51ήRFMc`uEK _6Н/;+B•9?kdBDq|c2Z٣V5ISZE- VKD͝9O>Qkwm*|hc 1_:i h mvV_d;I;kُ72C dsU|7em(Gk$V7vo0TZ>xN:o pr}V:Z > i$khכZ\j<wRDId=f"Hr명i6L(٫{MGip ?dq #qW]][@[FqDy?iiw߼ #UDFqԯhyǪy8h5OtXaE.8xZ]%w+#S*]N{6u>G FP0(55ͨ?u`1^%['?7+z417P:VKD+u .#$'41vh$i" >d(nUNkμw,<Tg&D]W=R'EH!@9ܫT}D3u-5)}S֞eduTWǪ::憌&.B9 `_iOl77Mzva6G$f)"+l#+o Cf~lg5h^TFw)Ԃvl׷0Z;;)\gg]k+ .VL*S"6yO "7]zIhQ{Ãq\Ṕs隵֓; de$[n8rNط\DYYHQ.ochRZCo5@= gXy7t2+m&9K@zvd[gXo>,83 Ո@135>~K1afz4m3uȭbKXnswff+$_P)?t8v6IpOEKsP ި?湧OF\:$}҃:t-sU,aY<֕ f\٭G''A՛2iF@biU;zf_1]͙rwZ}]Zz7L$#{*ywm硫2DQqDfqM+t'$ɷnq]/&~=W;m$x$m ҫʾ- (;CGY*[B2yZWֶJ6}c§!UIH4!ǓgY^ֹTLWY$lp-QPp62ӚJru MT}vD8\Ug|~n>+OŒs~lgԖb`p09CQf}c8I =8X rqښ$c v撎>B*cjӿ<&!YG9\g5HF9Zkֳ)7$dr+'j$eHRgCpN1L`2A->de;X.#p%!{SŝH]պΌ|-Ό 0AlkEфև"m5ysƩ˦Ζd;֎ZhݝvHl9_`1ӽnn9F]©_cVVщm /#R#;9T=񒭑TkB@0i`ƱomzVylg `QѣY%uc"H ` c@y_m2hW J5noEH{\ Nޙd*P:7TH~l>q{j h8hɻ"B$6}iv6ՆH #>& J_ByWLqJ0kuku pC0n}=W$8N"f]& iB%@pzQ+@j`5BIϵ MQ*a94pDZs< YN*]ҭͩcp+mpqB0pB?&}{kKА# $S2ެZ)ZYnN\ n"+Zc N: Fy Vfwwdk沅8Z8;)!D(N̂:jGcRХ+Wd!K8I ێ)LS#*g])-G gPrrY!IԀ~Ui G b^>\'$cw> F';L^n*m$J0#T.R!#ϭ$\ZKeY>U##PYY'ZxQ?ZTsYH#nF=' 8ɦNp?Z"IRj2DsQ4r;pv!;+*Ǭ$O֚ 5oAU?^V>x)n3N=wVqtyv򺅉̭pבJq{+vUø@O)OeqW6tQەYiq#ִkRi~ a$VXHeN3[S][D5$QF2XchҰ+>M9t$~5k&X7i≰?yuV 85~ 1r>VϽYM$8TGOpt]9K_hxdϿ{:[Gب4b0~S:UIGMBtӱXu$7zJg7 sIA柩[ƨsErE8Lj7~%q#ٺtO(w̅WnEuzN˃nøQZH3ڭTr2 S>#) sHxX=2M5!pZHZPg,je2#GaӃIrGRTnVZ6 3cZ~oަXX?¯# sI>嘀Jӊ6䐪GfȖ`>9J-2VQu=+RAěOsTVÈsYqӊͭSm?3`N2Fjh1ޠuSR\'yM*=44Ũy#)0댞VӪ6r*|{r38>AuD>@K7QTD=1XcV&=G4-Sn /#㊱ dN;sb[HA-LTQe(>Wk2l"||[s̒IM#э:v5U\ޕv,9ɮp'ҮzRcU™B0RеGMO$W78^ EUE*8&eV8ͫ+i~@i z ӓAe@df뚕͛HGqR %NHWd@M(UMna"B+6? {HI:PƖ2P4iD%[ZH8@M&[Y%!z&B u([:g=RaZXȨ]1wHsmKV5cJrV6vͨJi׏?3J5^!x%mG^kBH-6k̼aKc(I2ynZ)_CʅFN{tM:.q]/㺊OG|qZ{cQќR3XFk-#ڛA k7VSێլ*GfȩνC I?q[v*nn^hLCUUYX[+]1}Q)5/?v} (ce}5Asқ$%:VM#= ~  83Uy_qIT г3PIIWi-=O(qQNnZlj&0_-Eu̡U2GN{F//''Z[.#cr(c] T[4\׬,492<3A7trɖ5oNqT-*0ų?t3:2+~'TUt֝|Y ƶI*1Y,;MZ𨴅Kk3nA[PdB?kR&TM Y]:*SNƭ nCl-#SMRTV=Ćjպgk=mf?u- e^jK+*NAq[N2$cJMI\WۃBQt)T׺OkCK~j I@2sPisawjްXv+n]6ҕc\I3dc*V2&xp$cu#;Mk w+"9wүZCe U8`=6[m>,< їޒEW ʯLU{XeBF?%񐟳ǵ fjwZ3ITomI[->dچ^m*ʰjJ;Egj7H InD*,Wkm*Dݛx~{;*Pyxj֙G0#zY[ܝ\$S-"lPft]1]P7Mۙd"i5;H5Aa"KBȌȭJ2F8RçbTi,Wt}"+zWKiR.%G'ڭlnTⷋ-"`#WPt6, SVկ su J&GU}z7@?ÊVB&j,k&ß _Lnylװ"k: jOoe#?/@(U"P5yKCnX3:UU:֞kޙ< zϻQgbcZ56.:r.H.t[hِ=$[Xj[/`qVe!Ihĩ8)g5$gjGOEΏrB, ɦ^iV)l۩#TG3gL;yo&YE޷NÚH'|ZCPVȺ bF>c޽JD@1銧Zm#(a9EGjk"jΡᷚ!ܫ uվ (PG,,P\5V(͵%5s ˎfQ[[үxY{o5 #|Cy>kGc:2gxsCZH,kR_/Fuq&< g*}2kR;%JD'^q1W7uƒH8W}i6o.@9=rW1HH ] q9|Z:eͽ#RZZ+Ffh[ڍqpqWgeR#@`$sIrJ7JfrWT؄d*JEk簭E%nՓ֥q#$gZɭy~binLuɮk]J3Ajqa7ߑHMTOYBz,RLGY2,s}5wN^n9 wLPT'5NSIgy=/.,Trk_N"f { ]FKpAV5ܺmr[X5uc*.<*g9ۚ϶(i1^ky(y+-v54.98vCTWl㤔F{WiXrII{z5/Uv BBq(]-قph̤$R+Fk{,m.ȫ{TKwSOqYN$bwbƺ* %MB0d$Oi4s.nVli``I㎵M< ,gUC(r٭fJ( YZfC"3K$-t'ήASmcoSvMk@QiݙN:z㮒wo*z[mtw>*Wӳ_owq2HpLjU9e ^I)Q0׊l1FR99R8 aNwWfEɝF&R \d[I7\_+ &V`'NjcTB4i]³o֯s{P<[tZ.k.bzU0+װ\>NX b{z3[Iy T5~G-jZC:[]U !n+wycA iQy7:b G9=:kԈFOƶ@mrʀ{6utm%A[9|CUĆ=T߀d#]'ՁKFQgx[7qvaҭ[*ãqyd )ٹDB;Tc5|F./Rc·Mg~f4cdחy ̞PIu9ۊPȫSz#odf/jSpGsڳ%-I爛,8Oz㝖oAEFN+fmy.\Dž$6Y$GQ|@׊pi W mqN 6 !h|E ۉWMݦSD8Mr-DBq /wV)\Y*ӊ+~#XajIzKE5;Vپw5۬vA]MƧc+[!#Uχ][N>H??28M6h:-Aw)c~5KbTd7tRKl4]3gٴ"P68kRQ2#ry=*p\Wib2XÍWjȹHcW-[5} Sww+v7|o"*#<"F r\~x0G.}E]F09ר˲t\5M&v,".f~Rd,Yڐ$K%q+Ү yH_ZUݭs`{^gl6~Z"Wj~bmBsެFIgkРԵE7U0ESriii[-ÌNkcX#I/uVͼk(>[yHFCcT\2)Wi.\tH,y&  oߊqЮ,N\3JcKr%)BVNvԮII7B6/o]" Ќ^\Gf#v K&2KVqiiJ2["l^bT%Qy&H1!iRI~[[ٖig.Aa¹;Uw{$E ٠v=IWj222L:Emё'w =sYǚ̧6֑aZK vZl4c8Sja6nha]GcsǛ"vkHqwլU"kpFqM,'+OCL}&- L r?CXzZf%>3Uyl>F[_͵U\G4gHW΄u8Nx3xDžT; Rf c+І*^W95%c4i+:P2qT4eդF&L`rgxj:sSVt%GڑՕioXK"¼+kՌ,Gc׮ VAj$hW,ܮ^7WPj|ƏU-WZ1,е`:3L6OV5ޞ!Uޜ4dB䜚s};קJ-RKy@bLZZ]48e$&T.G7e]G.ṰPqWVK{8@1"uk{s3StT%QZ8g\2c[2Sin;ǛVDQӼxF.Xc -WejI6#P%V|6{{OІ%i:"( EsL]8jЊI3+tjgQң2I J#<66մmHSsܧ!9'4)5o愇 Ս9++0CmeA{h5'i7&QQ|j"#*B 5 >\5q"tH=sG2fr31G8`2݂sZ][Vir$f#=iѪǒI) 1T0#`eq(??iH"_;"9U\ rjr6ztXg=yD!;XgsR-)[9F҆ܤ5ɖS 1nf K`kD͝1øޡf&3)$M67LB| $'RU|jp(OvEzS$HBB3HWϔ7o9[&LGJiAӞ)Y1; 皵 T *MZ C"!uB}Α nہF>b}sPޣE6ǭKJĶӣEڇ'LKrsZ\A'πF9WR ΚX}Ol{ivxuKX:zUƻz3'NC.8 ,xVefVݣH$E%;=M' X;Kz#ROmUꜷ9<92X8@+m98ɪivCUCRqc#mLqq]Ɯ̪ NuEb>VE-%dqPg7;R51sU%upޤTHǥG,1<`uX'E<3$ذāN3B9X:AT7=i0F W;VURjp /<\dQ+ WKL 1֜mNcܠTC+jw>bwۊ~U8VDmo*Jeۈ9&,`w`NXa 'VXL6K5NGYălf,]Ehz~W.-~I7Vh2X֛qsm?^;|(By4Y',S9{MF<ƾj *B?w M.p16sbч2=UUЉn̑depk \/C;Ѐy㱢4{X{+"Y'Mppe%1t-YNǷ<' ǁVC6Th}N} D*513R'9^k((*3f@˞G8ML^OVٙ ʇN1mծSsh"_jZP ]&u.C)]NUbi#XI6ʠ*^< `Q'v<+X%ncTZ,O1Qkl+|3 m7-)5jB_,юoAGj,w3՟,*h 5-wZد6r \rpiX)|1sJG1Z-Y 7+ڐ\3p,v[>iLcC{2kt@\?w٪h1"^5Vo !e2I$p%n(ʴt:IW0U=ɭ[+$$Vc'M#f'u^֚,~Vf|%46 s޻];Đ.*8PzD82IIe1¸q}J6z/jEoc$EcZ-,rMLeRJ1{g36e V/?Zղ#18]<ք0MTwU*?2vj: I\gUJWwEjxW3259 k^Q+>fv8AIكl9Ա1].љ3*$ ,tт՝ s\>bSޟw X꿍hBip:ԴBi՚1o^ W6U82ћ}bV&(+ cQ osME`.u#@pOWP-I/SgRUbW *GW6#U> Y;hyTeͧ4X ,}IgUBlS^tluA knPӉ"udkĬ{t Se CF9+1p\8׊{|O.%D*&8OGӑB"2xu%yfm^iWbux) Kq&8W_oi*[:} .ҀMEjڕ M]-O1V\g^)pM{dү-p=Diʫ-ɢxN6g+ZFrL>–-FydI >ö&C\TJ9mܩ3nU8 Ӥ RkoKz}$16!q\5^ūb[fe構ÐX[v9!Ka:U{{`mrm7Aق[zsqܓQ;d5o}#`p)%˪Dn$6_h]geo\g.ۜU&ߜV5' k֜Xp_cpFzWhZ[Xxor͒5aSrhvf8h{JRxFmCPfL/9^ ɑo .є0^ݒGqhi95;V^G - (4%^황SɉI 9aoÝ$ +v\ }iEoy?3Vga7[֦c!HSԥE o<`Jnu ?ZԘuF@6q3ꝡ\}.`@IjK4L2j/NF9ɡT~30ᔇszv[ .ͭ%sQ;vLbm\-wmJe@O5&pn9 hYSךtacb6w]y6;vU9ڃ#$[Xɒ9Fbom&Ilҵn|Cd$V}u\$kUNɩ=QE"'焢AF$@`j״ ar ͎9Tsڌ jț]];צj:\3Ƞ.y_xV@Ylgk m#96\\>j9+4l  f<k[4wad:]r͂;L>,=O5 26cj֗ˉ+Ώ +K~߼mzB"Eq\l1ŹfcWEhFD`V]T<26ʑg%؊;P1R' X1[ѣET! OM(B髖:$R>bG5o[T1zUF``ҕO1ssGtm5k-䤶G6Y5̢1շݽ%XٙrXĚ%#3ҹEo|ƅnUgMO4Jܩ5ut>մc1A$uzv[ΌykdbJ(`"pqʓ|weWV9f!w{U#0+.<Cw~+=ֆ(Q%e@ҝ{b61vֹ;ǂ+_0:sM钥r}?g`lv=kȋqzkRhX#TLD,*=+ԷJKT8ym&?wq5b8!xv1=+ѓ;wmXY@֡b'zM&>y=qةmQr=j{kp.3WkY(/L#D*Y&ƫ=3(f;n~DcjK%̄r1P촑OXOL.1nƛ]/@:sɯ>Ҵ!*]+ťޫ8ZY U$˴RgP7nfl'GomTZ" elҒ2X:ma6#qWUbpkVVw*AZZ{)W~'40@5y=;6󍵑<-pWU6/)摰*\z|ɛ1_Ae,0yUk\NrsUVFʱ֟+{jS')pX3{;48.vyhrTpkT|ɛ{nu-$vmcak>_2 O%ۜg Z57%^+NZt+\$dYz5o(p\I"4ѴLc0+GPtG@X ުKqk:<5jF+M*BOY9lW\r麭CTQ^jlf8D:֨L,wZW(j-kiic#mWZuV *BN3q<9۟y%i#H{&dz ʍ KHt- O!G#:Ŗ0ϼ]jc̭3){}JmCȦhwW0ՈeA6 0$dmWO2Q3ت)U}djȲKȩ{+-[\$\pzqLԒk=Ew)IT8t;Bup\*շgł:Z7zRJEqjҋwsݱCQoގps~%˺Hu3FOqΊ,ڻF0*Ɵw-d"G33 D}RϯuDR(pw.̗ ͋GO±- "H͜qlҦqƲqs4X(ŧm>cr G 6qVVӡvd(r *y Mv֖N:dqI-EYl ڴdtmoOH]rR"$|mzQ[kQip͹raZDpa,q*cQIͭ,Y<7|L~v)~.H`C{$~!"&UCXWAw;^ϱfGz׆gYs ˊ?bF#s{j%Jܿe]xufs޻ x6t#3Xy{PJâ7x ƥrWm gDZx²(H凮2ֵ",mCY\zb|:Fi!G_|m;#,V}iuBKiaHwop+F}pqȣqjHn4uGGΣnG )f^Fs)jlq%5߲?J̯͋̑猷^۪Q)^l;?.#6o1+Vս|j_oTq^ּK-Rd#i;I'Won LzSƻ]/VRX>WgGsHۊ3)&5֛]Q"nB>s*|NKc$Y1kVs8(ʴ乭U{pZ?KpO3`uox+B&[Gqc`+v \AEyޫm'n{YQgE&̾Jn^iƦx> ,q^sFΈ[v΋,lOrhԖKt)X̅2sI}m:dZz-6&ԏ.qw"g/Y k/5y.kyq!QV|B6zï:lJ3Sx-QătM@i3@$OjSzNN.V42喨6Ƞf&PZ{&KbR}H[6 eP:)hSKX:Pri'9݌^[ZAlEt;5rυmY\>a/39UI;:ԐXcA>k|Q%+*^xnЪYsZσFh(C=SeR(-QLg<ⰼC-+5CG3$+!*HDte. OSe5SB=4:)0/8,&Gh#c8,5kw2ٕ$+[09$9Y!ϒZNQWWOi/[kGOS}t+y8 V/>lXXa8lhTt- A YWf].k2HgŽf{;+]|6ڮ3IpCY\mpt*ILsҪKyVE<}tZzRyvsw9 D| Sc$`ƈö87:ʩ8c+'&mG ׮WҺ[ <{84oԊ卙h:J!wF瞕kmic.F`7\JtaҢgIn6p*g$ <$ܮV֤:lF8#oj)]ɺNJX:2B!zՎ*oxk0vK#Z{| 2BN4 p6zG$ad:Vџ4yZ+fV2iGpb;iXo[͊v)[hGe0XCJ[0f}n @{kY؂yUOĻ#0Vul, qT줉ef~E..@$#JLutܧҸy\wh[BXT]u-jK8dg3Yn`Y'VuҠ~P H隰v5Fq\Wi",19s䓚~IPCSY'${cf* ϧj|e[I<㚉 >➬{+L*Η9P Sؗ^x r+&@5miɮU>,PiH-z1QIdpI) jE;[!OZ&RZH@#j6:⭴G;xP6*Ftg~*3;Z Q3cSyu!ST'9=HEm06֮B" >wе x܃ҭcSUo*= ^5I93Ң0\( 6g$07\gE_7Q(MӐ1zip֋Vpb4ɂ=C9>yjkDzТRƃ5RRTzT2}Ma >9w\WW*IY-!.HO$+m tr0N;ў(Kqc^ygrM:C"Gm\Dpc{+rvPJ.u`rthK Wl[F՚ .@=k6eǂ2zcv.:eYqI:%V;;"Ж%qQG%Gf\TU<>J%r@M{+Y%q?i=OzJ(jKF͍ilu5>ʢr+[Ċef#ֻ]ܮ 5iv7 UΙtϲ~~^yQ}kv`) U׭t,]])7rkɼg[۶m7 UirJppzv*^FF:n Vtک 5{rH"*HÎ> ? 3ĩ/y&@FUN@a权t"&`mWBRF ѩ l ]Vޕ'fڊߕjp[\mVi"RG@==jNݜVjL#`XY?'Vχ#iB +t!*#7+iWJ7\*chKhSy޶H8=W@0h+NVWGB٭=;KΨe|ҭjz1y$Z&I=}k*ڍч9#Z#?15qtյljv,H@38$v8"2ꑌ_4^J襗$LGR{W{<[~ mYz֪#`F'U;q}rzbuFpe+͛*ְghj:٢nhw IRE 3^?Z~k%E\]:waCVۡcɓPv,}] +__ྕmM}-EjX|.[u٭`m㚊8=xFW@ZU)15,J]j|áY][5G؞y9q5WSϥ^)T#;/ZҔMtj?:~3rg9+8zWx{_tRv:Lju 5~7S\:ӟ,6̻܅גj:d}]z9l`xI ⣛U@g]ʹhogĉv|*0+X= 8I. Z̺|ʐGrU#s;k#}I]4RqԆv=Uޝe*ev*t2c472HJ)ֵ5i :ŝaYW?=5iҗBJ*:ܱ<`M+1LWf@<% #=ju sK1+Y":q qJ"hǏ,i!u''(־)G$h֥1 iAYWzm('nTm,nZ4,OZKVE8vZu5Lk@nṮvIh]hlo%NnD(9zu#l9\4cԺWzHUWFܺWc*y$-Fi>Ȱ4$b9&.M;Wul+iSV0K[8bmރa`9WEmxPOV`p JIh5Fmc2 zE;\JvjD}NV#̗%;q֥b"wȫiK.H֯Eao#Q'Jr)dB#vd!V  0 TtqTW6#",=qY]98[.J)lucֶ,dq$KiQ綾?h*b2r>m8[xq+\U- /qtRf<^-;b5`־+֤4`Dw~$Ԃ3Nj\c˴W-/.:+7RC.땣.k)&<:֍*sسjDy8jU3ֳ_V\ѮmOz7m* qs\[qwv% Z}BDH&bb?g9GՓQtVvuhk\Fr3J-ŏcΤԣ>I+#ԒMSuE,t54#=(Z2͎iB)_QT"V6$p:]rBdX\\\bWdv.h)u=a/Kr{[8pǽWvHAWdm!It}Ѱrz W^C9Y٭LPrzԦ dM+{2_~TKd 4-[FWn+9dj([ufaie# 8j֧8,v[.wp $8q'I=.෌pqޠWbT\4+R (&,w[| ㊨kpuVw^"{|=Gri%f&\-l~zFӑ##JP$,,nņ7m\eݹPp sNkQ zf={B:j)33O{wo<Ğ]Mj2ڴJÍˊJH;KShؒYKg}>& iMt?e~2@Q7ْ4vU)jTxtCHjEAy 0Y+5̮ +PR;0Z=JB.ubsT@a^56%c c#+yev)8($2j/,R:m" .$h9o#|+pn;;dRE]:1q47\b4խ'v#KxkYgmP9ۗ#L]*Fmy[{fe";V nfKt]idrsڎv<1\-h!Bn.eu$+3q!JRqٝ H'+@S՟~>\66*Q~8,zFz=L`uUoX5>\j#bjZ<# ֪jZ#9S=6TYS˫J2FxTw|}:sJ5t=u!֊bOekwB Gk퐶:R̴ 1걦vZL,UkM* s] j>i4c#q-xffæj T/k}'䴓m\(um}bFUY/\)kxjib]^tqM'CoM}ƾ{[- 5Yڄ@K29*sGl7*{eEjit Vn5sժF?qKQ7xx=tu;ROMZtIj}ZJr'sH([]vko*oL}kg ^q-a>Ur⚶nv.⣅=*G17!ws,HC[6VT\d%am>hŹ6\s[%+Nv\^M˨ݗaֶм; p3^[xl6_J¾.a(YZnRIr{(IOSI?6(5MlVټ?92$r#G]v/e}7VRkFЩfEqxv@ei /3^a4ّ;sVlu!`su` znl]{jqiWñx9B Mz]қn29jy1,cZTtHvrgmdeB99s_fYJx89O,CJ>(}9_X؆1?Wy7SVAm \*V7EZ}wBcb:X &}8:s$]OWVC,wR3̃wAi,G?ZmcNGt`r M[io-b, n,8xނ/7lpeUk[m"H]VAXs\۱?lN1P1o}W_ݡ5Ad,vbff`r_8x{c$q6둵|Mݤd3W9Ji.zM3hG?vMǻ̟WT8@KiE>,lFVHڸ? vsujK%1Փc)au~LQY_<<+k6#AWj~С5 m g>sYl1[1IUc7:(P~gv66:oL2Kw${W9Y.^\B`b#ÖxIo=R,cN˞-^^<%AZu-9 y€yYzGm;+?zv񃹲ğֶQQO'կ.~#CdʴQ'1Z.4_Bk3?pij^DCŠ%89jT ^Jֵ;29a:voi`d~uɬ g,4giBbHqkcZ,;S&x+~aϸ(x)ͭ8F],iL\lrfZ5N IoWL,ԅǺDgKphr[~8jcec`UuҢKrOZ-Ty>+i`O3\5c5#У'r5MCTkeR%V}ȶ Q2]۔h_]PYJܒ}=E;"2u1hm.wkP{m+ g/X,-ZIO\9T9;-EXj_+CB[Oπ+Vb's+|<%G}('o..<@a~5-Ǝ&q~*@cፔ]HfX齗T[_0Xd`99?ž,B,#Ӏei)?i&R74.iwu#O7lcy=kֵ{HB5>Ea,D,0W+um#cT'iF$bH5<(Blu* =@Gq$ۤ$)֤皸Jcfq֜`7mR 9;2ơx4ݼYsM~ҫu)}P 8*< `]*zR!-Gzm5++Js)$`dcxcV!߾i?zkJ66CCLF{y>s p*"8 sO2zg;h >Ir21&Ӝ=3;A) N_9|sIXǒGCj S WO5$Ej&$wȦ=is/#=1M mN^YPw|Hi!8)c<`@g?*;d3u >H~qSkru V<|,S[;xO[[B$SFr0(BO'ڑvU}vBpҐs^mn}}I&$`FqIB>\~;'<(փ3/y%@4٤2Bt'P'85^LCEo)2첀LVSdaf(01Q+ic=KO/bG29sU}>M R4;Jy;rH {LXKȸӭG7<׭@3֚򝧧8rbAᚉr #֛!>H*O֩ !?7qLfdL$pq+ar >NAne8h҅,:Dw艚_}8F-P⚪Bչd{- kpz։f\u UM1֕ӐFqh7ZVD,8@PTK}+0"}:\5iT'}.\:б #,W4vxIDZSgDڅ9=j͵Ž\s?fvp\FW#GJR{{R2L!\5ʶhBw1* TBE/) LtT"߲?}SXep:#iW)FVUUɴv>$QBckWWtdάǧ$cMgq_uZsUZ{ @EtMTþ[Ř! )[(0*̮@3+Z|Hn՝:vWDf<ؓ>٧&v 9jod|r,0kRx].a)өG$V ȱq 9,1Cȯ:5:ޛis$f-JIhօ$MkIck Ru+G x8Ȑ*TaIUzg#ZfoG;!-0gk|Ϊa*:S5;%Jch$AÉYaMl dS D# Ib=qZͥU|/-u5I1?#ğ_rǦkM/ǠƑ}E?/'5z9R ˡ){ZxL0[*;Z-GP{0;Hlgc0~D_ =AXVT3n9":etu~ +*,@F+4ϱͥP21ּ<AkWNbF5Zzfpp՟s5c1qk(]2Uy㼖bDI# %NFy"DQs=ɏGR94 @Y7^#JZýMԊ'b̋nU H+ՠ;Ɲ0+;HfH]>`J֒&nmEYma!qbqݎ(7zסc6! 6F8JmN2Uw;sQJOTpkl7oM^OC횁P/􉶆#Ƴn8\2k.X^X R/c)GFc Ǿ^䜃կ$p*Ft_J{@ٸAڮCaDsXf#EZOSRP)@cejŢE %U{T;Z5^]0thۢPFYZA&'Z-©{5)f̑b@9cve\ƛs{HV"G\EI-xՂ[YIYhDZ&l19梺]EbBV|~!Ƕ:S|hl_4V6FsYf&>n{N:-9,u?vc+4n{-vFp:Զ>"4iA@y-޴hJ"Y.cƹ.?A֔j6Ĭힵ-ց+ fsj*!}tqm\VWgj2 '}̍ڻ&ݩطbqT4nY:֋ `rrbV<֕7|pMmh(,&zU=l̎ie6>X=붹K].W%jPjr:+"[\Ǟ*MS2,3$KsO95{ [cjh/5 FpZ'%̠ [)nkOM6ɟgaҹvP*ħiȧhsQ]s"l:_:UySڴm Ύ ZCwI?V-֓|'‚xh^5iYAƑe"?X- ܹjQ+m13Wj\\=: !dW Gx+!U b=ZK!M,f? .ĎGo ?k`\q9Y p{׎&Y 1]VtToqI=1cY ϭ-\*"\)Ӏ #n/mbˌ<|٘80}=V`=+~ ^3`8{kH O5LGw,!e1,`gj~)Ҭf8z~ẇeӌs˷`K.I= qKJHJWgk^;Yԧah:eD \ gj61q>4IfPtd&O<)K5I['ʒn8+R3l;sЊ0Fs55.s7+R$Sn'q5|s8WMqS]S[-5tKb"IkFr2U챸`.EeKm$s֭҇T8WsӯrH*:`6x u +#1lvvja׎FU[jy2L@ۃR1qYմ :2!i~8y=Ҭk 1S,ja&jF3IǓ;ޭqyOnk٧{I0i3 oxӦaK݉^'پPplZ6ŒXWms5iq8" eM\4eNZLRhS1/qGbtLҢ&'Ű@%Oq _)z幮 M`\^ap0^%H-*k -.#]WV *M=N>RG lO(@'@"oeJsgMh-x{ѭ ކ3>[=Wͧt#)Hc{thynZ{C/ʽVH_jf.!_$GL'jrFPn1nɊ-3OXa+sl{N<'Ðt>̑bM0{Zd~D QGOΰJNVZ0qBtՆyTVuH$֯MzYYZ%:EuETk |;\KY/-.~*>:0\Es]MON7sN9X즪G:=YdH$~P9uRشV+Dy+t- c<Im,='\6ՙXRY8/Dr}+mJKiFTUB_HObs(JzBAI$`jKe^^^.-#,GY7l'sN?ZmB_ UI9vmnῈaURM5oGt+ 뢎"<Ԧe>e择 2|n?~D0D\@j>Xn?ZݟɃ!HjOMĠ-j,J#Vms*\HzdނnYǣEAwH’6z'W 4oA;w/j֟+P87 j+_D ՠSQ'>_r9I'-KݴzԋKk H4$K< VYɋd7?ֹ;r7[7'&09xyD\]G#׽Ԑ} ^ai$EXc4ZkṯXis*GNMi}N͊8PWu;-#\1G9`bJ]/6IZ tv;xySZ:RV%ԣ_V= m;Fin >F5͸ :Cǚ/#)؄H>l?&5|ojһ[o#iH sT4?_dFI3D[md#[xb0r}pz*^Lt)%5)Q)*J} hrJJUb4fh r7X&9y`UmCxY,k%iBYnIX]x TKҼr3RLROÚdQI}T:.<+CeM&均,EIxbF,b>]͚[ZrۄO{ [fòQu­fez#RrRtYnY`z |=VEIZ#Uֿ1t%,Jѐ g:ymz?:m:Q*SoY c5^ưV5ϊR+FNT*m]Za>UVBKfVRARkYm˹ +m"' zNKa)TŬς̽sҸxpW9-nhꞆ7kƼ WkJ7 |܊ӎp ԟ"Rb~R1u<~l-~^@=x{IAmnHJ}TGTj]SZMG1$T:dt ^%Y|'~7ˎk[Wf0I$/J|M ,!l|5׏. 9zh!B t zG 閆 kXK ۽IS^_Jхb>\s4ssSKAW.I8cq5xWa>Fu5v26$^5QOsW[%!H=+k۞$%sݪKSUV=jT#s"Y[E'T֢nÃg8-x8ɵvpHH%sSrQWV"=>G4;F7#끚5{ lTqP9<;7g8ЛؔzdpjqjnS6GxH#Bshw\5-*MhQf*xi@rq֤1}5NĪD;7tDAV%Eٜ4uP:v]6zUr<0jɼzSa8c$+Uzԡw($Ѳߥ5I {Q{`#i{S6 bf!S +"ʿxzSdN=jb3O"Ei9^TJя+Ԁrzr`ў=iܕT)P(\<ԯHPeI9=)]2H.bv RG¯^M] $ NqS涂z"UMA=cP8놫`}hu5+hBܥ !ANOPd6r}VXǕA*&$UWhB#px5(ۍzIPb@4y+- JG"8:w'qԌpi{E@I<;F=zU)u)C94:)#WvN.GpÊdIr鷏ƙ'9灞e5xe.OQap瑚UЌ ~Ey^bdzES6jdž#8+UsN*y]iNp03VR֤FC櫘2Opj4``+r=\eb$+Io yLd!sҮo-$ `#̄" ցm>8(sK}Pi}ʹʹ+|A6kmVa^]m!mn=*uSm¢AEzTFsOٛLgtː#N3Y\esR1 jMo#SEsL^Ƒ_f^+3z$zW&-˪;40%hJqjIXUtiݮu7zM)$sVʱjkPOS h +icT\zƅWʐH$ 4Zd_ "uT\5[YZThRНP I; ;qiKQ4r.ǃ`wt:%X h4ySWn Y&Oz-;J޻S̠01Җ $C KUk`g#uSHp~LQtVsmvm \e}3N4ʾ!I*$u5\)'իEqܙl+g #|2';}k@(8g(q!UrJhʒr'o֎<,9ǭ,%C{kgC"0kId:c)FA+Esh^w潎$eEqDpqiP WFxTS]Q13'9bq 켌<=s$#IBE:pW9 ar_w+Kv5xKK?p8k" _5j)JI^,JpVcSU@aJw DouJwɜH#H Ĝw=KsQd̿n TT^KsV<v N4^I&aD-=}k#1J510z "#]^g^*\iw2`gCpaˏrg(}+FьHSS::zE%R+cW Fܑτ|z⹪R5fuפ{|mq4B<?< F"8j&c,}Ik5W\!r:)2W ooZڝΫk[],S}3]krB|1s\߈C>$y(ʯ:)^ORҦ-ЃG'ߓPS]0E\NyVb'iZr(Hm9"_ JݔD(Gz߄-.cEs^V*3+\p8sX촟 /sb20EtZ?O qg šqîp?mHHc yʼ$.f'?ۘ6pya ]ωnzTuFlϕfǨ|Ү{+/[V]ߊNd<8Lݶ6i^oA+|1tTvzXӬ\ wş@!ڢ5,-ߚ<73D+MoElzvЮW`}XtֆO.V[k'+ b=8>nR[@Xi÷{1[Qo89RFʢKd j,?5Aq}+iuQ;WTOtF(SAz#Ov5uKޱF"㙮2v"KV*=:q“Pn)'fNK1=jnZ@W8wIfo zW_kIo:D_6r|gnNXI}rЩ ɿdDУ(NIԄt*Qu\U[oڢ|Ҹ'[e~J]3lxbͷ.z)ҌW3iF~nA$p`]PqZ 6 TqX;q $ nNҲ+$XߥJ޵Xj)4j+N[ 9T7Ei4[O⎨෗]Xy5KKBC0Cav/eY *fZ&U9ljF]s/n <'=kKF9qv"$WJQu vqzgPtNk̀9[= R\//Ia|ehGV' GJu#57cJK(C<_(۹R ɨ|^YV)gq`僗 gvz=Y;ӵ]N&'j$W1Y=}8E5Ύ/y^L+79V"3QX)NcxfN 6$ڦ 倫Z̹r[ƊKCoV8{#`v+d9<`nISiqܒrqYԃKC^/FT`s}JIGXFjoll猚X+8vw2% Qέ#<)_#Zg?#ޞ3:޺&ch–wN22AiYʇKXdc88dna#9D⋢=٤=Zw\mt9d|1Z]~JIiSĠ.jjOָitE ,j:CC6WjHJ:JZ>h6K0Owc!ܠ;q F ) e2:IgI2#g}+@¡V]]R4 0sU#*%Hqe~z]X;)I`Xⷩ)_SGcr$vK~nAoZyʑ,51Z\nzc=- FHizt3w K@;ZWB,<#H<sTv]zW  w5bB2cǨ5.4䵍Uk/uC$uejkONLYw.E{v; =Vc)yW<**-5k,Jc>,:́6Ȫ_֦>'f:v;/*I{ +]& ]vw>dG)^ lquX-FsjVHb!~凹Cvm"nッPXZH:㌚\%ّ,5x;1=*Oc-(R)$LIq; yU$m&2Iq❥vM {HV.d-yOXuk_&w:oLqK|/r͑S89;Z1jE^H|R3kkTzZ1hs]^i RK|Ņe*PKޱq2ɡw/,'vYöc; ӆK#cHB0β_ #Iҕe붚'˶PB?ah &EUJkm GYO`k-sTpFr Da/w~ Vn-#u856ڑgq k5M6TMqvb *gfJ܄58JyjT+I;V=? HdRf1R^jʼnjS\|3ر309,neP|wsFnWSڔ7V1I!U^WN+W:bFr0>[Ci$c3*3Em8~j)bI+emm޼wu8w}UI+k[I?1qE`s̡abgB֩s S^Ng>%&9R}"xİ`)c^ysU{MxYtO {KWVnTQ裘c˷8mW`{EN7^TOaAW[㿅YA)U 6dBF\(/zv?0[۸HlW;/cXIKE}(5F([ƼlWYBVP蟚9){ܯ3RLVcmd++ݼ&6z-qq(ҝ6S5 Fp5'(J_4t>ԯ˨툑r))~gYyǍU yeii@-iOh1_=RohOr0+_Cχc[ &&} m ~UzonQzIrZTקT}?KHa֦m Knb{ׂ^hjΠ?d~Zoh"8-qk_g~qiB&ՌG8|לjRpl49%s<rN}+OQPV};_µY'g5E|M>ެִ&mcۗI`3k#S 0I$u[ IU \s8#sU9W~*=ŋbxѾ ]ӵ ^0c6Ś]ّ<QZLA{Qy^A'J5bح m:{k)W8\/Z? K+5a|v&=F7i!` Kt<k[Ci5Y8潇º6oDc( Ge[[2m;Fc}_ԗJM[&mFCʫ 6zF8AMɯ' ̑`y'\ie2r+g({=^=gg"5@MS:bKqA\ο+՞Mb#y'٭+^wsȒ |˦k,=0t~JGI3uO!֨ϭ#tBaO$ב}JIb16[9cu%{2B!dRRW0!kϢz]]M {'G<׹o+)Nr144KeZ'̵eRKSf]s|bmiK!|35j['鱼QdbkRWr_Òl5mu|H1i\J{z< {&cEu2=Ē"~E;YO-T4d%OtgިB(XwqKky^I$2TB62|Lp* S޵YPI `wsđ5ܱqT-Ub@4؃$6e!Y;T(qVR_tFFq)6.rCzj5'Sqqґ֤2x+m{!n6)Y^1! T2oFc7aEPNׯ&E'QIIAI9=OibWzԘWh"6"f;*M,5/9 qV|ݰKeׁz$R<ο)=Nx5mt@1O\Tnl=G͎fvdJZv/RO& ##9$riyM^U$Ӯ*@z})\f9lEI|`iQ'8؞mtQXZX'= p3F夒"2;ҕ'1O1 Ut_MJ('4 IO4H`$ Z)Zk }(zzonp1L|ث 223QlH=rzN\RL sI:)R#2qS )r9K} V+4@D2ǎ7zU_],N!%g'=bBjeAr=ie(3X T.9QC'(Br 沕{ntF} ӱ$V9AN+J㩈$$2  ֨b.r*ޚwʽs6uBqfH^dT\{HDx|*)Q$oZݜ.o8OMýqVJ\( H!ECS4`ֺExzM|>>n v Z$»owC8Xkť9ѫ(t7SR* YZntD~ lc=hx`1"㐦Y{#RzN? JpŁKkgr n8QxCqy&ݱDF;^!Fe!^BflwM^| Z#z>@s'Jm߃pOBoTϭZH:8棝p>j%܂1aa_ZWVƁW Od?A\#I9lr(eyrΘkzGճ$Jv#gqzV͜ukjtcR"^4U`?Zx5XgQ%^}Ob;lL4j;SN䁺|Q;ޫK=lPV%K6ҹ^E/H5Ŋ'-T&fNv { ֳe==jQG4G VOjF;Ƥ՚B\;` `m%Z(;}ASedPJ`1H1Uɠ*W/,ޤpjݑ+ A6F%`b2?!E)a[Cd& 1x{ZRԖVSF m;ns] + s*0aA|!k5(f>ulW0򝣦Xs_sֻ(9+._ʀa׎s[i 4{8bUxoQ6!a:Li9gSUsEq/浴}"?kŮoKj8V ^Gu)L bvx MhɬeD-^ i;Õcu` 9 >Ρ(//(1> Ϙ57ČMVLwq+JVǖ.j#!qlxT&vOZ!.#ejjќ.AUe:I]7} ?Ϋ̲r530RXcVBB5^R1fMhyR`f,.ʅ8ڭ\os1WhĹM;Vc$g㊵4&ݠ>+8p[ں 0Z&aLOf87'*hpq^ XJI85VEy) # ȜTy>oN26+>1^xe~if4C/:`t%TOC>_ʡGmu")֥#zM6# QRjb\׼0sO91Zè 4Iդ0ǛV3\dW&Dǧ=BUs26Ҡ[ru4O:jSzYˀUY}H@j I­ٲ:P9VanI8*^{aM {\n1U{ge(&^SZ杽O[)lB4;?{5}mu f 8g;mSKN$S[TDRMٻScose&ͻP,r+G7Akt`"$({"UPӓvOpUj Aۤr:h3w Q[:|[$AH/TkTVֆNFe$WE.g W~q yw=EIYCu:)I|5Q7=WT}.h wg;@Q\VVF=2qU*8'>pJ9U8IoLuaŒhGeku# w2y3d]9 # 3 L hO##ӅHuKޗ YV忥[J{'m$nZhz}l\2bA*e ,lG\kΣw䐷8t?yWR[ Qx:~ /.aȀH#5x^W q*>pk;9, uSi|k*s95 rv<[ӭ}]ZΗer.FMXOhlЈW=6RKyoDJ87j/^+XFF'[K.kf}2̘~ݣL k(֮Sׯe1(G?*߷汱,FfnĆjaRJL ,dYJ"ZU!S2ֽ&$MŨhࢃXbf.cJZE%$*/zW x zcN#zܷ?N(sఄy*Kc=+(-EwյRTvZ$B3a1qn#65 <:Gu-֑/tP&}R9J\M'KViD}x&gq!snOC`ҧřVp>j5KlYoVd"HɯunؔrVi)j;`z<j*JJuft:YJX*B٭Ǔ^Z{ A QkV(+Ҕ'MI(꺫R5voxk7WZIc'?wFFsgzӴ#d *h)_ƺjJBiTfV:cҼK>$Kp"J5͡ɈPkRE) ^wo1_9D^ՠiok'IG#V=>J\/:/7s¼C88k-|?]D[KE{͜ė+8 LI+WY-43g n_#dZN7:Ni I k;~SZ bpٮGd8w7ԭQLuG ƲV~ƭgx/nӵh<5kB$c::_C}#u;Cg8k6E]d"!*nѻ<-qm^xنvDs^Ŧi:4{G$4MM>1djeiǗ;tQ%E7(ݚh$*^K\|Z@b%|9V\}Nj|Ŏ}*_^T?Gqx Uiu_\ CnwndP6X!a)V#-Y>']Im?j%%mFVw&}ڏ"RŁY2үcdh%P3b,|".<bTvP%Jy59-RM5oyU[q{YAi EPִ]H1zfL*wap+&}|O?ȥAsK-ʰ.2 Meɸd]s,nRsX1,3A)[XՏ,fTqbT=EAuN:յe&qew)O,y4^-1c'g4xTa);Us\JB)QUXG"3͞lzRD8֋]3><lӶ#g=i|2\wق!MF *R *R='z_QU~^@ v)cwnyI*Næ_D0GT=J#3T0Z| Q{Sd, izYn=) 8 08/ԓ(l SFAco-zPֈߖ`Nޢ!RurKr'`9#ѷq{S9=)=>m15?ˁҙ <Ћ騫qO`dwfƸp0in:HsyAb&8N'=;Snxάin#{e]p;u2@=)V'Ս[Ap~)sJioXW'oBjDi;yڞ2?w6o'q{ 8Zhvq֓v-ӯcJϠc%M81pIhKQQTL#TM_޴:hTi\*|pK+vۙ OIQ!}UBs:ZjRtnǵkG,RӳXwH8XZ$ O2rRΰTdc`~͸3#ZӴt5#V 8W,U"ˌu:FUw6>n1zOIHw[p8|DŸP(b_7i_絻-r8_z֐ bG |i*5J>Ңtzo/f>0K[Y&+HzchEJqc)ɇLAiclgf=/1TƉ2}B"g=+g*ѓ'ztNF+bX#dYi")%N_&xV`=bɻtrһ^n,tA)@JVIwn4׺ǭjak2.=I3xh ni28SZ1KkR0fa 3JCMkˢ^ȉ6(N GLqYʝ4"?dA#/C3=:⺫M`r4Wc=sKF k:+`*N33NK!oV~њGEcKϴmLmmhމ`Y<q޶,a^sM;hِNjdT1[{Q޸ ̰m>f¬ x*4Q$~5^HmROp+&1-h2ず꽙*Jښ+#+ X{mH5e0.|VFKr:psUcԴimcii8^@lsR]"RwU֘7Iv'V^t{ձQ+@ksFO-F7vHfahC(bsvt"E,(Yc cC T6>cf '5)eSdhG( /b}FU%<sދJ5wo[xK47Sl`m;n6ԎN)A[qI^#\{R&\yw?q!GV-!^Zv¹X8[bj95q֬&=k{F<ΝNbyi6)$h m[jq1\TRYwzP&$AZrGS8zjjZވ"IkJŝ \Tڶlw#1P͖.zSTD䉶96X_j=y g t\֭q2)*u6e첰>cNL\sX| rn ݀zs4ahu vFIsڳ 3c@Z䞤+oLqЅjޜ*Et5/omؐjr5j~I]ۇPIa jYԩ:AE^H{;T4EFGN+Fa%աIV29K$||M:퍟5stnByvFߊ|g%ʊ9\焧_gTIPmޡnXx+)T8aeKݤ쿮Ԕ ')qKw&Rf\:che'Ri8U=Mfh>|OJ݋J?(I3K(5$C>^1VTnoMS(b:g55ql61*e+66YX,^UץmMOzkyG7%͕>`~UZ"Ljf_'.=*ƻt5IFkfIجV iP"! b4 f\]0,?tQJ6|y[:EPs&% ygsy&@IkkF[yN%8W S\ȿ3c<m[Xg*G5%_# l .͜+_K튢2sPKkV#Y] ߭OmuHW+<\$_b#։XTkCZ͕RAW^;Ve"NӮiSyq-δ/"j'e5u\/m.G?xIG$i8"vD'SI k8Yc)-e;77sO"N`|@$Tis Jq~*DWUCW3'O4r) c(Dl=ZNWV.R9.i'O3kF-y|Yw=T`ڜ'qĽH`paLb5+kWiyekUWȮ[MȻaus PZ}dT웿sF7^l5ld|]2=;WG(gD:0*ըwk"vSRK=!UloF /&XXJ[cs Wǎ= {֑Y\^ΨMGS<&6ڣ>^ŵEgj}^gymk𾱪z~רC>~EJ'DZ8^HkYox &^lxZZ#ȣVciB kE_ȺIO_&駰c]5g#A\~ Y]!o St_ȑK>݃<9%+oR7-et.kuK@2N5蚨bgs,ƺ.GZ{R5Ve8DO&'a֞'XQwgG1gutY5jwV[>* I$ӵ{ۋd9 };8FxK3i5>);z,uPLp cMO VψE[16󣝥~K FpF5 ԯ)TQv9?\s\V{c ;W)V1ysֹۋvUG!V׽?qZOuKI?svq7WC,Uzg5n($XƸ=-)>" *n,v<}k WmץfϫDHIu8tV݊42$k(XkIEGg q0WRܙF []. +ZNsaC kϯ[9$F:]&7FqƹK/G[1](}~㣨<k}}5t:qI ȁ3ڨ`rbQZE{Fk_ػ>zQCkΞ-f)F×#kRx&v._LkvZDp`5r0D{ 秽5W/z-M%Wle{FvbsJBj|G`n$ |cwGAAq]ppTGnV&8w_M{|]xm`gk&},MqkkYT ]Z"*:WkF% ''!{pMEZQ/GM*{56]ԅuK+TR$6Q{[]Lwi + ?f@A)+,\7VJ$t(ԏ3N+Eu {]fCqa&@V?V|_qqGf tse,a" A=/>#QkDFq8E?r)\֭eʣ7gE}ZgY@95[Sf3!u^#y5Vh,c̖9¤uٜlܖtExXյo#] gp3TƲdlZgkM>FtJ!-NMZ6 K,Y# Z𖡬hRK/]˚l.VKDB}a' <Ƭnk۔N{EV JAEU e=!;ya jn=x Wf=#c'Nfϭ7&T`Tndl*3sL=rvsےbD4=3=i94|yjMthdT19# FG{zSzK[URCu})#˜3p4dG8I##BpF ߗi>^e'ma7~T'Qe$䷦)#6䞣)M8HPqK zwr!ەݸ޴7~Uxi+cheXI9#Fެ|Oٗ(JjѕEzpN=}jXa֓` Hp})#*:3aVKCZXsڰnO+íOzW"qV\gOjPNۗlP7JPyx( |9S6jrp6Vq}VeFy泮.r3j'ܹpp 1eG@d.1l8[ ֵla#W?qŏV[5䒾lV \3a)yeV(b`FuŦ]*ɶ2@mi4-ҹU"dܵr˞GxbhUY:u#71sySIt[{*zuKL}B=1?ζF0A%Zw\LՍ6VUhezӹd*ϴ\r11jƛ+zDw6 Fz՘-U'ּ<]ֿ fb#-qX]Wz3Ѵp^3ZF:~CMm01TڶIh&F#G`*ZaV %^!HOcAߚFkin12]f Fr7Vvhf2n?\ens\]bkep3ֲ.[Q_n Vܯ@by'40GS1k49MEдS䇽ۻ2kҿl*F%nČ).1qMk|ym$!I+;ktPqdOM:-Im_N'`Vk C5-AUw1\LjaOt"k'P }h6ȧ\ҹ^h<,\ tia^ c(-NF*G#֟73ئJ]O?xsOK9摞Slfaأ95i$d-0܌]$7c*+ #jUi6^Qw"p{a;2& qϭnFq]TU,K 2 ff]zu=yWXkj4bU=8ExľNfH0+5s8 Kg ;J9MTuiHsw?g*e=gӻi6 8=kD|B -8Ninshi y夌S]iaB*}BA^!)8F_si)P%!~sӌש[hvBKO>d4;ⵊ9|u⧎[%=ɫS)O;}w\1V n.! &hR@xc˱r8uQ&gF"W߽QhcxZz5 =k*ڴ$g5'kgE$\j㚷oFOMh!Sf[kRG)\ҡQqF~q[#sv s`ce6l0RíJpSb@yqZӢ1_:-#H2yI<0eizՍ5nKmVDr{\{V?Fޡ5Ս1f9"ĠZghUqژڥ#X(vE';6W7ڨT|qnmLgoLF61]1]Mp|`vIӘ_cqVO"J%-P#9z|lZio/rبtiMQv97Su\.#-mwLq3 a[Ժ"v/#(ix:{2]5*2zܟu+Xo,q9y3hG5i뙛0V˞`fOl)y//ZѲM|=B$n7\ML|FO/FzBHo1Ilky=KL삜;MYd ekzSysF pZR=\͎Tĺzua~֍ 'گd5'ƤB 9Y`cOlZ_%~u@ UKE򅟪gZ@Uv*#67 Vb&3֨OE}'4Vvee{|*Ybw?jm REso F섄VC&+3)p)AB8ʥmIvxQ\n R,KZ߸i.o@qH.l5+kՃz~Ր+G~Py]6xL¶=gkV8/Wu+hE'*:uԊvb`FY2РY]&$F.AҸGHޯw@?|/cGp]:0HtOOA4em:1gq}Ef7"#j*ѴvڿOMPWWBKkYk_4pz/Bq[Z1U]}INJ8[;v-J wY5&N*iʜkΥ5ROL͓U{bR8G?JjSy2?k?r#ӴmvkѮ30UXS*/67O&֑min,{yM!qʸV:&HiSMhUڱ}g=1aVTVT+HHs\r{qm^ֻ8?v2q?V;JILҼ޲ƊGm2R'QCVͲ"D\`$8Kw3.|6q**i=9+T|18UT<0&z t7,vOdK;GmB).e$`T^_#m>-=V0CFrQssx:\,d{^F@95^#0tSڵrN .S,[X"*1B {od#JΌ˶In|1'ҹkbZr+]摜d~BkMI,1]..*w Xq x޹㛹т\0pZϊ̭'+)OK#JnPTTgB8ReCqOٴY49b3|{⋗;|ռC;ẏA<挮7yQzF| Sx7/HQioy~K!mұosXrb0K9ⶍG5$ԩUeN.[rwڝI&l%d}+୴u[K)b=+Ԝ,Ϙ,|/[I?yDMğ%&E}>B [,Bۣ> b4%FӔ kےpk|@o1jcKපڰ\VdÊ竎KE$< t6F{<ˌO]6 vI0q<૘j:akӼ7elKXǩ@*x~4. Mx+Mr7~4ǍqXMW5DshI67/#RO2מ*Mץ2O|ԓJ'9N]Yz-F@g 5R_-Zn 0bG@*k#H7JN[KEdA[I2,}j71.̠<Œd~ܪ->B|1 9WvNNNI5 lԸT㩍|^yQLk~Uu!:f +}55(Qwd(Քdhd>BecFz֍[WL6.4&3`1&݉3TDm~5rKbm-Qu0A[]05fXygHۣfodd/o?0+К;Dy?9[_ɾbpj0F1*1XZx_Fn"N9}6;sgn}*UGT9m[OhQi%<6݃N 5ǚ@cUq^66:vǡOYFi'iCc-^n#Ҽ-ڧP1NsTuoh;u$ WUY@"{G~ue[OUm_FUZQr??ih^Nr{sg1I-+JgH[2fS7W5=ݻٗV;IeS{D'z}u \n{\:3ҽ[o4v5GQk+ry)iYz)NkYtUǖauxAu.Gkm*ˌ#*ƶ"`MY'bZ cA,ځxhִH[d[cC5xfH.^FUL9jiF)S;¤ }$} Ū[4A|*;Ėp+gethv{V֩3Q6WneIHQ\Z|_f$v O(nKxE͟I&{3 jzZ9`L1LW[kvG$R0{YDIZGz5Լ2ujjmz5c{_ yc\sҹ1U31,M}CxK;1ӭU>y.lW?hǦx6'Q'Z"2JG|>5Եh ykƗ"lj Oj#Hnn<=$]\NkKmWBZBq{+_ǖ\њmv>GcݍW-'<" ~Mn.1?.pe(Qq#A@=.0jF ܍E< qV#]):j(Sַ!>m)S8?04XF6=) Ory Jz')]3Nȵ{ N'֑Cl+&I+7/BI玕6jy ȏۗ#9~4g犎dW>mD)%a TA'$H*麘n`QTd(Yϭ<!@rOI[cm6dP:qLtvO S)*ǒ9A*_֣qW6;SbU$RH"!ޑ9B(7Jm:k]QEI!{Ӈ3VgB RdNHI֤joߌ4聫nfFq4a׎%ezuZlI$jЄP4Y ʃFH򤝋jȍ oMd`p=aHHQȤ6QÎdt5U``{Smf_(8T@|جFأΗ/r:OaF{6qk-–c[M|EkYsXXĆZnnqYC3GZSvE [,tP8nFH5vWi]1|]QBS+#M-u8F 8Ʋ4ƻ{!$5@19Q^%Jsɭ:7l@Xv%Tֺ&yJ:W|"qJvWlȵ ̜UHqUX8溢mbːJ=mpOpjk0H?(HKt>6TD͌J/81PMW$:# ;z6p +$ӭ RNԺD!mcT﵅W+4 T2ϙKPNd衕GsM#lM`Kjr_$z2sOg850$*zr!:Ҋ]G5"\sY~ricߥ4{GcUfӥYO6E*+gs[|BHǠ#luNHT73u5l#ӵsZuʴ`mÅs[ c9[nl9;A' YպdlUF9ȡU4I&N*I-$,8c=0 k3ޫKI):u9@ͅ\g2=aP=F7廓.HZrA-VV\*v-23(Oq*/s1L7qZvXztQڨORjj"6ݐ\nu%*s\0e (=ֳ, 'Е4gAOlJP\M\h͐ysw&61\xZCcdb`Ɏs\ʘ$~5}]}g؉6gvQ#LPB4A54ٝ'ަL&oNDk0jvҸQW9jŝN: zte{荹mcvIPY@vA;[*4c.G=+/ȠnŴ*e*qijk@D2(l~FV;zǹ*Q]tr;SJDXr5vRJL{YI@b2S`0DT8jKm,`Pǯ<5ZcV`–=G4/{aFucVRǰ=kRP0+jurONyṭ>=M/ *E[UQ&eCQDaa߭ՑrPx9 @#isV`HjgL5n[R$nowܚwsb]0BH޲z0)|M:GwVF#ptvF;Tƚz0kR ZhmQf͓3[Zj*3Z,:BHݝv$mthHeuMrzt[A k2{b#kS6%P,!iQ"ܮo}|+U XFvZuߖzL(áj3X]+]]{YUzמx\t /9ubuHMT4 B"኎I }Oz+m~gO5{՜')JtyE,xq^3g{up85vV|S(II##kzFwcc*:eP er:5 71,:Zk:6 +RڲU-c ڣhKR "<1Wu$B~B,-+5v6U\)n!KVHFq\R4KAWM<5^X~LqWm5uTXq5 i8l. te*sg5,MH㨨䷐ Mt v5a-mn{u9{-6IdVas|18?ʡmBJ7v9a*kعiVg*2IepyL@AYntf+Ce: ޵n!ڛc09T/?y?Nlys|'/aoP H+=ɬ%Fa^@Pa ðz-c24IRiKb}4Q/&+O[тp>nZ}1ZkFtjZDr }j)}F%oŮQ&FոL~֚~1r^NY;g|{*i5GIwJgkKɑyYG ;ۧԕAvKJ*/[P\,ڰ/$F#FǬ##0G*'m+沭NGC{f1$yzݼq^i9 1]Vq@2zTJ/x3X΢L0f^P%ϘLV,Q ܆F*U[S[r^Yq2pqEi#+^P߳0Ĥjƞr_HT 鴽h!y3[QHGJ翳5I<tz_-mqpZڱb`=sR'eE('i2.X_gf4f ljz<7q fxyžVyM:q]6rk&]%2,>oN7x-[LY';!yxtmvʌy;ȝܙR< ?R5>{-6cު7i"\M;4^i5[ 0Tnn7Z9;|fF[5@Gtu4Ym`7+NҦ@Eoz[*1nVR=vYmNj ٩ipqq <3L T%]Mb]>IvzRYEQ&x~Xsx^D)(qs[!9bMv2 7 ľ&Od'fs]TNZr9'71.<𾢷FXxno\E"(TspnM̑=A#BoTTҍ4E_ xB?[ =BHl’ȼm s:`0St.ьD+8ҕ%|55qDo[Qn+ֹcXSh#a+5lqئܾ'Jڲ#ŵ4Dבz% q ^bi!MV:nmɸt5.g+EChAP rc;7vpq\>*3O9#|RUXc].|PJot]Z`FQWx'VI֡,,~5-Um6+97haTl6wzNgpPFG$ԚާoDGyN6H'ʾ%N[]NpjEC|c"K)vUTU8?.gw1Ƚ1f3>%csXfM]ؖfc;h-$=7ľ1ffbz[_?)6'5_Msqq9SX]JqRwVFQN֤ܿ>lqY Gs>GjtI#%kDPSNJ##rGDs_CGR:,6bk~|-eN1kZ#?|75mIdC_C|=;IJi_׸>Ճ1r(;Uyk1GH38^xL|%KKmʌ60n1]dmhŽ4aU Kqm9>w7chQFy XF z?#) #Vt`Km5psi^)-f`7E\7wD *=CL#yZj06VJ+ݿ5FΖ#[_[\݈?JҸy'/K+;N,Y-R>dq[:Go-cZڟ΅8oM_ѱjiz9'g&%Ozie`DI t}"vuebq\x·2|kJrJ\̺rqًu=ԑ ]DդYm>j.ldd@pqQYCz=m,YuK5fy]ʚ6uAc| 䗌n>CX7wۏJpm2ޤ&I?c.!n~dTWܛy-2Gkwˎ<}r+,6k;\Q?V<%u\YC2cҺv\61z7%Ӗ(>VG-H{7Ur./˕b2~m_#/@Ե}R18o|%ܦ15K75f' bNSG''Y#ƍ EsT<=U[Z514]{ȓO &332#Q1x>99 V/XueF@TnR63#C $B1VGNsx#5R `cUI¡TЏbݕ;<'5b략7>Y]1$tc95"I 9V=)1dZ}B (S6s$c8,sE{hDԃM!i稧) 4Iȡe&1xa;dq߿N+.}Y_Ziwcޤ2 Q/9$b'x95p3Z|38&`sXsJMNPpZx$t(qɤC9+ }w7銑A8lq*< VVnJcE \Zn֗h+o5<%r9ǩe.z2L= .P<)t皴ƣnI[ؘڑD.A1^B(oCS$xfLz&T9*~^?-Hpvҫ+"p |猁=eM:i0z楲z8AHWcǵN9~PhO*Rv*; FTaV"gL*IL w =)  2)SƘ7|sWc6w"B'W֦B}twKRI>Py9jE R~+H(䓚p }sH8^ tÔdKžLI#wC}H87qv>2qzТ h0ƞ^6>P[+Cҁ9ࣥc0rXisZv+rڴ#8~Z(yN&NکI.NCg(Eny$昬ǁQEtE-g&*A 8o#hk;D`c]_>ȬcQEaVMEuaiM&zCcn6лyK! 0zhJZ2I#(&`w=WO#p:ETNjtjPZFU FsMqҊ+JFykÞ$Zo-QE|k{h8I7C|}24H(&.)Nl/9GCQ_yόyq2&vs̭) (ۛArջKyd'ZTv-<*m\uŒGVJ#aӒŠ(6 BGg0qҴs& Q]2rBmCA_3iin$E))r\/+;T9osu]6b88݌UfG朦M$c֊+ .ed೿cf>V V|BԔfIodcMhژyRG|QI6ˮvD= hp 瓊(#Q5^\ƃ8ϮMr>(ҖPv+LRumAlysu/DQErgBd$9b2@Zu ٢'Z~${0i[ąl8}hBVl渻fV=z4E\cEiE&1mŻ&э tl/Z(-:8fܱ>qHfUE)1-͔fw+u+[+"4QXߚZ Z7DF&D2'4Q[Κ&5+˨ OlrI+rgjsF#+1z&㱢ͣz֧ңpb+2{$T2VZ֮gٺ-shrrI35;l2d1yj((cXϛVvƬ_;7V٥g^\b3#fWڊ*FP+#p9ҭӣ:֊+zzoY7|WMcʯ4QWH/y##Rp8TK`AE9obkl*HASsEt6rV-,+*ղ]cvEdcKʆj cMu8; xM5j [ &N[3tR匟uFܲdV3czեIi DsE uuiCf͜ZˉaG\} +7M!(v.SZs[4+#ߏ]2Hŵ;*c+*1q[ӴpBniЈlظ(Q&,2DUj%E8붊([29Th>lVmY`qQviҏ.ƃZ,aU_z`XB #snDldʬu[w/9Vwi3oGy+#8m>"ԉ#QE]j0R)YHk>Ɇ9+ N\FquTS}3XJJu W m2VΜZn U;\t[ Zf`O\ULlZ(m2rܵocs{2[),h2I;g.HB5eqna.Q*1S%t"Ĩ-[Zgّe+ V(Oi(KBgcqimW\}:Ut}jPI4Q[sseWCn,-B2J庚{]j{UHیȢRF>rݑgeyICu5?QKGBI6\p`rr9w^ҭcB<WڨioC*OH `590^29Q]-$b߽aҭ=y!=h2V-iö8qQQځۮS4QZ-\c*L^VGqX:>vyIV0\Yj G}jqEdz=j7ŧg袮8XeJqK~mVdF QR9[+ ҕMH-M"hc?)_WS?~4QOZRzi(-o-EA_EF6[z3(jn+cIG0fCj{&bhNe|#JBf(i%$I7W\JTtS4QR:-+gLMc埌s^ɦhxP^+eN*\C˚ў)kމJ[uȫi6_/^Nhg)&C^*hmd 2vx?ďI wQ[ٲ7/C|Ker& 7$뙹6͜Er՛m#)E]S33x{71 n2W*iCon-Ns]1(:b+9ɭhK/K8.<CiFe V+ĵg ,Md)w2YAFrEsTR/ZT s+4{[H҂~\czT"Fz(1> ݪh'梊lerT?Sӵ?SX-Ci(kwZ!@E:m)Rj=[_pRO=袚ԙɫXmՀZuYSv8;+ExNME mv[A#v,xŸ=FEU=LUVc'>1EI&6DLAJ(CXt$}hk4:7b uT*t>QDFz )ؼvn c7q{OAqE&TjoLQE&dPzuP8(;IA(@==s4QVBLJT:8( $d <ӟMVw*jdgp &+]3CP6QAE6,NHC ^T )h8+D (GgEE S%$/QN\QoQWK}Qr[?kore-4.2.3/examples/generic/assets/params.html000066400000000000000000000022711430066302400213650ustar00rootroot00000000000000 Kore params test

You can pass one GET parameter (arg1), any other GET parameter will be filtered out

Only two out of the three input fields will be visible to Kore.

The first field accepts the input "test"

The second field accepts anything like /test/[a-z]*

The third field will be removed by Kore, as it is not in the params block configured for this page.

GET param arg1: $arg1$

GET param arg2: $arg2$

test1: $test1$

test2: $test2$

test3: $test3$

kore-4.2.3/examples/generic/assets/private.html000066400000000000000000000005741430066302400215600ustar00rootroot00000000000000 Kore Authentication tests

The cookie session_id should now be set.

You can continue to view page handler in auth block

kore-4.2.3/examples/generic/assets/private_test.html000066400000000000000000000005411430066302400226110ustar00rootroot00000000000000 Kore Authentication tests

If you see this, the authentication worked. This page should redirect back to /private once you remove your session_id cookie.

kore-4.2.3/examples/generic/assets/style.css000066400000000000000000000003401430066302400210610ustar00rootroot00000000000000body { width: 100%; margin: 0px; color: #000; overflow: hidden; background-color: #fff; } .content { width: 800px; margin-left: auto; margin-right: auto; margin-top: 100px; font-size: 60px; text-align: center; } kore-4.2.3/examples/generic/assets/upload.html000066400000000000000000000007751430066302400213750ustar00rootroot00000000000000 Kore upload test

$upload$

$firstname$

kore-4.2.3/examples/generic/conf/000077500000000000000000000000001430066302400166355ustar00rootroot00000000000000kore-4.2.3/examples/generic/conf/build.conf000066400000000000000000000011131430066302400205770ustar00rootroot00000000000000# generic build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare mime_add=jpg:image/jpg mime_add=css:text/css; charset=utf-8 mime_add=html:text/html; charset=utf-8 dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/generic/conf/generic.conf000066400000000000000000000025031430066302400211200ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./generic.so example_load http_body_max 1024000000 http_body_disk_offload 1024000 validator v_example function v_example_func validator v_regex regex ^/test/[a-z]*$ validator v_number regex ^[0-9]*$ validator v_session function v_session_validate authentication auth_example { authentication_type cookie authentication_value session_id authentication_validator v_session authentication_uri /private } domain * { attach tls certfile cert/server.pem certkey cert/key.pem accesslog kore_access.log route /css/style.css { handler asset_serve_style_css methods get } route / { handler asset_serve_index_html methods get } route /intro.jpg { handler asset_serve_intro_jpg methods get } route /b64test { handler serve_b64test methods get } route /upload { handler serve_file_upload methods get post } route /validator { handler serve_validator methods get } route /params-test { handler serve_params_test methods get post validate qs:get arg1 v_example validate qs:get id v_number validate post test1 v_example validate post test2 v_regex } route /private { handler serve_private methods get } route /private/test { handler asset_serve_private_test_html authenticate auth_example } } kore-4.2.3/examples/generic/src/000077500000000000000000000000001430066302400164775ustar00rootroot00000000000000kore-4.2.3/examples/generic/src/example.c000066400000000000000000000161531430066302400203040ustar00rootroot00000000000000/* * Copyright (c) 2013 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "assets.h" int example_load(int); int serve_b64test(struct http_request *); int serve_file_upload(struct http_request *); int serve_validator(struct http_request *); int serve_params_test(struct http_request *); int serve_private(struct http_request *); int serve_private_test(struct http_request *); int v_example_func(struct http_request *, char *); int v_session_validate(struct http_request *, char *); void test_base64(u_int8_t *, u_int32_t, struct kore_buf *); char *b64tests[] = { "1234567890", "One two three four five", "Man", "any carnal pleasure.", "any carnal pleasure", "any carnal pleas", "I am a nobody, nobody is perfect, therefore I am.", NULL }; int example_load(int state) { switch (state) { case KORE_MODULE_LOAD: kore_log(LOG_NOTICE, "module loading"); /* Set server version */ http_server_version("Server/0.1"); break; case KORE_MODULE_UNLOAD: kore_log(LOG_NOTICE, "module unloading"); break; default: kore_log(LOG_NOTICE, "state %d unknown!", state); break; } return (KORE_RESULT_OK); } int serve_b64test(struct http_request *req) { int i; size_t len; struct kore_buf *res; u_int8_t *data; res = kore_buf_alloc(1024); for (i = 0; b64tests[i] != NULL; i++) test_base64((u_int8_t *)b64tests[i], strlen(b64tests[i]), res); data = kore_buf_release(res, &len); http_response_header(req, "content-type", "text/plain"); http_response(req, 200, data, len); kore_free(data); return (KORE_RESULT_OK); } int serve_file_upload(struct http_request *req) { u_int8_t *d; struct kore_buf *b; struct http_file *f; size_t len; char *name, buf[BUFSIZ]; b = kore_buf_alloc(asset_len_upload_html); kore_buf_append(b, asset_upload_html, asset_len_upload_html); if (req->method == HTTP_METHOD_POST) { if (req->http_body_fd != -1) kore_log(LOG_NOTICE, "file is on disk"); http_populate_multipart_form(req); if (http_argument_get_string(req, "firstname", &name)) { kore_buf_replace_string(b, "$firstname$", name, strlen(name)); } else { kore_buf_replace_string(b, "$firstname$", NULL, 0); } if ((f = http_file_lookup(req, "file")) != NULL) { (void)snprintf(buf, sizeof(buf), "%s is %ld bytes", f->filename, f->length); kore_buf_replace_string(b, "$upload$", buf, strlen(buf)); } else { kore_buf_replace_string(b, "$upload$", NULL, 0); } } else { kore_buf_replace_string(b, "$upload$", NULL, 0); kore_buf_replace_string(b, "$firstname$", NULL, 0); } d = kore_buf_release(b, &len); http_response_header(req, "content-type", "text/html"); http_response(req, 200, d, len); kore_free(d); return (KORE_RESULT_OK); } void test_base64(u_int8_t *src, u_int32_t slen, struct kore_buf *res) { char *in; size_t len; u_int8_t *out; kore_buf_appendf(res, "test '%s'\n", src); if (!kore_base64_encode(src, slen, &in)) { kore_buf_appendf(res, "encoding '%s' failed\n", src); } else { kore_buf_appendf(res, "encoded: '%s'\n", in); if (!kore_base64_decode(in, &out, &len)) { kore_buf_appendf(res, "decoding failed\n"); } else { kore_buf_appendf(res, "decoded: "); kore_buf_append(res, out, len); kore_buf_appendf(res, "\n"); kore_free(out); } kore_free(in); } kore_buf_appendf(res, "\n"); } int serve_validator(struct http_request *req) { if (kore_validator_run(NULL, "v_example", "test")) kore_log(LOG_NOTICE, "v_example ok (expected)"); else kore_log(LOG_NOTICE, "v_example failed"); if (kore_validator_run(NULL, "v_regex", "/test/123")) kore_log(LOG_NOTICE, "regex #1 ok"); else kore_log(LOG_NOTICE, "regex #1 failed (expected)"); if (kore_validator_run(NULL, "v_regex", "/test/joris")) kore_log(LOG_NOTICE, "regex #2 ok (expected)"); else kore_log(LOG_NOTICE, "regex #2 failed"); http_response(req, 200, "OK", 2); return (KORE_RESULT_OK); } int serve_params_test(struct http_request *req) { struct kore_buf *b; u_int8_t *d; size_t len; int r, i; char *test, name[10]; if (req->method == HTTP_METHOD_GET) http_populate_get(req); else if (req->method == HTTP_METHOD_POST) http_populate_post(req); b = kore_buf_alloc(asset_len_params_html); kore_buf_append(b, asset_params_html, asset_len_params_html); /* * The GET parameters will be filtered out on POST. */ if (http_argument_get_string(req, "arg1", &test)) { kore_buf_replace_string(b, "$arg1$", test, strlen(test)); } else { kore_buf_replace_string(b, "$arg1$", NULL, 0); } if (http_argument_get_string(req, "arg2", &test)) { kore_buf_replace_string(b, "$arg2$", test, strlen(test)); } else { kore_buf_replace_string(b, "$arg2$", NULL, 0); } if (req->method == HTTP_METHOD_GET) { kore_buf_replace_string(b, "$test1$", NULL, 0); kore_buf_replace_string(b, "$test2$", NULL, 0); kore_buf_replace_string(b, "$test3$", NULL, 0); if (http_argument_get_uint16(req, "id", &r)) kore_log(LOG_NOTICE, "id: %d", r); else kore_log(LOG_NOTICE, "No id set"); http_response_header(req, "content-type", "text/html"); d = kore_buf_release(b, &len); http_response(req, 200, d, len); kore_free(d); return (KORE_RESULT_OK); } for (i = 1; i < 4; i++) { (void)snprintf(name, sizeof(name), "test%d", i); if (http_argument_get_string(req, name, &test)) { (void)snprintf(name, sizeof(name), "$test%d$", i); kore_buf_replace_string(b, name, test, strlen(test)); } else { (void)snprintf(name, sizeof(name), "$test%d$", i); kore_buf_replace_string(b, name, NULL, 0); } } http_response_header(req, "content-type", "text/html"); d = kore_buf_release(b, &len); http_response(req, 200, d, len); kore_free(d); return (KORE_RESULT_OK); } int serve_private(struct http_request *req) { http_response_header(req, "content-type", "text/html"); http_response_header(req, "set-cookie", "session_id=test123"); http_response(req, 200, asset_private_html, asset_len_private_html); return (KORE_RESULT_OK); } int v_example_func(struct http_request *req, char *data) { kore_log(LOG_NOTICE, "v_example_func called"); if (!strcmp(data, "test")) return (KORE_RESULT_OK); return (KORE_RESULT_ERROR); } int v_session_validate(struct http_request *req, char *data) { kore_log(LOG_NOTICE, "v_session_validate: %s", data); if (!strcmp(data, "test123")) return (KORE_RESULT_OK); return (KORE_RESULT_ERROR); } kore-4.2.3/examples/headers/000077500000000000000000000000001430066302400157075ustar00rootroot00000000000000kore-4.2.3/examples/headers/.gitignore000077500000000000000000000000431430066302400176770ustar00rootroot00000000000000*.o .objs headers.so assets.h cert kore-4.2.3/examples/headers/README.md000066400000000000000000000003721430066302400171700ustar00rootroot00000000000000Example on how to read HTTP request headers and set your own custom ones. Run: ``` # kodev run ``` Test: ``` # curl -H "X-Custom-Header: testing" -i -k https://127.0.0.1:8888 ``` If X-Custom-Header is given, it will be mirrored in the response. kore-4.2.3/examples/headers/conf/000077500000000000000000000000001430066302400166345ustar00rootroot00000000000000kore-4.2.3/examples/headers/conf/build.conf000066400000000000000000000007471430066302400206120ustar00rootroot00000000000000# headers build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/headers/conf/headers.conf000077500000000000000000000003101430066302400211130ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./headers.so tls_dhparam dh2048.pem domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / page } kore-4.2.3/examples/headers/src/000077500000000000000000000000001430066302400164765ustar00rootroot00000000000000kore-4.2.3/examples/headers/src/headers.c000077500000000000000000000010741430066302400202620ustar00rootroot00000000000000#include #include int page(struct http_request *); int page(struct http_request *req) { const char *custom; /* * We'll lookup if the X-Custom-Header is given in the request. * If it is we'll set it as a response header as well. * * The value returned by http_request_header() should not be freed. */ if (http_request_header(req, "x-custom-header", &custom)) http_response_header(req, "x-custom-header", custom); /* Return 200 with "ok\n" to the client. */ http_response(req, 200, "ok\n", 3); return (KORE_RESULT_OK); } kore-4.2.3/examples/integers/000077500000000000000000000000001430066302400161145ustar00rootroot00000000000000kore-4.2.3/examples/integers/.gitignore000077500000000000000000000000441430066302400201050ustar00rootroot00000000000000*.o .objs integers.so static.h cert kore-4.2.3/examples/integers/README.md000066400000000000000000000003671430066302400174010ustar00rootroot00000000000000Test parameter to integer conversions. Run: ``` $ kodev run ``` Test: ``` $ curl -i -k https://127.0.0.1:8888/?id=123123 $ curl -i -k https://127.0.0.1:8888/?id=-123123 ``` The correct integer types should only be represented in the output. kore-4.2.3/examples/integers/conf/000077500000000000000000000000001430066302400170415ustar00rootroot00000000000000kore-4.2.3/examples/integers/conf/build.conf000066400000000000000000000007501430066302400210110ustar00rootroot00000000000000# integers build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/integers/conf/integers.conf000077500000000000000000000005551430066302400215400ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./integers.so workers 2 worker_max_connections 5000 validator v_id regex ^-?[0-9]*.?[0-9]+$ domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page methods get # allowed parameters in the query string for GETs validate get id v_id } } kore-4.2.3/examples/integers/src/000077500000000000000000000000001430066302400167035ustar00rootroot00000000000000kore-4.2.3/examples/integers/src/check_integers.c000077500000000000000000000024511430066302400220310ustar00rootroot00000000000000#include #include int page(struct http_request *); int page(struct http_request *req) { float fl; double dbl; int16_t s16; u_int16_t u16; int32_t s32; int64_t s64; u_int64_t u64; u_int32_t u32; size_t len; struct kore_buf *buf; u_int8_t c, *data; http_populate_get(req); buf = kore_buf_alloc(128); if (http_argument_get_byte(req, "id", &c)) kore_buf_appendf(buf, "byte\t%c\n", c); if (http_argument_get_int16(req, "id", &s16)) kore_buf_appendf(buf, "int16\t%d\n", s16); if (http_argument_get_uint16(req, "id", &u16)) kore_buf_appendf(buf, "uint16\t%d\n", u16); if (http_argument_get_int32(req, "id", &s32)) kore_buf_appendf(buf, "int32\t%d\n", s32); if (http_argument_get_uint32(req, "id", &u32)) kore_buf_appendf(buf, "uint32\t%d\n", u32); if (http_argument_get_int64(req, "id", &s64)) kore_buf_appendf(buf, "int64\t%ld\n", s64); if (http_argument_get_uint64(req, "id", &u64)) kore_buf_appendf(buf, "uint64\t%lu\n", u64); if (http_argument_get_float(req, "id", &fl)) kore_buf_appendf(buf, "float\t%g\n", fl); if (http_argument_get_double(req, "id", &dbl)) kore_buf_appendf(buf, "double\t%g\n", dbl); data = kore_buf_release(buf, &len); http_response(req, 200, data, len); kore_free(data); return (KORE_RESULT_OK); } kore-4.2.3/examples/json/000077500000000000000000000000001430066302400152455ustar00rootroot00000000000000kore-4.2.3/examples/json/.gitignore000077500000000000000000000000401430066302400172320ustar00rootroot00000000000000*.o .objs json.so assets.h cert kore-4.2.3/examples/json/README.md000066400000000000000000000003461430066302400165270ustar00rootroot00000000000000Native Kore JSON parser example. Run: ``` $ kodev run ``` Test: ``` $ curl -i -k -d '{"foo":{"bar": "Hello world"}}' https://127.0.0.1:8888 ``` The result should echo back the foo.bar JSON path value if it is a JSON string. kore-4.2.3/examples/json/conf/000077500000000000000000000000001430066302400161725ustar00rootroot00000000000000kore-4.2.3/examples/json/conf/build.conf000066400000000000000000000007441430066302400201450ustar00rootroot00000000000000# json build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/json/conf/json.conf000077500000000000000000000003241430066302400200140ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./json.so domain 127.0.0.1 { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page methods post } } kore-4.2.3/examples/json/src/000077500000000000000000000000001430066302400160345ustar00rootroot00000000000000kore-4.2.3/examples/json/src/json.c000077500000000000000000000034231430066302400171560ustar00rootroot00000000000000/* * Copyright (c) 2019 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include int page(struct http_request *); int page(struct http_request *req) { struct kore_buf buf; struct kore_json json; struct kore_json_item *item; kore_buf_init(&buf, 128); kore_json_init(&json, req->http_body->data, req->http_body->length); if (!kore_json_parse(&json)) { kore_buf_appendf(&buf, "%s\n", kore_json_strerror()); } else { item = kore_json_find_string(json.root, "foo/bar"); if (item != NULL) { kore_buf_appendf(&buf, "foo.bar = '%s'\n", item->data.string); } else { kore_buf_appendf(&buf, "foo.bar %s\n", kore_json_strerror()); } item = kore_json_find_integer_u64(json.root, "foo/integer"); if (item != NULL) { kore_buf_appendf(&buf, "foo.integer = '%" PRIu64 "'\n", item->data.u64); } else { kore_buf_appendf(&buf, "foo.integer %s\n", kore_json_strerror()); } } http_response(req, 200, buf.data, buf.offset); kore_buf_cleanup(&buf); kore_json_cleanup(&json); return (KORE_RESULT_OK); } kore-4.2.3/examples/jsonrpc/000077500000000000000000000000001430066302400157525ustar00rootroot00000000000000kore-4.2.3/examples/jsonrpc/.gitignore000077500000000000000000000000611430066302400177420ustar00rootroot00000000000000*.o .objs jsonrpc.so assets.h cert .*.swp .*.swo kore-4.2.3/examples/jsonrpc/README.md000066400000000000000000000034741430066302400172410ustar00rootroot00000000000000This example demonstrates how you can use the JSON-RPC module in your application. Note that the module depends upon the third-party library `yajl` (Yet Another JSON library) to parse and produce messages. As for the `yajl_json` example, conf/build.conf shows how to link to the library. This example needs kore having been compiled with `JSONRPC` (and so `HTTP`) activated. Run: ``` $ kodev run ``` Test: ``` $ curl -i -k \ -d '{"id":1,"jsonrpc":"2.0","method":"echo","params":["Hello world"]}' \ https://127.0.0.1:8888/v1 ``` The result should echo back the string at `params`: Hello world. Alternatively, if you have bats installed: ``` $ bats test/integ/jsonrpc.bats ``` Will run a small test suite. The yajl repo is available @ https://github.com/lloyd/yajl JSONRPC Request Lifetime ------------------------ Currently, one HTTP request will (in most cases) provoke one and only one response. Batch mode is not supported yet, neither is websocket. As such `jsonrpc\_error` and `jsonrpc\_result` do clean the request after call. If however you want to abort the processed request, like by returning `KORE\_RESULT\_ERROR`, after it having been read, you need to clean it by calling `jsonrpc\_destroy\_request`. Other than that you shouldn't think about this function. Message Handling Log -------------------- The `jsonrpc\_request` keeps a log of messages with levels similar to those of syslog. Messages are added with jsonrpc_log(). By default messages of the log are added to the data member of the error responses if at levels EMERG, ERROR, WARNING and NOTICE. If you don't want log messages to be outputted zero the log_levels flag of the jsonrpc_request. Formatting responses -------------------- By default responses are not prettyfied. To do that set the appropriate flag in the jsonrpc_request structure. kore-4.2.3/examples/jsonrpc/conf/000077500000000000000000000000001430066302400166775ustar00rootroot00000000000000kore-4.2.3/examples/jsonrpc/conf/build.conf000066400000000000000000000007671430066302400206570ustar00rootroot00000000000000# jsonrpc build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g ldflags=-lyajl } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/jsonrpc/conf/jsonrpc.conf000066400000000000000000000003421430066302400212230ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./jsonrpc.so domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler homepage } route /v1 { handler v1 } } kore-4.2.3/examples/jsonrpc/src/000077500000000000000000000000001430066302400165415ustar00rootroot00000000000000kore-4.2.3/examples/jsonrpc/src/home.c000066400000000000000000000005211430066302400176330ustar00rootroot00000000000000#include #include int homepage(struct http_request *); int homepage(struct http_request *req) { static const char response_body[] = "JSON-RPC API\n"; http_response_header(req, "content-type", "text/plain"); http_response(req, 200, response_body, sizeof(response_body) - 1); return (KORE_RESULT_OK); } kore-4.2.3/examples/jsonrpc/src/v1.c000066400000000000000000000062061430066302400172370ustar00rootroot00000000000000#include #include #include #include #include #include #include int v1(struct http_request *); static int write_string(struct jsonrpc_request *req, void *ctx) { const unsigned char *str = (unsigned char *)ctx; return yajl_gen_string(req->gen, str, strlen((const char *)str)); } static int write_string_array_params(struct jsonrpc_request *req, void *ctx) { int status = 0; if (!YAJL_GEN_KO(status = yajl_gen_array_open(req->gen))) { for (size_t i = 0; i < req->params->u.array.len; i++) { yajl_val yajl_str = req->params->u.array.values[i]; char *str = YAJL_GET_STRING(yajl_str); if (YAJL_GEN_KO(status = yajl_gen_string(req->gen, (unsigned char *)str, strlen(str)))) break; } if (status == 0) status = yajl_gen_array_close(req->gen); } return status; } int v1(struct http_request *http_req) { struct jsonrpc_request req; int ret; /* We only allow POST/PUT methods. */ if (http_req->method != HTTP_METHOD_POST && http_req->method != HTTP_METHOD_PUT) { http_response_header(http_req, "allow", "POST, PUT"); http_response(http_req, HTTP_STATUS_METHOD_NOT_ALLOWED, NULL, 0); return (KORE_RESULT_OK); } /* Read JSON-RPC request. */ if ((ret = jsonrpc_read_request(http_req, &req)) != 0) return jsonrpc_error(&req, ret, NULL); /* Echo command takes and gives back params. */ if (strcmp(req.method, "echo") == 0) { if (!YAJL_IS_ARRAY(req.params)) { jsonrpc_log(&req, LOG_ERR, "Echo only accepts positional params"); return jsonrpc_error(&req, JSONRPC_INVALID_PARAMS, NULL); } for (size_t i = 0; i < req.params->u.array.len; i++) { yajl_val v = req.params->u.array.values[i]; if (!YAJL_IS_STRING(v)) { jsonrpc_log(&req, -3, "Echo only accepts strings"); return jsonrpc_error(&req, JSONRPC_INVALID_PARAMS, NULL); } } return jsonrpc_result(&req, write_string_array_params, NULL); } /* Date command displays date and time according to parameters. */ if (strcmp(req.method, "date") == 0) { time_t time_value; struct tm time_info; char timestamp[33]; struct tm *(*gettm)(const time_t *, struct tm *) = localtime_r; if (YAJL_IS_OBJECT(req.params)) { const char *path[] = {"local", NULL}; yajl_val bf; bf = yajl_tree_get(req.params, path, yajl_t_false); if (bf != NULL) gettm = gmtime_r; } else if (req.params != NULL) { jsonrpc_log(&req, LOG_ERR, "Date only accepts named params"); return jsonrpc_error(&req, JSONRPC_INVALID_PARAMS, NULL); } if ((time_value = time(NULL)) == -1) return jsonrpc_error(&req, -2, "Failed to get date time"); if (gettm(&time_value, &time_info) == NULL) return jsonrpc_error(&req, -3, "Failed to get date time info"); memset(timestamp, 0, sizeof(timestamp)); if (strftime_l(timestamp, sizeof(timestamp) - 1, "%c", &time_info, LC_GLOBAL_LOCALE) == 0) return jsonrpc_error(&req, -4, "Failed to get printable date time"); return jsonrpc_result(&req, write_string, timestamp); } return jsonrpc_error(&req, JSONRPC_METHOD_NOT_FOUND, NULL); } kore-4.2.3/examples/jsonrpc/test/000077500000000000000000000000001430066302400167315ustar00rootroot00000000000000kore-4.2.3/examples/jsonrpc/test/integ/000077500000000000000000000000001430066302400200375ustar00rootroot00000000000000kore-4.2.3/examples/jsonrpc/test/integ/jsonrpc.bats000066400000000000000000000062161430066302400223750ustar00rootroot00000000000000#!/usr/bin/env bats # Simple and non exhaustive test suite using bats: # https://github.com/sstephenson/bats PIDFILE=run/jsonrpc.pid CONFFILE=conf/jsonrpc.conf # Start and stop have to be tweaked before being used stop_app() { if [ -f "$PIDFILE" ]; then kill -QUIT `cat "$PIDFILE"` sleep 3 fi if [ -f "$PIDFILE" ]; then kill -KILL `cat "$PIDFILE"` sleep 2 fi } start_app() { stop_app kore -nrc "$CONFFILE" } query_with_content_type() { curl -q \ -H "Content-Type: $1" \ -X POST \ --raw \ -d "$2" \ -s -S \ --insecure \ "https://127.0.0.1:8888/v1" } query() { query_with_content_type "application/json" "$1" } grepstr() { declare result=$1 shift printf "%s" "$result" | grep "$@" >/dev/null } printrep() { declare query=$1 declare result=$2 printf "Sent:\n" printf "%s\n" "$query" printf "Received:\n" printf "%s\n" "$result" } @test "requests with no protocol returns nothing" { query='{"method":"foo","id":"foo"}' result=`query "$query"` printrep "$query" "$result" [ "$result" = "" ] } @test "requests with invalid protocol (1) returns nothing" { query='{"jsonrpc":"1.0","method":"foo","id":"foo"}' result=`query "$query"` printrep "$query" "$result" [ "$result" = "" ] } @test "requests with invalid protocol (2) returns nothing" { query='{"jsonrpc":2.0,"method":"foo","id":"foo"}' result=`query "$query"` printrep "$query" "$result" [ "$result" = "" ] } @test "requests with no method raise errors" { query='{"jsonrpc":"2.0","id":"foo"}' result=`query "$query"` printrep "$query" "$result" grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"' } @test "requests with invalid method raise errors" { query='{"jsonrpc":"2.0","method":1,"id":"foo"}' result=`query "$query"` printrep "$query" "$result" grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"' } @test "requests with unknown method raise errors" { query='{"jsonrpc":"2.0","method":"foobar","id":"foo"}' result=`query "$query"` printrep "$query" "$result" grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"' } @test "error responses give back the string request id" { query='{"jsonrpc":"2.0","id":"foo"}' result=`query "$query"` printrep "$query" "$result" grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"' grepstr "$result" '"id"[ \t\n]*:[ \t\n]*"foo"' } @test "error responses give back the integer request id" { query='{"jsonrpc":"2.0","id":1}' result=`query "$query"` printrep "$query" "$result" grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"' grepstr "$result" '"id"[ \t\n]*:[ \t\n]*1' } @test "result responses give back the string request id" { query='{"jsonrpc":"2.0","method":"echo","params":["foobar"],"id":"tau"}' result=`query "$query"` printrep "$query" "$result" grepstr "$result" '"result"[ \t\n]*:[ \t\n]*[[ \t\n]*"foobar"[ \t\n]*]' grepstr "$result" '"id"[ \t\n]*:[ \t\n]*"tau"' } @test "result responses give back the integer request id" { query='{"jsonrpc":"2.0","method":"echo","params":["foobar"],"id":6}' result=`query "$query"` printrep "$query" "$result" grepstr "$result" '"result"[ \t\n]*:[ \t\n]*[[ \t\n]*"foobar"[ \t\n]*]' grepstr "$result" '"id"[ \t\n]*:[ \t\n]*6' } kore-4.2.3/examples/memtag/000077500000000000000000000000001430066302400155465ustar00rootroot00000000000000kore-4.2.3/examples/memtag/.gitignore000066400000000000000000000000521430066302400175330ustar00rootroot00000000000000*.o .flavor .objs memtag.so assets.h cert kore-4.2.3/examples/memtag/conf/000077500000000000000000000000001430066302400164735ustar00rootroot00000000000000kore-4.2.3/examples/memtag/conf/build.conf000066400000000000000000000017431430066302400204460ustar00rootroot00000000000000# memtag build config # You can switch flavors using: kodev flavor [newflavor] # Set to yes if you wish to produce a single binary instead # of a dynamic library. If you set this to yes you must also # set kore_source together with kore_flavor. #single_binary=no #kore_source=/home/joris/src/kore #kore_flavor= # The flags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare cxxflags=-Wall -Wmissing-declarations -Wshadow cxxflags=-Wpointer-arith -Wcast-qual -Wsign-compare # Mime types for assets served via the builtin asset_serve_* #mime_add=txt:text/plain; charset=utf-8 #mime_add=png:image/png #mime_add=html:text/html; charset=utf-8 dev { # These flags are added to the shared ones when # you build the "dev" flavor. cflags=-g cxxflags=-g } #prod { # You can specify additional flags here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/memtag/conf/memtag.conf000066400000000000000000000002771430066302400206220ustar00rootroot00000000000000# memtag configuration server tls { bind 127.0.0.1 8888 } load ./memtag.so init domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } } kore-4.2.3/examples/memtag/src/000077500000000000000000000000001430066302400163355ustar00rootroot00000000000000kore-4.2.3/examples/memtag/src/memtag.c000066400000000000000000000037021430066302400177550ustar00rootroot00000000000000/* * Copyright (c) 2017-2018 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* * This example demonstrates how dynamically reloadable modules * can use the memory tagging system in Kore in order to restore * the global pointers in the module. */ /* Some unique value. */ #define MEM_TAG_HELLO 100 int init(int); int page(struct http_request *); /* Global pointer, gets initialized to NULL when module loads/reloads. */ char *fixed_ptr = NULL; int init(int state) { /* Ignore unload(s). */ if (state == KORE_MODULE_UNLOAD) return (KORE_RESULT_OK); printf("fixed_ptr: %p\n", (void *)fixed_ptr); /* Attempt to lookup the original pointer. */ if ((fixed_ptr = kore_mem_lookup(MEM_TAG_HELLO)) == NULL) { /* Failed, grab a new chunk of memory and tag it. */ printf(" allocating fixed_ptr for the first time\n"); fixed_ptr = kore_malloc_tagged(6, MEM_TAG_HELLO); kore_strlcpy(fixed_ptr, "hello", 6); } else { printf(" fixed_ptr address resolved\n"); } printf(" fixed_ptr: %p\n", (void *)fixed_ptr); printf(" value : %s\n", fixed_ptr); return (KORE_RESULT_OK); } int page(struct http_request *req) { http_response(req, 200, fixed_ptr, strlen(fixed_ptr)); return (KORE_RESULT_OK); } kore-4.2.3/examples/messaging/000077500000000000000000000000001430066302400162515ustar00rootroot00000000000000kore-4.2.3/examples/messaging/.gitignore000066400000000000000000000000451430066302400202400ustar00rootroot00000000000000*.o .objs messaging.so assets.h cert kore-4.2.3/examples/messaging/README.md000066400000000000000000000004051430066302400175270ustar00rootroot00000000000000Kore message framework example Run: ``` # kodev run ``` Test: ``` Perform a simple GET request against the root page. This should trigger the example app to send a message to the other workers which will display it. # curl -k https://127.0.0.1:8888 ``` kore-4.2.3/examples/messaging/conf/000077500000000000000000000000001430066302400171765ustar00rootroot00000000000000kore-4.2.3/examples/messaging/conf/build.conf000066400000000000000000000007511430066302400211470ustar00rootroot00000000000000# messaging build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/messaging/conf/messaging.conf000066400000000000000000000003661430066302400220270ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./messaging.so init domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } route /shutdown { handler page_shutdown } } kore-4.2.3/examples/messaging/src/000077500000000000000000000000001430066302400170405ustar00rootroot00000000000000kore-4.2.3/examples/messaging/src/messaging.c000066400000000000000000000051421430066302400211630ustar00rootroot00000000000000/* * Copyright (c) 2015 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* * This example demonstrates how to use the messaging framework * in Kore. This framework allows you to send messages between * your workers with custom callbacks defined per message ID. */ /* Your code shouldn't use IDs <= KORE_MSG_APP_BASE. */ #define MY_MESSAGE_ID KORE_MSG_APP_BASE + 1 int init(int); int page(struct http_request *); int page_shutdown(struct http_request *req); void received_message(struct kore_msg *, const void *); /* Initialization callback. */ int init(int state) { if (state == KORE_MODULE_UNLOAD) return (KORE_RESULT_OK); /* * Register our message callback when the module is initialized. * kore_msg_register() fails if the message ID already exists, * but in our case that is OK. */ (void)kore_msg_register(MY_MESSAGE_ID, received_message); return (KORE_RESULT_OK); } /* * Callback for receiving a message MY_MESSAGE_ID. */ void received_message(struct kore_msg *msg, const void *data) { kore_log(LOG_INFO, "got message from %u (%zu bytes): %.*s", msg->src, msg->length, (int)msg->length, (const char *)data); } /* * Page request which will send a message to all other workers * with the ID set to MY_MESSAGE_ID and a payload of "hello". */ int page(struct http_request *req) { /* Send to all workers first. */ kore_msg_send(KORE_MSG_WORKER_ALL, MY_MESSAGE_ID, "hello", 5); /* Now send something to worker number #2 only. */ kore_msg_send(2, MY_MESSAGE_ID, "hello number 2", 14); http_response(req, 200, NULL, 0); return (KORE_RESULT_OK); } /* * Page request which will send a message to the parent * requesting process shutdown. */ int page_shutdown(struct http_request *req) { /* Send shutdown request to parent. */ kore_msg_send(KORE_MSG_PARENT, KORE_MSG_SHUTDOWN, "1", 1); http_response(req, 200, NULL, 0); return (KORE_RESULT_OK); } kore-4.2.3/examples/nohttp/000077500000000000000000000000001430066302400156105ustar00rootroot00000000000000kore-4.2.3/examples/nohttp/.gitignore000066400000000000000000000000421430066302400175740ustar00rootroot00000000000000*.o .objs nohttp.so assets.h cert kore-4.2.3/examples/nohttp/README.md000066400000000000000000000003401430066302400170640ustar00rootroot00000000000000Kore NOHTTP example Run: ``` $ kodev run ``` Test: ``` Connect to the server using openssl s_client, you will notice that anything sent is submitted back to your client. $ openssl s_client -connect 127.0.0.1:8888 ``` kore-4.2.3/examples/nohttp/conf/000077500000000000000000000000001430066302400165355ustar00rootroot00000000000000kore-4.2.3/examples/nohttp/conf/build.conf000066400000000000000000000013071430066302400205040ustar00rootroot00000000000000# nohttp build config # You can switch flavors using: kodev flavor [newflavor] # Set to yes if you wish to produce a single binary instead # of a dynamic library. If you set this to yes you must also # set kore_source together with kore_flavor. single_binary=yes kore_source=../../ kore_flavor=NOHTTP=1 # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/nohttp/conf/nohttp.conf000066400000000000000000000002321430066302400207150ustar00rootroot00000000000000# nohttp configuration server tls { bind 127.0.0.1 8888 connection_setup } domain * { attach tls certfile cert/server.pem certkey cert/key.pem } kore-4.2.3/examples/nohttp/src/000077500000000000000000000000001430066302400163775ustar00rootroot00000000000000kore-4.2.3/examples/nohttp/src/init.c000066400000000000000000000017161430066302400175130ustar00rootroot00000000000000/* * Copyright (c) 2020 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* Let kore handle the default option parsing. */ void kore_parent_configure(int argc, char **argv) { kore_default_getopt(argc, argv); } kore-4.2.3/examples/nohttp/src/nohttp.c000066400000000000000000000061311430066302400200600ustar00rootroot00000000000000/* * Copyright (c) 2015 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Example of using Kore as a network application server. * * We will get called for every new connection that has been established. * For TLS connections we will get called after the TLS handshake completed. * * From the setup we can queue up our own read commands and do whatever we * like with the newly connected client. */ #include void connection_setup(struct connection *); int connection_handle(struct connection *); int connection_recv_data(struct netbuf *); void connection_setup(struct connection *c) { kore_log(LOG_NOTICE, "%p: new connection", c); /* * Setup a read command that will read up to 128 bytes and will * always call the callback connection_recv_data even if not all * 128 bytes were read. */ net_recv_queue(c, 128, NETBUF_CALL_CB_ALWAYS, connection_recv_data); /* We are responsible for setting the connection state. */ c->state = CONN_STATE_ESTABLISHED; /* Override the handle function, called when new events occur. */ c->handle = connection_handle; } /* * This function is called every time a new event is triggered on the * connection. In this demo we just use it as a stub for the normal * callback kore_connection_handle(). * * In this callback you would generally look at the state of the connection * in c->state and perform the required actions like writing / reading using * net_send_flush() or net_recv_flush() if KORE_EVENT_WRITE or * KORE_EVENT_READ are set respectively in c->evt.flags. * Returning KORE_RESULT_ERROR from this callback will disconnect the * connection altogether. */ int connection_handle(struct connection *c) { kore_log(LOG_NOTICE, "connection_handle: %p", c); return (kore_connection_handle(c)); } /* * This function is called every time we get up to 128 bytes of data. * The connection can be found under nb->owner. * The data received can be found under nb->buf. * The length of the received data can be found under s_off. */ int connection_recv_data(struct netbuf *nb) { struct connection *c = (struct connection *)nb->owner; kore_log(LOG_NOTICE, "%p: received %zu bytes", (void *)c, nb->s_off); /* We will just dump these back to the client. */ net_send_queue(c, nb->buf, nb->s_off); net_send_flush(c); /* Now reset the receive command for the next one. */ net_recv_reset(c, 128, connection_recv_data); return (KORE_RESULT_OK); } kore-4.2.3/examples/parameters/000077500000000000000000000000001430066302400164375ustar00rootroot00000000000000kore-4.2.3/examples/parameters/.gitignore000077500000000000000000000000461430066302400204320ustar00rootroot00000000000000*.o .objs parameters.so assets.h cert kore-4.2.3/examples/parameters/README.md000066400000000000000000000004161430066302400177170ustar00rootroot00000000000000Example on how to handle GET/POST parameters in Kore. Run: ``` # kodev run ``` Test: ``` # curl -i -k https://127.0.0.1:8888/?id=123123 ``` The output will differ based on whether or not id is a valid u_int16_t number or not. (the string should always be present). kore-4.2.3/examples/parameters/conf/000077500000000000000000000000001430066302400173645ustar00rootroot00000000000000kore-4.2.3/examples/parameters/conf/build.conf000066400000000000000000000007521430066302400213360ustar00rootroot00000000000000# parameters build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/parameters/conf/parameters.conf000077500000000000000000000006341430066302400224040ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./parameters.so # The validator used to validate the 'id' parameter # defined below. We'll use a simple regex to make sure # it only matches positive numbers. validator v_id regex ^[0-9]*$ domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page methods get validate qs:get id v_id } } kore-4.2.3/examples/parameters/src/000077500000000000000000000000001430066302400172265ustar00rootroot00000000000000kore-4.2.3/examples/parameters/src/parameters.c000077500000000000000000000046751430066302400215540ustar00rootroot00000000000000/* * Copyright (c) 2014 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include int page(struct http_request *); int page(struct http_request *req) { u_int16_t id; char *sid; struct kore_buf *buf; /* * Before we are able to obtain any parameters given to * us via the query string we must tell Kore to parse and * validate them. * * NOTE: All parameters MUST be declared in a params {} block * inside the configuration for Kore! Kore will filter out * any parameters not explicitly defined. * * See conf/parameters.conf on how that is done, this is an * important step as without the params block you will never * get any parameters returned from Kore. */ http_populate_get(req); /* * Lets grab the "id" parameter if available. Kore can obtain * parameters in different data types native to C. * * In this scenario, lets grab it both as an actual string and * as an u_int16_t (unsigned short). * * When trying to obtain a parameter as something else then * a string, Kore will automatically check if the value fits * in said data type. * * For example if id is 65536 it won't fit in an u_int16_t * and Kore will return an error when trying to read it as such. */ buf = kore_buf_alloc(128); /* Grab it as a string, we shouldn't free the result in sid. */ if (http_argument_get_string(req, "id", &sid)) kore_buf_appendf(buf, "id as a string: '%s'\n", sid); /* Grab it as an actual u_int16_t. */ if (http_argument_get_uint16(req, "id", &id)) kore_buf_appendf(buf, "id as an u_int16_t: %d\n", id); /* Now return the result to the client with a 200 status code. */ http_response(req, 200, buf->data, buf->offset); kore_buf_free(buf); return (KORE_RESULT_OK); } kore-4.2.3/examples/pgsql-sync/000077500000000000000000000000001430066302400163745ustar00rootroot00000000000000kore-4.2.3/examples/pgsql-sync/.gitignore000066400000000000000000000000461430066302400203640ustar00rootroot00000000000000*.o .objs pgsql-sync.so assets.h cert kore-4.2.3/examples/pgsql-sync/conf/000077500000000000000000000000001430066302400173215ustar00rootroot00000000000000kore-4.2.3/examples/pgsql-sync/conf/build.conf000066400000000000000000000007521430066302400212730ustar00rootroot00000000000000# pgsql-sync build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/pgsql-sync/conf/pgsql-sync.conf000066400000000000000000000003071430066302400222700ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./pgsql-sync.so init domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } } kore-4.2.3/examples/pgsql-sync/src/000077500000000000000000000000001430066302400171635ustar00rootroot00000000000000kore-4.2.3/examples/pgsql-sync/src/pgsql-sync.c000066400000000000000000000050141430066302400214270ustar00rootroot00000000000000/* * Copyright (c) 2015 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This example demonstrates how to use synchronous PGSQL queries * with Kore. For an asynchronous example see pgsql/ under examples/. * * This example does the same as the asynchronous one, select all entries * from a table called "coders". */ #include #include #include int init(int); int page(struct http_request *); /* Called when our module is loaded (see config) */ int init(int state) { /* Register our database. */ kore_pgsql_register("db", "host=/tmp dbname=test"); return (KORE_RESULT_OK); } /* Page handler entry point (see config) */ int page(struct http_request *req) { struct kore_pgsql sql; char *name; int rows, i; req->status = HTTP_STATUS_INTERNAL_ERROR; kore_pgsql_init(&sql); /* * Initialise our kore_pgsql data structure with the database name * we want to connect to (note that we registered this earlier with * kore_pgsql_register()). We also say we will perform a synchronous * query (KORE_PGSQL_SYNC). */ if (!kore_pgsql_setup(&sql, "db", KORE_PGSQL_SYNC)) { kore_pgsql_logerror(&sql); goto out; } /* * Now we can fire off the query, once it returns we either have * a result on which we can operate or an error occurred. */ if (!kore_pgsql_query(&sql, "SELECT * FROM coders")) { kore_pgsql_logerror(&sql); goto out; } /* * Iterate over the result and dump it to somewhere. */ rows = kore_pgsql_ntuples(&sql); for (i = 0; i < rows; i++) { name = kore_pgsql_getvalue(&sql, i, 0); kore_log(LOG_NOTICE, "name: '%s'", name); } /* All good. */ req->status = HTTP_STATUS_OK; out: http_response(req, req->status, NULL, 0); /* Don't forget to cleanup the kore_pgsql data structure. */ kore_pgsql_cleanup(&sql); return (KORE_RESULT_OK); } kore-4.2.3/examples/pgsql/000077500000000000000000000000001430066302400154225ustar00rootroot00000000000000kore-4.2.3/examples/pgsql/.gitignore000077500000000000000000000000411430066302400174100ustar00rootroot00000000000000*.o .objs pgsql.so assets.h cert kore-4.2.3/examples/pgsql/README.md000066400000000000000000000005171430066302400167040ustar00rootroot00000000000000Kore pgsql example. This example demonstrates how one can use Kore state machines and the pgsql api to make fully asynchronous SQL queries. Asynchronous in this case meaning, without interrupting a Kore worker its other clients their I/O or http requests. Tons of comments inside on how everything works. Run: ``` # kodev run ``` kore-4.2.3/examples/pgsql/conf/000077500000000000000000000000001430066302400163475ustar00rootroot00000000000000kore-4.2.3/examples/pgsql/conf/build.conf000066400000000000000000000007451430066302400203230ustar00rootroot00000000000000# pgsql build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/pgsql/conf/pgsql.conf000077500000000000000000000004641430066302400203530ustar00rootroot00000000000000# Placeholder configuration load ./pgsql.so init server tls { bind 127.0.0.1 8888 } server other { bind 127.0.0.1 8889 connection_new } http_keepalive_time 0 domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } route /hello { handler hello } } kore-4.2.3/examples/pgsql/src/000077500000000000000000000000001430066302400162115ustar00rootroot00000000000000kore-4.2.3/examples/pgsql/src/init.c000066400000000000000000000024521430066302400173230ustar00rootroot00000000000000/* * Copyright (c) 2017-2018 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #if !defined(KORE_NO_HTTP) #include #endif int init(int); #if !defined(KORE_NO_HTTP) int hello(struct http_request *); #endif /* Called when our module is loaded (see config) */ int init(int state) { /* Register our database. */ kore_pgsql_register("db", "host=/tmp dbname=test"); return (KORE_RESULT_OK); } #if !defined(KORE_NO_HTTP) int hello(struct http_request *req) { http_response(req, HTTP_STATUS_OK, "hello", 5); return (KORE_RESULT_OK); } #endif kore-4.2.3/examples/pgsql/src/pgsql.c000066400000000000000000000153051430066302400175070ustar00rootroot00000000000000/* * Copyright (c) 2014-2018 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This example demonstrates on how to use state machines and * asynchronous pgsql queries. For a synchronous query example * see the pgsql-sync/ example under the examples/ directory. * * While this example might seem overly complex for a simple pgsql * query, there is a reason behind its complexity: * Asynchronous pgsql queries mean that Kore will not block while * executing the queries, giving a worker time to continue handling * other events such as I/O or other http requests. * * The state machine framework present in Kore makes it trivial * to get going into dropping from your page handler into the right * state that you are currently in. */ #if !defined(KORE_NO_HTTP) #include #include #include #define REQ_STATE_INIT 0 #define REQ_STATE_QUERY 1 #define REQ_STATE_DB_WAIT 2 #define REQ_STATE_DB_READ 3 #define REQ_STATE_ERROR 4 #define REQ_STATE_DONE 5 int page(struct http_request *); static int request_perform_init(struct http_request *); static int request_perform_query(struct http_request *); static int request_db_wait(struct http_request *); static int request_db_read(struct http_request *); static int request_error(struct http_request *); static int request_done(struct http_request *); struct http_state mystates[] = { { "REQ_STATE_INIT", request_perform_init }, { "REQ_STATE_QUERY", request_perform_query }, { "REQ_STATE_DB_WAIT", request_db_wait }, { "REQ_STATE_DB_READ", request_db_read }, { "REQ_STATE_ERROR", request_error }, { "REQ_STATE_DONE", request_done }, }; #define mystates_size (sizeof(mystates) / sizeof(mystates[0])) struct rstate { int cnt; struct kore_pgsql sql; }; /* Page handler entry point (see config) */ int page(struct http_request *req) { /* Drop into our state machine. */ kore_log(LOG_NOTICE, "%p: page start", (void *)req); return (http_state_run(mystates, mystates_size, req)); } /* Initialize our PGSQL data structure and prepare for an async query. */ int request_perform_init(struct http_request *req) { struct rstate *state; /* Setup our state context (if not yet set). */ if (!http_state_exists(req)) { state = http_state_create(req, sizeof(*state)); /* * Initialize the kore_pgsql data structure and bind it * to this request so we can be put to sleep / woken up * by the pgsql layer when required. */ kore_pgsql_init(&state->sql); kore_pgsql_bind_request(&state->sql, req); } else { state = http_state_get(req); } /* * Setup the query to be asynchronous in nature, aka just fire it * off and return back to us. */ if (!kore_pgsql_setup(&state->sql, "db", KORE_PGSQL_ASYNC)) { /* * If the state was still in INIT we need to go to sleep and * wait until the pgsql layer wakes us up again when there * an available connection to the database. */ if (state->sql.state == KORE_PGSQL_STATE_INIT) { req->fsm_state = REQ_STATE_INIT; return (HTTP_STATE_RETRY); } kore_pgsql_logerror(&state->sql); req->fsm_state = REQ_STATE_ERROR; } else { /* * The initial setup was complete, go for query. */ req->fsm_state = REQ_STATE_QUERY; } return (HTTP_STATE_CONTINUE); } /* After setting everything up we will execute our async query. */ int request_perform_query(struct http_request *req) { struct rstate *state = http_state_get(req); /* We want to move to read result after this. */ req->fsm_state = REQ_STATE_DB_WAIT; /* Fire off the query. */ if (!kore_pgsql_query(&state->sql, "SELECT * FROM coders, pg_sleep(5)")) { /* * Let the state machine continue immediately since we * have an error anyway. */ return (HTTP_STATE_CONTINUE); } /* Resume state machine later when the query results start coming in. */ return (HTTP_STATE_RETRY); } /* * After firing off the query, we returned HTTP_STATE_RETRY (see above). * When request_db_wait() finally is called by Kore we will have results * from pgsql so we'll process them. */ int request_db_wait(struct http_request *req) { struct rstate *state = http_state_get(req); kore_log(LOG_NOTICE, "request_db_wait: %d", state->sql.state); /* * When we get here, our asynchronous pgsql query has * given us something, check the state to figure out what. */ switch (state->sql.state) { case KORE_PGSQL_STATE_WAIT: return (HTTP_STATE_RETRY); case KORE_PGSQL_STATE_COMPLETE: req->fsm_state = REQ_STATE_DONE; break; case KORE_PGSQL_STATE_ERROR: req->fsm_state = REQ_STATE_ERROR; kore_pgsql_logerror(&state->sql); break; case KORE_PGSQL_STATE_RESULT: req->fsm_state = REQ_STATE_DB_READ; break; default: /* This MUST be present in order to advance the pgsql state */ kore_pgsql_continue(&state->sql); break; } return (HTTP_STATE_CONTINUE); } /* * Called when there's an actual result to be gotten. After we handle the * entire result, we'll drop back into REQ_STATE_DB_WAIT (above) in order * to continue until the pgsql API returns KORE_PGSQL_STATE_COMPLETE. */ int request_db_read(struct http_request *req) { char *name; int i, rows; struct rstate *state = http_state_get(req); /* We have sql data to read! */ rows = kore_pgsql_ntuples(&state->sql); for (i = 0; i < rows; i++) { name = kore_pgsql_getvalue(&state->sql, i, 0); kore_log(LOG_NOTICE, "name: '%s'", name); } /* Continue processing our query results. */ kore_pgsql_continue(&state->sql); /* Back to our DB waiting state. */ req->fsm_state = REQ_STATE_DB_WAIT; return (HTTP_STATE_CONTINUE); } /* An error occurred. */ int request_error(struct http_request *req) { struct rstate *state = http_state_get(req); kore_pgsql_cleanup(&state->sql); http_state_cleanup(req); http_response(req, 500, NULL, 0); return (HTTP_STATE_COMPLETE); } /* Request was completed successfully. */ int request_done(struct http_request *req) { struct rstate *state = http_state_get(req); kore_pgsql_cleanup(&state->sql); http_state_cleanup(req); http_response(req, 200, NULL, 0); return (HTTP_STATE_COMPLETE); } #endif /* !KORE_NO_HTTP */ kore-4.2.3/examples/pgsql/src/pgsql_cb.c000066400000000000000000000065551430066302400201620ustar00rootroot00000000000000/* * Copyright (c) 2018 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This is the same as pgsql.c except the query is fired off when * a new connection is made to Kore on port 8889. * * Instead of binding an http_request to the pgsql data structure we * use a callback function that is called for every state change. * * We pass the connection as an argument to this function. */ #include #include #include void connection_del(struct connection *c); void connection_new(struct connection *); void db_state_change(struct kore_pgsql *, void *); void db_init(struct connection *, struct kore_pgsql *); void db_results(struct kore_pgsql *, struct connection *); void connection_new(struct connection *c) { struct kore_pgsql *pgsql; c->disconnect = connection_del; c->proto = CONN_PROTO_UNKNOWN; c->state = CONN_STATE_ESTABLISHED; pgsql = kore_calloc(1, sizeof(*pgsql)); kore_pgsql_init(pgsql); kore_pgsql_bind_callback(pgsql, db_state_change, c); c->hdlr_extra = pgsql; printf("new connection %p\n", (void *)c); db_init(c, pgsql); } void db_init(struct connection *c, struct kore_pgsql *pgsql) { if (!kore_pgsql_setup(pgsql, "db", KORE_PGSQL_ASYNC)) { if (pgsql->state == KORE_PGSQL_STATE_INIT) { printf("\twaiting for available pgsql connection\n"); return; } kore_pgsql_logerror(pgsql); kore_connection_disconnect(c); return; } printf("\tgot pgsql connection\n"); if (!kore_pgsql_query(pgsql, "SELECT * FROM coders, pg_sleep(5)")) { kore_pgsql_logerror(pgsql); kore_connection_disconnect(c); return; } printf("\tquery fired off!\n"); } void connection_del(struct connection *c) { printf("%p: disconnecting\n", (void *)c); if (c->hdlr_extra != NULL) kore_pgsql_cleanup(c->hdlr_extra); kore_free(c->hdlr_extra); c->hdlr_extra = NULL; } void db_state_change(struct kore_pgsql *pgsql, void *arg) { struct connection *c = arg; printf("%p: state change on pgsql %d\n", arg, pgsql->state); switch (pgsql->state) { case KORE_PGSQL_STATE_INIT: db_init(c, pgsql); break; case KORE_PGSQL_STATE_WAIT: break; case KORE_PGSQL_STATE_COMPLETE: kore_connection_disconnect(c); break; case KORE_PGSQL_STATE_ERROR: kore_pgsql_logerror(pgsql); kore_connection_disconnect(c); break; case KORE_PGSQL_STATE_RESULT: db_results(pgsql, c); break; default: kore_pgsql_continue(pgsql); break; } } void db_results(struct kore_pgsql *pgsql, struct connection *c) { char *name; int i, rows; rows = kore_pgsql_ntuples(pgsql); for (i = 0; i < rows; i++) { name = kore_pgsql_getvalue(pgsql, i, 0); net_send_queue(c, name, strlen(name)); } net_send_flush(c); kore_pgsql_continue(pgsql); } kore-4.2.3/examples/pipe_task/000077500000000000000000000000001430066302400162535ustar00rootroot00000000000000kore-4.2.3/examples/pipe_task/.gitignore000077500000000000000000000000451430066302400202450ustar00rootroot00000000000000*.o .objs websocket.so assets.h cert kore-4.2.3/examples/pipe_task/README.md000066400000000000000000000010741430066302400175340ustar00rootroot00000000000000Kore example of tasks and websockets. This example connects Kore via task to a named unix pipe and spews out any output to all connected websocket clients. Before you run this make the pipe: $ mkfifo /tmp/pipe Run: ``` $ kodev run ``` Test: ``` Open a browser that does websockets, surf to https://127.0.0.1:8888 or whatever configured IP you have in the config. Hit the connect button to open a websocket session. Now connect a writer endpoint to the named pipe (/tmp/pipe): $ echo "hello" > /tmp/pipe You should see the result in your browser. ``` kore-4.2.3/examples/pipe_task/assets/000077500000000000000000000000001430066302400175555ustar00rootroot00000000000000kore-4.2.3/examples/pipe_task/assets/frontend.html000066400000000000000000000013541430066302400222650ustar00rootroot00000000000000
kore-4.2.3/examples/pipe_task/conf/000077500000000000000000000000001430066302400172005ustar00rootroot00000000000000kore-4.2.3/examples/pipe_task/conf/build.conf000066400000000000000000000013111430066302400211420ustar00rootroot00000000000000# pipe_task build config # You can switch flavors using: kodev flavor [newflavor] # Set to yes if you wish to produce a single binary instead # of a dynamic library. If you set this to yes you must also # set kore_source together with kore_flavor. single_binary=yes kore_source=../../ kore_flavor=TASKS=1 # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/pipe_task/conf/pipe_task.conf000077500000000000000000000004311430066302400220270ustar00rootroot00000000000000# Kore pipe_task example server tls { bind 127.0.0.1 8888 } websocket_maxframe 65536 websocket_timeout 10000 domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } route /connect { handler page_ws_connect methods get } } kore-4.2.3/examples/pipe_task/src/000077500000000000000000000000001430066302400170425ustar00rootroot00000000000000kore-4.2.3/examples/pipe_task/src/init.c000066400000000000000000000017161430066302400201560ustar00rootroot00000000000000/* * Copyright (c) 2020 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* Let kore handle the default option parsing. */ void kore_parent_configure(int argc, char **argv) { kore_default_getopt(argc, argv); } kore-4.2.3/examples/pipe_task/src/pipe_task.c000077500000000000000000000113671430066302400212000ustar00rootroot00000000000000/* * Copyright (c) 2015 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This example demos Kore its task and websocket capabilities. * * It will spawn a task which connects to a named pipe and writes * responses to all connected websocket clients. */ #include #include #include #include #include #include #include #include #include #include "assets.h" int init(int); int page(struct http_request *); int page_ws_connect(struct http_request *); void websocket_connect(struct connection *); void websocket_disconnect(struct connection *); void websocket_message(struct connection *, u_int8_t, void *, size_t); int pipe_reader(struct kore_task *); void pipe_data_available(struct kore_task *); /* Our pipe reader. */ struct kore_task pipe_task; void kore_worker_configure(void) { /* Only do this on a dedicated worker. */ if (worker->id != 1) return; /* Create our task. */ kore_task_create(&pipe_task, pipe_reader); /* Bind a callback whenever data is available from task. */ kore_task_bind_callback(&pipe_task, pipe_data_available); /* Start the task. */ kore_task_run(&pipe_task); } /* Called whenever we get a new websocket connection. */ void websocket_connect(struct connection *c) { kore_log(LOG_NOTICE, "%p: connected", c); } /* Called whenever we receive a websocket message from a client. */ void websocket_message(struct connection *c, u_int8_t op, void *data, size_t len) { /* Not doing anything with this. */ } /* Called whenever a websocket goes away. */ void websocket_disconnect(struct connection *c) { kore_log(LOG_NOTICE, "%p: disconnecting", c); } /* The / page. */ int page(struct http_request *req) { http_response_header(req, "content-type", "text/html"); http_response(req, 200, asset_frontend_html, asset_len_frontend_html); return (KORE_RESULT_OK); } /* The /connect page. */ int page_ws_connect(struct http_request *req) { kore_websocket_handshake(req, "websocket_connect", "websocket_message", "websocket_disconnect"); return (KORE_RESULT_OK); } /* * The pipe reader task. This task simply waits for a writer end * on a named pipe and reads from it. The bytes read are written * on the task channel because the task does not own any connection * data structures and shouldn't reference them directly. */ int pipe_reader(struct kore_task *t) { int fd; ssize_t ret; u_int8_t buf[BUFSIZ]; fd = -1; kore_log(LOG_INFO, "pipe_reader starting"); /* Just run forever. */ for (;;) { /* Attempt to open the pipe if needed. */ if (fd == -1) { kore_log(LOG_NOTICE, "waiting for writer"); if ((fd = open("/tmp/pipe", O_RDONLY)) == -1) { kore_log(LOG_NOTICE, "failed to open pipe"); sleep(1); continue; } kore_log(LOG_NOTICE, "writer connected"); } /* Got a writer on the other end so start reading. */ ret = read(fd, buf, sizeof(buf)); if (ret == -1) { kore_log(LOG_ERR, "read error on pipe"); (void)close(fd); fd = -1; continue; } if (ret == 0) { kore_log(LOG_NOTICE, "writer disconnected"); (void)close(fd); fd = -1; continue; } kore_log(LOG_NOTICE, "got %ld bytes from pipe", ret); /* * Write data on the task channel so our main event loop * will call the registered callback. */ kore_task_channel_write(t, buf, ret); } return (KORE_RESULT_OK); } /* Called on the main event loop whenever a task event fires. */ void pipe_data_available(struct kore_task *t) { size_t len; u_int8_t buf[BUFSIZ]; /* Deal with the task finishing, we could restart it from here. */ if (kore_task_finished(t)) { kore_log(LOG_WARNING, "task finished"); return; } /* Read data from the task channel. */ len = kore_task_channel_read(t, buf, sizeof(buf)); if (len > sizeof(buf)) kore_log(LOG_WARNING, "truncated data from task"); /* Broadcast it to all connected websocket clients. */ kore_log(LOG_NOTICE, "got %zu bytes from task", len); kore_websocket_broadcast(NULL, WEBSOCKET_OP_TEXT, buf, len, WEBSOCKET_BROADCAST_GLOBAL); } kore-4.2.3/examples/python-async/000077500000000000000000000000001430066302400167305ustar00rootroot00000000000000kore-4.2.3/examples/python-async/.gitignore000066400000000000000000000000601430066302400207140ustar00rootroot00000000000000*.o .flavor .objs python-async.so assets.h cert kore-4.2.3/examples/python-async/README.md000066400000000000000000000006571430066302400202170ustar00rootroot00000000000000Kore python async/await examples. This example also shows off the asynchronous HTTP client support and requires libcurl on your machine. Requires that Kore is built with PYTHON=1 CURL=1 Run: ``` $ kore app.py ``` Test: ``` $ curl -k http://127.0.0.1:8888/queue $ curl -k http://127.0.0.1:8888/lock $ curl -k http://127.0.0.1:8888/proc $ curl -k http://127.0.0.1:8888/socket $ curl -k http://127.0.0.1:8888/httpclient ``` kore-4.2.3/examples/python-async/app.py000066400000000000000000000017501430066302400200650ustar00rootroot00000000000000# # Copyright (c) 2018 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # import kore import async_http import async_queue import async_socket import async_process import async_process kore.server(ip="127.0.0.1", port="8888", tls=False) kore.domain("*") kore.task_create(async_queue.queue_helper()) kore-4.2.3/examples/python-async/async_http.py000066400000000000000000000032631430066302400214620ustar00rootroot00000000000000# # Copyright (c) 2019 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # # Asynchronous HTTP client example. # import kore # Handler called for /httpclient @kore.route("/httpclient", methods=["get"]) async def httpclient(req): # Create an httpclient. client = kore.httpclient("https://kore.io") # Do a simple GET request. print("firing off request") status, body = await client.get() print("status: %d, body: '%s'" % (status, body)) # Reuse and perform another GET request, returning headers too this time. status, headers, body = await client.get(return_headers=True) print("status: %d, headers: '%s'" % (status, headers)) # What happens if we post something? status, body = await client.post(body=b"hello world") print("status: %d, body: '%s'" % (status, body)) # Add some custom headers to our requests. status, body = await client.get( headers={ "x-my-header": "async-http" } ) req.response(200, b'async done') kore-4.2.3/examples/python-async/async_lock.py000066400000000000000000000024601430066302400214310ustar00rootroot00000000000000# # Copyright (c) 2018 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # # Locking example. # # The handler for /lock will grab the shared lock, suspend itself for # 5 seconds before releasing the lock and responding. # # While the lock is held, other requests to /lock will block until it # is released. import kore # The shared lock lock = kore.lock() @kore.route("/lock", methods=["get"]) async def async_lock(req): # A kore.lock should be used with the "async with" syntax. async with lock: # Suspend for 5 seconds. await kore.suspend(5000) # Now respond. req.response(200, b'') kore-4.2.3/examples/python-async/async_process.py000066400000000000000000000046021430066302400221570ustar00rootroot00000000000000# # Copyright (c) 2018 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # # Asynchronous process example. # # Wait for the result of an external process asynchronously. # The handler will execute "/bin/ls" on the current directory and # read the result. # import kore import json @kore.route("/proc", methods=["get"]) async def async_proc(req): # # You may specify a timeout when creating the kore.proc object. # If the timeout is reached before the process exits kore will # raise a TimeoutError exception. # # Ex: set timeout to 100ms: # proc = kore.proc("/bin/ls -lR", 100) proc = kore.proc("/bin/ls -lR") try: stdout = "" # Read until EOF (None is returned) while True: try: # Read from the process, with an optional 1 second timeout. # The recv() call will throw a TimeoutError exception if # the timeout has elapsed before any data was read. chunk = await proc.recv(1024, 1000) if chunk is None: break except TimeoutError as e: print("recv() timed out: %s" % e) continue stdout += chunk.decode() # Reap the process. retcode = await proc.reap() # Respond with the return code + the result as JSON. payload = { "retcode": retcode, "stdout": stdout } data = json.dumps(payload, indent=4) req.response(200, data.encode()) except Exception as e: # If an exception occurs we must kill the process first. proc.kill() errmsg = "Exception: %s" % e req.response(500, errmsg.encode()) kore-4.2.3/examples/python-async/async_queue.py000066400000000000000000000033431430066302400216260ustar00rootroot00000000000000# # Copyright (c) 2018 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # # Asynchronous queue example. # import kore # The shared queue. tq = kore.queue() # Entry point for our independent coroutine that is created when kore starts. async def queue_helper(): while True: # Wait for a dictionary to arrive. obj = await tq.pop() kore.log(kore.LOG_INFO, "coro(): received %s" % obj) # Create a message to send back. msg = "%d = %s" % (kore.time(), obj["msg"]) # Send it on the received queue. obj["rq"].push(msg) @kore.route("/queue", methods=["get"]) async def async_queue(req): # Create our own queue. rq = kore.queue() # The dictionary we are going to send. obj = { # Receive queue object. "rq": rq, "msg": "hello" } # Push it onto the tq queue now, which will wake up the other coroutine. tq.push(obj) # Wait for a response. response = await rq.pop() # Send the response to the client. req.response(200, response.encode()) kore-4.2.3/examples/python-async/async_socket.py000066400000000000000000000037351430066302400217770ustar00rootroot00000000000000# # Copyright (c) 2018 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # # Simple socket example. # # The handler will asynchronously connect to the kore app itself and # send an GET request to /socket-test and read the response. import kore import socket @kore.route("/socket", methods=["get"]) async def async_socket(req): # Create the socket using Pythons built-in socket class. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Set it to nonblocking. sock.setblocking(False) # Create a kore.socket with kore.socket_wrap(). conn = kore.socket_wrap(sock) # Asynchronously connect to 127.0.0.1 port 8888 await conn.connect("127.0.0.1", 8888) kore.log(kore.LOG_INFO, "connected!") # Now send the GET request msg = "GET /socket-test HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n" await conn.send(msg.encode()) kore.log(kore.LOG_INFO, "request sent!") # Read the response. data = await conn.recv(8192) kore.log(kore.LOG_INFO, "got response!") # Respond with the response from /socket-test. req.response(200, data) conn.close() @kore.route("/socket-test", methods=["get"]) async def socket_test(req): # Delay response a bit, just cause we can. await kore.suspend(5000) req.response(200, b'response from /socket-test') kore-4.2.3/examples/python-echo/000077500000000000000000000000001430066302400165315ustar00rootroot00000000000000kore-4.2.3/examples/python-echo/.gitignore000066400000000000000000000000541430066302400205200ustar00rootroot00000000000000*.o .flavor .objs python-echo assets.h cert kore-4.2.3/examples/python-echo/README.md000066400000000000000000000004321430066302400200070ustar00rootroot00000000000000Example of using the asynchronous python api to create a simple echo server. Kore must have been built with PYTHON=1. On the command-line run the following $ kore echo.py Then connect to 127.0.0.1 port 6969 using netcat or so and you'll see it echo back everything you send it. kore-4.2.3/examples/python-echo/echo.py000066400000000000000000000047711430066302400200320ustar00rootroot00000000000000# # Copyright (c) 2013-2018 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # import kore import socket class EchoServer: # Setup socket + wrap it inside of a kore socket so we can use it. def __init__(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setblocking(False) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 6969)) sock.listen() self.conn = kore.socket_wrap(sock) # Wait for a new client to connect, then create a new task # that calls handle_client with the ocnnected client as # the argument. async def run(self): while True: try: client = await self.conn.accept() kore.task_create(self.handle_client(client)) client = None except Exception as e: kore.fatal("exception %s" % e) # Each client will run as this co-routine. # In this case we pass a timeout of 1 second to the recv() call # which will throw a TimeoutError exception in case the timeout # is hit before data is read from the socket. # # This timeout argument is optional. If none is specified the call # will wait until data becomes available. async def handle_client(self, client): while True: try: data = await client.recv(1024, 1000) if data is None: break await client.send(data) except TimeoutError as e: print("timed out reading (%s)" % e) except Exception as e: print("client got exception %s" % e) client.close() # Setup the server object. server = EchoServer() # Create a task that will execute inside of Kore as a co-routine. kore.task_create(server.run()) kore-4.2.3/examples/python-pgsql/000077500000000000000000000000001430066302400167415ustar00rootroot00000000000000kore-4.2.3/examples/python-pgsql/.gitignore000066400000000000000000000000601430066302400207250ustar00rootroot00000000000000*.o .flavor .objs python-pgsql.so assets.h cert kore-4.2.3/examples/python-pgsql/README.md000066400000000000000000000004001430066302400202120ustar00rootroot00000000000000Kore pgsql python module example. This application requires kore to be built with PYTHON=1 PGSQL=1. Run: ``` $ kodev run ``` Test: ``` $ curl -k https://127.0.0.1:8888 $ curl -k https://127.0.0.1:8888/hello $ curl -k https://127.0.0.1:8888/slow ``` kore-4.2.3/examples/python-pgsql/__init__.py000066400000000000000000000002001430066302400210420ustar00rootroot00000000000000from .app import koreapp def kore_parent_configure(args): koreapp.configure(args) def kore_worker_configure(): return kore-4.2.3/examples/python-pgsql/app.py000066400000000000000000000037501430066302400201000ustar00rootroot00000000000000# # Copyright (c) 2017-2018 Joris Vink # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # # Asynchronous postgresql queries with Python. import json import kore class KoreApp: def configure(self, args): # Register the path to our database when Kore starts. kore.dbsetup("db", "host=/tmp dbname=test") # A handler that returns 200 OK with hello as body. def hello(self, req): req.response(200, b'hello\n') # # The query handler that fires of the query and returns a coroutine. # # Kore will resume this handler when the query returns a result or # is successful. # # The kore.pgsql() method can throw exceptions, most notably a # GeneratorExit in case the client connection went away before # the query was able to be completed. # # In this example we're not doing any exception handling. # async def query(self, req): result = await kore.dbquery("db", "SELECT * FROM coders") req.response(200, json.dumps(result).encode("utf-8")) # # A slow query that returns after 10 seconds. # async def slow(self, req): result = await kore.dbquery("db", "SELECT * FROM pg_sleep(10)") req.response(200, json.dumps(result).encode("utf-8")) # Set the application Kore will run to our class. koreapp = KoreApp() kore-4.2.3/examples/python-pgsql/kore.conf000066400000000000000000000003611430066302400205500ustar00rootroot00000000000000# sql configuration server tls { bind 127.0.0.1 8888 } tls_dhparam dh2048.pem domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / koreapp.query route /hello koreapp.hello route /slow koreapp.slow } kore-4.2.3/examples/sse/000077500000000000000000000000001430066302400150665ustar00rootroot00000000000000kore-4.2.3/examples/sse/.gitignore000077500000000000000000000000371430066302400170610ustar00rootroot00000000000000*.o .objs sse.so assets.h cert kore-4.2.3/examples/sse/README.md000066400000000000000000000005121430066302400163430ustar00rootroot00000000000000This example demonstrates SSE (Server Side Events) in Kore. Run: ``` $ kodev run ``` Test (run different times to see the events broadcast): ``` curl -H 'accept: text/event-stream' -ik https://127.0.0.1:8888/subscribe ``` If you point a browser to https://127.0.0.1:8888 you will see a small log of what events are arriving. kore-4.2.3/examples/sse/assets/000077500000000000000000000000001430066302400163705ustar00rootroot00000000000000kore-4.2.3/examples/sse/assets/index.html000077500000000000000000000021231430066302400203660ustar00rootroot00000000000000

events

kore-4.2.3/examples/sse/conf/000077500000000000000000000000001430066302400160135ustar00rootroot00000000000000kore-4.2.3/examples/sse/conf/build.conf000066400000000000000000000007431430066302400177650ustar00rootroot00000000000000# sse build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/sse/conf/sse.conf000077500000000000000000000003771430066302400174660ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./sse.so http_keepalive_time 600 domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } route /subscribe { handler subscribe } } kore-4.2.3/examples/sse/src/000077500000000000000000000000001430066302400156555ustar00rootroot00000000000000kore-4.2.3/examples/sse/src/sse.c000077500000000000000000000115371430066302400166250ustar00rootroot00000000000000/* * Copyright (c) 2015 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Simple example of how SSE (Server Side Events) could be used in Kore. * * Upon new arrivals, a join event is broadcast to all clients. * If a client goes away a leave event is broadcasted. * Each connection gets its own 10 second ping timer which will emit * a ping event to the connection endpoint. */ #include #include #include "assets.h" void sse_ping(void *, u_int64_t); int page(struct http_request *); int subscribe(struct http_request *); void sse_disconnect(struct connection *); void sse_send(struct connection *, void *, size_t); void sse_broadcast(struct connection *, void *, size_t); int check_header(struct http_request *, const char *, const char *); /* * Each client subscribed to our SSE gets a state attached * to their hdlr_extra pointer member. */ struct sse_state { struct kore_timer *timer; }; int page(struct http_request *req) { if (req->method != HTTP_METHOD_GET) { http_response_header(req, "allow", "get"); http_response(req, 405, NULL, 0); return (KORE_RESULT_OK); } http_response_header(req, "content-type", "text/html"); http_response(req, 200, asset_index_html, asset_len_index_html); return (KORE_RESULT_OK); } int subscribe(struct http_request *req) { struct sse_state *state; char *hello = "event:join\ndata: client\n\n"; /* Preventive paranoia. */ if (req->hdlr_extra != NULL) { kore_log(LOG_ERR, "%p: already subscribed", req->owner); http_response(req, 500, NULL, 0); return (KORE_RESULT_OK); } /* Only allow GET methods. */ if (req->method != HTTP_METHOD_GET) { http_response_header(req, "allow", "get"); http_response(req, 405, NULL, 0); return (KORE_RESULT_OK); } /* Only do SSE if the client told us it wanted too. */ if (!check_header(req, "accept", "text/event-stream")) return (KORE_RESULT_OK); /* Do not include content-length in our response. */ req->flags |= HTTP_REQUEST_NO_CONTENT_LENGTH; /* Notify existing clients of our new client now. */ sse_broadcast(req->owner, hello, strlen(hello)); /* Set a disconnection method so we know when this client goes away. */ req->owner->disconnect = sse_disconnect; /* Allocate a state to be carried by our connection. */ state = kore_malloc(sizeof(*state)); req->owner->hdlr_extra = state; /* Now start a timer to send a ping back every 10 second. */ state->timer = kore_timer_add(sse_ping, 10000, req->owner, 0); /* Respond that the SSE channel is now open. */ kore_log(LOG_NOTICE, "%p: connected for SSE", req->owner); http_response_header(req, "content-type", "text/event-stream"); http_response(req, 200, NULL, 0); return (KORE_RESULT_OK); } void sse_broadcast(struct connection *src, void *data, size_t len) { struct connection *c; /* Broadcast the message to all other clients. */ TAILQ_FOREACH(c, &connections, list) { if (c == src) continue; sse_send(c, data, len); } } void sse_send(struct connection *c, void *data, size_t len) { struct sse_state *state = c->hdlr_extra; /* Do not send to clients that do not have a state. */ if (state == NULL) return; /* Queue outgoing data now. */ net_send_queue(c, data, len); net_send_flush(c); } void sse_ping(void *arg, u_int64_t now) { struct connection *c = arg; char *ping = "event:ping\ndata:\n\n"; /* Send our ping to the client. */ sse_send(c, ping, strlen(ping)); } void sse_disconnect(struct connection *c) { struct sse_state *state = c->hdlr_extra; char *leaving = "event: leave\ndata: client\n\n"; kore_log(LOG_NOTICE, "%p: disconnecting for SSE", c); /* Tell others we are leaving. */ sse_broadcast(c, leaving, strlen(leaving)); /* Kill our timer and free/remove the state. */ kore_timer_remove(state->timer); kore_free(state); /* Prevent us to be called again. */ c->hdlr_extra = NULL; c->disconnect = NULL; } int check_header(struct http_request *req, const char *name, const char *value) { const char *hdr; if (!http_request_header(req, name, &hdr)) { http_response(req, 400, NULL, 0); return (KORE_RESULT_ERROR); } if (strcmp(hdr, value)) { http_response(req, 400, NULL, 0); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } kore-4.2.3/examples/tasks/000077500000000000000000000000001430066302400154215ustar00rootroot00000000000000kore-4.2.3/examples/tasks/README.md000066400000000000000000000005641430066302400167050ustar00rootroot00000000000000Kore task example. This example creates an asynchronous task from the page handler that performs a POST to the same server and fetches its data before returning to the client. Build: ``` $ kodev build ``` Run: ``` $ kodev run ``` Test: ``` $ curl -i -k https://127.0.0.1:8888/?user=astring The returned data must match what you supplied in user ([a-z] string) ``` kore-4.2.3/examples/tasks/conf/000077500000000000000000000000001430066302400163465ustar00rootroot00000000000000kore-4.2.3/examples/tasks/conf/build.conf000066400000000000000000000014031430066302400203120ustar00rootroot00000000000000# tasks build config # You can switch flavors using: kodev flavor [newflavor] # Set to yes if you wish to produce a single binary instead # of a dynamic library. If you set this to yes you must also # set kore_source together with kore_flavor. single_binary=yes kore_source=../../ kore_flavor=TASKS=1 # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare cflags=-I/usr/local/include dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g ldflags=-L/usr/local/lib -lcurl } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/tasks/conf/tasks.conf000066400000000000000000000006611430066302400203450ustar00rootroot00000000000000# Kore config for tasks example server tls { bind 127.0.0.1 8888 } task_threads 4 worker_max_connections 1000 http_keepalive_time 0 validator v_user regex ^[a-z]*$ domain * { attach tls certfile cert/server.pem certkey cert/key.pem accesslog kore_access.log route / { handler page_handler validate qs:get user v_user } route /post_back { handler post_back methods post validate post user v_user } } kore-4.2.3/examples/tasks/src/000077500000000000000000000000001430066302400162105ustar00rootroot00000000000000kore-4.2.3/examples/tasks/src/init.c000066400000000000000000000017161430066302400173240ustar00rootroot00000000000000/* * Copyright (c) 2020 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include /* Let kore handle the default option parsing. */ void kore_parent_configure(int argc, char **argv) { kore_default_getopt(argc, argv); } kore-4.2.3/examples/tasks/src/tasks.c000066400000000000000000000161321430066302400175040ustar00rootroot00000000000000/* * Copyright (c) 2014 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * In this example, we use the background tasks available in Kore * to fire off a POST to our /post_back page handler containing * the user argument that was passed to us in our GET request to /. * * This illustrates how Kore its background tasks in effect work and * how to operate on the channel in order to pass data back and forth. * * You need libcurl installed for this to build (including headers) * * Read README.md on how to build and run this example. */ #include #include #include #include /* We need to allow some more syscalls on linux. */ #if defined(__linux__) #include KORE_SECCOMP_FILTER("tasks", /* Allow sockets and libcurl to call connect. */ KORE_SYSCALL_ALLOW(bind), KORE_SYSCALL_ALLOW(ioctl), KORE_SYSCALL_ALLOW(connect), KORE_SYSCALL_ALLOW(getsockopt), KORE_SYSCALL_ALLOW(getsockname), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_NETLINK), /* Other */ KORE_SYSCALL_ALLOW(ioctl), KORE_SYSCALL_ALLOW(madvise), KORE_SYSCALL_ALLOW(recvmsg), KORE_SYSCALL_ALLOW(sendmmsg), KORE_SYSCALL_ALLOW(getpeername), ); #endif int run_curl(struct kore_task *); int post_back(struct http_request *); int page_handler(struct http_request *); size_t curl_write_cb(char *, size_t, size_t, void *); struct rstate { struct kore_task task; }; int page_handler(struct http_request *req) { u_int32_t len; struct rstate *state; char *user, result[64]; /* * Lets check if a task has been created yet, this is important * as we only want to fire this off once and we will be called * again once it has been created. * * In this example, we'll store our state with our task in hdlr_extra. */ if (req->hdlr_extra == NULL) { /* Grab the user argument */ http_populate_get(req); if (!http_argument_get_string(req, "user", &user)) { http_response(req, 500, "ERROR\n", 6); return (KORE_RESULT_OK); } /* * Allocate rstate and bind it to the hdlr_extra field. * Kore automatically frees this when freeing the result. */ state = kore_malloc(sizeof(*state)); req->hdlr_extra = state; /* * Create a new task that will execute the run_curl() * function and bind it to our request. * * Binding a task to a request means Kore will reschedule * the page handler for that request to refire after the * task has completed or when it writes on the task channel. */ kore_task_create(&state->task, run_curl); kore_task_bind_request(&state->task, req); /* * Start the task and write the user we received in our * GET request to its channel. */ kore_task_run(&state->task); kore_task_channel_write(&state->task, user, strlen(user)); /* * Tell Kore to retry us later. */ return (KORE_RESULT_RETRY); } else { state = req->hdlr_extra; } /* * Our page handler is scheduled to be called when either the * task finishes or has written data onto the channel. * * In order to distinguish between the two we can inspect the * state of the task. */ if (kore_task_state(&state->task) != KORE_TASK_STATE_FINISHED) { http_request_sleep(req); return (KORE_RESULT_RETRY); } /* * Task is finished, check the result. */ if (kore_task_result(&state->task) != KORE_RESULT_OK) { kore_task_destroy(&state->task); http_response(req, 500, NULL, 0); return (KORE_RESULT_OK); } /* * Lets read what our task has written to the channel. * * kore_task_channel_read() will return the amount of bytes * that it received for that read. If the returned bytes is * larger then the buffer you passed this is a sign of truncation * and should be treated carefully. */ len = kore_task_channel_read(&state->task, result, sizeof(result)); if (len > sizeof(result)) { http_response(req, 500, NULL, 0); } else { http_response(req, 200, result, len); } /* We good, destroy the task. */ kore_task_destroy(&state->task); return (KORE_RESULT_OK); } int post_back(struct http_request *req) { char *user; if (req->method != HTTP_METHOD_POST) { http_response(req, 500, NULL, 0); return (KORE_RESULT_OK); } http_populate_post(req); if (!http_argument_get_string(req, "user", &user)) { http_response(req, 500, NULL, 0); return (KORE_RESULT_OK); } /* Simply echo the supplied user argument back. */ http_response(req, 200, user, strlen(user)); return (KORE_RESULT_OK); } /* * This is the function that is executed by our task which is created * in the page_handler() callback. * * It sets up a CURL POST request to /post_back passing along the * user argument which it receives from its channel from page_handler(). */ int run_curl(struct kore_task *t) { struct kore_buf *b; size_t len; CURLcode res; u_int8_t *data; CURL *curl; char user[64], fields[128]; /* * Read the channel in order to obtain the user argument * that was written to it by page_handler(). */ len = kore_task_channel_read(t, user, sizeof(user)); if (len > sizeof(user)) return (KORE_RESULT_ERROR); if (!kore_snprintf(fields, sizeof(fields), NULL, "user=%.*s", len, user)) return (KORE_RESULT_ERROR); if ((curl = curl_easy_init()) == NULL) return (KORE_RESULT_ERROR); b = kore_buf_alloc(128); /* Do CURL magic. */ curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_WRITEDATA, b); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb); #if !defined(KORE_NO_TLS) curl_easy_setopt(curl, CURLOPT_URL, "https://127.0.0.1:8888/post_back"); #else curl_easy_setopt(curl, CURLOPT_URL, "http://127.0.0.1:8888/post_back"); #endif res = curl_easy_perform(curl); if (res != CURLE_OK) { kore_buf_free(b); curl_easy_cleanup(curl); return (KORE_RESULT_ERROR); } /* * Grab the response from the CURL request and write the * result back to the task channel. */ data = kore_buf_release(b, &len); kore_task_channel_write(t, data, len); kore_free(data); return (KORE_RESULT_OK); } size_t curl_write_cb(char *ptr, size_t size, size_t nmemb, void *udata) { struct kore_buf *b = udata; kore_buf_append(b, ptr, size * nmemb); return (size * nmemb); } kore-4.2.3/examples/tls-proxy/000077500000000000000000000000001430066302400162555ustar00rootroot00000000000000kore-4.2.3/examples/tls-proxy/.gitignore000077500000000000000000000000451430066302400202470ustar00rootroot00000000000000*.o .objs tls-proxy.so assets.h cert kore-4.2.3/examples/tls-proxy/README.md000066400000000000000000000006321430066302400175350ustar00rootroot00000000000000Kore as a TLS-proxy. Edit src/proxy.c and add your backends to the backends[] data structure. If you want to reduce attack surface you can build Kore with NOHTTP=1 to completely remove the HTTP component and only run the net code. Run: ``` $ kodev run ``` Test: ``` Connect to the server and notice that it proxies data between you and your destination. $ openssl s_client -connect 127.0.0.1:8888 ``` kore-4.2.3/examples/tls-proxy/conf/000077500000000000000000000000001430066302400172025ustar00rootroot00000000000000kore-4.2.3/examples/tls-proxy/conf/build.conf000066400000000000000000000007511430066302400211530ustar00rootroot00000000000000# tls-proxy build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/tls-proxy/conf/tls-proxy.conf000077500000000000000000000005431430066302400220370ustar00rootroot00000000000000# Kore as a TLS proxy configuration. load ./tls-proxy.so # # Bind the proxy to a given IP and port. For every # connection we receive we will call client_setup # so it can kick things in action. # server tls { bind 127.0.0.1 8888 client_setup } # Setup domain for TLS usage. domain * { attach tls certfile cert/server.pem certkey cert/key.pem } kore-4.2.3/examples/tls-proxy/src/000077500000000000000000000000001430066302400170445ustar00rootroot00000000000000kore-4.2.3/examples/tls-proxy/src/proxy.c000077500000000000000000000165561430066302400204110ustar00rootroot00000000000000/* * Copyright (c) 2015 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include /* * In this example Kore acts as a TLS proxy shuffling data between * an encrypted connection and a plain text backend. * * It will look at the TLS SNI extension to figure out what backend * to use for the connection when it comes in. * * Add your backends to the data structure below. */ /* Default timeouts, 5 seconds for connecting, 15 seconds otherwise. */ #define PROXY_TIMEOUT (15 * 1000) #define PROXY_CONNECT_TIMEOUT (5 * 1000) /* All domains and their backends. */ struct { const char *name; const char *ip; const u_int16_t port; } backends[] = { { "localhost", "127.0.0.1", 8080 }, { NULL, NULL, 0 } }; int client_handle(struct connection *); void client_setup(struct connection *); void disconnect(struct connection *); int pipe_data(struct netbuf *); int backend_handle_connect(struct connection *); int backend_handle_default(struct connection *); /* * Called for every new connection on a certain ip/port. Which one is * configured in the TLS proxy its configuration file. */ void client_setup(struct connection *c) { int i, fd; const char *name; struct connection *backend; /* Paranoia. */ name = SSL_get_servername(c->ssl, TLSEXT_NAMETYPE_host_name); if (name == NULL) { kore_connection_disconnect(c); return; } /* Figure out what backend to use. */ for (i = 0; backends[i].name != NULL; i++) { if (!strcasecmp(backends[i].name, name)) break; } /* If we don't have any backends, we just disconnect the client. */ if (backends[i].name == NULL) { kore_connection_disconnect(c); return; } /* Create new socket for the backend connection. */ if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { kore_log(LOG_ERR, "socket(): %s", errno_s); kore_connection_disconnect(c); return; } /* Set it to non blocking as well. */ if (!kore_connection_nonblock(fd, 1)) { close(fd); kore_connection_disconnect(c); return; } /* Grab a new connection from Kore to hook backend into. */ backend = kore_connection_new(NULL); /* Prepare our connection. */ backend->family = AF_INET; backend->addr.ipv4.sin_family = AF_INET; backend->addr.ipv4.sin_port = htons(backends[i].port); backend->addr.ipv4.sin_addr.s_addr = inet_addr(backends[i].ip); /* Set the file descriptor for the backend. */ backend->fd = fd; /* Default write/read callbacks for backend. */ backend->read = net_read; backend->write = net_write; /* Connection type (unknown to Kore). */ backend->proto = CONN_PROTO_UNKNOWN; backend->state = CONN_STATE_ESTABLISHED; /* The backend idle timer is set first to connection timeout. */ backend->idle_timer.length = PROXY_CONNECT_TIMEOUT; /* The client idle timer is set to default idle time. */ c->idle_timer.length = PROXY_TIMEOUT; /* Now link both the client and the backend connection together. */ c->hdlr_extra = backend; backend->hdlr_extra = c; /* * The handle function pointer for the backend is set to the * backend_handle_connect() while connecting. */ c->handle = client_handle; backend->handle = backend_handle_connect; /* Set the disconnect method for both connections. */ c->disconnect = disconnect; backend->disconnect = disconnect; /* Queue write events for the backend connection for now. */ kore_platform_schedule_write(backend->fd, backend); /* Start idle timer for the backend. */ kore_connection_start_idletimer(backend); /* Set our client connection to established. */ c->state = CONN_STATE_ESTABLISHED; /* Insert the backend into the list of Kore connections. */ TAILQ_INSERT_TAIL(&connections, backend, list); /* Kick off connecting. */ backend->evt.flags |= KORE_EVENT_WRITE; backend->handle(backend); } /* * This function is called for backends while they are connecting. * In here we check for write events and attempt to connect() to the * backend. * * Once a connection is established we set the backend handle function * pointer to the backend_handle_default() callback and setup the reads * for both the backend and the client connection we received. */ int backend_handle_connect(struct connection *c) { int ret; struct connection *src; /* We will get a write notification when we can progress. */ if (!(c->evt.flags & KORE_EVENT_WRITE)) return (KORE_RESULT_OK); kore_connection_stop_idletimer(c); /* Attempt connecting. */ ret = connect(c->fd, (struct sockaddr *)&c->addr.ipv4, sizeof(c->addr.ipv4)); /* If we failed check why, we are non blocking. */ if (ret == -1) { /* If we got a real error, disconnect. */ if (errno != EALREADY && errno != EINPROGRESS && errno != EISCONN) { kore_log(LOG_ERR, "connect(): %s", errno_s); return (KORE_RESULT_ERROR); } /* Clean the write flag, we'll be called later. */ if (errno != EISCONN) { c->evt.flags &= ~KORE_EVENT_WRITE; kore_connection_start_idletimer(c); return (KORE_RESULT_OK); } } /* The connection to the backend succeeded. */ c->handle = backend_handle_default; /* Setup read calls for both backend and its client. */ net_recv_queue(c, NETBUF_SEND_PAYLOAD_MAX, NETBUF_CALL_CB_ALWAYS, pipe_data); net_recv_queue(c->hdlr_extra, NETBUF_SEND_PAYLOAD_MAX, NETBUF_CALL_CB_ALWAYS, pipe_data); /* Allow for all events now. */ kore_connection_start_idletimer(c); kore_platform_event_all(c->fd, c); /* Allow events from source now. */ src = c->hdlr_extra; kore_platform_event_all(src->fd, src); /* Now lets start. */ return (c->handle(c)); } /* * Called for connection activity on a backend, just forwards * to the default Kore connection handling for now. */ int backend_handle_default(struct connection *c) { return (kore_connection_handle(c)); } /* * Called for connection activity on a client, just forwards * to the default Kore connection handling for now. */ int client_handle(struct connection *c) { return (kore_connection_handle(c)); } /* * Called whenever a client or its backend have disconnected. * This will disconnect the matching paired connection as well. */ void disconnect(struct connection *c) { struct connection *pair = c->hdlr_extra; c->hdlr_extra = NULL; if (pair != NULL) { pair->hdlr_extra = NULL; kore_connection_disconnect(pair); } } /* * Called whenever data is available that must be piped through * to the paired connection. (client<>backend or backend<>client). */ int pipe_data(struct netbuf *nb) { struct connection *src = nb->owner; struct connection *dst = src->hdlr_extra; /* Flush data out towards destination. */ net_send_queue(dst, nb->buf, nb->s_off); net_send_flush(dst); /* Reset read for source. */ net_recv_reset(src, NETBUF_SEND_PAYLOAD_MAX, pipe_data); return (KORE_RESULT_OK); } kore-4.2.3/examples/upload/000077500000000000000000000000001430066302400155605ustar00rootroot00000000000000kore-4.2.3/examples/upload/.gitignore000066400000000000000000000000421430066302400175440ustar00rootroot00000000000000*.o .objs upload.so assets.h cert kore-4.2.3/examples/upload/conf/000077500000000000000000000000001430066302400165055ustar00rootroot00000000000000kore-4.2.3/examples/upload/conf/build.conf000066400000000000000000000007461430066302400204620ustar00rootroot00000000000000# upload build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/upload/conf/upload.conf000066400000000000000000000003661430066302400206450ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./upload.so http_body_max 1024000000 http_body_disk_offload 4096 domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } } kore-4.2.3/examples/upload/src/000077500000000000000000000000001430066302400163475ustar00rootroot00000000000000kore-4.2.3/examples/upload/src/upload.c000066400000000000000000000062651430066302400200100ustar00rootroot00000000000000/* * Copyright (c) 2016-2018 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This example demonstrates how to properly deal with file uploads * coming from a multipart form. * * The basics are quite trivial: * 1) call http_populate_multipart_form() * 2) find the file using http_file_lookup(). * 3) read the file data using http_file_read(). * * In this example the contents is written to a newly created file * on the server that matches the naming given by the uploader. * * Note that the above is probably not what you want to do in real life. */ #include #include #include #include int page(struct http_request *); int page(struct http_request *req) { int fd; struct http_file *file; u_int8_t buf[BUFSIZ]; ssize_t ret, written; /* Only deal with POSTs. */ if (req->method != HTTP_METHOD_POST) { http_response(req, 405, NULL, 0); return (KORE_RESULT_OK); } /* Parse the multipart data that was present. */ http_populate_multipart_form(req); /* Find our file. */ if ((file = http_file_lookup(req, "file")) == NULL) { http_response(req, 400, NULL, 0); return (KORE_RESULT_OK); } /* Open dump file where we will write file contents. */ fd = open(file->filename, O_CREAT | O_TRUNC | O_WRONLY, 0700); if (fd == -1) { http_response(req, 500, NULL, 0); return (KORE_RESULT_OK); } /* While we have data from http_file_read(), write it. */ /* Alternatively you could look at file->offset and file->length. */ ret = KORE_RESULT_ERROR; for (;;) { ret = http_file_read(file, buf, sizeof(buf)); if (ret == -1) { kore_log(LOG_ERR, "failed to read from file"); http_response(req, 500, NULL, 0); goto cleanup; } if (ret == 0) break; written = write(fd, buf, ret); if (written == -1) { kore_log(LOG_ERR,"write(%s): %s", file->filename, errno_s); http_response(req, 500, NULL, 0); goto cleanup; } if (written != ret) { kore_log(LOG_ERR, "partial write on %s", file->filename); http_response(req, 500, NULL, 0); goto cleanup; } } ret = KORE_RESULT_OK; http_response(req, 200, NULL, 0); kore_log(LOG_INFO, "file '%s' successfully received", file->filename); cleanup: if (close(fd) == -1) kore_log(LOG_WARNING, "close(%s): %s", file->filename, errno_s); if (ret == KORE_RESULT_ERROR) { if (unlink(file->filename) == -1) { kore_log(LOG_WARNING, "unlink(%s): %s", file->filename, errno_s); } ret = KORE_RESULT_OK; } return (KORE_RESULT_OK); } kore-4.2.3/examples/video_stream/000077500000000000000000000000001430066302400167555ustar00rootroot00000000000000kore-4.2.3/examples/video_stream/.gitignore000077500000000000000000000000421430066302400207440ustar00rootroot00000000000000*.o .objs stream.so assets.h cert kore-4.2.3/examples/video_stream/README.md000066400000000000000000000010321430066302400202300ustar00rootroot00000000000000A simple HTML5 video streaming service using Kore. Building: ``` You must first place a test video inside the videos/ folder. I tested this using Big Buck Bunny (ogg version) on Chrome. But any video that can be played with HTML5 works. If you did not save your video as videos/video.ogg make sure you update the assets/video.html file to point to the right video. When done, run a kodev build. ``` Run: ``` kodev run ``` Visit the URI and you should see a video stream. Frontend parts from video.js: http://www.videojs.com/ kore-4.2.3/examples/video_stream/assets/000077500000000000000000000000001430066302400202575ustar00rootroot00000000000000kore-4.2.3/examples/video_stream/assets/video.html000066400000000000000000000007211430066302400222530ustar00rootroot00000000000000 Video stream over Kore kore-4.2.3/examples/video_stream/conf/000077500000000000000000000000001430066302400177025ustar00rootroot00000000000000kore-4.2.3/examples/video_stream/conf/build.conf000066400000000000000000000010241430066302400216450ustar00rootroot00000000000000# video_stream build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare mime_add=html:text/html; charset=utf-8 dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/video_stream/conf/video_stream.conf000077500000000000000000000005051430066302400232350ustar00rootroot00000000000000# Placeholder configuration server tls { bind 127.0.0.1 8888 } load ./video_stream.so init http_keepalive_time 600 domain * { attach tls certfile cert/server.pem certkey cert/key.pem accesslog access.log route / { handler asset_serve_video_html } route ^/[a-z]*.[a-z0-9]{3}$ { handler video_stream } } kore-4.2.3/examples/video_stream/src/000077500000000000000000000000001430066302400175445ustar00rootroot00000000000000kore-4.2.3/examples/video_stream/src/stream.c000077500000000000000000000137261430066302400212170ustar00rootroot00000000000000/* * Copyright (c) 2014 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "assets.h" struct video { int fd; int ref; off_t size; char *path; u_int8_t *data; void *base; TAILQ_ENTRY(video) list; }; int init(int); int video_stream(struct http_request *); static void video_unmap(struct video *); static int video_stream_finish(struct netbuf *); static int video_mmap(struct http_request *, struct video *); static int video_open(struct http_request *, struct video **); TAILQ_HEAD(, video) videos; int init(int state) { if (state == KORE_MODULE_UNLOAD) { kore_log(LOG_NOTICE, "not reloading module"); return (KORE_RESULT_ERROR); } TAILQ_INIT(&videos); return (KORE_RESULT_OK); } int video_stream(struct http_request *req) { struct video *v; const char *header; off_t start, end; int n, err, status; char *bytes, *range[3], rb[128], *ext, ctype[32]; if (!video_open(req, &v)) return (KORE_RESULT_OK); if ((ext = strrchr(req->path, '.')) == NULL) { v->ref--; http_response(req, 400, NULL, 0); return (KORE_RESULT_OK); } if (!kore_snprintf(ctype, sizeof(ctype), NULL, "video/%s", ext + 1)) { v->ref--; http_response(req, 500, NULL, 0); return (KORE_RESULT_OK); } kore_log(LOG_NOTICE, "%p: opened %s (%s) for streaming (%jd ref:%d)", (void *)req->owner, v->path, ctype, (intmax_t)v->size, v->ref); if (http_request_header(req, "range", &header)) { if ((bytes = strchr(header, '=')) == NULL) { v->ref--; http_response(req, 416, NULL, 0); return (KORE_RESULT_OK); } bytes++; n = kore_split_string(bytes, "-", range, 3); if (n == 0) { v->ref--; http_response(req, 416, NULL, 0); return (KORE_RESULT_OK); } if (n >= 1) { start = kore_strtonum64(range[0], 1, &err); if (err != KORE_RESULT_OK) { v->ref--; http_response(req, 416, NULL, 0); return (KORE_RESULT_OK); } } if (n > 1) { end = kore_strtonum64(range[1], 1, &err); if (err != KORE_RESULT_OK) { v->ref--; http_response(req, 416, NULL, 0); return (KORE_RESULT_OK); } } else { end = 0; } if (end == 0) end = v->size; if (start > end || start > v->size || end > v->size) { v->ref--; http_response(req, 416, NULL, 0); return (KORE_RESULT_OK); } status = 206; if (!kore_snprintf(rb, sizeof(rb), NULL, "bytes %ld-%ld/%ld", start, end - 1, v->size)) { v->ref--; http_response(req, 500, NULL, 0); return (KORE_RESULT_OK); } kore_log(LOG_NOTICE, "%p: %s sending: %jd-%jd/%jd", (void *)req->owner, v->path, (intmax_t)start, (intmax_t)end - 1, (intmax_t)v->size); http_response_header(req, "content-range", rb); } else { start = 0; status = 200; end = v->size; } http_response_header(req, "content-type", ctype); http_response_header(req, "accept-ranges", "bytes"); http_response_stream(req, status, v->data + start, end - start, video_stream_finish, v); return (KORE_RESULT_OK); } static int video_open(struct http_request *req, struct video **out) { struct stat st; struct video *v; char fpath[MAXPATHLEN]; if (!kore_snprintf(fpath, sizeof(fpath), NULL, "videos%s", req->path)) { http_response(req, 500, NULL, 0); return (KORE_RESULT_ERROR); } TAILQ_FOREACH(v, &videos, list) { if (!strcmp(v->path, fpath)) { if (video_mmap(req, v)) { *out = v; return (KORE_RESULT_OK); } close(v->fd); TAILQ_REMOVE(&videos, v, list); kore_free(v->path); kore_free(v); http_response(req, 500, NULL, 0); return (KORE_RESULT_ERROR); } } v = kore_malloc(sizeof(*v)); v->ref = 0; v->base = NULL; v->data = NULL; v->path = kore_strdup(fpath); if ((v->fd = open(fpath, O_RDONLY)) == -1) { kore_free(v->path); kore_free(v); if (errno == ENOENT) http_response(req, 404, NULL, 0); else http_response(req, 500, NULL, 0); return (KORE_RESULT_ERROR); } if (fstat(v->fd, &st) == -1) { close(v->fd); kore_free(v->path); kore_free(v); http_response(req, 500, NULL, 0); return (KORE_RESULT_ERROR); } v->size = st.st_size; if (!video_mmap(req, v)) { close(v->fd); kore_free(v->path); kore_free(v); http_response(req, 500, NULL, 0); return (KORE_RESULT_ERROR); } *out = v; TAILQ_INSERT_TAIL(&videos, v, list); return (KORE_RESULT_OK); } static int video_mmap(struct http_request *req, struct video *v) { if (v->base != NULL && v->data != NULL) { v->ref++; return (KORE_RESULT_OK); } v->base = mmap(NULL, v->size, PROT_READ, MAP_SHARED, v->fd, 0); if (v->base == MAP_FAILED) return (KORE_RESULT_ERROR); v->ref++; v->data = v->base; return (KORE_RESULT_OK); } static int video_stream_finish(struct netbuf *nb) { struct video *v = nb->extra; v->ref--; kore_log(LOG_NOTICE, "%p: video stream %s done (%zu/%zu ref:%d)", (void *)nb->owner, v->path, nb->s_off, nb->b_len, v->ref); if (v->ref == 0) video_unmap(v); return (KORE_RESULT_OK); } static void video_unmap(struct video *v) { if (munmap(v->base, v->size) == -1) { kore_log(LOG_ERR, "munmap(%s): %s", v->path, errno_s); } else { v->base = NULL; v->data = NULL; kore_log(LOG_NOTICE, "unmapped %s for streaming, no refs left", v->path); } } kore-4.2.3/examples/video_stream/videos/000077500000000000000000000000001430066302400202465ustar00rootroot00000000000000kore-4.2.3/examples/video_stream/videos/placeholder000066400000000000000000000000371430066302400224530ustar00rootroot00000000000000Drop your HTML5 videos in here kore-4.2.3/examples/websocket/000077500000000000000000000000001430066302400162625ustar00rootroot00000000000000kore-4.2.3/examples/websocket/.gitignore000077500000000000000000000000451430066302400202540ustar00rootroot00000000000000*.o .objs websocket.so assets.h cert kore-4.2.3/examples/websocket/README.md000066400000000000000000000007231430066302400175430ustar00rootroot00000000000000Kore example websocket server Run: ``` # kodev run ``` Test: ``` Open a browser that does websockets, surf to https://127.0.0.1:8888 or whatever configured IP you have in the config. Hit the connect button to open a websocket session, open a second tab and surf to the same address and hit the connection button there as well. This should cause the number of messages sent/recv to keep incrementing as each message is broadcast to the other connection. ``` kore-4.2.3/examples/websocket/assets/000077500000000000000000000000001430066302400175645ustar00rootroot00000000000000kore-4.2.3/examples/websocket/assets/frontend.html000066400000000000000000000020241430066302400222670ustar00rootroot00000000000000
kore-4.2.3/examples/websocket/conf/000077500000000000000000000000001430066302400172075ustar00rootroot00000000000000kore-4.2.3/examples/websocket/conf/build.conf000066400000000000000000000007511430066302400211600ustar00rootroot00000000000000# websocket build config # You can switch flavors using: kodev flavor [newflavor] # The cflags below are shared between flavors cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { # These cflags are added to the shared ones when # you build the "dev" flavor. cflags=-g } #prod { # You can specify additional CFLAGS here which are only # included if you build with the "prod" flavor. #} kore-4.2.3/examples/websocket/conf/websocket.conf000077500000000000000000000006201430066302400220450ustar00rootroot00000000000000# Kore websocket example server tls { bind 127.0.0.1 8888 } load ./websocket.so # Increase workers so connections are spread # across them to demonstrate WEBSOCKET_BROADCAST_GLOBAL. workers 4 websocket_maxframe 65536 websocket_timeout 20 domain * { attach tls certfile cert/server.pem certkey cert/key.pem route / { handler page } route /connect { handler page_ws_connect } } kore-4.2.3/examples/websocket/src/000077500000000000000000000000001430066302400170515ustar00rootroot00000000000000kore-4.2.3/examples/websocket/src/websocket.c000077500000000000000000000036471430066302400212200ustar00rootroot00000000000000/* * Copyright (c) 2014 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "assets.h" int page(struct http_request *); int page_ws_connect(struct http_request *); void websocket_connect(struct connection *); void websocket_disconnect(struct connection *); void websocket_message(struct connection *, u_int8_t, void *, size_t); /* Called whenever we get a new websocket connection. */ void websocket_connect(struct connection *c) { kore_log(LOG_NOTICE, "%p: connected", c); } void websocket_message(struct connection *c, u_int8_t op, void *data, size_t len) { kore_websocket_broadcast(c, op, data, len, WEBSOCKET_BROADCAST_GLOBAL); } void websocket_disconnect(struct connection *c) { kore_log(LOG_NOTICE, "%p: disconnecting", c); } int page(struct http_request *req) { http_response_header(req, "content-type", "text/html"); http_response(req, 200, asset_frontend_html, asset_len_frontend_html); return (KORE_RESULT_OK); } int page_ws_connect(struct http_request *req) { /* Perform the websocket handshake, passing our callbacks. */ kore_websocket_handshake(req, "websocket_connect", "websocket_message", "websocket_disconnect"); return (KORE_RESULT_OK); } kore-4.2.3/include/000077500000000000000000000000001430066302400141015ustar00rootroot00000000000000kore-4.2.3/include/kore/000077500000000000000000000000001430066302400150415ustar00rootroot00000000000000kore-4.2.3/include/kore/acme.h000066400000000000000000000041611430066302400161210ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __H_ACME_H #define __H_ACME_H #if defined(__cplusplus) extern "C" { #endif /* * All acme paths are relative to the keymgr_root directory. */ #define KORE_ACME_ACCOUNT_KEY "account-key.pem" #define KORE_ACME_CERTDIR "certificates" #define KORE_ACME_RSAKEY_E (KORE_MSG_ACME_BASE + 1) #define KORE_ACME_RSAKEY_N (KORE_MSG_ACME_BASE + 2) #define KORE_ACME_SIGN (KORE_MSG_ACME_BASE + 3) #define KORE_ACME_SIGN_RESULT (KORE_MSG_ACME_BASE + 4) #define KORE_ACME_PROC_READY (KORE_MSG_ACME_BASE + 5) #define KORE_ACME_ACCOUNT_CREATE (KORE_MSG_ACME_BASE + 10) #define KORE_ACME_ACCOUNT_RESOLVE (KORE_MSG_ACME_BASE + 11) #define KORE_ACME_ORDER_CREATE (KORE_MSG_ACME_BASE + 12) #define KORE_ACME_CSR_REQUEST (KORE_MSG_ACME_BASE + 13) #define KORE_ACME_CSR_RESPONSE (KORE_MSG_ACME_BASE + 14) #define KORE_ACME_INSTALL_CERT (KORE_MSG_ACME_BASE + 15) #define KORE_ACME_ORDER_FAILED (KORE_MSG_ACME_BASE + 16) #define KORE_ACME_CHALLENGE_CERT (KORE_MSG_ACME_BASE + 20) #define KORE_ACME_CHALLENGE_SET_CERT (KORE_MSG_ACME_BASE + 21) #define KORE_ACME_CHALLENGE_CLEAR_CERT (KORE_MSG_ACME_BASE + 22) void kore_acme_init(void); void kore_acme_run(void); void kore_acme_setup(void); void kore_acme_get_paths(const char *, char **, char **); extern char *acme_email; extern int acme_domains; extern char *acme_provider; #if defined(__cplusplus) } #endif #endif kore-4.2.3/include/kore/curl.h000066400000000000000000000056641430066302400161720ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __H_CURL_H #define __H_CURL_H #if defined(__cplusplus) extern "C" { #endif #include #include "http.h" #define KORE_CURL_TIMEOUT 60 #define KORE_CURL_RECV_MAX (1024 * 1024 * 2) #define KORE_CURL_FLAG_HTTP_PARSED_HEADERS 0x0001 #define KORE_CURL_FLAG_BOUND 0x0002 #define KORE_CURL_SYNC 0x1000 #define KORE_CURL_ASYNC 0x2000 #define KORE_CURL_TYPE_CUSTOM 1 #define KORE_CURL_TYPE_HTTP_CLIENT 2 struct kore_curl { int type; int flags; CURLcode result; char *url; CURL *handle; struct kore_buf *response; struct http_request *req; void *arg; void (*cb)(struct kore_curl *, void *); char errbuf[CURL_ERROR_SIZE]; /* For the simplified HTTP api. */ struct { long status; struct curl_slist *hdrlist; struct kore_buf *tosend; struct kore_buf *headers; TAILQ_HEAD(, http_header) resp_hdrs; } http; LIST_ENTRY(kore_curl) list; }; extern u_int16_t kore_curl_timeout; extern u_int64_t kore_curl_recv_max; void kore_curl_sysinit(void); void kore_curl_do_timeout(void); void kore_curl_run_scheduled(void); void kore_curl_run(struct kore_curl *); void kore_curl_cleanup(struct kore_curl *); int kore_curl_success(struct kore_curl *); void kore_curl_run_sync(struct kore_curl *); void kore_curl_logerror(struct kore_curl *); int kore_curl_init(struct kore_curl *, const char *, int); size_t kore_curl_tobuf(char *, size_t, size_t, void *); size_t kore_curl_frombuf(char *, size_t, size_t, void *); void kore_curl_http_parse_headers(struct kore_curl *); void kore_curl_http_set_header(struct kore_curl *, const char *, const char *); int kore_curl_http_get_header(struct kore_curl *, const char *, const char **); void kore_curl_http_setup(struct kore_curl *, int, const void *, size_t); char *kore_curl_response_as_string(struct kore_curl *); void kore_curl_response_as_bytes(struct kore_curl *, const u_int8_t **, size_t *); void kore_curl_bind_request(struct kore_curl *, struct http_request *); void kore_curl_bind_callback(struct kore_curl *, void (*cb)(struct kore_curl *, void *), void *); const char *kore_curl_strerror(struct kore_curl *); #if defined(__cplusplus) } #endif #endif kore-4.2.3/include/kore/hooks.h000066400000000000000000000023141430066302400163350ustar00rootroot00000000000000/* * Copyright (c) 2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __H_HOOKS_H #define __H_HOOKS_H #define KORE_CONFIG_HOOK "kore_parent_configure" #define KORE_TEARDOWN_HOOK "kore_parent_teardown" #define KORE_DAEMONIZED_HOOK "kore_parent_daemonized" void kore_seccomp_hook(void); void kore_worker_signal(int); void kore_worker_teardown(void); void kore_parent_teardown(void); void kore_worker_configure(void); void kore_parent_daemonized(void); void kore_parent_configure(int, char **); #endif kore-4.2.3/include/kore/http.h000066400000000000000000000334771430066302400162070ustar00rootroot00000000000000/* * Copyright (c) 2013 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #if !defined(KORE_NO_HTTP) #ifndef __H_HTTP_H #define __H_HTTP_H #include #include #include "sha2.h" #if defined(__cplusplus) extern "C" { #endif /* Keep the http_populate_get symbol around. */ #define http_populate_get http_populate_qs #define HTTP_KEEPALIVE_TIME 20 #define HTTP_HSTS_ENABLE 31536000 #define HTTP_HEADER_MAX_LEN 4096 #define HTTP_BODY_MAX_LEN 1024000 #define HTTP_URI_LEN 2000 #define HTTP_USERAGENT_LEN 256 #define HTTP_REFERER_LEN 256 #define HTTP_REQ_HEADER_MAX 25 #define HTTP_MAX_QUERY_ARGS 20 #define HTTP_MAX_COOKIES 10 #define HTTP_MAX_COOKIENAME 255 #define HTTP_HEADER_BUFSIZE 1024 #define HTTP_COOKIE_BUFSIZE 1024 #define HTTP_DATE_MAXSIZE 255 #define HTTP_REQUEST_LIMIT 1000 #define HTTP_REQUEST_MS 10 #define HTTP_BODY_DISK_PATH "tmp_files" #define HTTP_BODY_DISK_OFFLOAD 0 #define HTTP_BODY_PATH_MAX 256 #define HTTP_BOUNDARY_MAX 80 #define HTTP_HEADER_TIMEOUT 10 #define HTTP_BODY_TIMEOUT 60 #define HTTP_ARG_TYPE_RAW 0 #define HTTP_ARG_TYPE_BYTE 1 #define HTTP_ARG_TYPE_INT16 2 #define HTTP_ARG_TYPE_UINT16 3 #define HTTP_ARG_TYPE_INT32 4 #define HTTP_ARG_TYPE_UINT32 5 #define HTTP_ARG_TYPE_STRING 6 #define HTTP_ARG_TYPE_INT64 7 #define HTTP_ARG_TYPE_UINT64 8 #define HTTP_ARG_TYPE_FLOAT 9 #define HTTP_ARG_TYPE_DOUBLE 10 #define HTTP_STATE_ERROR 0 #define HTTP_STATE_CONTINUE 1 #define HTTP_STATE_COMPLETE 2 #define HTTP_STATE_RETRY 3 struct http_runlock_queue { struct http_request *req; LIST_ENTRY(http_runlock_queue) list; }; struct http_runlock { struct http_request *owner; LIST_HEAD(, http_runlock_queue) queue; }; struct http_header { char *header; char *value; TAILQ_ENTRY(http_header) list; }; #define HTTP_COOKIE_HTTPONLY 0x0001 #define HTTP_COOKIE_SECURE 0x0002 struct http_cookie { char *name; char *value; char *path; char *domain; u_int32_t maxage; time_t expires; u_int16_t flags; TAILQ_ENTRY(http_cookie) list; }; struct http_arg { char *name; char *s_value; TAILQ_ENTRY(http_arg) list; }; #define COPY_ARG_TYPE(v, t) \ do { \ *(t *)nout = v; \ } while (0) #define COPY_ARG_INT64(type, sign) \ do { \ int err; \ type nval; \ nval = (type)kore_strtonum64(data, sign, &err); \ if (err != KORE_RESULT_OK) \ return (KORE_RESULT_ERROR); \ COPY_ARG_TYPE(nval, type); \ } while (0) #define COPY_ARG_DOUBLE(min, max, type) \ do { \ int err; \ type nval; \ nval = kore_strtodouble(data, min, max, &err); \ if (err != KORE_RESULT_OK) \ return (KORE_RESULT_ERROR); \ COPY_ARG_TYPE(nval, type); \ } while (0) #define COPY_ARG_INT(min, max, type) \ do { \ int err; \ int64_t nval; \ nval = kore_strtonum(data, 10, min, max, &err); \ if (err != KORE_RESULT_OK) \ return (KORE_RESULT_ERROR); \ COPY_ARG_TYPE(nval, type); \ } while (0) #define COPY_AS_INTTYPE_64(type, sign) \ do { \ if (nout == NULL) \ return (KORE_RESULT_ERROR); \ COPY_ARG_INT64(type, sign); \ } while (0) #define COPY_AS_INTTYPE(min, max, type) \ do { \ if (nout == NULL) \ return (KORE_RESULT_ERROR); \ COPY_ARG_INT(min, max, type); \ } while (0) #define http_argument_get_string(r, n, o) \ http_argument_get(r, n, (void **)o, NULL, HTTP_ARG_TYPE_STRING) #define http_argument_get_byte(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_BYTE) #define http_argument_get_uint16(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_UINT16) #define http_argument_get_int16(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_INT16) #define http_argument_get_uint32(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_UINT32) #define http_argument_get_int32(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_INT32) #define http_argument_get_uint64(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_UINT64) #define http_argument_get_int64(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_INT64) #define http_argument_get_float(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_FLOAT) #define http_argument_get_double(r, n, o) \ http_argument_get(r, n, NULL, o, HTTP_ARG_TYPE_DOUBLE) #define http_request_header_byte(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_BYTE) #define http_request_header_uint16(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_UINT16) #define http_request_header_int16(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_INT16) #define http_request_header_uint32(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_UINT32) #define http_request_header_int32(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_INT32) #define http_request_header_uint64(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_UINT64) #define http_request_header_int64(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_INT64) #define http_request_header_float(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_FLOAT) #define http_request_header_double(r, n, o) \ http_request_header_get(r, n, NULL, o, HTTP_ARG_TYPE_DOUBLE) struct http_file { char *name; char *filename; size_t position; size_t offset; size_t length; struct http_request *req; TAILQ_ENTRY(http_file) list; }; #define HTTP_METHOD_GET 0x0001 #define HTTP_METHOD_POST 0x0002 #define HTTP_METHOD_PUT 0x0004 #define HTTP_METHOD_DELETE 0x0010 #define HTTP_METHOD_HEAD 0x0020 #define HTTP_METHOD_OPTIONS 0x0040 #define HTTP_METHOD_PATCH 0x0080 #define HTTP_METHOD_ALL (HTTP_METHOD_GET | HTTP_METHOD_POST | \ HTTP_METHOD_PUT | HTTP_METHOD_DELETE | HTTP_METHOD_HEAD | \ HTTP_METHOD_OPTIONS | HTTP_METHOD_PATCH) #define HTTP_REQUEST_COMPLETE 0x0001 #define HTTP_REQUEST_DELETE 0x0002 #define HTTP_REQUEST_SLEEPING 0x0004 #define HTTP_REQUEST_EXPECT_BODY 0x0020 #define HTTP_REQUEST_RETAIN_EXTRA 0x0040 #define HTTP_REQUEST_NO_CONTENT_LENGTH 0x0080 #define HTTP_REQUEST_AUTHED 0x0100 #define HTTP_VERSION_1_1 0x1000 #define HTTP_VERSION_1_0 0x2000 #define HTTP_VALIDATOR_IS_REQUEST 0x8000 #define HTTP_BODY_DIGEST_LEN 32 #define HTTP_BODY_DIGEST_STRLEN ((HTTP_BODY_DIGEST_LEN * 2) + 1) #define HTTP_CAPTURE_GROUPS 17 struct reqcall; struct kore_task; struct http_client; struct http_redirect { regex_t rctx; int status; char *target; TAILQ_ENTRY(http_redirect) list; }; struct http_request { u_int8_t method; u_int8_t fsm_state; u_int16_t flags; u_int16_t status; u_int64_t ms; u_int64_t start; u_int64_t end; u_int64_t total; const char *path; const char *host; const char *agent; const char *referer; struct connection *owner; SHA2_CTX hashctx; u_int8_t *headers; struct kore_buf *http_body; int http_body_fd; char *http_body_path; u_int64_t http_body_length; u_int64_t http_body_offset; u_int64_t content_length; void *hdlr_extra; size_t state_len; char *query_string; struct kore_route *rt; struct http_runlock_queue *runlock; void (*onfree)(struct http_request *); #if defined(KORE_USE_PYTHON) void *py_req; void *py_coro; void *py_validator; struct reqcall *py_rqnext; #endif regmatch_t cgroups[HTTP_CAPTURE_GROUPS]; u_int8_t http_body_digest[HTTP_BODY_DIGEST_LEN]; #if defined(KORE_USE_CURL) LIST_HEAD(, kore_curl) chandles; #endif #if defined(KORE_USE_TASKS) LIST_HEAD(, kore_task) tasks; #endif #if defined(KORE_USE_PGSQL) LIST_HEAD(, kore_pgsql) pgsqls; #endif TAILQ_HEAD(, http_cookie) req_cookies; TAILQ_HEAD(, http_cookie) resp_cookies; TAILQ_HEAD(, http_header) req_headers; TAILQ_HEAD(, http_header) resp_headers; TAILQ_HEAD(, http_arg) arguments; TAILQ_HEAD(, http_file) files; TAILQ_ENTRY(http_request) list; TAILQ_ENTRY(http_request) olist; }; #define KORE_HTTP_STATE(f) { #f, f } struct http_state { const char *name; int (*cb)(struct http_request *); }; struct http_media_type { char *ext; char *type; LIST_ENTRY(http_media_type) list; }; extern size_t http_body_max; extern u_int16_t http_body_timeout; extern u_int16_t http_header_max; extern u_int16_t http_header_timeout; extern u_int32_t http_request_ms; extern u_int64_t http_hsts_enable; extern u_int16_t http_keepalive_time; extern u_int32_t http_request_limit; extern u_int32_t http_request_count; extern u_int64_t http_body_disk_offload; extern int http_pretty_error; extern char *http_body_disk_path; extern struct kore_pool http_header_pool; void kore_accesslog(struct http_request *); void http_init(void); void http_parent_init(void); void http_cleanup(void); void http_server_version(const char *); void http_process(void); const char *http_status_text(int); const char *http_method_text(int); time_t http_date_to_time(char *); char *http_validate_header(char *); int http_method_value(const char *); void http_start_recv(struct connection *); void http_request_free(struct http_request *); void http_request_sleep(struct http_request *); void http_request_wakeup(struct http_request *); void http_process_request(struct http_request *); int http_body_rewind(struct http_request *); int http_media_register(const char *, const char *); int http_check_timeout(struct connection *, u_int64_t); ssize_t http_body_read(struct http_request *, void *, size_t); int http_body_digest(struct http_request *, char *, size_t); int http_redirect_add(struct kore_domain *, const char *, int, const char *); void http_response(struct http_request *, int, const void *, size_t); void http_response_json(struct http_request *, int, struct kore_json_item *); void http_response_close(struct http_request *, int, const void *, size_t); void http_response_fileref(struct http_request *, int, struct kore_fileref *); void http_serveable(struct http_request *, const void *, size_t, const char *, const char *); void http_response_stream(struct http_request *, int, void *, size_t, int (*cb)(struct netbuf *), void *); int http_request_header(struct http_request *, const char *, const char **); void http_response_header(struct http_request *, const char *, const char *); int http_state_run(struct http_state *, u_int8_t, struct http_request *); int http_request_cookie(struct http_request *, const char *, char **); void http_response_cookie(struct http_request *, const char *, const char *, const char *, time_t, u_int32_t, struct http_cookie **); void http_runlock_init(struct http_runlock *); void http_runlock_release(struct http_runlock *, struct http_request *); int http_runlock_acquire(struct http_runlock *, struct http_request *); const char *http_media_type(const char *); void *http_state_get(struct http_request *); int http_state_exists(struct http_request *); void http_state_cleanup(struct http_request *); void *http_state_create(struct http_request *, size_t); int http_argument_urldecode(char *); int http_header_recv(struct netbuf *); void http_populate_qs(struct http_request *); void http_populate_post(struct http_request *); void http_populate_multipart_form(struct http_request *); void http_populate_cookies(struct http_request *); int http_argument_get(struct http_request *, const char *, void **, void *, int); int http_request_header_get(struct http_request *, const char *, void **, void *, int); void http_file_rewind(struct http_file *); ssize_t http_file_read(struct http_file *, void *, size_t); struct http_file *http_file_lookup(struct http_request *, const char *); enum http_status_code { HTTP_STATUS_CONTINUE = 100, HTTP_STATUS_SWITCHING_PROTOCOLS = 101, HTTP_STATUS_OK = 200, HTTP_STATUS_CREATED = 201, HTTP_STATUS_ACCEPTED = 202, HTTP_STATUS_NON_AUTHORITATIVE = 203, HTTP_STATUS_NO_CONTENT = 204, HTTP_STATUS_RESET_CONTENT = 205, HTTP_STATUS_PARTIAL_CONTENT = 206, HTTP_STATUS_MULTIPLE_CHOICES = 300, HTTP_STATUS_MOVED_PERMANENTLY = 301, HTTP_STATUS_FOUND = 302, HTTP_STATUS_SEE_OTHER = 303, HTTP_STATUS_NOT_MODIFIED = 304, HTTP_STATUS_USE_PROXY = 305, HTTP_STATUS_TEMPORARY_REDIRECT = 307, HTTP_STATUS_BAD_REQUEST = 400, HTTP_STATUS_UNAUTHORIZED = 401, HTTP_STATUS_PAYMENT_REQUIRED = 402, HTTP_STATUS_FORBIDDEN = 403, HTTP_STATUS_NOT_FOUND = 404, HTTP_STATUS_METHOD_NOT_ALLOWED = 405, HTTP_STATUS_NOT_ACCEPTABLE = 406, HTTP_STATUS_PROXY_AUTH_REQUIRED = 407, HTTP_STATUS_REQUEST_TIMEOUT = 408, HTTP_STATUS_CONFLICT = 409, HTTP_STATUS_GONE = 410, HTTP_STATUS_LENGTH_REQUIRED = 411, HTTP_STATUS_PRECONDITION_FAILED = 412, HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE = 413, HTTP_STATUS_REQUEST_URI_TOO_LARGE = 414, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, HTTP_STATUS_REQUEST_RANGE_INVALID = 416, HTTP_STATUS_EXPECTATION_FAILED = 417, HTTP_STATUS_MISDIRECTED_REQUEST = 421, HTTP_STATUS_INTERNAL_ERROR = 500, HTTP_STATUS_NOT_IMPLEMENTED = 501, HTTP_STATUS_BAD_GATEWAY = 502, HTTP_STATUS_SERVICE_UNAVAILABLE = 503, HTTP_STATUS_GATEWAY_TIMEOUT = 504, HTTP_STATUS_BAD_VERSION = 505 }; #if defined(__cplusplus) } #endif #endif /* !__H_HTTP_H */ #endif /* ! KORE_NO_HTTP */ kore-4.2.3/include/kore/jsonrpc.h000066400000000000000000000052511430066302400166730ustar00rootroot00000000000000/* * Copyright (c) 2016 Raphaël Monrouzeau * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #if !defined(KORE_NO_HTTP) #ifndef __H_JSONRPC_H #define __H_JSONRPC_H #if defined(__cplusplus) extern "C" { #endif /* JSON RPC request handling log entry. */ struct jsonrpc_log { char *msg; struct jsonrpc_log *next, *prev; int lvl; }; /* JSON RPC request. */ struct jsonrpc_request { struct jsonrpc_log log; struct kore_buf buf; struct http_request *http; yajl_gen gen; yajl_val json; yajl_val id; char *method; yajl_val params; unsigned int flags; int log_levels; }; #define YAJL_GEN_CONST_STRING(CTX, STR) \ yajl_gen_string((CTX), (unsigned char *)(STR), sizeof (STR) - 1) #define YAJL_GEN_CONST_NUMBER(CTX, STR) \ yajl_gen_number((CTX), (unsigned char *)(STR), sizeof (STR) - 1) #define YAJL_GEN_KO(OPERATION) \ ((OPERATION) != yajl_gen_status_ok) enum jsonrpc_error_code { #define JSONRPC_PARSE_ERROR_MSG "Parse error" JSONRPC_PARSE_ERROR = -32700, #define JSONRPC_INVALID_REQUEST_MSG "Invalid Request" JSONRPC_INVALID_REQUEST = -32600, #define JSONRPC_METHOD_NOT_FOUND_MSG "Method not found" JSONRPC_METHOD_NOT_FOUND = -32601, #define JSONRPC_INVALID_PARAMS_MSG "Invalid params" JSONRPC_INVALID_PARAMS = -32602, #define JSONRPC_INTERNAL_ERROR_MSG "Internal error" JSONRPC_INTERNAL_ERROR = -32603, #define JSONRPC_SERVER_ERROR_MSG "Server error" JSONRPC_SERVER_ERROR = -32000, #define JSONRPC_LIMIT_REACHED_MSG "Limit reached" JSONRPC_LIMIT_REACHED = -31997 }; void jsonrpc_log(struct jsonrpc_request *, int, const char *, ...) __attribute__((format (printf, 3, 4))); int jsonrpc_read_request(struct http_request *, struct jsonrpc_request *); void jsonrpc_destroy_request(struct jsonrpc_request *); int jsonrpc_error(struct jsonrpc_request *, int, const char *); int jsonrpc_result(struct jsonrpc_request *, int (*)(struct jsonrpc_request *, void *), void *); #if defined(__cplusplus) } #endif #endif /* !__H_JSONRPC_H */ #endif /* ! KORE_NO_HTTP */ kore-4.2.3/include/kore/kore.h000066400000000000000000000760431430066302400161640ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __H_KORE_H #define __H_KORE_H #if defined(__APPLE__) #define daemon portability_is_king #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__cplusplus) extern "C" { #endif #if defined(__APPLE__) #undef daemon extern int daemon(int, int); #define st_mtim st_mtimespec #endif #if !defined(KORE_NO_SENDFILE) #if defined(__MACH__) || defined(__FreeBSD_version) || defined(__linux__) #define KORE_USE_PLATFORM_SENDFILE 1 #endif #endif #if defined(__OpenBSD__) #define KORE_USE_PLATFORM_PLEDGE 1 #endif #if defined(TLS_BACKEND_OPENSSL) #include #include typedef X509 KORE_X509; typedef SSL KORE_TLS; typedef SSL_CTX KORE_TLS_CTX; typedef X509_NAME KORE_X509_NAMES; typedef EVP_PKEY KORE_PRIVATE_KEY; #else typedef void KORE_X509; typedef void KORE_TLS; typedef void KORE_TLS_CTX; typedef void KORE_X509_NAMES; typedef void KORE_PRIVATE_KEY; #endif #define KORE_RSAKEY_BITS 4096 /* Kore quit reasons. */ #define KORE_QUIT_NONE -1 #define KORE_QUIT_NORMAL 0 #define KORE_QUIT_FATAL 1 #define KORE_RESULT_ERROR 0 #define KORE_RESULT_OK 1 #define KORE_RESULT_RETRY 2 #define KORE_TLS_VERSION_1_3 0 #define KORE_TLS_VERSION_1_2 1 #define KORE_TLS_VERSION_BOTH 2 #define KORE_BASE64_RAW 0x0001 #define KORE_WAIT_INFINITE (u_int64_t)-1 #define KORE_RESEED_TIME (1800 * 1000) #define errno_s strerror(errno) #define ssl_errno_s ERR_error_string(ERR_get_error(), NULL) #define KORE_DOMAINNAME_LEN 255 #define KORE_PIDFILE_DEFAULT "kore.pid" #define KORE_DHPARAM_PATH PREFIX "/share/kore/ffdhe4096.pem" #define KORE_DEFAULT_CIPHER_LIST "AEAD-AES256-GCM-SHA384:AEAD-CHACHA20-POLY1305-SHA256:AEAD-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256" #define NETBUF_RECV 0 #define NETBUF_SEND 1 #define NETBUF_SEND_PAYLOAD_MAX 8192 #define SENDFILE_PAYLOAD_MAX (1024 * 1024 * 10) #define NETBUF_LAST_CHAIN 0 #define NETBUF_BEFORE_CHAIN 1 #define NETBUF_CALL_CB_ALWAYS 0x01 #define NETBUF_FORCE_REMOVE 0x02 #define NETBUF_MUST_RESEND 0x04 #define NETBUF_IS_STREAM 0x10 #define NETBUF_IS_FILEREF 0x20 #define KORE_X509_COMMON_NAME_ONLY 0x0001 #define KORE_PEM_CERT_CHAIN 1 #define KORE_DER_CERT_DATA 2 /* XXX hackish. */ #if !defined(KORE_NO_HTTP) struct http_request; struct http_redirect; #endif #define KORE_FILEREF_SOFT_REMOVED 0x1000 struct kore_fileref { int cnt; int flags; int ontls; off_t size; char *path; u_int64_t mtime; time_t mtime_sec; u_int64_t expiration; void *base; int fd; TAILQ_ENTRY(kore_fileref) list; }; struct netbuf { u_int8_t *buf; size_t s_off; size_t b_len; size_t m_len; u_int8_t type; u_int8_t flags; struct kore_fileref *file_ref; off_t fd_off; off_t fd_len; struct connection *owner; void *extra; int (*cb)(struct netbuf *); TAILQ_ENTRY(netbuf) list; }; TAILQ_HEAD(netbuf_head, netbuf); #define KORE_TYPE_LISTENER 1 #define KORE_TYPE_CONNECTION 2 #define KORE_TYPE_PGSQL_CONN 3 #define KORE_TYPE_TASK 4 #define KORE_TYPE_PYSOCKET 5 #define KORE_TYPE_CURL_HANDLE 6 #define CONN_STATE_UNKNOWN 0 #define CONN_STATE_TLS_SHAKE 1 #define CONN_STATE_ESTABLISHED 2 #define CONN_STATE_DISCONNECTING 3 #define CONN_PROTO_UNKNOWN 0 #define CONN_PROTO_HTTP 1 #define CONN_PROTO_WEBSOCKET 2 #define CONN_PROTO_MSG 3 #define CONN_PROTO_ACME_ALPN 200 #define KORE_EVENT_READ 0x01 #define KORE_EVENT_WRITE 0x02 #define KORE_EVENT_ERROR 0x04 #define CONN_IDLE_TIMER_ACT 0x0001 #define CONN_CLOSE_EMPTY 0x0002 #define CONN_WS_CLOSE_SENT 0x0004 #define CONN_IS_BUSY 0x0008 #define CONN_LOG_TLS_FAILURE 0x0020 #define CONN_TLS_ALPN_ACME_SEEN 0x0040 #define CONN_TLS_SNI_SEEN 0x0080 #define KORE_IDLE_TIMER_MAX 5000 #define WEBSOCKET_OP_CONT 0x00 #define WEBSOCKET_OP_TEXT 0x01 #define WEBSOCKET_OP_BINARY 0x02 #define WEBSOCKET_OP_CLOSE 0x08 #define WEBSOCKET_OP_PING 0x09 #define WEBSOCKET_OP_PONG 0x0a #define WEBSOCKET_BROADCAST_LOCAL 1 #define WEBSOCKET_BROADCAST_GLOBAL 2 #define KORE_TIMER_ONESHOT 0x01 #define KORE_TIMER_FLAGS (KORE_TIMER_ONESHOT) #define KORE_CONNECTION_PRUNE_DISCONNECT 0 #define KORE_CONNECTION_PRUNE_ALL 1 struct kore_event { int type; int flags; void (*handle)(void *, int); } __attribute__((packed)); struct connection { struct kore_event evt; int fd; u_int8_t state; u_int8_t proto; struct listener *owner; KORE_TLS *tls; KORE_X509 *tls_cert; char *tls_sni; int tls_reneg; u_int16_t flags; void *hdlr_extra; int (*handle)(struct connection *); void (*disconnect)(struct connection *); int (*read)(struct connection *, size_t *); int (*write)(struct connection *, size_t, size_t *); int family; union { struct sockaddr_in ipv4; struct sockaddr_in6 ipv6; struct sockaddr_un sun; } addr; struct { u_int64_t length; u_int64_t start; } idle_timer; struct netbuf_head send_queue; struct netbuf *snb; struct netbuf *rnb; #if !defined(KORE_NO_HTTP) u_int64_t http_start; u_int64_t http_timeout; struct kore_runtime_call *ws_connect; struct kore_runtime_call *ws_message; struct kore_runtime_call *ws_disconnect; TAILQ_HEAD(, http_request) http_requests; #endif TAILQ_ENTRY(connection) list; }; TAILQ_HEAD(connection_list, connection); extern struct connection_list connections; extern struct connection_list disconnected; #define KORE_RUNTIME_NATIVE 0 #define KORE_RUNTIME_PYTHON 1 struct kore_runtime { int type; #if !defined(KORE_NO_HTTP) int (*http_request)(void *, struct http_request *); void (*http_request_free)(void *, struct http_request *); void (*http_body_chunk)(void *, struct http_request *, const void *, size_t); int (*validator)(void *, struct http_request *, const void *); void (*wsconnect)(void *, struct connection *); void (*wsdisconnect)(void *, struct connection *); void (*wsmessage)(void *, struct connection *, u_int8_t, const void *, size_t); #endif void (*execute)(void *); int (*onload)(void *, int); void (*signal)(void *, int); void (*connect)(void *, struct connection *); void (*configure)(void *, int, char **); }; struct kore_runtime_call { void *addr; struct kore_runtime *runtime; }; #if !defined(KORE_NO_HTTP) struct kore_route_params { char *name; int flags; u_int8_t method; struct kore_validator *validator; TAILQ_ENTRY(kore_route_params) list; }; struct kore_route { char *path; char *func; int type; int errors; int methods; regex_t rctx; struct kore_domain *dom; struct kore_auth *auth; struct kore_runtime_call *rcall; struct kore_runtime_call *on_free; struct kore_runtime_call *on_headers; struct kore_runtime_call *on_body_chunk; TAILQ_HEAD(, kore_route_params) params; TAILQ_ENTRY(kore_route) list; }; #endif struct kore_domain { u_int16_t id; int logerr; u_int64_t logwarn; int accesslog; char *domain; struct kore_buf *logbuf; struct kore_server *server; #if defined(KORE_USE_ACME) int acme; int acme_challenge; void *acme_cert; size_t acme_cert_len; #endif char *cafile; char *crlfile; char *certfile; char *certkey; KORE_TLS_CTX *tls_ctx; int x509_verify_depth; #if !defined(KORE_NO_HTTP) TAILQ_HEAD(, kore_route) routes; TAILQ_HEAD(, http_redirect) redirects; #endif TAILQ_ENTRY(kore_domain) list; }; TAILQ_HEAD(kore_domain_h, kore_domain); extern struct kore_runtime kore_native_runtime; struct listener { struct kore_event evt; int fd; int family; char *port; char *host; struct kore_server *server; struct kore_runtime_call *connect; LIST_ENTRY(listener) list; }; struct kore_server { int tls; char *name; struct kore_domain_h domains; LIST_HEAD(, listener) listeners; LIST_ENTRY(kore_server) list; }; LIST_HEAD(kore_server_list, kore_server); #if !defined(KORE_NO_HTTP) #define KORE_PARAMS_QUERY_STRING 0x0001 #define KORE_AUTH_TYPE_COOKIE 1 #define KORE_AUTH_TYPE_HEADER 2 #define KORE_AUTH_TYPE_REQUEST 3 struct kore_auth { u_int8_t type; char *name; char *value; char *redirect; struct kore_validator *validator; TAILQ_ENTRY(kore_auth) list; }; #define HANDLER_TYPE_STATIC 1 #define HANDLER_TYPE_DYNAMIC 2 #endif /* !KORE_NO_HTTP */ #define KORE_MODULE_LOAD 1 #define KORE_MODULE_UNLOAD 2 #define KORE_MODULE_NATIVE 0 #define KORE_MODULE_PYTHON 1 struct kore_module; struct kore_module_functions { void (*free)(struct kore_module *); void (*reload)(struct kore_module *); int (*callback)(struct kore_module *, int); void (*load)(struct kore_module *); void *(*getsym)(struct kore_module *, const char *); }; struct kore_module { void *handle; char *path; char *onload; int type; struct kore_runtime_call *ocb; struct kore_module_functions *fun; struct kore_runtime *runtime; TAILQ_ENTRY(kore_module) list; }; /* * The workers get a 128KB log buffer per worker, and parent will fetch their * logs when it reached at least 75% of that or if its been > 1 second since * it was last synced. */ #define KORE_ACCESSLOG_BUFLEN 131072U #define KORE_ACCESSLOG_SYNC 98304U struct kore_alog_header { u_int16_t domain; u_int16_t loglen; } __attribute__((packed)); struct kore_privsep { char *root; char *runas; int skip_runas; int skip_chroot; }; struct kore_worker { u_int16_t id; u_int16_t cpu; int ready; int running; #if defined(__linux__) int tracing; #endif pid_t pid; int pipe[2]; struct connection *msg[2]; u_int8_t has_lock; int restarted; u_int64_t time_locked; struct kore_route *active_route; struct kore_privsep *ps; /* Used by the workers to store accesslogs. */ struct { int lock; size_t offset; char buf[KORE_ACCESSLOG_BUFLEN]; } lb; }; #if !defined(KORE_NO_HTTP) #define KORE_VALIDATOR_TYPE_REGEX 1 #define KORE_VALIDATOR_TYPE_FUNCTION 2 struct kore_validator { u_int8_t type; char *name; char *arg; regex_t rctx; struct kore_runtime_call *rcall; TAILQ_ENTRY(kore_validator) list; }; #endif /* !KORE_NO_HTTP */ #define KORE_BUF_OWNER_API 0x0001 struct kore_buf { u_int8_t *data; int flags; size_t length; size_t offset; }; #define KORE_JSON_TYPE_OBJECT 0x0001 #define KORE_JSON_TYPE_ARRAY 0x0002 #define KORE_JSON_TYPE_STRING 0x0004 #define KORE_JSON_TYPE_NUMBER 0x0008 #define KORE_JSON_TYPE_LITERAL 0x0010 #define KORE_JSON_TYPE_INTEGER 0x0020 #define KORE_JSON_TYPE_INTEGER_U64 0x0040 #define KORE_JSON_FALSE 0 #define KORE_JSON_TRUE 1 #define KORE_JSON_NULL 2 #define KORE_JSON_DEPTH_MAX 10 #define KORE_JSON_ERR_NONE 0 #define KORE_JSON_ERR_INVALID_OBJECT 1 #define KORE_JSON_ERR_INVALID_ARRAY 2 #define KORE_JSON_ERR_INVALID_STRING 3 #define KORE_JSON_ERR_INVALID_NUMBER 4 #define KORE_JSON_ERR_INVALID_LITERAL 5 #define KORE_JSON_ERR_DEPTH 6 #define KORE_JSON_ERR_EOF 7 #define KORE_JSON_ERR_INVALID_JSON 8 #define KORE_JSON_ERR_INVALID_SEARCH 9 #define KORE_JSON_ERR_NOT_FOUND 10 #define KORE_JSON_ERR_TYPE_MISMATCH 11 #define KORE_JSON_ERR_LAST KORE_JSON_ERR_TYPE_MISMATCH #define kore_json_find_object(j, p) \ kore_json_find(j, p, KORE_JSON_TYPE_OBJECT) #define kore_json_find_array(j, p) \ kore_json_find(j, p, KORE_JSON_TYPE_ARRAY) #define kore_json_find_string(j, p) \ kore_json_find(j, p, KORE_JSON_TYPE_STRING) #define kore_json_find_number(j, p) \ kore_json_find(j, p, KORE_JSON_TYPE_NUMBER) #define kore_json_find_integer(j, p) \ kore_json_find(j, p, KORE_JSON_TYPE_INTEGER) #define kore_json_find_integer_u64(j, p) \ kore_json_find(j, p, KORE_JSON_TYPE_INTEGER_U64) #define kore_json_find_literal(j, p) \ kore_json_find(j, p, KORE_JSON_TYPE_LITERAL) #define kore_json_create_object(o, n) \ kore_json_create_item(o, n, KORE_JSON_TYPE_OBJECT) #define kore_json_create_array(o, n) \ kore_json_create_item(o, n, KORE_JSON_TYPE_ARRAY) #define kore_json_create_string(o, n, v) \ kore_json_create_item(o, n, KORE_JSON_TYPE_STRING, v) #define kore_json_create_number(o, n, v) \ kore_json_create_item(o, n, KORE_JSON_TYPE_NUMBER, (double)v) #define kore_json_create_integer(o, n, v) \ kore_json_create_item(o, n, KORE_JSON_TYPE_INTEGER, (int64_t)v) #define kore_json_create_integer_u64(o, n, v) \ kore_json_create_item(o, n, KORE_JSON_TYPE_INTEGER_U64, (u_int64_t)v) #define kore_json_create_literal(o, n, v) \ kore_json_create_item(o, n, KORE_JSON_TYPE_LITERAL, v) struct kore_json { const u_int8_t *data; int depth; size_t length; size_t offset; struct kore_buf tmpbuf; struct kore_json_item *root; }; struct kore_json_item { u_int32_t type; char *name; struct kore_json_item *parent; union { TAILQ_HEAD(, kore_json_item) items; char *string; double number; int literal; int64_t integer; u_int64_t u64; } data; int (*parse)(struct kore_json *, struct kore_json_item *); TAILQ_ENTRY(kore_json_item) list; }; struct kore_pool_region { void *start; size_t length; LIST_ENTRY(kore_pool_region) list; }; struct kore_pool_entry { u_int8_t state; struct kore_pool_region *region; LIST_ENTRY(kore_pool_entry) list; }; struct kore_pool { size_t elen; size_t slen; size_t elms; size_t inuse; size_t growth; volatile int lock; char *name; LIST_HEAD(, kore_pool_region) regions; LIST_HEAD(, kore_pool_entry) freelist; }; struct kore_timer { u_int64_t nextrun; u_int64_t interval; int flags; void *arg; void (*cb)(void *, u_int64_t); TAILQ_ENTRY(kore_timer) list; }; /* * Keymgr process is worker index 0, but id 2000. * Acme process is worker index 1, but id 2001. */ #define KORE_WORKER_KEYMGR_IDX 0 #define KORE_WORKER_ACME_IDX 1 #define KORE_WORKER_BASE 2 #define KORE_WORKER_KEYMGR 2000 #define KORE_WORKER_ACME 2001 #define KORE_WORKER_MAX UCHAR_MAX #define KORE_WORKER_POLICY_RESTART 1 #define KORE_WORKER_POLICY_TERMINATE 2 /* Reserved message ids, registered on workers. */ #define KORE_MSG_WEBSOCKET 1 #define KORE_MSG_KEYMGR_REQ 2 #define KORE_MSG_KEYMGR_RESP 3 #define KORE_MSG_SHUTDOWN 4 #define KORE_MSG_ENTROPY_REQ 5 #define KORE_MSG_ENTROPY_RESP 6 #define KORE_MSG_CERTIFICATE 7 #define KORE_MSG_CERTIFICATE_REQ 8 #define KORE_MSG_CRL 9 #define KORE_MSG_ACCEPT_AVAILABLE 10 #define KORE_PYTHON_SEND_OBJ 11 #define KORE_MSG_WORKER_LOG 12 #define KORE_MSG_FATALX 13 #define KORE_MSG_ACME_BASE 100 /* messages for applications should start at 201. */ #define KORE_MSG_APP_BASE 200 /* Predefined message targets. */ #define KORE_MSG_PARENT 1000 #define KORE_MSG_WORKER_ALL 1001 struct kore_msg { u_int8_t id; u_int16_t src; u_int16_t dst; size_t length; }; struct kore_keyreq { int padding; char domain[KORE_DOMAINNAME_LEN + 1]; size_t data_len; u_int8_t data[]; }; struct kore_x509_msg { char domain[KORE_DOMAINNAME_LEN + 1]; size_t data_len; u_int8_t data[]; }; #if !defined(KORE_SINGLE_BINARY) extern char *config_file; #endif extern pid_t kore_pid; extern int kore_quit; extern int kore_quiet; extern int skip_chroot; extern int skip_runas; extern int kore_foreground; extern char *kore_pidfile; extern volatile sig_atomic_t sig_recv; extern char *kore_rand_file; extern int kore_keymgr_active; extern struct kore_privsep worker_privsep; extern struct kore_privsep keymgr_privsep; extern struct kore_privsep acme_privsep; extern u_int8_t nlisteners; extern u_int16_t cpu_count; extern u_int8_t worker_count; extern const char *kore_version; extern const char *kore_build_date; extern int worker_policy; extern u_int8_t worker_set_affinity; extern u_int32_t worker_rlimit_nofiles; extern u_int32_t worker_max_connections; extern u_int32_t worker_active_connections; extern u_int32_t worker_accept_threshold; extern u_int64_t kore_websocket_maxframe; extern u_int64_t kore_websocket_timeout; extern u_int32_t kore_socket_backlog; extern struct kore_worker *worker; extern struct kore_pool nb_pool; extern struct kore_domain *primary_dom; extern struct kore_server_list kore_servers; /* kore.c */ void kore_signal(int); void kore_shutdown(void); void kore_signal_trap(int); void kore_signal_setup(void); void kore_proctitle(const char *); void kore_default_getopt(int, char **); void kore_server_closeall(void); void kore_server_cleanup(void); void kore_server_free(struct kore_server *); void kore_server_finalize(struct kore_server *); struct kore_server *kore_server_create(const char *); struct kore_server *kore_server_lookup(const char *); void kore_listener_accept(void *, int); struct listener *kore_listener_lookup(const char *); void kore_listener_free(struct listener *); struct listener *kore_listener_create(struct kore_server *); int kore_listener_init(struct listener *, int, const char *); int kore_sockopt(int, int, int); int kore_server_bind_unix(struct kore_server *, const char *, const char *); int kore_server_bind(struct kore_server *, const char *, const char *, const char *); /* worker.c */ void kore_worker_reap(void); int kore_worker_init(void); void kore_worker_privsep(void); void kore_worker_started(void); void kore_worker_make_busy(void); void kore_worker_shutdown(void); void kore_worker_dispatch_signal(int); int kore_worker_spawn(u_int16_t, u_int16_t, u_int16_t); int kore_worker_keymgr_response_verify(struct kore_msg *, const void *, struct kore_domain **); void kore_worker_entry(struct kore_worker *) __attribute__((noreturn)); struct kore_worker *kore_worker_data(u_int8_t); struct kore_worker *kore_worker_data_byid(u_int16_t); /* platform code (linux.c, bsd.c) */ void kore_platform_init(void); void kore_platform_sandbox(void); void kore_platform_event_init(void); void kore_platform_event_cleanup(void); void kore_platform_disable_read(int); void kore_platform_disable_write(int); void kore_platform_enable_accept(void); void kore_platform_disable_accept(void); void kore_platform_event_wait(u_int64_t); void kore_platform_event_all(int, void *); void kore_platform_event_level_all(int, void *); void kore_platform_event_level_read(int, void *); void kore_platform_proctitle(const char *); void kore_platform_schedule_read(int, void *); void kore_platform_schedule_write(int, void *); void kore_platform_event_schedule(int, int, int, void *); void kore_platform_worker_setcpu(struct kore_worker *); #if defined(KORE_USE_PLATFORM_SENDFILE) int kore_platform_sendfile(struct connection *, struct netbuf *); #endif #if defined(KORE_USE_PLATFORM_PLEDGE) void kore_platform_pledge(void); void kore_platform_add_pledge(const char *); #endif /* tls variants. */ #define KORE_X509_NAME_COMMON_NAME 1 void kore_tls_init(void); void kore_tls_cleanup(void); void kore_tls_dh_check(void); int kore_tls_supported(void); void kore_tls_version_set(int); void kore_tls_keymgr_init(void); int kore_tls_dh_load(const char *); void kore_tls_seed(const void *, size_t); int kore_tls_ciphersuite_set(const char *); int kore_tls_read(struct connection *, size_t *); void kore_tls_domain_cleanup(struct kore_domain *); int kore_tls_connection_accept(struct connection *); void kore_tls_connection_cleanup(struct connection *); int kore_tls_write(struct connection *, size_t, size_t *); void kore_tls_domain_crl(struct kore_domain *, const void *, size_t); void kore_tls_domain_setup(struct kore_domain *, int, const void *, size_t); KORE_PRIVATE_KEY *kore_tls_rsakey_load(const char *); KORE_PRIVATE_KEY *kore_tls_rsakey_generate(const char *); int kore_tls_x509_data(struct connection *, u_int8_t **, size_t *); KORE_X509_NAMES *kore_tls_x509_issuer_name(struct connection *); KORE_X509_NAMES *kore_tls_x509_subject_name(struct connection *); int kore_tls_x509name_foreach(KORE_X509_NAMES *, int, void *, int (*)(void *, int, int, const char *, const void *, size_t, int)); /* accesslog.c */ void kore_accesslog_init(u_int16_t); void kore_accesslog_worker_init(void); void kore_accesslog_run(void *, u_int64_t); void kore_accesslog_gather(void *, u_int64_t, int); #if !defined(KORE_NO_HTTP) /* auth.c */ int kore_auth_run(struct http_request *, struct kore_auth *); int kore_auth_cookie(struct http_request *, struct kore_auth *); int kore_auth_header(struct http_request *, struct kore_auth *); int kore_auth_request(struct http_request *, struct kore_auth *); void kore_auth_init(void); int kore_auth_new(const char *); struct kore_auth *kore_auth_lookup(const char *); #endif /* timer.c */ void kore_timer_init(void); void kore_timer_run(u_int64_t); u_int64_t kore_timer_next_run(u_int64_t); void kore_timer_remove(struct kore_timer *); struct kore_timer *kore_timer_add(void (*cb)(void *, u_int64_t), u_int64_t, void *, int); /* connection.c */ void kore_connection_init(void); void kore_connection_cleanup(void); void kore_connection_prune(int); struct connection *kore_connection_new(void *); void kore_connection_event(void *, int); int kore_connection_nonblock(int, int); void kore_connection_check_timeout(u_int64_t); int kore_connection_handle(struct connection *); void kore_connection_remove(struct connection *); void kore_connection_disconnect(struct connection *); void kore_connection_start_idletimer(struct connection *); void kore_connection_stop_idletimer(struct connection *); void kore_connection_check_idletimer(u_int64_t, struct connection *); int kore_connection_accept(struct listener *, struct connection **); void kore_log_init(void); void kore_log_file(const char *); #if defined(KORE_USE_PYTHON) int kore_configure_setting(const char *, char *); #endif /* config.c */ void kore_parse_config(void); void kore_parse_config_file(FILE *); /* mem.c */ void *kore_malloc(size_t); void *kore_calloc(size_t, size_t); void *kore_realloc(void *, size_t); void kore_free(void *); void kore_mem_init(void); void kore_mem_cleanup(void); void kore_mem_untag(void *); void *kore_mem_lookup(u_int32_t); void kore_mem_zero(void *, size_t); void kore_mem_tag(void *, u_int32_t); void *kore_malloc_tagged(size_t, u_int32_t); /* pool.c */ void *kore_pool_get(struct kore_pool *); void kore_pool_put(struct kore_pool *, void *); void kore_pool_init(struct kore_pool *, const char *, size_t, size_t); void kore_pool_cleanup(struct kore_pool *); /* utils.c */ void fatal(const char *, ...) __attribute__((noreturn)) __attribute__((format (printf, 1, 2))); void fatalx(const char *, ...) __attribute__((noreturn)) __attribute__((format (printf, 1, 2))); u_int64_t kore_time_ms(void); char *kore_time_to_date(time_t); char *kore_strdup(const char *); time_t kore_date_to_time(const char *); void kore_log(int, const char *, ...) __attribute__((format (printf, 2, 3))); u_int64_t kore_strtonum64(const char *, int, int *); size_t kore_strlcpy(char *, const char *, const size_t); void kore_server_disconnect(struct connection *); int kore_split_string(char *, const char *, char **, size_t); void kore_strip_chars(char *, const char, char **); int kore_snprintf(char *, size_t, int *, const char *, ...) __attribute__((format (printf, 4, 5))); long long kore_strtonum(const char *, int, long long, long long, int *); double kore_strtodouble(const char *, long double, long double, int *); int kore_base64_encode(const void *, size_t, char **); int kore_base64_decode(const char *, u_int8_t **, size_t *); int kore_base64url_encode(const void *, size_t, char **, int); int kore_base64url_decode(const char *, u_int8_t **, size_t *, int); int kore_x509_issuer_name(struct connection *, char **, int); int kore_x509_subject_name(struct connection *, char **, int); void *kore_mem_find(void *, size_t, const void *, size_t); char *kore_text_trim(char *, size_t); char *kore_read_line(FILE *, char *, size_t); #if !defined(KORE_NO_HTTP) /* websocket.c */ void kore_websocket_handshake(struct http_request *, const char *, const char *, const char *); int kore_websocket_send_clean(struct netbuf *); void kore_websocket_send(struct connection *, u_int8_t, const void *, size_t); void kore_websocket_broadcast(struct connection *, u_int8_t, const void *, size_t, int); #endif /* msg.c */ void kore_msg_init(void); void kore_msg_worker_init(void); void kore_msg_parent_init(void); void kore_msg_unregister(u_int8_t); void kore_msg_parent_add(struct kore_worker *); void kore_msg_parent_remove(struct kore_worker *); void kore_msg_send(u_int16_t, u_int8_t, const void *, size_t); int kore_msg_register(u_int8_t, void (*cb)(struct kore_msg *, const void *)); #if !defined(KORE_NO_HTTP) /* filemap.c */ void kore_filemap_init(void); void kore_filemap_resolve_paths(void); extern char *kore_filemap_ext; extern char *kore_filemap_index; struct kore_route *kore_filemap_create(struct kore_domain *, const char *, const char *, const char *); #endif /* fileref.c */ void kore_fileref_init(void); struct kore_fileref *kore_fileref_get(const char *, int); struct kore_fileref *kore_fileref_create(struct kore_server *, const char *, int, off_t, struct timespec *); void kore_fileref_release(struct kore_fileref *); /* domain.c */ struct kore_domain *kore_domain_new(const char *); struct kore_domain *kore_domain_byid(u_int16_t); struct kore_domain *kore_domain_lookup(struct kore_server *, const char *); void kore_domain_init(void); void kore_domain_cleanup(void); void kore_domain_free(struct kore_domain *); void kore_module_init(void); void kore_module_cleanup(void); void kore_module_reload(int); void kore_module_onload(void); int kore_module_loaded(void); void kore_domain_closelogs(void); void *kore_module_getsym(const char *, struct kore_runtime **); void kore_domain_load_crl(void); void kore_domain_keymgr_init(void); void kore_domain_callback(void (*cb)(struct kore_domain *)); int kore_domain_attach(struct kore_domain *, struct kore_server *); #if !defined(KORE_NO_HTTP) /* route.c */ void kore_route_reload(void); void kore_route_free(struct kore_route *); void kore_route_callback(struct kore_route *, const char *); struct kore_route *kore_route_create(struct kore_domain *, const char *, int); int kore_route_lookup(struct http_request *, struct kore_domain *, int, struct kore_route **); #endif /* runtime.c */ struct kore_runtime_call *kore_runtime_getcall(const char *); struct kore_module *kore_module_load(const char *, const char *, int); void kore_runtime_execute(struct kore_runtime_call *); int kore_runtime_onload(struct kore_runtime_call *, int); void kore_runtime_signal(struct kore_runtime_call *, int); void kore_runtime_configure(struct kore_runtime_call *, int, char **); void kore_runtime_connect(struct kore_runtime_call *, struct connection *); #if !defined(KORE_NO_HTTP) int kore_runtime_http_request(struct kore_runtime_call *, struct http_request *); void kore_runtime_http_request_free(struct kore_runtime_call *, struct http_request *); void kore_runtime_http_body_chunk(struct kore_runtime_call *, struct http_request *, const void *, size_t); int kore_runtime_validator(struct kore_runtime_call *, struct http_request *, const void *); void kore_runtime_wsconnect(struct kore_runtime_call *, struct connection *); void kore_runtime_wsdisconnect(struct kore_runtime_call *, struct connection *); void kore_runtime_wsmessage(struct kore_runtime_call *, struct connection *, u_int8_t, const void *, size_t); #endif #if !defined(KORE_NO_HTTP) /* validator.c */ void kore_validator_init(void); void kore_validator_reload(void); int kore_validator_add(const char *, u_int8_t, const char *); int kore_validator_run(struct http_request *, const char *, char *); int kore_validator_check(struct http_request *, struct kore_validator *, const void *); struct kore_validator *kore_validator_lookup(const char *); #endif const char *kore_worker_name(int); /* net.c */ u_int16_t net_read16(u_int8_t *); u_int32_t net_read32(u_int8_t *); u_int64_t net_read64(u_int8_t *); void net_write16(u_int8_t *, u_int16_t); void net_write32(u_int8_t *, u_int32_t); void net_write64(u_int8_t *, u_int64_t); void net_init(void); void net_cleanup(void); struct netbuf *net_netbuf_get(void); int net_send(struct connection *); int net_send_flush(struct connection *); int net_recv_flush(struct connection *); int net_read(struct connection *, size_t *); int net_write(struct connection *, size_t, size_t *); void net_recv_reset(struct connection *, size_t, int (*cb)(struct netbuf *)); void net_remove_netbuf(struct connection *, struct netbuf *); void net_recv_queue(struct connection *, size_t, int, int (*cb)(struct netbuf *)); void net_recv_expand(struct connection *c, size_t, int (*cb)(struct netbuf *)); void net_send_queue(struct connection *, const void *, size_t); void net_send_stream(struct connection *, void *, size_t, int (*cb)(struct netbuf *), struct netbuf **); void net_send_fileref(struct connection *, struct kore_fileref *); /* buf.c */ void kore_buf_free(struct kore_buf *); struct kore_buf *kore_buf_alloc(size_t); void kore_buf_init(struct kore_buf *, size_t); void kore_buf_append(struct kore_buf *, const void *, size_t); u_int8_t *kore_buf_release(struct kore_buf *, size_t *); void kore_buf_reset(struct kore_buf *); void kore_buf_cleanup(struct kore_buf *); char *kore_buf_stringify(struct kore_buf *, size_t *); void kore_buf_appendf(struct kore_buf *, const char *, ...) __attribute__((format (printf, 2, 3))); void kore_buf_appendv(struct kore_buf *, const char *, va_list) __attribute__((format (printf, 2, 0))); void kore_buf_replace_string(struct kore_buf *, const char *, const void *, size_t); /* json.c */ int kore_json_errno(void); int kore_json_parse(struct kore_json *); void kore_json_cleanup(struct kore_json *); void kore_json_item_free(struct kore_json_item *); void kore_json_init(struct kore_json *, const void *, size_t); void kore_json_item_tobuf(struct kore_json_item *, struct kore_buf *); void kore_json_item_attach(struct kore_json_item *, struct kore_json_item *); const char *kore_json_strerror(void); struct kore_json_item *kore_json_find(struct kore_json_item *, const char *, u_int32_t); struct kore_json_item *kore_json_create_item(struct kore_json_item *, const char *, u_int32_t, ...); /* keymgr.c */ void kore_keymgr_run(void); void kore_keymgr_cleanup(int); #if defined(__cplusplus) } #endif #endif /* !__H_KORE_H */ kore-4.2.3/include/kore/pgsql.h000066400000000000000000000065151430066302400163470ustar00rootroot00000000000000/* * Copyright (c) 2014-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _H_KORE_PGSQL #define _H_KORE_PGSQL #include #define KORE_PGSQL_FORMAT_TEXT 0 #define KORE_PGSQL_FORMAT_BINARY 1 #define KORE_PGSQL_SYNC 0x0001 #define KORE_PGSQL_ASYNC 0x0002 #define KORE_PGSQL_SCHEDULED 0x0004 #define KORE_PGSQL_PARAM_BINARY(v, l) v, l, 1 #define KORE_PGSQL_PARAM_TEXT_LEN(v, l) v, l, 0 #define KORE_PGSQL_PARAM_TEXT(v) v, strlen(v), 0 #if defined(__cplusplus) extern "C" { #endif struct pgsql_conn { struct kore_event evt; u_int8_t flags; char *name; PGconn *db; struct pgsql_job *job; TAILQ_ENTRY(pgsql_conn) list; }; struct pgsql_db { char *name; char *conn_string; u_int16_t conn_max; u_int16_t conn_count; LIST_ENTRY(pgsql_db) rlist; }; struct kore_pgsql { u_int8_t state; int flags; char *error; PGresult *result; struct pgsql_conn *conn; struct { char *channel; char *extra; } notify; struct http_request *req; void *arg; void (*cb)(struct kore_pgsql *, void *); LIST_ENTRY(kore_pgsql) rlist; }; extern u_int16_t pgsql_conn_max; extern u_int32_t pgsql_queue_limit; void kore_pgsql_sys_init(void); void kore_pgsql_sys_cleanup(void); void kore_pgsql_init(struct kore_pgsql *); void kore_pgsql_bind_request(struct kore_pgsql *, struct http_request *); void kore_pgsql_bind_callback(struct kore_pgsql *, void (*cb)(struct kore_pgsql *, void *), void *); int kore_pgsql_setup(struct kore_pgsql *, const char *, int); void kore_pgsql_handle(void *, int); void kore_pgsql_cleanup(struct kore_pgsql *); void kore_pgsql_continue(struct kore_pgsql *); int kore_pgsql_query(struct kore_pgsql *, const void *); int kore_pgsql_query_params(struct kore_pgsql *, const void *, int, int, ...); int kore_pgsql_v_query_params(struct kore_pgsql *, const void *, int, int, va_list); int kore_pgsql_query_param_fields(struct kore_pgsql *, const void *, int, int, const char **, int *, int *); int kore_pgsql_register(const char *, const char *); int kore_pgsql_ntuples(struct kore_pgsql *); int kore_pgsql_nfields(struct kore_pgsql *); void kore_pgsql_logerror(struct kore_pgsql *); char *kore_pgsql_fieldname(struct kore_pgsql *, int); char *kore_pgsql_getvalue(struct kore_pgsql *, int, int); int kore_pgsql_getlength(struct kore_pgsql *, int, int); int kore_pgsql_column_binary(struct kore_pgsql *, int); #if defined(__cplusplus) } #endif #define KORE_PGSQL_STATE_INIT 1 #define KORE_PGSQL_STATE_WAIT 2 #define KORE_PGSQL_STATE_RESULT 3 #define KORE_PGSQL_STATE_ERROR 4 #define KORE_PGSQL_STATE_DONE 5 #define KORE_PGSQL_STATE_COMPLETE 6 #define KORE_PGSQL_STATE_NOTIFY 7 #endif kore-4.2.3/include/kore/python_api.h000066400000000000000000000040371430066302400173700ustar00rootroot00000000000000/* * Copyright (c) 2016 Stanislav Yudin * Copyright (c) 2017-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __H_PYTHON_H #define __H_PYTHON_H #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE #define PY_SSIZE_T_CLEAN 1 #include #include void kore_python_init(void); void kore_python_preinit(void); void kore_python_cleanup(void); void kore_python_coro_run(void); void kore_python_proc_reap(void); int kore_python_coro_pending(void); void kore_python_path(const char *); void kore_python_coro_delete(void *); void kore_python_routes_resolve(void); void kore_python_log_error(const char *); PyObject *kore_python_callable(PyObject *, const char *); #if defined(__linux__) void kore_python_seccomp_cleanup(void); void kore_python_seccomp_hook(const char *); #endif #if !defined(KORE_SINGLE_BINARY) extern const char *kore_pymodule; #endif extern struct kore_module_functions kore_python_module; extern struct kore_runtime kore_python_runtime; #define KORE_PYTHON_SIGNAL_HOOK "koreapp.signal" #define KORE_PYTHON_TEARDOWN_HOOK "koreapp.cleanup" #define KORE_PYTHON_CONFIG_HOOK "koreapp.configure" #define KORE_PYTHON_DAEMONIZED_HOOK "koreapp.daemonized" #define KORE_PYTHON_WORKER_STOP_HOOK "koreapp.workerstop" #define KORE_PYTHON_WORKER_START_HOOK "koreapp.workerstart" #endif kore-4.2.3/include/kore/python_methods.h000066400000000000000000001002661430066302400202630ustar00rootroot00000000000000/* * Copyright (c) 2017-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define CORO_STATE_RUNNABLE 1 #define CORO_STATE_SUSPENDED 2 struct python_coro { u_int64_t id; int state; int killed; PyObject *obj; char *name; PyObject *result; struct pylock_op *lockop; struct pysocket_op *sockop; struct pygather_op *gatherop; struct http_request *request; PyObject *exception; char *exception_msg; TAILQ_ENTRY(python_coro) list; }; TAILQ_HEAD(coro_list, python_coro); static PyObject *python_kore_app(PyObject *, PyObject *); static PyObject *python_kore_log(PyObject *, PyObject *); static PyObject *python_kore_time(PyObject *, PyObject *); static PyObject *python_kore_lock(PyObject *, PyObject *); static PyObject *python_kore_proc(PyObject *, PyObject *); static PyObject *python_kore_fatal(PyObject *, PyObject *); static PyObject *python_kore_queue(PyObject *, PyObject *); static PyObject *python_kore_worker(PyObject *, PyObject *); static PyObject *python_kore_tracer(PyObject *, PyObject *); static PyObject *python_kore_fatalx(PyObject *, PyObject *); static PyObject *python_kore_task_id(PyObject *, PyObject *); static PyObject *python_kore_sigtrap(PyObject *, PyObject *); static PyObject *python_kore_setname(PyObject *, PyObject *); static PyObject *python_kore_suspend(PyObject *, PyObject *); static PyObject *python_kore_shutdown(PyObject *, PyObject *); static PyObject *python_kore_coroname(PyObject *, PyObject *); static PyObject *python_kore_corotrace(PyObject *, PyObject *); static PyObject *python_kore_task_kill(PyObject *, PyObject *); static PyObject *python_kore_prerequest(PyObject *, PyObject *); static PyObject *python_kore_task_create(PyObject *, PyObject *); static PyObject *python_kore_socket_wrap(PyObject *, PyObject *); static PyObject *python_kore_route(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_timer(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_domain(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_gather(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_sendobj(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_server(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_privsep(PyObject *, PyObject *, PyObject *); #if defined(KORE_USE_PGSQL) static PyObject *python_kore_pgsql_query(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_pgsql_register(PyObject *, PyObject *); #endif #if defined(KORE_USE_CURL) static PyObject *python_kore_curl_handle(PyObject *, PyObject *); static PyObject *python_kore_httpclient(PyObject *, PyObject *, PyObject *); #endif static PyObject *python_websocket_broadcast(PyObject *, PyObject *); #define METHOD(n, c, a) { n, (PyCFunction)c, a, NULL } #define GETTER(n, g) { n, (getter)g, NULL, NULL, NULL } #define SETTER(n, s) { n, NULL, (setter)s, NULL, NULL } #define GETSET(n, g, s) { n, (getter)g, (setter)s, NULL, NULL } static struct PyMethodDef pykore_methods[] = { METHOD("app", python_kore_app, METH_VARARGS), METHOD("log", python_kore_log, METH_VARARGS), METHOD("time", python_kore_time, METH_NOARGS), METHOD("lock", python_kore_lock, METH_NOARGS), METHOD("proc", python_kore_proc, METH_VARARGS), METHOD("queue", python_kore_queue, METH_VARARGS), METHOD("worker", python_kore_worker, METH_VARARGS), METHOD("tracer", python_kore_tracer, METH_VARARGS), METHOD("fatal", python_kore_fatal, METH_VARARGS), METHOD("fatalx", python_kore_fatalx, METH_VARARGS), METHOD("task_id", python_kore_task_id, METH_NOARGS), METHOD("sigtrap", python_kore_sigtrap, METH_VARARGS), METHOD("setname", python_kore_setname, METH_VARARGS), METHOD("suspend", python_kore_suspend, METH_VARARGS), METHOD("shutdown", python_kore_shutdown, METH_NOARGS), METHOD("coroname", python_kore_coroname, METH_VARARGS), METHOD("corotrace", python_kore_corotrace, METH_VARARGS), METHOD("task_kill", python_kore_task_kill, METH_VARARGS), METHOD("prerequest", python_kore_prerequest, METH_VARARGS), METHOD("task_create", python_kore_task_create, METH_VARARGS), METHOD("socket_wrap", python_kore_socket_wrap, METH_VARARGS), METHOD("route", python_kore_route, METH_VARARGS | METH_KEYWORDS), METHOD("timer", python_kore_timer, METH_VARARGS | METH_KEYWORDS), METHOD("domain", python_kore_domain, METH_VARARGS | METH_KEYWORDS), METHOD("server", python_kore_server, METH_VARARGS | METH_KEYWORDS), METHOD("gather", python_kore_gather, METH_VARARGS | METH_KEYWORDS), METHOD("privsep", python_kore_privsep, METH_VARARGS | METH_KEYWORDS), METHOD("sendobj", python_kore_sendobj, METH_VARARGS | METH_KEYWORDS), METHOD("websocket_broadcast", python_websocket_broadcast, METH_VARARGS), #if defined(KORE_USE_PGSQL) METHOD("dbsetup", python_kore_pgsql_register, METH_VARARGS), METHOD("dbquery", python_kore_pgsql_query, METH_VARARGS | METH_KEYWORDS), #endif #if defined(KORE_USE_CURL) METHOD("curl", python_kore_curl_handle, METH_VARARGS), METHOD("httpclient", python_kore_httpclient, METH_VARARGS | METH_KEYWORDS), #endif { NULL, NULL, 0, NULL } }; static struct PyModuleDef pykore_module = { PyModuleDef_HEAD_INIT, "kore", NULL, -1, pykore_methods }; struct pyconfig { PyObject_HEAD }; static int pyconfig_setattr(PyObject *, PyObject *, PyObject *); static PyTypeObject pyconfig_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.config", .tp_doc = "kore configuration", .tp_setattro = pyconfig_setattr, .tp_basicsize = sizeof(struct pyconfig), .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #if defined(__linux__) struct pyseccomp { PyObject_HEAD size_t elm; u_int8_t *filters; }; static PyObject *pyseccomp_allow(struct pyseccomp *, PyObject *); static PyObject *pyseccomp_allow_arg(struct pyseccomp *, PyObject *); static PyObject *pyseccomp_allow_flag(struct pyseccomp *, PyObject *); static PyObject *pyseccomp_allow_mask(struct pyseccomp *, PyObject *); static PyObject *pyseccomp_deny(struct pyseccomp *, PyObject *, PyObject *); static PyObject *pyseccomp_deny_arg(struct pyseccomp *, PyObject *, PyObject *); static PyObject *pyseccomp_deny_flag(struct pyseccomp *, PyObject *, PyObject *); static PyObject *pyseccomp_deny_mask(struct pyseccomp *, PyObject *, PyObject *); static PyObject *pyseccomp_bpf_stmt(struct pyseccomp *, PyObject *); static PyMethodDef pyseccomp_methods[] = { METHOD("allow", pyseccomp_allow, METH_VARARGS), METHOD("bpf_stmt", pyseccomp_bpf_stmt, METH_VARARGS), METHOD("allow_arg", pyseccomp_allow_arg, METH_VARARGS), METHOD("allow_flag", pyseccomp_allow_flag, METH_VARARGS), METHOD("allow_mask", pyseccomp_allow_mask, METH_VARARGS), METHOD("deny", pyseccomp_deny, METH_VARARGS | METH_KEYWORDS), METHOD("deny_arg", pyseccomp_deny_arg, METH_VARARGS | METH_KEYWORDS), METHOD("deny_flag", pyseccomp_deny_flag, METH_VARARGS | METH_KEYWORDS), METHOD("deny_mask", pyseccomp_deny_mask, METH_VARARGS | METH_KEYWORDS), METHOD(NULL, NULL, -1) }; static void pyseccomp_dealloc(struct pyseccomp *); static PyTypeObject pyseccomp_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.seccomp", .tp_doc = "kore seccomp configuration", .tp_methods = pyseccomp_methods, .tp_basicsize = sizeof(struct pyseccomp), .tp_dealloc = (destructor)pyseccomp_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #endif struct pyroute { PyObject_HEAD char *path; PyObject *func; PyObject *kwargs; struct kore_domain *domain; TAILQ_ENTRY(pyroute) list; }; static PyObject *pyroute_inner(struct pyroute *, PyObject *); static void pyroute_dealloc(struct pyroute *); static PyMethodDef pyroute_methods[] = { METHOD("inner", pyroute_inner, METH_VARARGS), METHOD(NULL, NULL, -1) }; static PyTypeObject pyroute_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.route", .tp_doc = "kore route function", .tp_methods = pyroute_methods, .tp_basicsize = sizeof(struct pyroute), .tp_dealloc = (destructor)pyroute_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pydomain { PyObject_HEAD struct kore_domain *config; struct kore_module_handle *next; PyObject *kwargs; }; static PyObject *pydomain_filemaps(struct pydomain *, PyObject *); static PyObject *pydomain_route(struct pydomain *, PyObject *, PyObject *); static PyMethodDef pydomain_methods[] = { METHOD("filemaps", pydomain_filemaps, METH_VARARGS), METHOD("route", pydomain_route, METH_VARARGS | METH_KEYWORDS), METHOD(NULL, NULL, -1) }; static int pydomain_set_accesslog(struct pydomain *, PyObject *, void *); static PyGetSetDef pydomain_getset[] = { SETTER("accesslog", pydomain_set_accesslog), SETTER(NULL, NULL), }; static void pydomain_dealloc(struct pydomain *); static PyTypeObject pydomain_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.domain", .tp_doc = "kore domain configuration", .tp_getset = pydomain_getset, .tp_methods = pydomain_methods, .tp_basicsize = sizeof(struct pydomain), .tp_dealloc = (destructor)pydomain_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #define PYSUSPEND_OP_INIT 1 #define PYSUSPEND_OP_WAIT 2 #define PYSUSPEND_OP_CONTINUE 3 struct pysuspend_op { PyObject_HEAD int state; int delay; struct python_coro *coro; struct kore_timer *timer; }; static void pysuspend_op_dealloc(struct pysuspend_op *); static PyObject *pysuspend_op_await(PyObject *); static PyObject *pysuspend_op_iternext(struct pysuspend_op *); static PyAsyncMethods pysuspend_op_async = { (unaryfunc)pysuspend_op_await, NULL, NULL }; static PyTypeObject pysuspend_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.suspend", .tp_doc = "suspension operation", .tp_as_async = &pysuspend_op_async, .tp_iternext = (iternextfunc)pysuspend_op_iternext, .tp_basicsize = sizeof(struct pysuspend_op), .tp_dealloc = (destructor)pysuspend_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pytimer { PyObject_HEAD int flags; struct kore_timer *run; PyObject *callable; PyObject *udata; }; static PyObject *pytimer_close(struct pytimer *, PyObject *); static PyMethodDef pytimer_methods[] = { METHOD("close", pytimer_close, METH_NOARGS), METHOD(NULL, NULL, -1) }; static void pytimer_dealloc(struct pytimer *); static PyTypeObject pytimer_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.timer", .tp_doc = "kore timer implementation", .tp_methods = pytimer_methods, .tp_basicsize = sizeof(struct pytimer), .tp_dealloc = (destructor)pytimer_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; /* XXX */ struct pysocket; struct pysocket_op; struct pysocket_event { struct kore_event evt; struct pysocket *s; }; struct pysocket { PyObject_HEAD int fd; int family; int protocol; int scheduled; PyObject *socket; socklen_t addr_len; struct pysocket_event event; struct pysocket_op *recvop; struct pysocket_op *sendop; union { struct sockaddr_in ipv4; struct sockaddr_un sun; } addr; }; static PyObject *pysocket_send(struct pysocket *, PyObject *); static PyObject *pysocket_recv(struct pysocket *, PyObject *); static PyObject *pysocket_close(struct pysocket *, PyObject *); static PyObject *pysocket_accept(struct pysocket *, PyObject *); static PyObject *pysocket_sendto(struct pysocket *, PyObject *); static PyObject *pysocket_connect(struct pysocket *, PyObject *); static PyObject *pysocket_recvmsg(struct pysocket *, PyObject *); static PyObject *pysocket_recvfrom(struct pysocket *, PyObject *); static PyMethodDef pysocket_methods[] = { METHOD("recv", pysocket_recv, METH_VARARGS), METHOD("send", pysocket_send, METH_VARARGS), METHOD("close", pysocket_close, METH_NOARGS), METHOD("accept", pysocket_accept, METH_NOARGS), METHOD("sendto", pysocket_sendto, METH_VARARGS), METHOD("connect", pysocket_connect, METH_VARARGS), METHOD("recvmsg", pysocket_recvmsg, METH_VARARGS), METHOD("recvfrom", pysocket_recvfrom, METH_VARARGS), METHOD(NULL, NULL, -1), }; static void pysocket_dealloc(struct pysocket *); static PyTypeObject pysocket_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.socket", .tp_doc = "kore socket implementation", .tp_methods = pysocket_methods, .tp_basicsize = sizeof(struct pysocket), .tp_dealloc = (destructor)pysocket_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #define PYSOCKET_TYPE_ACCEPT 1 #define PYSOCKET_TYPE_CONNECT 2 #define PYSOCKET_TYPE_RECV 3 #define PYSOCKET_TYPE_SEND 4 #define PYSOCKET_TYPE_RECVFROM 5 #define PYSOCKET_TYPE_SENDTO 6 #define PYSOCKET_TYPE_RECVMSG 7 struct pysocket_op { PyObject_HEAD int eof; int type; void *self; struct python_coro *coro; int state; size_t length; struct kore_buf buffer; struct pysocket *socket; struct kore_timer *timer; union { struct sockaddr_in ipv4; struct sockaddr_un sun; } sendaddr; }; static void pysocket_op_dealloc(struct pysocket_op *); static PyObject *pysocket_op_await(PyObject *); static PyObject *pysocket_op_iternext(struct pysocket_op *); static PyAsyncMethods pysocket_op_async = { (unaryfunc)pysocket_op_await, NULL, NULL }; static PyTypeObject pysocket_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.socketop", .tp_doc = "socket operation", .tp_as_async = &pysocket_op_async, .tp_iternext = (iternextfunc)pysocket_op_iternext, .tp_basicsize = sizeof(struct pysocket_op), .tp_dealloc = (destructor)pysocket_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pyqueue_waiting { struct python_coro *coro; struct pyqueue_op *op; TAILQ_ENTRY(pyqueue_waiting) list; }; struct pyqueue_object { PyObject *obj; TAILQ_ENTRY(pyqueue_object) list; }; struct pyqueue { PyObject_HEAD TAILQ_HEAD(, pyqueue_object) objects; TAILQ_HEAD(, pyqueue_waiting) waiting; }; static PyObject *pyqueue_pop(struct pyqueue *, PyObject *); static PyObject *pyqueue_push(struct pyqueue *, PyObject *); static PyObject *pyqueue_popnow(struct pyqueue *, PyObject *); static PyMethodDef pyqueue_methods[] = { METHOD("pop", pyqueue_pop, METH_NOARGS), METHOD("push", pyqueue_push, METH_VARARGS), METHOD("popnow", pyqueue_popnow, METH_NOARGS), METHOD(NULL, NULL, -1) }; static void pyqueue_dealloc(struct pyqueue *); static PyTypeObject pyqueue_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.queue", .tp_doc = "queue", .tp_methods = pyqueue_methods, .tp_basicsize = sizeof(struct pyqueue), .tp_dealloc = (destructor)pyqueue_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pyqueue_op { PyObject_HEAD struct pyqueue *queue; struct pyqueue_waiting *waiting; }; static void pyqueue_op_dealloc(struct pyqueue_op *); static PyObject *pyqueue_op_await(PyObject *); static PyObject *pyqueue_op_iternext(struct pyqueue_op *); static PyAsyncMethods pyqueue_op_async = { (unaryfunc)pyqueue_op_await, NULL, NULL }; static PyTypeObject pyqueue_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.queueop", .tp_doc = "queue waitable", .tp_as_async = &pyqueue_op_async, .tp_iternext = (iternextfunc)pyqueue_op_iternext, .tp_basicsize = sizeof(struct pyqueue_op), .tp_dealloc = (destructor)pyqueue_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pylock { PyObject_HEAD struct python_coro *owner; TAILQ_HEAD(, pylock_op) ops; }; static PyObject *pylock_aexit(struct pylock *, PyObject *); static PyObject *pylock_aenter(struct pylock *, PyObject *); static PyObject *pylock_release(struct pylock *, PyObject *); static PyObject *pylock_trylock(struct pylock *, PyObject *); static PyMethodDef pylock_methods[] = { METHOD("acquire", pylock_aenter, METH_NOARGS), METHOD("release", pylock_release, METH_NOARGS), METHOD("trylock", pylock_trylock, METH_NOARGS), METHOD("__aexit__", pylock_aexit, METH_VARARGS), METHOD("__aenter__", pylock_aenter, METH_NOARGS), METHOD(NULL, NULL, -1) }; static void pylock_dealloc(struct pylock *); static PyTypeObject pylock_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.lock", .tp_doc = "locking mechanism", .tp_methods = pylock_methods, .tp_basicsize = sizeof(struct pylock), .tp_dealloc = (destructor)pylock_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pylock_op { PyObject_HEAD int locking; int active; struct pylock *lock; struct python_coro *coro; TAILQ_ENTRY(pylock_op) list; }; static void pylock_op_dealloc(struct pylock_op *); static PyObject *pylock_op_await(PyObject *); static PyObject *pylock_op_iternext(struct pylock_op *); static PyAsyncMethods pylock_op_async = { (unaryfunc)pylock_op_await, NULL, NULL }; static PyTypeObject pylock_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.lockop", .tp_doc = "lock awaitable", .tp_as_async = &pylock_op_async, .tp_iternext = (iternextfunc)pylock_op_iternext, .tp_basicsize = sizeof(struct pylock_op), .tp_dealloc = (destructor)pylock_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pyproc { PyObject_HEAD pid_t pid; pid_t apid; int reaped; int status; struct pyproc_op *op; struct pysocket *in; struct pysocket *out; struct python_coro *coro; struct kore_timer *timer; TAILQ_ENTRY(pyproc) list; }; static void pyproc_dealloc(struct pyproc *); static PyObject *pyproc_kill(struct pyproc *, PyObject *); static PyObject *pyproc_reap(struct pyproc *, PyObject *); static PyObject *pyproc_recv(struct pyproc *, PyObject *); static PyObject *pyproc_send(struct pyproc *, PyObject *); static PyObject *pyproc_close_stdin(struct pyproc *, PyObject *); static PyMethodDef pyproc_methods[] = { METHOD("kill", pyproc_kill, METH_NOARGS), METHOD("reap", pyproc_reap, METH_NOARGS), METHOD("recv", pyproc_recv, METH_VARARGS), METHOD("send", pyproc_send, METH_VARARGS), METHOD("close_stdin", pyproc_close_stdin, METH_NOARGS), METHOD(NULL, NULL, -1), }; static PyObject *pyproc_get_pid(struct pyproc *, void *); static PyGetSetDef pyproc_getset[] = { GETTER("pid", pyproc_get_pid), GETTER(NULL, NULL), }; static PyTypeObject pyproc_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.proc", .tp_doc = "async process", .tp_getset = pyproc_getset, .tp_methods = pyproc_methods, .tp_basicsize = sizeof(struct pyproc), .tp_dealloc = (destructor)pyproc_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pyproc_op { PyObject_HEAD struct pyproc *proc; struct python_coro *coro; }; static void pyproc_op_dealloc(struct pyproc_op *); static PyObject *pyproc_op_await(PyObject *); static PyObject *pyproc_op_iternext(struct pyproc_op *); static PyAsyncMethods pyproc_op_async = { (unaryfunc)pyproc_op_await, NULL, NULL }; static PyTypeObject pyproc_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.proc_op", .tp_doc = "proc reaper awaitable", .tp_as_async = &pyproc_op_async, .tp_iternext = (iternextfunc)pyproc_op_iternext, .tp_basicsize = sizeof(struct pyproc_op), .tp_dealloc = (destructor)pyproc_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pygather_coro { struct python_coro *coro; PyObject *result; TAILQ_ENTRY(pygather_coro) list; }; struct pygather_result { PyObject *obj; TAILQ_ENTRY(pygather_result) list; }; struct pygather_op { PyObject_HEAD int count; int running; int concurrency; struct python_coro *coro; TAILQ_HEAD(, pygather_result) results; TAILQ_HEAD(, pygather_coro) coroutines; }; static void pygather_op_dealloc(struct pygather_op *); static PyObject *pygather_op_await(PyObject *); static PyObject *pygather_op_iternext(struct pygather_op *); static PyAsyncMethods pygather_op_async = { (unaryfunc)pygather_op_await, NULL, NULL }; static PyTypeObject pygather_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.pygather_op", .tp_doc = "coroutine gathering", .tp_as_async = &pygather_op_async, .tp_iternext = (iternextfunc)pygather_op_iternext, .tp_basicsize = sizeof(struct pygather_op), .tp_dealloc = (destructor)pygather_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pyconnection { PyObject_HEAD struct connection *c; }; static PyObject *pyconnection_disconnect(struct pyconnection *, PyObject *); static PyObject *pyconnection_websocket_send(struct pyconnection *, PyObject *); static PyMethodDef pyconnection_methods[] = { METHOD("disconnect", pyconnection_disconnect, METH_NOARGS), METHOD("websocket_send", pyconnection_websocket_send, METH_VARARGS), METHOD(NULL, NULL, -1), }; static PyObject *pyconnection_get_fd(struct pyconnection *, void *); static PyObject *pyconnection_get_addr(struct pyconnection *, void *); static PyObject *pyconnection_get_peer_x509(struct pyconnection *, void *); static PyObject *pyconnection_get_peer_x509dict(struct pyconnection *, void *); static PyGetSetDef pyconnection_getset[] = { GETTER("fd", pyconnection_get_fd), GETTER("addr", pyconnection_get_addr), GETTER("x509", pyconnection_get_peer_x509), GETTER("x509dict", pyconnection_get_peer_x509dict), GETTER(NULL, NULL), }; static void pyconnection_dealloc(struct pyconnection *); static PyTypeObject pyconnection_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.connection", .tp_doc = "struct connection", .tp_getset = pyconnection_getset, .tp_methods = pyconnection_methods, .tp_basicsize = sizeof(struct pyconnection), .tp_dealloc = (destructor)pyconnection_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #define PYHTTP_STATE_INIT 0 #define PYHTTP_STATE_PREPROCESS 1 #define PYHTTP_STATE_RUN 2 struct pyhttp_request { PyObject_HEAD struct http_request *req; PyObject *dict; PyObject *data; }; struct pyhttp_iterobj { int remove; PyObject *iterator; struct connection *connection; struct kore_buf buf; }; struct pyhttp_file { PyObject_HEAD struct http_file *file; }; static void pyhttp_dealloc(struct pyhttp_request *); static void pyhttp_file_dealloc(struct pyhttp_file *); static PyObject *pyhttp_cookie(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_response(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_argument(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_body_read(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_file_lookup(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_populate_get(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_populate_post(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_populate_multi(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_populate_cookies(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_request_header(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_response_header(struct pyhttp_request *, PyObject *); static PyObject *pyhttp_websocket_handshake(struct pyhttp_request *, PyObject *); static PyMethodDef pyhttp_request_methods[] = { METHOD("cookie", pyhttp_cookie, METH_VARARGS), METHOD("response", pyhttp_response, METH_VARARGS), METHOD("argument", pyhttp_argument, METH_VARARGS), METHOD("body_read", pyhttp_body_read, METH_VARARGS), METHOD("file_lookup", pyhttp_file_lookup, METH_VARARGS), METHOD("populate_get", pyhttp_populate_get, METH_NOARGS), METHOD("populate_post", pyhttp_populate_post, METH_NOARGS), METHOD("populate_multi", pyhttp_populate_multi, METH_NOARGS), METHOD("populate_cookies", pyhttp_populate_cookies, METH_NOARGS), METHOD("request_header", pyhttp_request_header, METH_VARARGS), METHOD("response_header", pyhttp_response_header, METH_VARARGS), METHOD("websocket_handshake", pyhttp_websocket_handshake, METH_VARARGS), METHOD(NULL, NULL, -1) }; static PyObject *pyhttp_get_host(struct pyhttp_request *, void *); static PyObject *pyhttp_get_path(struct pyhttp_request *, void *); static PyObject *pyhttp_get_body(struct pyhttp_request *, void *); static PyObject *pyhttp_get_agent(struct pyhttp_request *, void *); static PyObject *pyhttp_get_method(struct pyhttp_request *, void *); static PyObject *pyhttp_get_protocol(struct pyhttp_request *, void *); static PyObject *pyhttp_get_body_path(struct pyhttp_request *, void *); static PyObject *pyhttp_get_connection(struct pyhttp_request *, void *); static PyObject *pyhttp_get_body_digest(struct pyhttp_request *, void *); static PyGetSetDef pyhttp_request_getset[] = { GETTER("host", pyhttp_get_host), GETTER("path", pyhttp_get_path), GETTER("body", pyhttp_get_body), GETTER("agent", pyhttp_get_agent), GETTER("method", pyhttp_get_method), GETTER("protocol", pyhttp_get_protocol), GETTER("body_path", pyhttp_get_body_path), GETTER("body_digest", pyhttp_get_body_digest), GETTER("connection", pyhttp_get_connection), GETTER(NULL, NULL) }; static PyTypeObject pyhttp_request_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.http_request", .tp_doc = "struct http_request", .tp_setattro = PyObject_GenericSetAttr, .tp_getattro = PyObject_GenericGetAttr, .tp_getset = pyhttp_request_getset, .tp_methods = pyhttp_request_methods, .tp_dealloc = (destructor)pyhttp_dealloc, .tp_basicsize = sizeof(struct pyhttp_request), .tp_dictoffset = offsetof(struct pyhttp_request, dict), .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; static PyObject *pyhttp_file_read(struct pyhttp_file *, PyObject *); static PyMethodDef pyhttp_file_methods[] = { METHOD("read", pyhttp_file_read, METH_VARARGS), METHOD(NULL, NULL, -1) }; static PyObject *pyhttp_file_get_name(struct pyhttp_file *, void *); static PyObject *pyhttp_file_get_filename(struct pyhttp_file *, void *); static PyGetSetDef pyhttp_file_getset[] = { GETTER("name", pyhttp_file_get_name), GETTER("filename", pyhttp_file_get_filename), GETTER(NULL, NULL) }; static PyTypeObject pyhttp_file_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.http_file", .tp_doc = "struct http_file", .tp_getset = pyhttp_file_getset, .tp_methods = pyhttp_file_methods, .tp_dealloc = (destructor)pyhttp_file_dealloc, .tp_basicsize = sizeof(struct pyhttp_file), .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #if defined(KORE_USE_CURL) #define CURL_CLIENT_OP_RUN 1 #define CURL_CLIENT_OP_RESULT 2 struct pycurl_slist { struct curl_slist *slist; LIST_ENTRY(pycurl_slist) list; }; struct pycurl_data { struct kore_curl curl; LIST_HEAD(, pycurl_slist) slists; }; struct pycurl_handle { PyObject_HEAD char *url; struct kore_buf *body; struct pycurl_data data; }; struct pycurl_handle_op { PyObject_HEAD int state; struct python_coro *coro; struct pycurl_handle *handle; }; static PyObject *pycurl_handle_op_await(PyObject *); static PyObject *pycurl_handle_op_iternext(struct pycurl_handle_op *); static void pycurl_handle_dealloc(struct pycurl_handle *); static void pycurl_handle_op_dealloc(struct pycurl_handle_op *); static PyObject *pycurl_handle_run(struct pycurl_handle *, PyObject *); static PyObject *pycurl_handle_setopt(struct pycurl_handle *, PyObject *); static PyObject *pycurl_handle_setbody(struct pycurl_handle *, PyObject *); static PyObject *pycurl_handle_setopt_string(struct pycurl_data *, int, PyObject *); static PyObject *pycurl_handle_setopt_long(struct pycurl_data *, int, PyObject *); static PyObject *pycurl_handle_setopt_slist(struct pycurl_data *, int, PyObject *); static PyMethodDef pycurl_handle_methods[] = { METHOD("run", pycurl_handle_run, METH_VARARGS), METHOD("setopt", pycurl_handle_setopt, METH_VARARGS), METHOD("setbody", pycurl_handle_setbody, METH_VARARGS), METHOD(NULL, NULL, -1) }; static PyTypeObject pycurl_handle_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.curl", .tp_doc = "An asynchronous CURL handle", .tp_methods = pycurl_handle_methods, .tp_basicsize = sizeof(struct pycurl_handle), .tp_dealloc = (destructor)pycurl_handle_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; static PyAsyncMethods pycurl_handle_op_async = { (unaryfunc)pycurl_handle_op_await, NULL, NULL }; static PyTypeObject pycurl_handle_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.curlop", .tp_doc = "Asynchronous CURL operation", .tp_as_async = &pycurl_handle_op_async, .tp_iternext = (iternextfunc)pycurl_handle_op_iternext, .tp_basicsize = sizeof(struct pycurl_handle_op), .tp_dealloc = (destructor)pycurl_handle_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; struct pyhttp_client { PyObject_HEAD char *url; char *unix; char *tlskey; char *tlscert; char *cabundle; PyObject *curlopt; int tlsverify; }; struct pyhttp_client_op { PyObject_HEAD int state; int headers; struct python_coro *coro; struct pyhttp_client *client; struct pycurl_data data; }; static PyObject *pyhttp_client_op_await(PyObject *); static PyObject *pyhttp_client_op_iternext(struct pyhttp_client_op *); static void pyhttp_client_dealloc(struct pyhttp_client *); static void pyhttp_client_op_dealloc(struct pyhttp_client_op *); static PyObject *pyhttp_client_get(struct pyhttp_client *, PyObject *, PyObject *); static PyObject *pyhttp_client_put(struct pyhttp_client *, PyObject *, PyObject *); static PyObject *pyhttp_client_post(struct pyhttp_client *, PyObject *, PyObject *); static PyObject *pyhttp_client_head(struct pyhttp_client *, PyObject *, PyObject *); static PyObject *pyhttp_client_patch(struct pyhttp_client *, PyObject *, PyObject *); static PyObject *pyhttp_client_delete(struct pyhttp_client *, PyObject *, PyObject *); static PyObject *pyhttp_client_options(struct pyhttp_client *, PyObject *, PyObject *); static PyMethodDef pyhttp_client_methods[] = { METHOD("get", pyhttp_client_get, METH_VARARGS | METH_KEYWORDS), METHOD("put", pyhttp_client_put, METH_VARARGS | METH_KEYWORDS), METHOD("post", pyhttp_client_post, METH_VARARGS | METH_KEYWORDS), METHOD("head", pyhttp_client_head, METH_VARARGS | METH_KEYWORDS), METHOD("patch", pyhttp_client_patch, METH_VARARGS | METH_KEYWORDS), METHOD("delete", pyhttp_client_delete, METH_VARARGS | METH_KEYWORDS), METHOD("options", pyhttp_client_options, METH_VARARGS | METH_KEYWORDS), METHOD(NULL, NULL, -1) }; static PyTypeObject pyhttp_client_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.httpclient", .tp_doc = "An asynchronous HTTP client", .tp_methods = pyhttp_client_methods, .tp_basicsize = sizeof(struct pyhttp_client), .tp_dealloc = (destructor)pyhttp_client_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; static PyAsyncMethods pyhttp_client_op_async = { (unaryfunc)pyhttp_client_op_await, NULL, NULL }; static PyTypeObject pyhttp_client_op_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.httpclientop", .tp_doc = "Asynchronous HTTP client operation", .tp_as_async = &pyhttp_client_op_async, .tp_iternext = (iternextfunc)pyhttp_client_op_iternext, .tp_basicsize = sizeof(struct pyhttp_client_op), .tp_dealloc = (destructor)pyhttp_client_op_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #endif #if defined(KORE_USE_PGSQL) #define PYKORE_PGSQL_PREINIT 1 #define PYKORE_PGSQL_INITIALIZE 2 #define PYKORE_PGSQL_QUERY 3 #define PYKORE_PGSQL_WAIT 4 struct pykore_pgsql { PyObject_HEAD struct kore_pgsql sql; int state; int binary; char *db; struct python_coro *coro; char *query; PyObject *result; struct { int count; const char **values; int *lengths; int *formats; PyObject **objs; } param; }; static void pykore_pgsql_dealloc(struct pykore_pgsql *); static PyObject *pykore_pgsql_await(PyObject *); static PyObject *pykore_pgsql_iternext(struct pykore_pgsql *); static PyAsyncMethods pykore_pgsql_async = { (unaryfunc)pykore_pgsql_await, NULL, NULL }; static PyTypeObject pykore_pgsql_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "kore.pgsql", .tp_doc = "struct kore_pgsql", .tp_as_async = &pykore_pgsql_async, .tp_iternext = (iternextfunc)pykore_pgsql_iternext, .tp_basicsize = sizeof(struct pykore_pgsql), .tp_dealloc = (destructor)pykore_pgsql_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, }; #endif kore-4.2.3/include/kore/seccomp.h000066400000000000000000000141641430066302400166510ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __H_SECCOMP_H #define __H_SECCOMP_H #include #include #include #include #include #if __BYTE_ORDER == __LITTLE_ENDIAN #define ARGS_LO_OFFSET 0 #define ARGS_HI_OFFSET sizeof(u_int32_t) #elif __BYTE_ORDER == __BIG_ENDIAN #define ARGS_LO_OFFSET sizeof(u_int32_t) #define ARGS_HI_OFFSET 0 #else #error "__BYTE_ORDER unknown" #endif /* Do something with a syscall with a user-supplied action. */ #define KORE_SYSCALL_FILTER(_name, _action) \ KORE_BPF_CMP(SYS_##_name, 0, 1), \ KORE_BPF_RET(_action) /* * Check if a system call is called with the supplied value as argument. * * This is checked in 2 steps due to args being 64-bit and the accumulator * only being a 32-bit register. * * If true we return the given action, otherwise nothing happens. */ #define KORE_SYSCALL_ARG(_name, _arg, _val, _action) \ KORE_BPF_CMP(SYS_##_name, 0, 6), \ KORE_BPF_LOAD(args[(_arg)], ARGS_LO_OFFSET), \ KORE_BPF_CMP(((_val) & 0xffffffff), 0, 3), \ KORE_BPF_LOAD(args[(_arg)], ARGS_HI_OFFSET), \ KORE_BPF_CMP((((uint32_t)((uint64_t)(_val) >> 32)) & 0xffffffff), 0, 1), \ KORE_BPF_RET(_action), \ KORE_BPF_LOAD(nr, 0) /* * Check if a system call is called with the supplied mask as argument. * * As KORE_SYSCALL_ARG() this is done in 2 steps. */ #define KORE_SYSCALL_MASK(_name, _arg, _mask, _action) \ KORE_BPF_CMP(SYS_##_name, 0, 8), \ KORE_BPF_LOAD(args[(_arg)], ARGS_LO_OFFSET), \ KORE_BPF_AND(~((_mask) & 0xffffffff)), \ KORE_BPF_CMP(0, 0, 4), \ KORE_BPF_LOAD(args[(_arg)], ARGS_HI_OFFSET), \ KORE_BPF_AND(~(((uint32_t)((uint64_t)(_mask) >> 32)) & 0xffffffff)), \ KORE_BPF_CMP(0, 0, 1), \ KORE_BPF_RET(_action), \ KORE_BPF_LOAD(nr, 0) /* * Check if the system call is called with the given value in the argument * contains the given flag. */ #define KORE_SYSCALL_WITH_FLAG(_name, _arg, _flag, _action) \ KORE_BPF_CMP(SYS_##_name, 0, 8), \ KORE_BPF_LOAD(args[(_arg)], ARGS_LO_OFFSET), \ KORE_BPF_AND(((_flag) & 0xffffffff)), \ KORE_BPF_CMP(((_flag) & 0xffffffff), 0, 4), \ KORE_BPF_LOAD(args[(_arg)], ARGS_HI_OFFSET), \ KORE_BPF_AND((((uint32_t)((uint64_t)(_flag) >> 32)) & 0xffffffff)), \ KORE_BPF_CMP((((uint32_t)((uint64_t)(_flag) >> 32)) & 0xffffffff), 0, 1), \ KORE_BPF_RET(_action), \ KORE_BPF_LOAD(nr, 0) /* Denying of system calls macros (with an errno). */ #define KORE_SYSCALL_DENY(_name, _errno) \ KORE_SYSCALL_FILTER(_name, SECCOMP_RET_ERRNO|(_errno)) #define KORE_SYSCALL_DENY_ARG(_name, _arg, _val, _errno) \ KORE_SYSCALL_ARG(_name, _arg, _val, SECCOMP_RET_ERRNO|(_errno)) #define KORE_SYSCALL_DENY_MASK(_name, _arg, _val, _errno) \ KORE_SYSCALL_MASK(_name, _arg, _val, SECCOMP_RET_ERRNO|(_errno)) #define KORE_SYSCALL_DENY_WITH_FLAG(_name, _arg, _flag, _errno) \ KORE_SYSCALL_WITH_FLAG(_name, _arg, _flag, SECCOMP_RET_ERRNO|(_errno)) /* Allowing of system call macros. */ #define KORE_SYSCALL_ALLOW(_name) \ KORE_SYSCALL_FILTER(_name, SECCOMP_RET_ALLOW) #define KORE_SYSCALL_ALLOW_LOG(_name) \ KORE_SYSCALL_FILTER(_name, SECCOMP_RET_LOG) #define KORE_SYSCALL_ALLOW_ARG(_name, _arg, _val) \ KORE_SYSCALL_ARG(_name, _arg, _val, SECCOMP_RET_ALLOW) #define KORE_SYSCALL_ALLOW_MASK(_name, _arg, _mask) \ KORE_SYSCALL_MASK(_name, _arg, _mask, SECCOMP_RET_ALLOW) #define KORE_SYSCALL_ALLOW_WITH_FLAG(_name, _arg, _flag) \ KORE_SYSCALL_WITH_FLAG(_name, _arg, _flag, SECCOMP_RET_ALLOW) /* Load field of seccomp_data into accumulator. */ #define KORE_BPF_LOAD(_field, _off) \ BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, _field) + _off) /* Return a constant from a BPF program. */ #define KORE_BPF_RET(_retval) \ BPF_STMT(BPF_RET+BPF_K, _retval) /* Compare the accumulator against a constant (==). */ #define KORE_BPF_CMP(_k, _jt, _jf) \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, _k, _jt, _jf) /* AND operation on the accumulator. */ #define KORE_BPF_AND(_k) \ BPF_STMT(BPF_ALU+BPF_AND+BPF_K, _k) /* The length of a filter. */ #define KORE_FILTER_LEN(x) (sizeof(x) / sizeof(x[0])) /* Used to mark the end of a BPF program. */ #define KORE_BPF_GUARD { USHRT_MAX, UCHAR_MAX, UCHAR_MAX, UINT_MAX } /* * Macro for applications to make easily define custom filter. * * eg: * KORE_SECCOMP_FILTER("filter", * KORE_SYSCALL_DENY_ERRNO(socket, EACCESS), * KORE_SYSCALL_DENY_ERRNO(ioctl, EACCESS), * KORE_SYSCALL_ALLOW(poll), * ) */ #define KORE_SECCOMP_FILTER(name, ...) \ struct sock_filter _scfilt[] = { \ __VA_ARGS__ \ }; \ void \ kore_seccomp_hook(void) \ { \ kore_seccomp_filter(name, _scfilt, \ KORE_FILTER_LEN(_scfilt)); \ } extern int kore_seccomp_tracing; void kore_seccomp_init(void); void kore_seccomp_drop(void); void kore_seccomp_enable(void); void kore_seccomp_traceme(void); int kore_seccomp_trace(pid_t, int); int kore_seccomp_syscall_resolve(const char *); int kore_seccomp_filter(const char *, void *, size_t); const char *kore_seccomp_syscall_name(long); struct sock_filter *kore_seccomp_syscall_filter(const char *, int); struct sock_filter *kore_seccomp_syscall_arg(const char *, int, int, int); struct sock_filter *kore_seccomp_syscall_flag(const char *, int, int, int); struct sock_filter *kore_seccomp_syscall_mask(const char *, int, int, int); #endif kore-4.2.3/include/kore/sha1.h000066400000000000000000000033011430066302400160430ustar00rootroot00000000000000/* $OpenBSD: sha1.h,v 1.24 2012/12/05 23:19:57 deraadt Exp $ */ /* * SHA-1 in C * By Steve Reid * 100% Public Domain */ #ifndef _SHA1_H #define _SHA1_H #ifndef WITH_OPENSSL #define SHA1_BLOCK_LENGTH 64 #define SHA1_DIGEST_LENGTH 20 #define SHA1_DIGEST_STRING_LENGTH (SHA1_DIGEST_LENGTH * 2 + 1) typedef struct { u_int32_t state[5]; u_int64_t count; u_int8_t buffer[SHA1_BLOCK_LENGTH]; } SHA1_CTX; void SHA1Init(SHA1_CTX *); void SHA1Pad(SHA1_CTX *); void SHA1Transform(u_int32_t [5], const u_int8_t [SHA1_BLOCK_LENGTH]); void SHA1Update(SHA1_CTX *, const u_int8_t *, size_t); void SHA1Final(u_int8_t [SHA1_DIGEST_LENGTH], SHA1_CTX *); char *SHA1End(SHA1_CTX *, char *); char *SHA1File(const char *, char *); char *SHA1FileChunk(const char *, char *, off_t, off_t); char *SHA1Data(const u_int8_t *, size_t, char *); #define HTONDIGEST(x) do { \ x[0] = htonl(x[0]); \ x[1] = htonl(x[1]); \ x[2] = htonl(x[2]); \ x[3] = htonl(x[3]); \ x[4] = htonl(x[4]); } while (0) #define NTOHDIGEST(x) do { \ x[0] = ntohl(x[0]); \ x[1] = ntohl(x[1]); \ x[2] = ntohl(x[2]); \ x[3] = ntohl(x[3]); \ x[4] = ntohl(x[4]); } while (0) #endif /* !WITH_OPENSSL */ #endif /* _SHA1_H */ kore-4.2.3/include/kore/sha2.h000066400000000000000000000066071430066302400160600ustar00rootroot00000000000000/* $OpenBSD: sha2.h,v 1.10 2016/09/03 17:00:29 tedu Exp $ */ /* * FILE: sha2.h * AUTHOR: Aaron D. Gifford * * Copyright (c) 2000-2001, Aaron D. Gifford * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $ */ /* OPENBSD ORIGINAL: include/sha2.h */ #ifndef _SSHSHA2_H #define _SSHSHA2_H /*** SHA-256/384/512 Various Length Definitions ***********************/ #define SHA256_BLOCK_LENGTH 64 #define SHA256_DIGEST_LENGTH 32 #define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1) #define SHA384_BLOCK_LENGTH 128 #define SHA384_DIGEST_LENGTH 48 #define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1) #define SHA512_BLOCK_LENGTH 128 #define SHA512_DIGEST_LENGTH 64 #define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1) /*** SHA-224/256/384/512 Context Structure *******************************/ typedef struct _SHA2_CTX { union { u_int32_t st32[8]; u_int64_t st64[8]; } state; u_int64_t bitcount[2]; u_int8_t buffer[SHA512_BLOCK_LENGTH]; } SHA2_CTX; void SHA256Init(SHA2_CTX *); void SHA256Transform(u_int32_t state[8], const u_int8_t [SHA256_BLOCK_LENGTH]); void SHA256Update(SHA2_CTX *, const u_int8_t *, size_t); void SHA256Pad(SHA2_CTX *); void SHA256Final(u_int8_t [SHA256_DIGEST_LENGTH], SHA2_CTX *); char *SHA256End(SHA2_CTX *, char *); void SHA384Init(SHA2_CTX *); void SHA384Transform(u_int64_t state[8], const u_int8_t [SHA384_BLOCK_LENGTH]); void SHA384Update(SHA2_CTX *, const u_int8_t *, size_t); void SHA384Pad(SHA2_CTX *); void SHA384Final(u_int8_t [SHA384_DIGEST_LENGTH], SHA2_CTX *); char *SHA384End(SHA2_CTX *, char *); void SHA512Init(SHA2_CTX *); void SHA512Transform(u_int64_t state[8], const u_int8_t [SHA512_BLOCK_LENGTH]); void SHA512Update(SHA2_CTX *, const u_int8_t *, size_t); void SHA512Pad(SHA2_CTX *); void SHA512Final(u_int8_t [SHA512_DIGEST_LENGTH], SHA2_CTX *); char *SHA512End(SHA2_CTX *, char *); #endif /* _SSHSHA2_H */ kore-4.2.3/include/kore/tasks.h000066400000000000000000000050501430066302400163370ustar00rootroot00000000000000/* * Copyright (c) 2014 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef __H_KORE_TASKS #define __H_KORE_TASKS #include #define KORE_TASK_STATE_CREATED 1 #define KORE_TASK_STATE_RUNNING 2 #define KORE_TASK_STATE_FINISHED 3 #define KORE_TASK_STATE_ABORT 4 #define KORE_TASK_THREADS 2 #if defined(__cplusplus) extern "C" { #endif #if !defined(KORE_NO_HTTP) struct http_request; #endif struct kore_task { struct kore_event evt; int state; int result; pthread_rwlock_t lock; #if !defined(KORE_NO_HTTP) struct http_request *req; #endif int fds[2]; int (*entry)(struct kore_task *); void (*cb)(struct kore_task *); struct kore_task_thread *thread; TAILQ_ENTRY(kore_task) list; LIST_ENTRY(kore_task) rlist; }; struct kore_task_thread { u_int8_t idx; pthread_t tid; pthread_mutex_t lock; pthread_cond_t cond; TAILQ_HEAD(, kore_task) tasks; TAILQ_ENTRY(kore_task_thread) list; }; void kore_task_init(void); void kore_task_handle(void *, int); void kore_task_run(struct kore_task *); void kore_task_finish(struct kore_task *); void kore_task_destroy(struct kore_task *); int kore_task_finished(struct kore_task *); #if !defined(KORE_NO_HTTP) void kore_task_bind_request(struct kore_task *, struct http_request *); #endif void kore_task_bind_callback(struct kore_task *, void (*cb)(struct kore_task *)); void kore_task_create(struct kore_task *, int (*entry)(struct kore_task *)); u_int32_t kore_task_channel_read(struct kore_task *, void *, u_int32_t); void kore_task_channel_write(struct kore_task *, void *, u_int32_t); void kore_task_set_state(struct kore_task *, int); void kore_task_set_result(struct kore_task *, int); int kore_task_state(struct kore_task *); int kore_task_result(struct kore_task *); extern u_int16_t kore_task_threads; #if defined(__cplusplus) } #endif #endif kore-4.2.3/kodev/000077500000000000000000000000001430066302400135665ustar00rootroot00000000000000kore-4.2.3/kodev/Makefile000066400000000000000000000022551430066302400152320ustar00rootroot00000000000000# kodev Makefile CC?=cc PREFIX?=/usr/local OBJDIR?=obj KODEV=kodev DESTDIR?= INSTALL_DIR=$(PREFIX)/bin S_SRC= ../src/cli.c CFLAGS+=-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes CFLAGS+=-Wmissing-declarations -Wshadow -Wpointer-arith -Wcast-qual CFLAGS+=-Wsign-compare -Iincludes -std=c99 -pedantic CFLAGS+=-DPREFIX='"$(PREFIX)"' LDFLAGS=-lcrypto ifneq ("$(NOOPT)", "") CFLAGS+=-O0 else CFLAGS+=-O2 endif ifneq ("$(MINIMAL)", "") CFLAGS+=-DKODEV_MINIMAL LDFLAGS= endif OSNAME=$(shell uname -s | sed -e 's/[-_].*//g' | tr A-Z a-z) ifeq ("$(OSNAME)", "darwin") CFLAGS+=$(shell pkg-config openssl --cflags) LDFLAGS+=$(shell pkg-config openssl --libs) else ifeq ("$(OSNAME)", "linux") CFLAGS+=-D_GNU_SOURCE=1 endif S_OBJS= $(S_SRC:../src/%.c=$(OBJDIR)/%.o) $(KODEV): $(OBJDIR) $(S_OBJS) $(CC) $(S_OBJS) $(LDFLAGS) -o $(KODEV) $(OBJDIR): @mkdir -p $(OBJDIR) install: $(KODEV) mkdir -p $(DESTDIR)$(INSTALL_DIR) install -m 555 $(KODEV) $(DESTDIR)$(INSTALL_DIR)/$(KODEV) uninstall: rm -f $(DESTDIR)$(INSTALL_DIR)/$(KODEV) $(OBJDIR)/%.o: ../src/%.c $(CC) $(CFLAGS) -c $< -o $@ clean: find . -type f -name \*.o -exec rm {} \; rm -rf $(KODEV) $(OBJDIR) .PHONY: all clean kore-4.2.3/minisign.pub000066400000000000000000000001451430066302400150030ustar00rootroot00000000000000untrusted comment: Kore minisign public key RWSxkEDc2y+whfKTmvhqs/YaFmEwblmvar7l6RXMjnu6o9tZW3LC0Hc9 kore-4.2.3/misc/000077500000000000000000000000001430066302400134115ustar00rootroot00000000000000kore-4.2.3/misc/curl-extract-opt.sh000077500000000000000000000022431430066302400171660ustar00rootroot00000000000000#!/bin/sh if [ $# -ne 1 ]; then echo "Usage: curl-extract-opt.sh /path/to/include/curl" exit 1 fi if [ ! -d $1 ]; then echo "given argument is not a directory" exit 1 fi if [ ! -f "$1/curl.h" ]; then echo "$1 does not contain curl.h" exit 1 fi if [ ! -f "$1/curlver.h" ]; then echo "$1 does not contain curlver.h" fi version=`egrep "#define LIBCURL_VERSION " "$1/curlver.h" | awk '{ print $3 }' | sed s/\"//g` echo "/* Auto generated on `date` from $version */" cat << __EOF struct { const char *name; int value; PyObject *(*cb)(struct pycurl_handle *, int, PyObject *); } py_curlopt[] = { __EOF egrep "^.*CURLOPT\(.*\),$" "$1/curl.h" | \ cut -d'(' -f 2 | cut -d ')' -f 1 | sed 's/,/ /g' | \ sed 's/CURLOPTTYPE_OBJECTPOINT/NULL/g' | \ sed 's/CURLOPTTYPE_STRINGPOINT/pycurl_handle_setopt_string/g' | \ sed 's/CURLOPTTYPE_LONG/pycurl_handle_setopt_long/g' | \ sed 's/CURLOPTTYPE_SLISTPOINT/pycurl_handle_setopt_slist/g' | \ sed 's/CURLOPTTYPE_FUNCTIONPOINT/NULL/g' | \ sed 's/CURLOPTTYPE_OFF_T/NULL/g' | \ sed 's/CURLOPTTYPE_BLOB/NULL/g' | \ awk '{ printf "\t{ \"%s\", %s, %s },\n", $1, $3, $2 } ' echo "\t{ NULL, 0, 0 }" echo "};" kore-4.2.3/misc/curl/000077500000000000000000000000001430066302400143565ustar00rootroot00000000000000kore-4.2.3/misc/curl/python_curlopt.h000066400000000000000000000375371430066302400176370ustar00rootroot00000000000000/* Auto generated on Wed Jul 1 14:38:36 CEST 2020 from 7.71.1 */ struct { const char *name; int value; PyObject *(*cb)(struct pycurl_data *, int, PyObject *); } py_curlopt[] = { { "CURLOPT_WRITEDATA", 1, NULL }, { "CURLOPT_URL", 2, pycurl_handle_setopt_string }, { "CURLOPT_PORT", 3, pycurl_handle_setopt_long }, { "CURLOPT_PROXY", 4, pycurl_handle_setopt_string }, { "CURLOPT_USERPWD", 5, pycurl_handle_setopt_string }, { "CURLOPT_PROXYUSERPWD", 6, pycurl_handle_setopt_string }, { "CURLOPT_RANGE", 7, pycurl_handle_setopt_string }, { "CURLOPT_READDATA", 9, NULL }, { "CURLOPT_ERRORBUFFER", 10, NULL }, { "CURLOPT_WRITEFUNCTION", 11, NULL }, { "CURLOPT_READFUNCTION", 12, NULL }, { "CURLOPT_TIMEOUT", 13, pycurl_handle_setopt_long }, { "CURLOPT_INFILESIZE", 14, pycurl_handle_setopt_long }, { "CURLOPT_POSTFIELDS", 15, NULL }, { "CURLOPT_REFERER", 16, pycurl_handle_setopt_string }, { "CURLOPT_FTPPORT", 17, pycurl_handle_setopt_string }, { "CURLOPT_USERAGENT", 18, pycurl_handle_setopt_string }, { "CURLOPT_LOW_SPEED_LIMIT", 19, pycurl_handle_setopt_long }, { "CURLOPT_LOW_SPEED_TIME", 20, pycurl_handle_setopt_long }, { "CURLOPT_RESUME_FROM", 21, pycurl_handle_setopt_long }, { "CURLOPT_COOKIE", 22, pycurl_handle_setopt_string }, { "CURLOPT_HTTPHEADER", 23, pycurl_handle_setopt_slist }, { "CURLOPT_HTTPPOST", 24, NULL }, { "CURLOPT_SSLCERT", 25, pycurl_handle_setopt_string }, { "CURLOPT_KEYPASSWD", 26, pycurl_handle_setopt_string }, { "CURLOPT_CRLF", 27, pycurl_handle_setopt_long }, { "CURLOPT_QUOTE", 28, pycurl_handle_setopt_slist }, { "CURLOPT_HEADERDATA", 29, NULL }, { "CURLOPT_COOKIEFILE", 31, pycurl_handle_setopt_string }, { "CURLOPT_SSLVERSION", 32, pycurl_handle_setopt_long }, { "CURLOPT_TIMECONDITION", 33, pycurl_handle_setopt_long }, { "CURLOPT_TIMEVALUE", 34, pycurl_handle_setopt_long }, { "CURLOPT_CUSTOMREQUEST", 36, pycurl_handle_setopt_string }, { "CURLOPT_STDERR", 37, NULL }, { "CURLOPT_POSTQUOTE", 39, pycurl_handle_setopt_slist }, { "CURLOPT_OBSOLETE40", 40, NULL }, { "CURLOPT_VERBOSE", 41, pycurl_handle_setopt_long }, { "CURLOPT_HEADER", 42, pycurl_handle_setopt_long }, { "CURLOPT_NOPROGRESS", 43, pycurl_handle_setopt_long }, { "CURLOPT_NOBODY", 44, pycurl_handle_setopt_long }, { "CURLOPT_FAILONERROR", 45, pycurl_handle_setopt_long }, { "CURLOPT_UPLOAD", 46, pycurl_handle_setopt_long }, { "CURLOPT_POST", 47, pycurl_handle_setopt_long }, { "CURLOPT_DIRLISTONLY", 48, pycurl_handle_setopt_long }, { "CURLOPT_APPEND", 50, pycurl_handle_setopt_long }, { "CURLOPT_NETRC", 51, pycurl_handle_setopt_long }, { "CURLOPT_FOLLOWLOCATION", 52, pycurl_handle_setopt_long }, { "CURLOPT_TRANSFERTEXT", 53, pycurl_handle_setopt_long }, { "CURLOPT_PUT", 54, pycurl_handle_setopt_long }, { "CURLOPT_PROGRESSFUNCTION", 56, NULL }, { "CURLOPT_PROGRESSDATA", 57, NULL }, { "CURLOPT_AUTOREFERER", 58, pycurl_handle_setopt_long }, { "CURLOPT_PROXYPORT", 59, pycurl_handle_setopt_long }, { "CURLOPT_POSTFIELDSIZE", 60, pycurl_handle_setopt_long }, { "CURLOPT_HTTPPROXYTUNNEL", 61, pycurl_handle_setopt_long }, { "CURLOPT_INTERFACE", 62, pycurl_handle_setopt_string }, { "CURLOPT_KRBLEVEL", 63, pycurl_handle_setopt_string }, { "CURLOPT_SSL_VERIFYPEER", 64, pycurl_handle_setopt_long }, { "CURLOPT_CAINFO", 65, pycurl_handle_setopt_string }, { "CURLOPT_MAXREDIRS", 68, pycurl_handle_setopt_long }, { "CURLOPT_FILETIME", 69, pycurl_handle_setopt_long }, { "CURLOPT_TELNETOPTIONS", 70, pycurl_handle_setopt_slist }, { "CURLOPT_MAXCONNECTS", 71, pycurl_handle_setopt_long }, { "CURLOPT_OBSOLETE72", 72, pycurl_handle_setopt_long }, { "CURLOPT_FRESH_CONNECT", 74, pycurl_handle_setopt_long }, { "CURLOPT_FORBID_REUSE", 75, pycurl_handle_setopt_long }, { "CURLOPT_RANDOM_FILE", 76, pycurl_handle_setopt_string }, { "CURLOPT_EGDSOCKET", 77, pycurl_handle_setopt_string }, { "CURLOPT_CONNECTTIMEOUT", 78, pycurl_handle_setopt_long }, { "CURLOPT_HEADERFUNCTION", 79, NULL }, { "CURLOPT_HTTPGET", 80, pycurl_handle_setopt_long }, { "CURLOPT_SSL_VERIFYHOST", 81, pycurl_handle_setopt_long }, { "CURLOPT_COOKIEJAR", 82, pycurl_handle_setopt_string }, { "CURLOPT_SSL_CIPHER_LIST", 83, pycurl_handle_setopt_string }, { "CURLOPT_HTTP_VERSION", 84, pycurl_handle_setopt_long }, { "CURLOPT_FTP_USE_EPSV", 85, pycurl_handle_setopt_long }, { "CURLOPT_SSLCERTTYPE", 86, pycurl_handle_setopt_string }, { "CURLOPT_SSLKEY", 87, pycurl_handle_setopt_string }, { "CURLOPT_SSLKEYTYPE", 88, pycurl_handle_setopt_string }, { "CURLOPT_SSLENGINE", 89, pycurl_handle_setopt_string }, { "CURLOPT_SSLENGINE_DEFAULT", 90, pycurl_handle_setopt_long }, { "CURLOPT_DNS_USE_GLOBAL_CACHE", 91, pycurl_handle_setopt_long }, { "CURLOPT_DNS_CACHE_TIMEOUT", 92, pycurl_handle_setopt_long }, { "CURLOPT_PREQUOTE", 93, pycurl_handle_setopt_slist }, { "CURLOPT_DEBUGFUNCTION", 94, NULL }, { "CURLOPT_DEBUGDATA", 95, NULL }, { "CURLOPT_COOKIESESSION", 96, pycurl_handle_setopt_long }, { "CURLOPT_CAPATH", 97, pycurl_handle_setopt_string }, { "CURLOPT_BUFFERSIZE", 98, pycurl_handle_setopt_long }, { "CURLOPT_NOSIGNAL", 99, pycurl_handle_setopt_long }, { "CURLOPT_SHARE", 100, NULL }, { "CURLOPT_PROXYTYPE", 101, pycurl_handle_setopt_long }, { "CURLOPT_ACCEPT_ENCODING", 102, pycurl_handle_setopt_string }, { "CURLOPT_PRIVATE", 103, NULL }, { "CURLOPT_HTTP200ALIASES", 104, pycurl_handle_setopt_slist }, { "CURLOPT_UNRESTRICTED_AUTH", 105, pycurl_handle_setopt_long }, { "CURLOPT_FTP_USE_EPRT", 106, pycurl_handle_setopt_long }, { "CURLOPT_HTTPAUTH", 107, pycurl_handle_setopt_long }, { "CURLOPT_SSL_CTX_FUNCTION", 108, NULL }, { "CURLOPT_SSL_CTX_DATA", 109, NULL }, { "CURLOPT_FTP_CREATE_MISSING_DIRS", 110, pycurl_handle_setopt_long }, { "CURLOPT_PROXYAUTH", 111, pycurl_handle_setopt_long }, { "CURLOPT_FTP_RESPONSE_TIMEOUT", 112, pycurl_handle_setopt_long }, { "CURLOPT_IPRESOLVE", 113, pycurl_handle_setopt_long }, { "CURLOPT_MAXFILESIZE", 114, pycurl_handle_setopt_long }, { "CURLOPT_INFILESIZE_LARGE", 115, NULL }, { "CURLOPT_RESUME_FROM_LARGE", 116, NULL }, { "CURLOPT_MAXFILESIZE_LARGE", 117, NULL }, { "CURLOPT_NETRC_FILE", 118, pycurl_handle_setopt_string }, { "CURLOPT_USE_SSL", 119, pycurl_handle_setopt_long }, { "CURLOPT_POSTFIELDSIZE_LARGE", 120, NULL }, { "CURLOPT_TCP_NODELAY", 121, pycurl_handle_setopt_long }, { "CURLOPT_FTPSSLAUTH", 129, pycurl_handle_setopt_long }, { "CURLOPT_IOCTLFUNCTION", 130, NULL }, { "CURLOPT_IOCTLDATA", 131, NULL }, { "CURLOPT_FTP_ACCOUNT", 134, pycurl_handle_setopt_string }, { "CURLOPT_COOKIELIST", 135, pycurl_handle_setopt_string }, { "CURLOPT_IGNORE_CONTENT_LENGTH", 136, pycurl_handle_setopt_long }, { "CURLOPT_FTP_SKIP_PASV_IP", 137, pycurl_handle_setopt_long }, { "CURLOPT_FTP_FILEMETHOD", 138, pycurl_handle_setopt_long }, { "CURLOPT_LOCALPORT", 139, pycurl_handle_setopt_long }, { "CURLOPT_LOCALPORTRANGE", 140, pycurl_handle_setopt_long }, { "CURLOPT_CONNECT_ONLY", 141, pycurl_handle_setopt_long }, { "CURLOPT_CONV_FROM_NETWORK_FUNCTION", 142, NULL }, { "CURLOPT_CONV_TO_NETWORK_FUNCTION", 143, NULL }, { "CURLOPT_CONV_FROM_UTF8_FUNCTION", 144, NULL }, { "CURLOPT_MAX_SEND_SPEED_LARGE", 145, NULL }, { "CURLOPT_MAX_RECV_SPEED_LARGE", 146, NULL }, { "CURLOPT_FTP_ALTERNATIVE_TO_USER", 147, pycurl_handle_setopt_string }, { "CURLOPT_SOCKOPTFUNCTION", 148, NULL }, { "CURLOPT_SOCKOPTDATA", 149, NULL }, { "CURLOPT_SSL_SESSIONID_CACHE", 150, pycurl_handle_setopt_long }, { "CURLOPT_SSH_AUTH_TYPES", 151, pycurl_handle_setopt_long }, { "CURLOPT_SSH_PUBLIC_KEYFILE", 152, pycurl_handle_setopt_string }, { "CURLOPT_SSH_PRIVATE_KEYFILE", 153, pycurl_handle_setopt_string }, { "CURLOPT_FTP_SSL_CCC", 154, pycurl_handle_setopt_long }, { "CURLOPT_TIMEOUT_MS", 155, pycurl_handle_setopt_long }, { "CURLOPT_CONNECTTIMEOUT_MS", 156, pycurl_handle_setopt_long }, { "CURLOPT_HTTP_TRANSFER_DECODING", 157, pycurl_handle_setopt_long }, { "CURLOPT_HTTP_CONTENT_DECODING", 158, pycurl_handle_setopt_long }, { "CURLOPT_NEW_FILE_PERMS", 159, pycurl_handle_setopt_long }, { "CURLOPT_NEW_DIRECTORY_PERMS", 160, pycurl_handle_setopt_long }, { "CURLOPT_POSTREDIR", 161, pycurl_handle_setopt_long }, { "CURLOPT_SSH_HOST_PUBLIC_KEY_MD5", 162, pycurl_handle_setopt_string }, { "CURLOPT_OPENSOCKETFUNCTION", 163, NULL }, { "CURLOPT_OPENSOCKETDATA", 164, NULL }, { "CURLOPT_COPYPOSTFIELDS", 165, NULL }, { "CURLOPT_PROXY_TRANSFER_MODE", 166, pycurl_handle_setopt_long }, { "CURLOPT_SEEKFUNCTION", 167, NULL }, { "CURLOPT_SEEKDATA", 168, NULL }, { "CURLOPT_CRLFILE", 169, pycurl_handle_setopt_string }, { "CURLOPT_ISSUERCERT", 170, pycurl_handle_setopt_string }, { "CURLOPT_ADDRESS_SCOPE", 171, pycurl_handle_setopt_long }, { "CURLOPT_CERTINFO", 172, pycurl_handle_setopt_long }, { "CURLOPT_USERNAME", 173, pycurl_handle_setopt_string }, { "CURLOPT_PASSWORD", 174, pycurl_handle_setopt_string }, { "CURLOPT_PROXYUSERNAME", 175, pycurl_handle_setopt_string }, { "CURLOPT_PROXYPASSWORD", 176, pycurl_handle_setopt_string }, { "CURLOPT_NOPROXY", 177, pycurl_handle_setopt_string }, { "CURLOPT_TFTP_BLKSIZE", 178, pycurl_handle_setopt_long }, { "CURLOPT_SOCKS5_GSSAPI_SERVICE", 179, pycurl_handle_setopt_string }, { "CURLOPT_SOCKS5_GSSAPI_NEC", 180, pycurl_handle_setopt_long }, { "CURLOPT_PROTOCOLS", 181, pycurl_handle_setopt_long }, { "CURLOPT_REDIR_PROTOCOLS", 182, pycurl_handle_setopt_long }, { "CURLOPT_SSH_KNOWNHOSTS", 183, pycurl_handle_setopt_string }, { "CURLOPT_SSH_KEYFUNCTION", 184, NULL }, { "CURLOPT_SSH_KEYDATA", 185, NULL }, { "CURLOPT_MAIL_FROM", 186, pycurl_handle_setopt_string }, { "CURLOPT_MAIL_RCPT", 187, pycurl_handle_setopt_slist }, { "CURLOPT_FTP_USE_PRET", 188, pycurl_handle_setopt_long }, { "CURLOPT_RTSP_REQUEST", 189, pycurl_handle_setopt_long }, { "CURLOPT_RTSP_SESSION_ID", 190, pycurl_handle_setopt_string }, { "CURLOPT_RTSP_STREAM_URI", 191, pycurl_handle_setopt_string }, { "CURLOPT_RTSP_TRANSPORT", 192, pycurl_handle_setopt_string }, { "CURLOPT_RTSP_CLIENT_CSEQ", 193, pycurl_handle_setopt_long }, { "CURLOPT_RTSP_SERVER_CSEQ", 194, pycurl_handle_setopt_long }, { "CURLOPT_INTERLEAVEDATA", 195, NULL }, { "CURLOPT_INTERLEAVEFUNCTION", 196, NULL }, { "CURLOPT_WILDCARDMATCH", 197, pycurl_handle_setopt_long }, { "CURLOPT_CHUNK_BGN_FUNCTION", 198, NULL }, { "CURLOPT_CHUNK_END_FUNCTION", 199, NULL }, { "CURLOPT_FNMATCH_FUNCTION", 200, NULL }, { "CURLOPT_CHUNK_DATA", 201, NULL }, { "CURLOPT_FNMATCH_DATA", 202, NULL }, { "CURLOPT_RESOLVE", 203, pycurl_handle_setopt_slist }, { "CURLOPT_TLSAUTH_USERNAME", 204, pycurl_handle_setopt_string }, { "CURLOPT_TLSAUTH_PASSWORD", 205, pycurl_handle_setopt_string }, { "CURLOPT_TLSAUTH_TYPE", 206, pycurl_handle_setopt_string }, { "CURLOPT_TRANSFER_ENCODING", 207, pycurl_handle_setopt_long }, { "CURLOPT_CLOSESOCKETFUNCTION", 208, NULL }, { "CURLOPT_CLOSESOCKETDATA", 209, NULL }, { "CURLOPT_GSSAPI_DELEGATION", 210, pycurl_handle_setopt_long }, { "CURLOPT_DNS_SERVERS", 211, pycurl_handle_setopt_string }, { "CURLOPT_ACCEPTTIMEOUT_MS", 212, pycurl_handle_setopt_long }, { "CURLOPT_TCP_KEEPALIVE", 213, pycurl_handle_setopt_long }, { "CURLOPT_TCP_KEEPIDLE", 214, pycurl_handle_setopt_long }, { "CURLOPT_TCP_KEEPINTVL", 215, pycurl_handle_setopt_long }, { "CURLOPT_SSL_OPTIONS", 216, pycurl_handle_setopt_long }, { "CURLOPT_MAIL_AUTH", 217, pycurl_handle_setopt_string }, { "CURLOPT_SASL_IR", 218, pycurl_handle_setopt_long }, { "CURLOPT_XFERINFOFUNCTION", 219, NULL }, { "CURLOPT_XOAUTH2_BEARER", 220, pycurl_handle_setopt_string }, { "CURLOPT_DNS_INTERFACE", 221, pycurl_handle_setopt_string }, { "CURLOPT_DNS_LOCAL_IP4", 222, pycurl_handle_setopt_string }, { "CURLOPT_DNS_LOCAL_IP6", 223, pycurl_handle_setopt_string }, { "CURLOPT_LOGIN_OPTIONS", 224, pycurl_handle_setopt_string }, { "CURLOPT_SSL_ENABLE_NPN", 225, pycurl_handle_setopt_long }, { "CURLOPT_SSL_ENABLE_ALPN", 226, pycurl_handle_setopt_long }, { "CURLOPT_EXPECT_100_TIMEOUT_MS", 227, pycurl_handle_setopt_long }, { "CURLOPT_PROXYHEADER", 228, pycurl_handle_setopt_slist }, { "CURLOPT_HEADEROPT", 229, pycurl_handle_setopt_long }, { "CURLOPT_PINNEDPUBLICKEY", 230, pycurl_handle_setopt_string }, { "CURLOPT_UNIX_SOCKET_PATH", 231, pycurl_handle_setopt_string }, { "CURLOPT_SSL_VERIFYSTATUS", 232, pycurl_handle_setopt_long }, { "CURLOPT_SSL_FALSESTART", 233, pycurl_handle_setopt_long }, { "CURLOPT_PATH_AS_IS", 234, pycurl_handle_setopt_long }, { "CURLOPT_PROXY_SERVICE_NAME", 235, pycurl_handle_setopt_string }, { "CURLOPT_SERVICE_NAME", 236, pycurl_handle_setopt_string }, { "CURLOPT_PIPEWAIT", 237, pycurl_handle_setopt_long }, { "CURLOPT_DEFAULT_PROTOCOL", 238, pycurl_handle_setopt_string }, { "CURLOPT_STREAM_WEIGHT", 239, pycurl_handle_setopt_long }, { "CURLOPT_STREAM_DEPENDS", 240, NULL }, { "CURLOPT_STREAM_DEPENDS_E", 241, NULL }, { "CURLOPT_TFTP_NO_OPTIONS", 242, pycurl_handle_setopt_long }, { "CURLOPT_CONNECT_TO", 243, pycurl_handle_setopt_slist }, { "CURLOPT_TCP_FASTOPEN", 244, pycurl_handle_setopt_long }, { "CURLOPT_KEEP_SENDING_ON_ERROR", 245, pycurl_handle_setopt_long }, { "CURLOPT_PROXY_CAINFO", 246, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_CAPATH", 247, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_SSL_VERIFYPEER", 248, pycurl_handle_setopt_long }, { "CURLOPT_PROXY_SSL_VERIFYHOST", 249, pycurl_handle_setopt_long }, { "CURLOPT_PROXY_SSLVERSION", 250, pycurl_handle_setopt_long }, { "CURLOPT_PROXY_TLSAUTH_USERNAME", 251, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_TLSAUTH_PASSWORD", 252, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_TLSAUTH_TYPE", 253, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_SSLCERT", 254, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_SSLCERTTYPE", 255, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_SSLKEY", 256, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_SSLKEYTYPE", 257, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_KEYPASSWD", 258, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_SSL_CIPHER_LIST", 259, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_CRLFILE", 260, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_SSL_OPTIONS", 261, pycurl_handle_setopt_long }, { "CURLOPT_PRE_PROXY", 262, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_PINNEDPUBLICKEY", 263, pycurl_handle_setopt_string }, { "CURLOPT_ABSTRACT_UNIX_SOCKET", 264, pycurl_handle_setopt_string }, { "CURLOPT_SUPPRESS_CONNECT_HEADERS", 265, pycurl_handle_setopt_long }, { "CURLOPT_REQUEST_TARGET", 266, pycurl_handle_setopt_string }, { "CURLOPT_SOCKS5_AUTH", 267, pycurl_handle_setopt_long }, { "CURLOPT_SSH_COMPRESSION", 268, pycurl_handle_setopt_long }, { "CURLOPT_MIMEPOST", 269, NULL }, { "CURLOPT_TIMEVALUE_LARGE", 270, NULL }, { "CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS", 271, pycurl_handle_setopt_long }, { "CURLOPT_RESOLVER_START_FUNCTION", 272, NULL }, { "CURLOPT_RESOLVER_START_DATA", 273, NULL }, { "CURLOPT_HAPROXYPROTOCOL", 274, pycurl_handle_setopt_long }, { "CURLOPT_DNS_SHUFFLE_ADDRESSES", 275, pycurl_handle_setopt_long }, { "CURLOPT_TLS13_CIPHERS", 276, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_TLS13_CIPHERS", 277, pycurl_handle_setopt_string }, { "CURLOPT_DISALLOW_USERNAME_IN_URL", 278, pycurl_handle_setopt_long }, { "CURLOPT_DOH_URL", 279, pycurl_handle_setopt_string }, { "CURLOPT_UPLOAD_BUFFERSIZE", 280, pycurl_handle_setopt_long }, { "CURLOPT_UPKEEP_INTERVAL_MS", 281, pycurl_handle_setopt_long }, { "CURLOPT_CURLU", 282, NULL }, { "CURLOPT_TRAILERFUNCTION", 283, NULL }, { "CURLOPT_TRAILERDATA", 284, NULL }, { "CURLOPT_HTTP09_ALLOWED", 285, pycurl_handle_setopt_long }, { "CURLOPT_ALTSVC_CTRL", 286, pycurl_handle_setopt_long }, { "CURLOPT_ALTSVC", 287, pycurl_handle_setopt_string }, { "CURLOPT_MAXAGE_CONN", 288, pycurl_handle_setopt_long }, { "CURLOPT_SASL_AUTHZID", 289, pycurl_handle_setopt_string }, { "CURLOPT_MAIL_RCPT_ALLLOWFAILS", 290, pycurl_handle_setopt_long }, { "CURLOPT_SSLCERT_BLOB", 291, NULL }, { "CURLOPT_SSLKEY_BLOB", 292, NULL }, { "CURLOPT_PROXY_SSLCERT_BLOB", 293, NULL }, { "CURLOPT_PROXY_SSLKEY_BLOB", 294, NULL }, { "CURLOPT_ISSUERCERT_BLOB", 295, NULL }, { "CURLOPT_PROXY_ISSUERCERT", 296, pycurl_handle_setopt_string }, { "CURLOPT_PROXY_ISSUERCERT_BLOB", 297, NULL }, { NULL, 0, 0 } }; kore-4.2.3/misc/ffdhe4096.pem000066400000000000000000000014011430066302400155070ustar00rootroot00000000000000-----BEGIN DH PARAMETERS----- MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e 8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI= -----END DH PARAMETERS----- kore-4.2.3/misc/kore-build/000077500000000000000000000000001430066302400154465ustar00rootroot00000000000000kore-4.2.3/misc/kore-build/build-curl.sh000077500000000000000000000005631430066302400200530ustar00rootroot00000000000000#!/bin/sh set -e . ./env.sh if [ $# -ne 3 ]; then echo "Usage: build-curl.sh [release] [openssl] [nghttp2]" exit 1 fi export PKG_CONFIG="pkg-config --static" export PKG_CONFIG_PATH="$FAKEROOT/openssl-$2/lib/pkgconfig:$FAKEROOT/nghttp2-$3/lib/pkgconfig" NAME=curl-$1 fetch "https://curl.haxx.se/download/$NAME.tar.gz" $NAME default_build $NAME --enable-shared=no kore-4.2.3/misc/kore-build/build-kodev.sh000077500000000000000000000004161430066302400202130ustar00rootroot00000000000000#!/bin/sh set -e . /home/build/env.sh if [ $# -ne 4 ]; then echo "Usage: build-kore.sh [openssl] [python] [curl] [nghttp2]" exit 1 fi export PATH=$FAKEROOT/Python-$2/bin:$FAKEROOT/curl-$3/bin:$PATH export OPENSSL_PATH=$FAKEROOT/openssl-$1 kodev clean kodev build kore-4.2.3/misc/kore-build/build-kore.sh000077500000000000000000000027031430066302400200440ustar00rootroot00000000000000#!/bin/sh set -e if [ $# -ne 4 ]; then echo "Usage: build-kore.sh [openssl] [python] [curl] [nghttp2]" exit 1 fi # Set ROOT based on the versions given. VERSION=kore_ossl-$1_python-$2_curl-$3_nghttp2-$4 ROOT=`pwd`/$VERSION # Pull in the rest of the functions. . ./helpers.sh OPENSSL=openssl-$1 PYTHON=Python-$2 CURL=curl-$3 NGHTTP2=nghttp2-$4 # Build OpenSSL echo "Building $OPENSSL" fetch "https://www.openssl.org/source/$OPENSSL.tar.gz" $OPENSSL build $OPENSSL ./config no-shared --prefix=$FAKEROOT/$OPENSSL # Build Python echo "Building $PYTHON" fetch "https://www.python.org/ftp/python/$2/$PYTHON.tgz" $PYTHON default_build $PYTHON # Build nghttp2 echo "Building $NGHTTP2" fetch \ "https://github.com/nghttp2/nghttp2/releases/download/v$4/$NGHTTP2.tar.gz" \ $NGHTTP2 default_build $NGHTTP2 --enable-lib-only --prefix=$FAKEROOT/$NGHTTP2 \ --enable-shared=no # Build curl echo "Building $CURL" export PKG_CONFIG="pkg-config --static" export PKG_CONFIG_PATH="$FAKEROOT/$OPENSSL/lib/pkgconfig:$FAKEROOT/$NGHTTP2/lib/pkgconfig" fetch "https://curl.haxx.se/download/$CURL.tar.gz" $CURL default_build $CURL --enable-shared=no # Now we can build kore. unset PKG_CONFIG unset PKG_CONFIG_PATH export PATH=$FAKEROOT/bin:$PATH export OPENSSL_PATH=$FAKEROOT/$OPENSSL cd $ROOT if [ ! -d kore ]; then git clone https://git.kore.io/kore.git fi pushd kore make clean LDFLAGS=-L$FAKEROOT/$NGHTTP2/lib make PYTHON=1 CURL=1 ACME=1 mv kore $ROOT/kore.bin popd kore-4.2.3/misc/kore-build/build-nghttp2.sh000077500000000000000000000004431430066302400204710ustar00rootroot00000000000000#!/bin/sh set -e . ./env.sh if [ $# -ne 1 ]; then echo "Usage: build-nghttp2.sh [release]" exit 1 fi NAME=nghttp2-$1 fetch "https://github.com/nghttp2/nghttp2/releases/download/v$1/$NAME.tar.gz" $NAME default_build $NAME --enable-lib-only --prefix=$FAKEROOT/$NAME --enable-shared=no kore-4.2.3/misc/kore-build/build-openssl.sh000077500000000000000000000003571430066302400205720ustar00rootroot00000000000000#!/bin/sh set -e . ./env.sh if [ $# -ne 1 ]; then echo "Usage: build-openssl.sh [release]" exit 1 fi NAME=openssl-$1 fetch "https://www.openssl.org/source/$NAME.tar.gz" $NAME build $NAME ./config no-shared --prefix=$FAKEROOT/$NAME kore-4.2.3/misc/kore-build/build-python.sh000077500000000000000000000003231430066302400204210ustar00rootroot00000000000000#!/bin/sh set -e set -x . ./env.sh if [ $# -ne 1 ]; then echo "Usage: build-python.sh [release]" exit 1 fi NAME=Python-$1 fetch "https://www.python.org/ftp/python/$1/$NAME.tgz" $NAME default_build $NAME kore-4.2.3/misc/kore-build/helpers.sh000066400000000000000000000014361430066302400174500ustar00rootroot00000000000000if [ -z "$ROOT" ]; then echo "No ROOT set" exit 1 fi SOURCES=sources BUILD=$ROOT/build FAKEROOT=$ROOT/fakeroot LASTDIR=`pwd` pushd() { LASTDIR=`pwd` cd $1 } popd() { cd $LASTDIR } fetch() { url=$1 name=$2 if [ -f $SOURCES/$name.tar.gz ]; then return fi curl -L "$url" > $SOURCES/$name.tar.gz } default_build() { name=$1 shift config=$* build $name ./configure --prefix=$FAKEROOT/$NAME $config } build() { name=$1 shift configcmd=$* if [ -f $BUILD/$name/.built ]; then return fi rm -rf $BUILD/$name rm -rf $FAKEROOT/$name tar -zvxf $SOURCES/$name.tar.gz -C $BUILD pushd $BUILD/$name mkdir -p $FAKEROOT/$name $configcmd make -j make install popd touch $BUILD/$name/.built } setup() { mkdir -p $BUILD mkdir -p $SOURCES mkdir -p $FAKEROOT } setup kore-4.2.3/misc/linux-platform.sh000077500000000000000000000016411430066302400167330ustar00rootroot00000000000000#!/bin/sh # # Linux specific defines and system call maps. if [ -z "$TARGET_PLATFORM" ]; then PLATFORM=$(uname -m) else PLATFORM=$TARGET_PLATFORM fi BASE=$(dirname $0) case "$PLATFORM" in x86_64*) seccomp_audit_arch=AUDIT_ARCH_X86_64 syscall_file=$BASE/linux/x86_64_syscall.h.in ;; i*86*) >&2 echo "i386 not supported" exit 1 ;; arm*) seccomp_audit_arch=AUDIT_ARCH_ARM syscall_file=$BASE/linux/arm_syscall.h.in ;; aarch64*) seccomp_audit_arch=AUDIT_ARCH_AARCH64 syscall_file=$BASE/linux/aarch64_syscall.h.in ;; esac cat << __EOF /* Auto generated by linux-platform.sh - DO NOT EDIT */ #include #define SECCOMP_AUDIT_ARCH $seccomp_audit_arch struct { const char *name; int nr; } kore_syscall_map [] = { __EOF sed 's/__NR_//' $syscall_file | awk '/#define/ { syscall = $2; number = $3; printf " { \"%s\", %d },\n", syscall, number }' cat << __EOF { NULL, 0 } }; __EOF kore-4.2.3/misc/linux/000077500000000000000000000000001430066302400145505ustar00rootroot00000000000000kore-4.2.3/misc/linux/aarch64_syscall.h.in000066400000000000000000000170611430066302400203150ustar00rootroot00000000000000#define __NR_io_setup 0 #define __NR_io_destroy 1 #define __NR_io_submit 2 #define __NR_io_cancel 3 #define __NR_io_getevents 4 #define __NR_setxattr 5 #define __NR_lsetxattr 6 #define __NR_fsetxattr 7 #define __NR_getxattr 8 #define __NR_lgetxattr 9 #define __NR_fgetxattr 10 #define __NR_listxattr 11 #define __NR_llistxattr 12 #define __NR_flistxattr 13 #define __NR_removexattr 14 #define __NR_lremovexattr 15 #define __NR_fremovexattr 16 #define __NR_getcwd 17 #define __NR_lookup_dcookie 18 #define __NR_eventfd2 19 #define __NR_epoll_create1 20 #define __NR_epoll_ctl 21 #define __NR_epoll_pwait 22 #define __NR_dup 23 #define __NR_dup3 24 #define __NR_fcntl 25 #define __NR_inotify_init1 26 #define __NR_inotify_add_watch 27 #define __NR_inotify_rm_watch 28 #define __NR_ioctl 29 #define __NR_ioprio_set 30 #define __NR_ioprio_get 31 #define __NR_flock 32 #define __NR_mknodat 33 #define __NR_mkdirat 34 #define __NR_unlinkat 35 #define __NR_symlinkat 36 #define __NR_linkat 37 #define __NR_renameat 38 #define __NR_umount2 39 #define __NR_mount 40 #define __NR_pivot_root 41 #define __NR_nfsservctl 42 #define __NR_statfs 43 #define __NR_fstatfs 44 #define __NR_truncate 45 #define __NR_ftruncate 46 #define __NR_fallocate 47 #define __NR_faccessat 48 #define __NR_chdir 49 #define __NR_fchdir 50 #define __NR_chroot 51 #define __NR_fchmod 52 #define __NR_fchmodat 53 #define __NR_fchownat 54 #define __NR_fchown 55 #define __NR_openat 56 #define __NR_close 57 #define __NR_vhangup 58 #define __NR_pipe2 59 #define __NR_quotactl 60 #define __NR_getdents64 61 #define __NR_lseek 62 #define __NR_read 63 #define __NR_write 64 #define __NR_readv 65 #define __NR_writev 66 #define __NR_pread64 67 #define __NR_pwrite64 68 #define __NR_preadv 69 #define __NR_pwritev 70 #define __NR_sendfile 71 #define __NR_pselect6 72 #define __NR_ppoll 73 #define __NR_signalfd4 74 #define __NR_vmsplice 75 #define __NR_splice 76 #define __NR_tee 77 #define __NR_readlinkat 78 #define __NR_newfstatat 79 #define __NR_fstat 80 #define __NR_sync 81 #define __NR_fsync 82 #define __NR_fdatasync 83 #define __NR_sync_file_range 84 #define __NR_timerfd_create 85 #define __NR_timerfd_settime 86 #define __NR_timerfd_gettime 87 #define __NR_utimensat 88 #define __NR_acct 89 #define __NR_capget 90 #define __NR_capset 91 #define __NR_personality 92 #define __NR_exit 93 #define __NR_exit_group 94 #define __NR_waitid 95 #define __NR_set_tid_address 96 #define __NR_unshare 97 #define __NR_futex 98 #define __NR_set_robust_list 99 #define __NR_get_robust_list 100 #define __NR_nanosleep 101 #define __NR_getitimer 102 #define __NR_setitimer 103 #define __NR_kexec_load 104 #define __NR_init_module 105 #define __NR_delete_module 106 #define __NR_timer_create 107 #define __NR_timer_gettime 108 #define __NR_timer_getoverrun 109 #define __NR_timer_settime 110 #define __NR_timer_delete 111 #define __NR_clock_settime 112 #define __NR_clock_gettime 113 #define __NR_clock_getres 114 #define __NR_clock_nanosleep 115 #define __NR_syslog 116 #define __NR_ptrace 117 #define __NR_sched_setparam 118 #define __NR_sched_setscheduler 119 #define __NR_sched_getscheduler 120 #define __NR_sched_getparam 121 #define __NR_sched_setaffinity 122 #define __NR_sched_getaffinity 123 #define __NR_sched_yield 124 #define __NR_sched_get_priority_max 125 #define __NR_sched_get_priority_min 126 #define __NR_sched_rr_get_interval 127 #define __NR_restart_syscall 128 #define __NR_kill 129 #define __NR_tkill 130 #define __NR_tgkill 131 #define __NR_sigaltstack 132 #define __NR_rt_sigsuspend 133 #define __NR_rt_sigaction 134 #define __NR_rt_sigprocmask 135 #define __NR_rt_sigpending 136 #define __NR_rt_sigtimedwait 137 #define __NR_rt_sigqueueinfo 138 #define __NR_rt_sigreturn 139 #define __NR_setpriority 140 #define __NR_getpriority 141 #define __NR_reboot 142 #define __NR_setregid 143 #define __NR_setgid 144 #define __NR_setreuid 145 #define __NR_setuid 146 #define __NR_setresuid 147 #define __NR_getresuid 148 #define __NR_setresgid 149 #define __NR_getresgid 150 #define __NR_setfsuid 151 #define __NR_setfsgid 152 #define __NR_times 153 #define __NR_setpgid 154 #define __NR_getpgid 155 #define __NR_getsid 156 #define __NR_setsid 157 #define __NR_getgroups 158 #define __NR_setgroups 159 #define __NR_uname 160 #define __NR_sethostname 161 #define __NR_setdomainname 162 #define __NR_getrlimit 163 #define __NR_setrlimit 164 #define __NR_getrusage 165 #define __NR_umask 166 #define __NR_prctl 167 #define __NR_getcpu 168 #define __NR_gettimeofday 169 #define __NR_settimeofday 170 #define __NR_adjtimex 171 #define __NR_getpid 172 #define __NR_getppid 173 #define __NR_getuid 174 #define __NR_geteuid 175 #define __NR_getgid 176 #define __NR_getegid 177 #define __NR_gettid 178 #define __NR_sysinfo 179 #define __NR_mq_open 180 #define __NR_mq_unlink 181 #define __NR_mq_timedsend 182 #define __NR_mq_timedreceive 183 #define __NR_mq_notify 184 #define __NR_mq_getsetattr 185 #define __NR_msgget 186 #define __NR_msgctl 187 #define __NR_msgrcv 188 #define __NR_msgsnd 189 #define __NR_semget 190 #define __NR_semctl 191 #define __NR_semtimedop 192 #define __NR_semop 193 #define __NR_shmget 194 #define __NR_shmctl 195 #define __NR_shmat 196 #define __NR_shmdt 197 #define __NR_socket 198 #define __NR_socketpair 199 #define __NR_bind 200 #define __NR_listen 201 #define __NR_accept 202 #define __NR_connect 203 #define __NR_getsockname 204 #define __NR_getpeername 205 #define __NR_sendto 206 #define __NR_recvfrom 207 #define __NR_setsockopt 208 #define __NR_getsockopt 209 #define __NR_shutdown 210 #define __NR_sendmsg 211 #define __NR_recvmsg 212 #define __NR_readahead 213 #define __NR_brk 214 #define __NR_munmap 215 #define __NR_mremap 216 #define __NR_add_key 217 #define __NR_request_key 218 #define __NR_keyctl 219 #define __NR_clone 220 #define __NR_execve 221 #define __NR_mmap 222 #define __NR_fadvise64 223 #define __NR_swapon 224 #define __NR_swapoff 225 #define __NR_mprotect 226 #define __NR_msync 227 #define __NR_mlock 228 #define __NR_munlock 229 #define __NR_mlockall 230 #define __NR_munlockall 231 #define __NR_mincore 232 #define __NR_madvise 233 #define __NR_remap_file_pages 234 #define __NR_mbind 235 #define __NR_get_mempolicy 236 #define __NR_set_mempolicy 237 #define __NR_migrate_pages 238 #define __NR_move_pages 239 #define __NR_rt_tgsigqueueinfo 240 #define __NR_perf_event_open 241 #define __NR_accept4 242 #define __NR_recvmmsg 243 #define __NR_wait4 260 #define __NR_prlimit64 261 #define __NR_fanotify_init 262 #define __NR_fanotify_mark 263 #define __NR_name_to_handle_at 264 #define __NR_open_by_handle_at 265 #define __NR_clock_adjtime 266 #define __NR_syncfs 267 #define __NR_setns 268 #define __NR_sendmmsg 269 #define __NR_process_vm_readv 270 #define __NR_process_vm_writev 271 #define __NR_kcmp 272 #define __NR_finit_module 273 #define __NR_sched_setattr 274 #define __NR_sched_getattr 275 #define __NR_renameat2 276 #define __NR_seccomp 277 #define __NR_getrandom 278 #define __NR_memfd_create 279 #define __NR_bpf 280 #define __NR_execveat 281 #define __NR_userfaultfd 282 #define __NR_membarrier 283 #define __NR_mlock2 284 #define __NR_copy_file_range 285 #define __NR_preadv2 286 #define __NR_pwritev2 287 #define __NR_pkey_mprotect 288 #define __NR_pkey_alloc 289 #define __NR_pkey_free 290 #define __NR_statx 291 #define __NR_io_pgetevents 292 #define __NR_rseq 293 #define __NR_kexec_file_load 294 #define __NR_pidfd_send_signal 424 #define __NR_io_uring_setup 425 #define __NR_io_uring_enter 426 #define __NR_io_uring_register 427 #define __NR_open_tree 428 #define __NR_move_mount 429 #define __NR_fsopen 430 #define __NR_fsconfig 431 #define __NR_fsmount 432 #define __NR_fspick 433 kore-4.2.3/misc/linux/arm_syscall.h.in000066400000000000000000000250111430066302400176360ustar00rootroot00000000000000#define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_creat 8 #define __NR_link 9 #define __NR_unlink 10 #define __NR_execve 11 #define __NR_chdir 12 #define __NR_mknod 14 #define __NR_chmod 15 #define __NR_lchown 16 #define __NR_lseek 19 #define __NR_getpid 20 #define __NR_mount 21 #define __NR_setuid 23 #define __NR_getuid 24 #define __NR_ptrace 26 #define __NR_pause 29 #define __NR_access 33 #define __NR_nice 34 #define __NR_sync 36 #define __NR_kill 37 #define __NR_rename 38 #define __NR_mkdir 39 #define __NR_rmdir 40 #define __NR_dup 41 #define __NR_pipe 42 #define __NR_times 43 #define __NR_brk 45 #define __NR_setgid 46 #define __NR_getgid 47 #define __NR_geteuid 49 #define __NR_getegid 50 #define __NR_acct 51 #define __NR_umount2 52 #define __NR_ioctl 54 #define __NR_fcntl 55 #define __NR_setpgid 57 #define __NR_umask 60 #define __NR_chroot 61 #define __NR_ustat 62 #define __NR_dup2 63 #define __NR_getppid 64 #define __NR_getpgrp 65 #define __NR_setsid 66 #define __NR_sigaction 67 #define __NR_setreuid 70 #define __NR_setregid 71 #define __NR_sigsuspend 72 #define __NR_sigpending 73 #define __NR_sethostname 74 #define __NR_setrlimit 75 #define __NR_getrusage 77 #define __NR_gettimeofday 78 #define __NR_settimeofday 79 #define __NR_getgroups 80 #define __NR_setgroups 81 #define __NR_symlink 83 #define __NR_readlink 85 #define __NR_uselib 86 #define __NR_swapon 87 #define __NR_reboot 88 #define __NR_munmap 91 #define __NR_truncate 92 #define __NR_ftruncate 93 #define __NR_fchmod 94 #define __NR_fchown 95 #define __NR_getpriority 96 #define __NR_setpriority 97 #define __NR_statfs 99 #define __NR_fstatfs 100 #define __NR_syslog 103 #define __NR_setitimer 104 #define __NR_getitimer 105 #define __NR_stat 106 #define __NR_lstat 107 #define __NR_fstat 108 #define __NR_vhangup 111 #define __NR_wait4 114 #define __NR_swapoff 115 #define __NR_sysinfo 116 #define __NR_fsync 118 #define __NR_sigreturn 119 #define __NR_clone 120 #define __NR_setdomainname 121 #define __NR_uname 122 #define __NR_adjtimex 124 #define __NR_mprotect 125 #define __NR_sigprocmask 126 #define __NR_init_module 128 #define __NR_delete_module 129 #define __NR_quotactl 131 #define __NR_getpgid 132 #define __NR_fchdir 133 #define __NR_bdflush 134 #define __NR_sysfs 135 #define __NR_personality 136 #define __NR_setfsuid 138 #define __NR_setfsgid 139 #define __NR__llseek 140 #define __NR_getdents 141 #define __NR__newselect 142 #define __NR_flock 143 #define __NR_msync 144 #define __NR_readv 145 #define __NR_writev 146 #define __NR_getsid 147 #define __NR_fdatasync 148 #define __NR__sysctl 149 #define __NR_mlock 150 #define __NR_munlock 151 #define __NR_mlockall 152 #define __NR_munlockall 153 #define __NR_sched_setparam 154 #define __NR_sched_getparam 155 #define __NR_sched_setscheduler 156 #define __NR_sched_getscheduler 157 #define __NR_sched_yield 158 #define __NR_sched_get_priority_max 159 #define __NR_sched_get_priority_min 160 #define __NR_sched_rr_get_interval 161 #define __NR_nanosleep 162 #define __NR_mremap 163 #define __NR_setresuid 164 #define __NR_getresuid 165 #define __NR_poll 168 #define __NR_nfsservctl 169 #define __NR_setresgid 170 #define __NR_getresgid 171 #define __NR_prctl 172 #define __NR_rt_sigreturn 173 #define __NR_rt_sigaction 174 #define __NR_rt_sigprocmask 175 #define __NR_rt_sigpending 176 #define __NR_rt_sigtimedwait 177 #define __NR_rt_sigqueueinfo 178 #define __NR_rt_sigsuspend 179 #define __NR_pread64 180 #define __NR_pwrite64 181 #define __NR_chown 182 #define __NR_getcwd 183 #define __NR_capget 184 #define __NR_capset 185 #define __NR_sigaltstack 186 #define __NR_sendfile 187 #define __NR_vfork 190 #define __NR_ugetrlimit 191 #define __NR_mmap2 192 #define __NR_truncate64 193 #define __NR_ftruncate64 194 #define __NR_stat64 195 #define __NR_lstat64 196 #define __NR_fstat64 197 #define __NR_lchown32 198 #define __NR_getuid32 199 #define __NR_getgid32 200 #define __NR_geteuid32 201 #define __NR_getegid32 202 #define __NR_setreuid32 203 #define __NR_setregid32 204 #define __NR_getgroups32 205 #define __NR_setgroups32 206 #define __NR_fchown32 207 #define __NR_setresuid32 208 #define __NR_getresuid32 209 #define __NR_setresgid32 210 #define __NR_getresgid32 211 #define __NR_chown32 212 #define __NR_setuid32 213 #define __NR_setgid32 214 #define __NR_setfsuid32 215 #define __NR_setfsgid32 216 #define __NR_getdents64 217 #define __NR_pivot_root 218 #define __NR_mincore 219 #define __NR_madvise 220 #define __NR_fcntl64 221 #define __NR_gettid 224 #define __NR_readahead 225 #define __NR_setxattr 226 #define __NR_lsetxattr 227 #define __NR_fsetxattr 228 #define __NR_getxattr 229 #define __NR_lgetxattr 230 #define __NR_fgetxattr 231 #define __NR_listxattr 232 #define __NR_llistxattr 233 #define __NR_flistxattr 234 #define __NR_removexattr 235 #define __NR_lremovexattr 236 #define __NR_fremovexattr 237 #define __NR_tkill 238 #define __NR_sendfile64 239 #define __NR_futex 240 #define __NR_sched_setaffinity 241 #define __NR_sched_getaffinity 242 #define __NR_io_setup 243 #define __NR_io_destroy 244 #define __NR_io_getevents 245 #define __NR_io_submit 246 #define __NR_io_cancel 247 #define __NR_exit_group 248 #define __NR_lookup_dcookie 249 #define __NR_epoll_create 250 #define __NR_epoll_ctl 251 #define __NR_epoll_wait 252 #define __NR_remap_file_pages 253 #define __NR_set_tid_address 256 #define __NR_timer_create 257 #define __NR_timer_settime 258 #define __NR_timer_gettime 259 #define __NR_timer_getoverrun 260 #define __NR_timer_delete 261 #define __NR_clock_settime 262 #define __NR_clock_gettime 263 #define __NR_clock_getres 264 #define __NR_clock_nanosleep 265 #define __NR_statfs64 266 #define __NR_fstatfs64 267 #define __NR_tgkill 268 #define __NR_utimes 269 #define __NR_fadvise64_64 270 #define __NR_arm_fadvise64_64 270 #define __NR_pciconfig_iobase 271 #define __NR_pciconfig_read 272 #define __NR_pciconfig_write 273 #define __NR_mq_open 274 #define __NR_mq_unlink 275 #define __NR_mq_timedsend 276 #define __NR_mq_timedreceive 277 #define __NR_mq_notify 278 #define __NR_mq_getsetattr 279 #define __NR_waitid 280 #define __NR_socket 281 #define __NR_bind 282 #define __NR_connect 283 #define __NR_listen 284 #define __NR_accept 285 #define __NR_getsockname 286 #define __NR_getpeername 287 #define __NR_socketpair 288 #define __NR_send 289 #define __NR_sendto 290 #define __NR_recv 291 #define __NR_recvfrom 292 #define __NR_shutdown 293 #define __NR_setsockopt 294 #define __NR_getsockopt 295 #define __NR_sendmsg 296 #define __NR_recvmsg 297 #define __NR_semop 298 #define __NR_semget 299 #define __NR_semctl 300 #define __NR_msgsnd 301 #define __NR_msgrcv 302 #define __NR_msgget 303 #define __NR_msgctl 304 #define __NR_shmat 305 #define __NR_shmdt 306 #define __NR_shmget 307 #define __NR_shmctl 308 #define __NR_add_key 309 #define __NR_request_key 310 #define __NR_keyctl 311 #define __NR_semtimedop 312 #define __NR_vserver 313 #define __NR_ioprio_set 314 #define __NR_ioprio_get 315 #define __NR_inotify_init 316 #define __NR_inotify_add_watch 317 #define __NR_inotify_rm_watch 318 #define __NR_mbind 319 #define __NR_get_mempolicy 320 #define __NR_set_mempolicy 321 #define __NR_openat 322 #define __NR_mkdirat 323 #define __NR_mknodat 324 #define __NR_fchownat 325 #define __NR_futimesat 326 #define __NR_fstatat64 327 #define __NR_unlinkat 328 #define __NR_renameat 329 #define __NR_linkat 330 #define __NR_symlinkat 331 #define __NR_readlinkat 332 #define __NR_fchmodat 333 #define __NR_faccessat 334 #define __NR_pselect6 335 #define __NR_ppoll 336 #define __NR_unshare 337 #define __NR_set_robust_list 338 #define __NR_get_robust_list 339 #define __NR_splice 340 #define __NR_sync_file_range2 341 #define __NR_arm_sync_file_range 341 #define __NR_tee 342 #define __NR_vmsplice 343 #define __NR_move_pages 344 #define __NR_getcpu 345 #define __NR_epoll_pwait 346 #define __NR_kexec_load 347 #define __NR_utimensat 348 #define __NR_signalfd 349 #define __NR_timerfd_create 350 #define __NR_eventfd 351 #define __NR_fallocate 352 #define __NR_timerfd_settime 353 #define __NR_timerfd_gettime 354 #define __NR_signalfd4 355 #define __NR_eventfd2 356 #define __NR_epoll_create1 357 #define __NR_dup3 358 #define __NR_pipe2 359 #define __NR_inotify_init1 360 #define __NR_preadv 361 #define __NR_pwritev 362 #define __NR_rt_tgsigqueueinfo 363 #define __NR_perf_event_open 364 #define __NR_recvmmsg 365 #define __NR_accept4 366 #define __NR_fanotify_init 367 #define __NR_fanotify_mark 368 #define __NR_prlimit64 369 #define __NR_name_to_handle_at 370 #define __NR_open_by_handle_at 371 #define __NR_clock_adjtime 372 #define __NR_syncfs 373 #define __NR_sendmmsg 374 #define __NR_setns 375 #define __NR_process_vm_readv 376 #define __NR_process_vm_writev 377 #define __NR_kcmp 378 #define __NR_finit_module 379 #define __NR_sched_setattr 380 #define __NR_sched_getattr 381 #define __NR_renameat2 382 #define __NR_seccomp 383 #define __NR_getrandom 384 #define __NR_memfd_create 385 #define __NR_bpf 386 #define __NR_execveat 387 #define __NR_userfaultfd 388 #define __NR_membarrier 389 #define __NR_mlock2 390 #define __NR_copy_file_range 391 #define __NR_preadv2 392 #define __NR_pwritev2 393 #define __NR_pkey_mprotect 394 #define __NR_pkey_alloc 395 #define __NR_pkey_free 396 #define __NR_statx 397 #define __NR_rseq 398 #define __NR_io_pgetevents 399 #define __NR_migrate_pages 400 #define __NR_kexec_file_load 401 #define __NR_clock_gettime64 403 #define __NR_clock_settime64 404 #define __NR_clock_adjtime64 405 #define __NR_clock_getres_time64 406 #define __NR_clock_nanosleep_time64 407 #define __NR_timer_gettime64 408 #define __NR_timer_settime64 409 #define __NR_timerfd_gettime64 410 #define __NR_timerfd_settime64 411 #define __NR_utimensat_time64 412 #define __NR_pselect6_time64 413 #define __NR_ppoll_time64 414 #define __NR_io_pgetevents_time64 416 #define __NR_recvmmsg_time64 417 #define __NR_mq_timedsend_time64 418 #define __NR_mq_timedreceive_time64 419 #define __NR_semtimedop_time64 420 #define __NR_rt_sigtimedwait_time64 421 #define __NR_futex_time64 422 #define __NR_sched_rr_get_interval_time64 423 #define __NR_pidfd_send_signal 424 #define __NR_io_uring_setup 425 #define __NR_io_uring_enter 426 #define __NR_io_uring_register 427 #define __NR_open_tree 428 #define __NR_move_mount 429 #define __NR_fsopen 430 #define __NR_fsconfig 431 #define __NR_fsmount 432 #define __NR_fspick 433 #define __ARM_NR_breakpoint 0x0f0001 #define __ARM_NR_cacheflush 0x0f0002 #define __ARM_NR_usr26 0x0f0003 #define __ARM_NR_usr32 0x0f0004 #define __ARM_NR_set_tls 0x0f0005 #define __ARM_NR_get_tls 0x0f0006 kore-4.2.3/misc/linux/x86_64_syscall.h.in000066400000000000000000000234271430066302400200260ustar00rootroot00000000000000#define __NR_read 0 #define __NR_write 1 #define __NR_open 2 #define __NR_close 3 #define __NR_stat 4 #define __NR_fstat 5 #define __NR_lstat 6 #define __NR_poll 7 #define __NR_lseek 8 #define __NR_mmap 9 #define __NR_mprotect 10 #define __NR_munmap 11 #define __NR_brk 12 #define __NR_rt_sigaction 13 #define __NR_rt_sigprocmask 14 #define __NR_rt_sigreturn 15 #define __NR_ioctl 16 #define __NR_pread64 17 #define __NR_pwrite64 18 #define __NR_readv 19 #define __NR_writev 20 #define __NR_access 21 #define __NR_pipe 22 #define __NR_select 23 #define __NR_sched_yield 24 #define __NR_mremap 25 #define __NR_msync 26 #define __NR_mincore 27 #define __NR_madvise 28 #define __NR_shmget 29 #define __NR_shmat 30 #define __NR_shmctl 31 #define __NR_dup 32 #define __NR_dup2 33 #define __NR_pause 34 #define __NR_nanosleep 35 #define __NR_getitimer 36 #define __NR_alarm 37 #define __NR_setitimer 38 #define __NR_getpid 39 #define __NR_sendfile 40 #define __NR_socket 41 #define __NR_connect 42 #define __NR_accept 43 #define __NR_sendto 44 #define __NR_recvfrom 45 #define __NR_sendmsg 46 #define __NR_recvmsg 47 #define __NR_shutdown 48 #define __NR_bind 49 #define __NR_listen 50 #define __NR_getsockname 51 #define __NR_getpeername 52 #define __NR_socketpair 53 #define __NR_setsockopt 54 #define __NR_getsockopt 55 #define __NR_clone 56 #define __NR_fork 57 #define __NR_vfork 58 #define __NR_execve 59 #define __NR_exit 60 #define __NR_wait4 61 #define __NR_kill 62 #define __NR_uname 63 #define __NR_semget 64 #define __NR_semop 65 #define __NR_semctl 66 #define __NR_shmdt 67 #define __NR_msgget 68 #define __NR_msgsnd 69 #define __NR_msgrcv 70 #define __NR_msgctl 71 #define __NR_fcntl 72 #define __NR_flock 73 #define __NR_fsync 74 #define __NR_fdatasync 75 #define __NR_truncate 76 #define __NR_ftruncate 77 #define __NR_getdents 78 #define __NR_getcwd 79 #define __NR_chdir 80 #define __NR_fchdir 81 #define __NR_rename 82 #define __NR_mkdir 83 #define __NR_rmdir 84 #define __NR_creat 85 #define __NR_link 86 #define __NR_unlink 87 #define __NR_symlink 88 #define __NR_readlink 89 #define __NR_chmod 90 #define __NR_fchmod 91 #define __NR_chown 92 #define __NR_fchown 93 #define __NR_lchown 94 #define __NR_umask 95 #define __NR_gettimeofday 96 #define __NR_getrlimit 97 #define __NR_getrusage 98 #define __NR_sysinfo 99 #define __NR_times 100 #define __NR_ptrace 101 #define __NR_getuid 102 #define __NR_syslog 103 #define __NR_getgid 104 #define __NR_setuid 105 #define __NR_setgid 106 #define __NR_geteuid 107 #define __NR_getegid 108 #define __NR_setpgid 109 #define __NR_getppid 110 #define __NR_getpgrp 111 #define __NR_setsid 112 #define __NR_setreuid 113 #define __NR_setregid 114 #define __NR_getgroups 115 #define __NR_setgroups 116 #define __NR_setresuid 117 #define __NR_getresuid 118 #define __NR_setresgid 119 #define __NR_getresgid 120 #define __NR_getpgid 121 #define __NR_setfsuid 122 #define __NR_setfsgid 123 #define __NR_getsid 124 #define __NR_capget 125 #define __NR_capset 126 #define __NR_rt_sigpending 127 #define __NR_rt_sigtimedwait 128 #define __NR_rt_sigqueueinfo 129 #define __NR_rt_sigsuspend 130 #define __NR_sigaltstack 131 #define __NR_utime 132 #define __NR_mknod 133 #define __NR_uselib 134 #define __NR_personality 135 #define __NR_ustat 136 #define __NR_statfs 137 #define __NR_fstatfs 138 #define __NR_sysfs 139 #define __NR_getpriority 140 #define __NR_setpriority 141 #define __NR_sched_setparam 142 #define __NR_sched_getparam 143 #define __NR_sched_setscheduler 144 #define __NR_sched_getscheduler 145 #define __NR_sched_get_priority_max 146 #define __NR_sched_get_priority_min 147 #define __NR_sched_rr_get_interval 148 #define __NR_mlock 149 #define __NR_munlock 150 #define __NR_mlockall 151 #define __NR_munlockall 152 #define __NR_vhangup 153 #define __NR_modify_ldt 154 #define __NR_pivot_root 155 #define __NR__sysctl 156 #define __NR_prctl 157 #define __NR_arch_prctl 158 #define __NR_adjtimex 159 #define __NR_setrlimit 160 #define __NR_chroot 161 #define __NR_sync 162 #define __NR_acct 163 #define __NR_settimeofday 164 #define __NR_mount 165 #define __NR_umount2 166 #define __NR_swapon 167 #define __NR_swapoff 168 #define __NR_reboot 169 #define __NR_sethostname 170 #define __NR_setdomainname 171 #define __NR_iopl 172 #define __NR_ioperm 173 #define __NR_create_module 174 #define __NR_init_module 175 #define __NR_delete_module 176 #define __NR_get_kernel_syms 177 #define __NR_query_module 178 #define __NR_quotactl 179 #define __NR_nfsservctl 180 #define __NR_getpmsg 181 #define __NR_putpmsg 182 #define __NR_afs_syscall 183 #define __NR_tuxcall 184 #define __NR_security 185 #define __NR_gettid 186 #define __NR_readahead 187 #define __NR_setxattr 188 #define __NR_lsetxattr 189 #define __NR_fsetxattr 190 #define __NR_getxattr 191 #define __NR_lgetxattr 192 #define __NR_fgetxattr 193 #define __NR_listxattr 194 #define __NR_llistxattr 195 #define __NR_flistxattr 196 #define __NR_removexattr 197 #define __NR_lremovexattr 198 #define __NR_fremovexattr 199 #define __NR_tkill 200 #define __NR_time 201 #define __NR_futex 202 #define __NR_sched_setaffinity 203 #define __NR_sched_getaffinity 204 #define __NR_set_thread_area 205 #define __NR_io_setup 206 #define __NR_io_destroy 207 #define __NR_io_getevents 208 #define __NR_io_submit 209 #define __NR_io_cancel 210 #define __NR_get_thread_area 211 #define __NR_lookup_dcookie 212 #define __NR_epoll_create 213 #define __NR_epoll_ctl_old 214 #define __NR_epoll_wait_old 215 #define __NR_remap_file_pages 216 #define __NR_getdents64 217 #define __NR_set_tid_address 218 #define __NR_restart_syscall 219 #define __NR_semtimedop 220 #define __NR_fadvise64 221 #define __NR_timer_create 222 #define __NR_timer_settime 223 #define __NR_timer_gettime 224 #define __NR_timer_getoverrun 225 #define __NR_timer_delete 226 #define __NR_clock_settime 227 #define __NR_clock_gettime 228 #define __NR_clock_getres 229 #define __NR_clock_nanosleep 230 #define __NR_exit_group 231 #define __NR_epoll_wait 232 #define __NR_epoll_ctl 233 #define __NR_tgkill 234 #define __NR_utimes 235 #define __NR_vserver 236 #define __NR_mbind 237 #define __NR_set_mempolicy 238 #define __NR_get_mempolicy 239 #define __NR_mq_open 240 #define __NR_mq_unlink 241 #define __NR_mq_timedsend 242 #define __NR_mq_timedreceive 243 #define __NR_mq_notify 244 #define __NR_mq_getsetattr 245 #define __NR_kexec_load 246 #define __NR_waitid 247 #define __NR_add_key 248 #define __NR_request_key 249 #define __NR_keyctl 250 #define __NR_ioprio_set 251 #define __NR_ioprio_get 252 #define __NR_inotify_init 253 #define __NR_inotify_add_watch 254 #define __NR_inotify_rm_watch 255 #define __NR_migrate_pages 256 #define __NR_openat 257 #define __NR_mkdirat 258 #define __NR_mknodat 259 #define __NR_fchownat 260 #define __NR_futimesat 261 #define __NR_newfstatat 262 #define __NR_unlinkat 263 #define __NR_renameat 264 #define __NR_linkat 265 #define __NR_symlinkat 266 #define __NR_readlinkat 267 #define __NR_fchmodat 268 #define __NR_faccessat 269 #define __NR_pselect6 270 #define __NR_ppoll 271 #define __NR_unshare 272 #define __NR_set_robust_list 273 #define __NR_get_robust_list 274 #define __NR_splice 275 #define __NR_tee 276 #define __NR_sync_file_range 277 #define __NR_vmsplice 278 #define __NR_move_pages 279 #define __NR_utimensat 280 #define __NR_epoll_pwait 281 #define __NR_signalfd 282 #define __NR_timerfd_create 283 #define __NR_eventfd 284 #define __NR_fallocate 285 #define __NR_timerfd_settime 286 #define __NR_timerfd_gettime 287 #define __NR_accept4 288 #define __NR_signalfd4 289 #define __NR_eventfd2 290 #define __NR_epoll_create1 291 #define __NR_dup3 292 #define __NR_pipe2 293 #define __NR_inotify_init1 294 #define __NR_preadv 295 #define __NR_pwritev 296 #define __NR_rt_tgsigqueueinfo 297 #define __NR_perf_event_open 298 #define __NR_recvmmsg 299 #define __NR_fanotify_init 300 #define __NR_fanotify_mark 301 #define __NR_prlimit64 302 #define __NR_name_to_handle_at 303 #define __NR_open_by_handle_at 304 #define __NR_clock_adjtime 305 #define __NR_syncfs 306 #define __NR_sendmmsg 307 #define __NR_setns 308 #define __NR_getcpu 309 #define __NR_process_vm_readv 310 #define __NR_process_vm_writev 311 #define __NR_kcmp 312 #define __NR_finit_module 313 #define __NR_sched_setattr 314 #define __NR_sched_getattr 315 #define __NR_renameat2 316 #define __NR_seccomp 317 #define __NR_getrandom 318 #define __NR_memfd_create 319 #define __NR_kexec_file_load 320 #define __NR_bpf 321 #define __NR_execveat 322 #define __NR_userfaultfd 323 #define __NR_membarrier 324 #define __NR_mlock2 325 #define __NR_copy_file_range 326 #define __NR_preadv2 327 #define __NR_pwritev2 328 #define __NR_pkey_mprotect 329 #define __NR_pkey_alloc 330 #define __NR_pkey_free 331 #define __NR_statx 332 #define __NR_io_pgetevents 333 #define __NR_rseq 334 #define __NR_pidfd_send_signal 424 #define __NR_io_uring_setup 425 #define __NR_io_uring_enter 426 #define __NR_io_uring_register 427 #define __NR_open_tree 428 #define __NR_move_mount 429 #define __NR_fsopen 430 #define __NR_fsconfig 431 #define __NR_fsmount 432 #define __NR_fspick 433 kore-4.2.3/misc/python3-config.sh000077500000000000000000000003251430066302400166170ustar00rootroot00000000000000#!/bin/sh if [ $# -ne 1 ]; then echo "python3-flags.sh [--ldflags|--includes]" exit 1 fi python3-config $1 --embed > /dev/null 2>&1 if [ $? -eq 0 ]; then python3-config $1 --embed else python3-config $1 fi kore-4.2.3/share/000077500000000000000000000000001430066302400135605ustar00rootroot00000000000000kore-4.2.3/share/man/000077500000000000000000000000001430066302400143335ustar00rootroot00000000000000kore-4.2.3/share/man/kodev.1000066400000000000000000000111771430066302400155340ustar00rootroot00000000000000.TH KODEV 1 .SH NAME kodev \- Kore project management tool .SH SYNOPSIS .BR kodev [\fIOPTION\fR] ... .SH DESCRIPTION This documentation describes the application management tools for building and running a Kore project. For information regarding the technical interface and C bindings, look to .BR kore(3) where these functions and structures are described. .BR Kore projects may be managed using the following OPTIONS; .BR create .RS Create a new application skeleton with the name that is passed to it. This will create a new directory with all the files required to begin hacking. See the \fBGENERATED FILES\fR section for more information. .RE .BR build .RS Build the application. See the \fBBUILDING\fR section for more information. .RE .BR run .RS Start the application in the foreground. See the \fBRUNNING\fR section for more information. .RE .BR reload .RS Reload the application. This is a shortcut to sending SIGHUP to the parent process (see kore_pid). .RE .BR info .RS Show information about the application configuration. Namely; active flavor, output type, Kore features, Kore source and Kore binary. .RE .BR clean .RS Cleanup the build files. .RE .BR flavor .RS Switch between build flavors with the argument being the new flavor. .RE .BR help .RS Show the help synopsis. .RE .SH GENERATED FILES Executing the .BR create command will generate several new files under the directory matching the application name specified. These files are: .RS .BR conf/build.conf .RS The build configuration. .RE .BR conf/app.conf .RS The Kore application configuration. .RE .BR src/app.c .RS The initial placeholder source code. .RE .BR cert/server.pem .RS The self-signed auto-generated x509 certificate. .RE .BR cert/key.pem .RS The key matching the self-signed x509 certificate. .RE .BR dh2048.pem .RS The 2048-bit DH parameters used by TLS. .RE .RE Those files are: .RS .BR kore.conf .RS The Kore application configuration. .RE .RE .RS .BR handlers.py .RS The initial placeholder python page handler. .RE .RE .RS .BR __init__.py .RS The python initialization code. Sets up the kore listener, etc. .RE .RE .RS .BR cert/server.pem .RS The self-signed auto-generated x509 certificate. .RE .RE .RS .BR cert/key.pem .RS The key matching the self-signed x509 certificate. .RE .RE .RS .BR dh2048.pem .RS The 2048-bit DH parameters used by TLS. .RE .RE .SH BUILDING Executing the .BR build command will build your application. How this happens is instructed by the .BR conf/build.conf configuration file. This file supports the following directives: .RS .BR single_binary [yes|no] .RS If set to \fByes\fR the build system will produce a single binary containing both your application code and the Kore code allowing you to distribute your application more easily. If set to \fBno\fR the build system will produce a standard dynamically linked library that will be loaded into Kore at runtime. .RE .RE .RS .BR kore_source [path] .RS Must be set to point to the a Kore source code directory. Used only if .BR single_binary option is set to \fByes\fR. .BR Example: kore_source=/home/joris/src/kore .RE .RE .RE .RS .BR kore_flavor [build options] .RS Defines the build arguments for building Kore. Used only if .BR single_binary option is set to \fByes\fR. .BR Example: kore_flavor=NOTLS=1 .RE .RE .RS .BR cflags .RS Standard .BR CFLAGS used when compiling the application source code. .RE .RE .RS .BR ldflags .RS Standard .BR LDFLAGS used when linking the application source code. .RE .RE Note that the .BR build command obeys the environment variables .BR CC and .BR CXX .SH RUNNING Executing the .BR run command will start your application in the foreground. What binary it executes depends on whether or not the .BR single_binary flag was set in build configuration. If the .BR single_binary flag was enabled the .BR run command will execute the binary produced by the build system. If the .BR single_binary flag was not enabled the .BR run command will execute the .BR $PREFIX/bin/kore binary. In both cases the .BR run command will pass the \fB\-fnr\fR command line options to the binary. .RE .SH EXAMPLES Changing flavor of the build; .RS $ kodev flavor osx .RE Building your application; .RS $ kodev build .RE .SH REPORTING BUGS, CONTRIBUTING && MORE If you run into any bugs, have suggestions or patches, please contact me at .BR More information can be found at .BR .SH AUTHOR .BR Kore developed by Joris Vink .BR Manpage authored by Guy Nankivell .BR .SH LICENCE Usage of this software is provided under the .BR ISC license which may be found, with the source, at .BR kore-4.2.3/src/000077500000000000000000000000001430066302400132455ustar00rootroot00000000000000kore-4.2.3/src/accesslog.c000066400000000000000000000172061430066302400153620ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "kore.h" #include "http.h" /* * The worker will write accesslogs to its worker data structure which is * held in shared memory. * * Each accesslog is prefixed with the internal domain ID (2 bytes) and * the length of the log entry (2 bytes) (packed in kore_alog_header). * * The parent will every 10ms fetch the produced accesslogs from the workers * and copy them to its own log buffer. Once this log buffer becomes full * or 1 second has passed the parent will parse the logs and append them * to the correct domain logbuffer which is eventually flushed to disk. */ #define LOGBUF_SIZE (KORE_ACCESSLOG_BUFLEN * worker_count) #define DOMAIN_LOGBUF_LEN (1024 * 1024) #define LOG_ENTRY_MINSIZE_GUESS 90 static void accesslog_lock(struct kore_worker *); static void accesslog_unlock(struct kore_worker *); static void accesslog_flush_cb(struct kore_domain *); static void accesslog_flush(struct kore_domain *, u_int64_t, int); static u_int64_t time_cache = 0; static char tbuf[128] = { '\0' }; static struct kore_buf *logbuf = NULL; void kore_accesslog_worker_init(void) { kore_domain_closelogs(); } void kore_accesslog(struct http_request *req) { struct timespec ts; struct tm *tm; u_int64_t now; struct kore_alog_header *hdr; size_t avail; time_t curtime; int len, attempts; char addr[INET6_ADDRSTRLEN], *cn_value; const char *ptr, *method, *http_version, *cn, *referer; switch (req->method) { case HTTP_METHOD_GET: method = "GET"; break; case HTTP_METHOD_POST: method = "POST"; break; case HTTP_METHOD_PUT: method = "PUT"; break; case HTTP_METHOD_DELETE: method = "DELETE"; break; case HTTP_METHOD_HEAD: method = "HEAD"; break; case HTTP_METHOD_PATCH: method = "PATCH"; break; default: method = "UNKNOWN"; break; } if (req->flags & HTTP_VERSION_1_0) http_version = "HTTP/1.0"; else http_version = "HTTP/1.1"; if (req->referer != NULL) referer = req->referer; else referer = "-"; if (req->agent == NULL) req->agent = "-"; cn = "-"; cn_value = NULL; if (req->owner->tls_cert != NULL) { if (kore_x509_subject_name(req->owner, &cn_value, KORE_X509_COMMON_NAME_ONLY)) cn = cn_value; } switch (req->owner->family) { case AF_INET: ptr = inet_ntop(req->owner->family, &(req->owner->addr.ipv4.sin_addr), addr, sizeof(addr)); break; case AF_INET6: ptr = inet_ntop(req->owner->family, &(req->owner->addr.ipv6.sin6_addr), addr, sizeof(addr)); break; case AF_UNIX: ptr = NULL; break; default: fatal("unknown family %d", req->owner->family); } if (ptr == NULL) { addr[0] = '-'; addr[1] = '\0'; } now = kore_time_ms(); if ((now - time_cache) >= 1000) { time(&curtime); tm = localtime(&curtime); (void)strftime(tbuf, sizeof(tbuf), "%d/%b/%Y:%H:%M:%S %z", tm); time_cache = now; } attempts = 0; ts.tv_sec = 0; ts.tv_nsec = 1000000; for (;;) { if (attempts++ > 1000) { if (getppid() == 1) { if (kill(worker->pid, SIGQUIT) == -1) fatal("failed to shutdown"); return; } attempts = 0; } accesslog_lock(worker); avail = KORE_ACCESSLOG_BUFLEN - worker->lb.offset; if (avail < sizeof(*hdr) + LOG_ENTRY_MINSIZE_GUESS) { accesslog_unlock(worker); nanosleep(&ts, NULL); continue; } hdr = (struct kore_alog_header *) (worker->lb.buf + worker->lb.offset); worker->lb.offset += sizeof(*hdr); len = snprintf(worker->lb.buf + worker->lb.offset, avail, "%s - %s [%s] \"%s %s %s\" %d %" PRIu64" \"%s\" \"%s\"\n", addr, cn, tbuf, method, req->path, http_version, req->status, req->content_length, referer, req->agent); if (len == -1) fatal("failed to create log entry"); if ((size_t)len >= avail) { worker->lb.offset -= sizeof(*hdr); accesslog_unlock(worker); nanosleep(&ts, NULL); continue; } if ((size_t)len > USHRT_MAX) { kore_log(LOG_WARNING, "log entry length exceeds limit (%d)", len); worker->lb.offset -= sizeof(*hdr); break; } hdr->loglen = len; hdr->domain = req->rt->dom->id; worker->lb.offset += (size_t)len; break; } kore_free(cn_value); accesslog_unlock(worker); } void kore_accesslog_gather(void *arg, u_int64_t now, int force) { int id; struct kore_worker *kw; struct kore_alog_header *hdr; struct kore_domain *dom; size_t off, remain; if (logbuf == NULL) logbuf = kore_buf_alloc(LOGBUF_SIZE); for (id = KORE_WORKER_BASE; id < worker_count; id++) { kw = kore_worker_data(id); accesslog_lock(kw); if (force || kw->lb.offset >= KORE_ACCESSLOG_SYNC) { kore_buf_append(logbuf, kw->lb.buf, kw->lb.offset); kw->lb.offset = 0; } accesslog_unlock(kw); } if (force || logbuf->offset >= LOGBUF_SIZE) { off = 0; remain = logbuf->offset; while (remain > 0) { if (remain < sizeof(*hdr)) { kore_log(LOG_ERR, "invalid log buffer: (%zu remain)", remain); break; } hdr = (struct kore_alog_header *)(logbuf->data + off); off += sizeof(*hdr); remain -= sizeof(*hdr); if (hdr->loglen > remain) { kore_log(LOG_ERR, "invalid log header: %u (%zu remain)", hdr->loglen, remain); break; } if ((dom = kore_domain_byid(hdr->domain)) == NULL) fatal("unknown domain id %u", hdr->domain); if (dom->logbuf == NULL) dom->logbuf = kore_buf_alloc(DOMAIN_LOGBUF_LEN); kore_buf_append(dom->logbuf, &logbuf->data[off], hdr->loglen); off += hdr->loglen; remain -= hdr->loglen; accesslog_flush(dom, now, force); } kore_buf_reset(logbuf); } if (force) kore_domain_callback(accesslog_flush_cb); } void kore_accesslog_run(void *arg, u_int64_t now) { static int ticks = 0; kore_accesslog_gather(arg, now, ticks++ % 100 ? 0 : 1); } static void accesslog_flush_cb(struct kore_domain *dom) { accesslog_flush(dom, 0, 1); } static void accesslog_flush(struct kore_domain *dom, u_int64_t now, int force) { ssize_t written; if (force && dom->logbuf == NULL) return; if (force || dom->logbuf->offset >= DOMAIN_LOGBUF_LEN) { written = write(dom->accesslog, dom->logbuf->data, dom->logbuf->offset); if (written == -1) { if (errno == EINTR) return; if (dom->logwarn == 0 || errno != dom->logerr) { kore_log(LOG_NOTICE, "error writing log for %s (%s)", dom->domain, errno_s); dom->logwarn = now; dom->logerr = errno; } kore_buf_reset(dom->logbuf); return; } if ((size_t)written != dom->logbuf->offset) { kore_log(LOG_ERR, "partial accesslog write for %s", dom->domain); } kore_buf_reset(dom->logbuf); } } static void accesslog_lock(struct kore_worker *kw) { for (;;) { if (__sync_bool_compare_and_swap(&kw->lb.lock, 0, 1)) break; } } static void accesslog_unlock(struct kore_worker *kw) { if (!__sync_bool_compare_and_swap(&kw->lb.lock, 1, 0)) fatal("accesslog_unlock: failed to release"); } kore-4.2.3/src/acme.c000066400000000000000000001257451430066302400143340ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * ACMEv2 protocol implementation. * * The acme process is responsible for talking to the acme servers, parsing * their JSON responses and requesting signed data / a csr from the keymgr * process. * * The acme process does not hold your account or domain keys, so anything * that needs to be signed is sent to the keymgr process instead. */ #include #include #include #include #include #include "kore.h" #include "acme.h" #include "curl.h" #define ACME_CREATE_ACCOUNT 0 #define ACME_RESOLVE_ACCOUNT 1 #define ACME_STATUS_PENDING 1 #define ACME_STATUS_PROCESSING 2 #define ACME_STATUS_VALID 3 #define ACME_STATUS_INVALID 4 #define ACME_STATUS_READY 5 #define ACME_STATUS_EXPIRED 6 #define ACME_STATUS_REVOKED 7 /* * The default provider is letsencrypt, can be overwritten via the config * file its acme_provider setting. */ #define ACME_DEFAULT_PROVIDER "https://acme-v02.api.letsencrypt.org/directory" #if defined(__linux__) #include "seccomp.h" /* * The syscalls our acme worker is allowed to perform, only. * * Since we drop all previously loaded seccomp rules to apply our own * we will have to reinclude the ones curl does. */ static struct sock_filter filter_acme[] = { KORE_SYSCALL_ALLOW(prctl), #if defined(SYS_poll) KORE_SYSCALL_ALLOW(poll), #endif KORE_SYSCALL_ALLOW(ppoll), KORE_SYSCALL_ALLOW(sendto), KORE_SYSCALL_ALLOW(recvfrom), #if defined(SYS_epoll_wait) KORE_SYSCALL_ALLOW(epoll_wait), #endif KORE_SYSCALL_ALLOW(epoll_pwait), KORE_SYSCALL_ALLOW(recvmsg), KORE_SYSCALL_ALLOW(sendmsg), KORE_SYSCALL_ALLOW(sendmmsg), KORE_SYSCALL_ALLOW(getpeername), KORE_SYSCALL_ALLOW(gettid), KORE_SYSCALL_ALLOW(exit), KORE_SYSCALL_ALLOW(brk), #if defined(SYS_mmap) KORE_SYSCALL_ALLOW(mmap), #endif #if defined(SYS_mmap2) KORE_SYSCALL_ALLOW(mmap2), #endif KORE_SYSCALL_ALLOW(ioctl), KORE_SYSCALL_ALLOW(uname), KORE_SYSCALL_ALLOW(munmap), KORE_SYSCALL_ALLOW(madvise), KORE_SYSCALL_ALLOW(faccessat), KORE_SYSCALL_ALLOW(newfstatat), KORE_SYSCALL_ALLOW(clock_gettime), KORE_SYSCALL_ALLOW(bind), KORE_SYSCALL_ALLOW(ioctl), KORE_SYSCALL_ALLOW(connect), KORE_SYSCALL_ALLOW(getsockopt), KORE_SYSCALL_ALLOW(socketpair), KORE_SYSCALL_ALLOW(getsockname), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_NETLINK), KORE_SYSCALL_ALLOW(clone), KORE_SYSCALL_ALLOW(membarrier), KORE_SYSCALL_ALLOW(set_robust_list), }; #endif struct acme_request { struct kore_curl curl; }; struct acme_sign_op { u_int32_t id; struct kore_timer *t; void *udata; char *nonce; char *payload; char *protected; void (*cb)(struct acme_sign_op *, struct kore_buf *); LIST_ENTRY(acme_sign_op) list; }; #define ACME_AUTH_STATE_DOWNLOAD 1 #define ACME_AUTH_STATE_CHALLENGE 2 struct acme_auth { char *url; struct acme_order *order; int status; struct acme_challenge *challenge; LIST_ENTRY(acme_auth) list; }; #define ACME_ORDER_STATE_RUNNING 1 #define ACME_ORDER_STATE_ERROR 2 #define ACME_ORDER_STATE_CANCELLED 3 #define ACME_ORDER_STATE_UPDATE 4 #define ACME_ORDER_STATE_UPDATE_AUTH 5 #define ACME_ORDER_STATE_WAITING 6 #define ACME_ORDER_STATE_FETCH_CERT 7 #define ACME_ORDER_STATE_COMPLETE 8 #define ACME_ORDER_TICK 1000 #define ACME_ORDER_TIMEOUT 120000 #define ACME_ORDER_CSR_REQUESTED 0x1000 struct acme_order { int state; int status; int flags; int auths; u_int64_t start; char *id; char *final; char *domain; char *certloc; struct acme_auth *curauth; LIST_HEAD(, acme_auth) auth; LIST_ENTRY(acme_order) list; }; static LIST_HEAD(, acme_order) orders; #define ACME_FLAG_CHALLENGE_CREATED 0x0001 #define ACME_CHALLENGE_TOKEN_MAXLEN 64 struct acme_challenge { int status; int flags; char *url; char *type; char *token; char *error_type; char *error_detail; int (*process)(struct acme_order *, struct acme_challenge *); }; static LIST_HEAD(, acme_sign_op) signops; static int acme_status_type(const char *); static int acme_request_run(struct acme_request *); static void acme_request_cleanup(struct acme_request *); static void acme_request_prepare(struct acme_request *, int, const char *, const void *, size_t); static void acme_request_json(struct kore_buf *, const char *, const char *, const char *); static char *acme_nonce_fetch(void); static char *acme_thumbprint_component(void); static char *acme_base64url(const void *, size_t); static char *acme_protected_component(const char *, const char *); static void acme_keymgr_key_req(const char *, const void *, size_t, int); static void acme_parse_directory(void); static void acme_directory_set(struct kore_json *, const char *, char **); static void acme_sign_expire(void *, u_int64_t); static void acme_sign_result(struct kore_msg *, const void *); static void acme_sign_submit(struct kore_json_item *, const char *, void *, void (*cb)(struct acme_sign_op *, struct kore_buf *)); static void acme_rsakey_exp(struct kore_msg *, const void *); static void acme_rsakey_mod(struct kore_msg *, const void *); static void acme_account_reg(int); static void acme_account_create(struct kore_msg *, const void *); static void acme_account_resolve(struct kore_msg *, const void *); static void acme_generic_submit(struct acme_sign_op *, struct kore_buf *); static void acme_account_reg_submit(struct acme_sign_op *, struct kore_buf *); static void acme_order_retry(const char *); static void acme_order_process(void *, u_int64_t); static void acme_order_update(struct acme_order *); static void acme_order_update_submit(struct acme_sign_op *, struct kore_buf *); static void acme_order_request_csr(struct acme_order *); static void acme_order_fetch_certificate(struct acme_order *); static void acme_order_fetch_certificate_submit(struct acme_sign_op *, struct kore_buf *); static void acme_order_create(struct kore_msg *, const void *); static void acme_order_remove(struct acme_order *, const char *); static void acme_order_csr_response(struct kore_msg *, const void *); static void acme_order_create_submit(struct acme_sign_op *, struct kore_buf *); static void acme_order_auth_log_error(struct acme_order *); static void acme_order_auth_deactivate(struct acme_order *); static int acme_order_auth_process(struct acme_order *, struct acme_auth *); static void acme_order_auth_update(struct acme_order *, struct acme_auth *); static void acme_order_auth_update_submit(struct acme_sign_op *, struct kore_buf *); static int acme_challenge_tls_alpn_01(struct acme_order *, struct acme_challenge *); static void acme_challenge_tls_alpn_01_create(struct acme_order *, struct acme_challenge *); static void acme_challenge_respond(struct acme_order *, const char *, const char *); static int signop_id = 0; static char *rsakey_n = NULL; static char *rsakey_e = NULL; static char *nonce_url = NULL; static char *order_url = NULL; static char *revoke_url = NULL; static char *account_id = NULL; static char *account_url = NULL; struct kore_privsep acme_privsep; int acme_domains = 0; char *acme_email = NULL; char *acme_provider = NULL; u_int32_t acme_request_timeout = 8; void kore_acme_init(void) { acme_provider = kore_strdup(ACME_DEFAULT_PROVIDER); } void kore_acme_run(void) { int quit; u_int64_t now, netwait; quit = 0; kore_server_closeall(); kore_module_cleanup(); net_init(); kore_timer_init(); kore_connection_init(); kore_platform_event_init(); kore_msg_worker_init(); kore_msg_register(KORE_ACME_RSAKEY_E, acme_rsakey_exp); kore_msg_register(KORE_ACME_RSAKEY_N, acme_rsakey_mod); kore_msg_register(KORE_ACME_SIGN_RESULT, acme_sign_result); kore_msg_register(KORE_ACME_ORDER_CREATE, acme_order_create); kore_msg_register(KORE_ACME_ACCOUNT_CREATE, acme_account_create); kore_msg_register(KORE_ACME_ACCOUNT_RESOLVE, acme_account_resolve); kore_msg_register(KORE_ACME_CSR_RESPONSE, acme_order_csr_response); #if defined(__linux__) /* Drop all enabled seccomp filters, and add only ours. */ kore_seccomp_drop(); kore_seccomp_filter("acme", filter_acme, KORE_FILTER_LEN(filter_acme)); #endif #if defined(KORE_USE_PYTHON) kore_msg_unregister(KORE_PYTHON_SEND_OBJ); #endif kore_worker_privsep(); #if defined(__OpenBSD__) if (unveil("/etc/ssl/", "r") == -1) fatal("unveil: %s", errno_s); if (pledge("stdio inet dns rpath", NULL) == -1) fatal("pledge acme process: %s", errno_s); #endif http_init(); LIST_INIT(&orders); LIST_INIT(&signops); kore_worker_started(); acme_parse_directory(); while (quit != 1) { now = kore_time_ms(); netwait = kore_timer_next_run(now); kore_platform_event_wait(netwait); if (sig_recv != 0) { switch (sig_recv) { case SIGQUIT: case SIGINT: case SIGTERM: quit = 1; break; default: break; } sig_recv = 0; } if (quit) break; now = kore_time_ms(); kore_timer_run(now); kore_connection_prune(KORE_CONNECTION_PRUNE_DISCONNECT); } kore_platform_event_cleanup(); kore_connection_cleanup(); net_cleanup(); } void kore_acme_get_paths(const char *domain, char **key, char **cert) { int len; char path[MAXPATHLEN]; len = snprintf(path, sizeof(path), "%s/%s/fullchain.pem", KORE_ACME_CERTDIR, domain); if (len == -1 || (size_t)len >= sizeof(path)) fatal("failed to create certfile path"); *cert = kore_strdup(path); len = snprintf(path, sizeof(path), "%s/%s/key.pem", KORE_ACME_CERTDIR, domain); if (len == -1 || (size_t)len >= sizeof(path)) fatal("failed to create certkey path"); *key = kore_strdup(path); } static void acme_parse_directory(void) { struct acme_request req; size_t len; struct kore_json json; const u_int8_t *body; acme_request_prepare(&req, HTTP_METHOD_GET, acme_provider, NULL, 0); if (!acme_request_run(&req)) { acme_request_cleanup(&req); return; } if (req.curl.http.status != HTTP_STATUS_OK) { kore_log(LOG_NOTICE, "request to '%s' failed: got %ld - expected 200", req.curl.url, req.curl.http.status); acme_request_cleanup(&req); return; } kore_curl_response_as_bytes(&req.curl, &body, &len); kore_json_init(&json, body, len); if (!kore_json_parse(&json)) { kore_log(LOG_NOTICE, "failed to parse directory payload from ACME server (%s)", kore_json_strerror()); goto cleanup; } acme_directory_set(&json, "newNonce", &nonce_url); acme_directory_set(&json, "newOrder", &order_url); acme_directory_set(&json, "newAccount", &account_url); acme_directory_set(&json, "revokeCert", &revoke_url); cleanup: kore_json_cleanup(&json); acme_request_cleanup(&req); } static char * acme_nonce_fetch(void) { struct acme_request req; char *ret; const char *nonce; ret = NULL; acme_request_prepare(&req, HTTP_METHOD_HEAD, nonce_url, NULL, 0); if (!acme_request_run(&req)) goto cleanup; if (req.curl.http.status != HTTP_STATUS_OK) { kore_log(LOG_NOTICE, "request to '%s' failed: got %ld - expected 200", req.curl.url, req.curl.http.status); goto cleanup; } if (!kore_curl_http_get_header(&req.curl, "replay-nonce", &nonce)) { kore_log(LOG_NOTICE, "new-nonce: no replay-nonce header found"); goto cleanup; } ret = kore_strdup(nonce); cleanup: acme_request_cleanup(&req); return (ret); } static void acme_account_create(struct kore_msg *msg, const void *data) { acme_account_reg(ACME_CREATE_ACCOUNT); } static void acme_account_resolve(struct kore_msg *msg, const void *data) { acme_account_reg(ACME_RESOLVE_ACCOUNT); } static void acme_account_reg(int resolve_only) { int len; char mail[1024]; struct kore_json_item *json, *contact; if (account_url == NULL) return; kore_free(account_id); account_id = NULL; kore_log(LOG_INFO, "%s account with ACME provider", resolve_only ? "resolving" : "creating"); json = kore_json_create_object(NULL, NULL); kore_json_create_literal(json, "termsOfServiceAgreed", KORE_JSON_TRUE); if (acme_email) { len = snprintf(mail, sizeof(mail), "mailto:%s", acme_email); if (len == -1 || (size_t)len >= sizeof(mail)) fatalx("mail contact '%s' too large", acme_email); contact = kore_json_create_array(json, "contact"); kore_json_create_string(contact, NULL, mail); } if (resolve_only) { kore_json_create_literal(json, "onlyReturnExisting", KORE_JSON_TRUE); } acme_sign_submit(json, account_url, NULL, acme_account_reg_submit); kore_json_item_free(json); } static void acme_account_reg_submit(struct acme_sign_op *op, struct kore_buf *payload) { struct acme_request req; const char *header; acme_request_prepare(&req, HTTP_METHOD_POST, account_url, payload->data, payload->offset); if (!acme_request_run(&req)) goto cleanup; switch (req.curl.http.status) { case HTTP_STATUS_OK: case HTTP_STATUS_CREATED: break; default: kore_log(LOG_NOTICE, "request to '%s' failed: status %ld - body '%s'", req.curl.url, req.curl.http.status, kore_curl_response_as_string(&req.curl)); goto cleanup; } if (!kore_curl_http_get_header(&req.curl, "location", &header)) { kore_log(LOG_NOTICE, "new-acct: no location header found"); goto cleanup; } account_id = kore_strdup(header); kore_log(LOG_INFO, "account_id = %s", account_id); kore_msg_send(KORE_WORKER_KEYMGR, KORE_ACME_PROC_READY, NULL, 0); cleanup: acme_request_cleanup(&req); } static void acme_order_create(struct kore_msg *msg, const void *data) { char *domain; struct kore_json_item *json, *identifiers, *identifier; domain = kore_calloc(1, msg->length + 1); memcpy(domain, data, msg->length); domain[msg->length] = '\0'; kore_log(LOG_INFO, "[%s] creating order", domain); json = kore_json_create_object(NULL, NULL); identifiers = kore_json_create_array(json, "identifiers"); identifier = kore_json_create_object(identifiers, NULL); kore_json_create_string(identifier, "type", "dns"); kore_json_create_string(identifier, "value", domain); acme_sign_submit(json, order_url, domain, acme_order_create_submit); kore_json_item_free(json); } static void acme_order_create_submit(struct acme_sign_op *op, struct kore_buf *payload) { struct acme_request req; size_t len; struct kore_json json; int stval; const u_int8_t *body; struct acme_auth *auth; struct acme_order *order; const char *header; const char *domain; struct kore_json_item *item, *array, *final, *status; order = NULL; domain = op->udata; acme_request_prepare(&req, HTTP_METHOD_POST, order_url, payload->data, payload->offset); if (!acme_request_run(&req)) { acme_request_cleanup(&req); acme_order_retry(domain); return; } if (req.curl.http.status != HTTP_STATUS_CREATED) { kore_log(LOG_NOTICE, "[%s] - request to '%s' failed: status %ld - body '%s'", domain, req.curl.url, req.curl.http.status, kore_curl_response_as_string(&req.curl)); acme_request_cleanup(&req); acme_order_retry(domain); return; } if (!kore_curl_http_get_header(&req.curl, "location", &header)) { kore_log(LOG_NOTICE, "[%s] new-order: no order id found", domain); acme_request_cleanup(&req); acme_order_retry(domain); return; } kore_curl_response_as_bytes(&req.curl, &body, &len); kore_json_init(&json, body, len); if (!kore_json_parse(&json)) { kore_log(LOG_NOTICE, "[%s] failed to parse order payload from ACME server (%s)", domain, kore_json_strerror()); goto cleanup; } array = kore_json_find_array(json.root, "authorizations"); if (array == NULL) { kore_log(LOG_NOTICE, "[%s] body has no 'authorizations' array", domain); goto cleanup; } if (TAILQ_EMPTY(&array->data.items)) { kore_log(LOG_NOTICE, "[%s] no authoritization URLs in payload", domain); goto cleanup; } if ((status = kore_json_find_string(json.root, "status")) == NULL) { kore_log(LOG_NOTICE, "[%s] order has no 'status' string", domain); goto cleanup; } if ((final = kore_json_find_string(json.root, "finalize")) == NULL) { kore_log(LOG_NOTICE, "[%s] order has no 'finalize' string", domain); goto cleanup; } if ((stval = acme_status_type(status->data.string)) == -1) { kore_log(LOG_NOTICE, "[%s] order has invalid status", domain); goto cleanup; } order = kore_calloc(1, sizeof(*order)); LIST_INSERT_HEAD(&orders, order, list); LIST_INIT(&order->auth); order->status = stval; order->start = kore_time_ms(); order->id = kore_strdup(header); order->domain = kore_strdup(domain); order->state = ACME_ORDER_STATE_UPDATE; order->final = kore_strdup(final->data.string); kore_timer_add(acme_order_process, ACME_ORDER_TICK, order, KORE_TIMER_ONESHOT); TAILQ_FOREACH(item, &array->data.items, list) { if (item->type != KORE_JSON_TYPE_STRING) continue; auth = kore_calloc(1, sizeof(*auth)); auth->order = order; auth->url = kore_strdup(item->data.string); LIST_INSERT_HEAD(&order->auth, auth, list); } order->curauth = LIST_FIRST(&order->auth); kore_log(LOG_INFO, "[%s] order_id = %s", order->domain, order->id); cleanup: if (order == NULL) acme_order_retry(domain); kore_json_cleanup(&json); acme_request_cleanup(&req); } static void acme_order_update(struct acme_order *order) { acme_sign_submit(NULL, order->id, order, acme_order_update_submit); } static void acme_order_update_submit(struct acme_sign_op *op, struct kore_buf *payload) { struct acme_request req; size_t len; struct kore_json json; struct acme_order *order; const u_int8_t *body; int stval, ret; struct kore_json_item *status, *cert; order = op->udata; op->udata = NULL; acme_request_prepare(&req, HTTP_METHOD_POST, order->id, payload->data, payload->offset); if (!acme_request_run(&req)) { acme_request_cleanup(&req); order->state = ACME_ORDER_STATE_ERROR; return; } if (req.curl.http.status != HTTP_STATUS_OK) { kore_log(LOG_NOTICE, "[%s] - request to '%s' failed: status %ld - body '%s'", order->domain, req.curl.url, req.curl.http.status, kore_curl_response_as_string(&req.curl)); acme_request_cleanup(&req); order->state = ACME_ORDER_STATE_ERROR; return; } ret = KORE_RESULT_ERROR; kore_curl_response_as_bytes(&req.curl, &body, &len); kore_json_init(&json, body, len); if (!kore_json_parse(&json)) { kore_log(LOG_NOTICE, "[%s] failed to parse order payload from ACME server (%s)", order->domain, kore_json_strerror()); goto cleanup; } if ((status = kore_json_find_string(json.root, "status")) == NULL) { kore_log(LOG_NOTICE, "[%s] order has no 'status' string", order->domain); goto cleanup; } if ((stval = acme_status_type(status->data.string)) == -1) { kore_log(LOG_NOTICE, "[%s] order has invalid status", order->domain); goto cleanup; } order->status = stval; if (order->status == ACME_STATUS_VALID) { cert = kore_json_find_string(json.root, "certificate"); if (cert == NULL) { kore_log(LOG_NOTICE, "[%s] order has 'certificate' member", order->domain); goto cleanup; } order->certloc = kore_strdup(cert->data.string); } ret = KORE_RESULT_OK; cleanup: if (ret == KORE_RESULT_ERROR) order->state = ACME_ORDER_STATE_ERROR; else order->state = ACME_ORDER_STATE_UPDATE_AUTH; kore_json_cleanup(&json); acme_request_cleanup(&req); } /* * We currently don't care why an order may have failed, (rate-limited, * auth failed, etc). * * It would be neat if we could obey that a bit better. */ static void acme_order_retry(const char *domain) { u_int32_t retry_after; /* arbitrary number */ retry_after = 60000; acme_keymgr_key_req(domain, &retry_after, sizeof(retry_after), KORE_ACME_ORDER_FAILED); } /* * Process an order, step by step. * * This callback is called every second to check on an active order. * It will first update the order if required, and updated any of its * active awuthoritizations to get the latest data. */ static void acme_order_process(void *udata, u_int64_t now) { struct acme_auth *auth; struct acme_order *order = udata; if ((now - order->start) >= ACME_ORDER_TIMEOUT) { acme_order_auth_deactivate(order); acme_order_remove(order, "order ran too long"); return; } switch (order->state) { case ACME_ORDER_STATE_WAITING: break; case ACME_ORDER_STATE_UPDATE: acme_order_update(order); order->state = ACME_ORDER_STATE_WAITING; break; case ACME_ORDER_STATE_UPDATE_AUTH: order->auths = 0; LIST_FOREACH(auth, &order->auth, list) { acme_order_auth_update(order, auth); order->auths++; } order->state = ACME_ORDER_STATE_WAITING; break; case ACME_ORDER_STATE_CANCELLED: acme_order_remove(order, "cancelled"); order = NULL; break; case ACME_ORDER_STATE_COMPLETE: acme_order_remove(order, "completed"); order = NULL; break; case ACME_ORDER_STATE_FETCH_CERT: acme_order_fetch_certificate(order); order->state = ACME_ORDER_STATE_WAITING; break; case ACME_ORDER_STATE_RUNNING: switch (order->status) { case ACME_STATUS_PENDING: if (!acme_order_auth_process(order, order->curauth)) { acme_order_auth_log_error(order); acme_order_remove(order, "cancelled"); order = NULL; } break; case ACME_STATUS_READY: acme_order_request_csr(order); break; case ACME_STATUS_PROCESSING: kore_log(LOG_INFO, "[%s] waiting for certificate", order->domain); break; case ACME_STATUS_VALID: kore_log(LOG_INFO, "[%s] certificate available", order->domain); order->state = ACME_ORDER_STATE_FETCH_CERT; break; case ACME_STATUS_INVALID: kore_log(LOG_INFO, "[%s] order authorization failed", order->domain); acme_order_auth_log_error(order); acme_order_remove(order, "authorization failure"); order = NULL; break; default: acme_order_auth_deactivate(order); acme_order_remove(order, "unknown status"); order = NULL; break; } break; case ACME_ORDER_STATE_ERROR: acme_order_auth_deactivate(order); acme_order_remove(order, "error"); order = NULL; break; default: fatal("%s: invalid order state %d", __func__, order->state); } if (order != NULL) { /* Do not go back to update if we are ready for the cert. */ if (order->state != ACME_ORDER_STATE_FETCH_CERT) order->state = ACME_ORDER_STATE_UPDATE; kore_timer_add(acme_order_process, ACME_ORDER_TICK, order, KORE_TIMER_ONESHOT); } } static void acme_order_remove(struct acme_order *order, const char *reason) { struct acme_auth *auth; LIST_REMOVE(order, list); while ((auth = LIST_FIRST(&order->auth)) != NULL) { LIST_REMOVE(auth, list); if (auth->challenge != NULL) { kore_free(auth->challenge->error_detail); kore_free(auth->challenge->error_type); kore_free(auth->challenge->token); kore_free(auth->challenge->type); kore_free(auth->challenge->url); kore_free(auth->challenge); } kore_free(auth->url); kore_free(auth); } kore_log(LOG_INFO, "[%s] order removed (%s)", order->domain, reason); if (strcmp(reason, "completed")) acme_order_retry(order->domain); kore_free(order->domain); kore_free(order->final); kore_free(order->id); kore_free(order); } static void acme_order_fetch_certificate(struct acme_order *order) { acme_sign_submit(NULL, order->certloc, order, acme_order_fetch_certificate_submit); } static void acme_order_fetch_certificate_submit(struct acme_sign_op *op, struct kore_buf *payload) { struct acme_request req; size_t len; const u_int8_t *body; struct acme_order *order; order = op->udata; op->udata = NULL; acme_request_prepare(&req, HTTP_METHOD_POST, order->certloc, payload->data, payload->offset); if (!acme_request_run(&req)) { acme_request_cleanup(&req); order->state = ACME_ORDER_STATE_CANCELLED; return; } if (req.curl.http.status != HTTP_STATUS_OK) { kore_log(LOG_NOTICE, "[%s] request to '%s' failed: got %ld - expected 200", order->domain, order->certloc, req.curl.http.status); acme_request_cleanup(&req); order->state = ACME_ORDER_STATE_CANCELLED; return; } kore_curl_response_as_bytes(&req.curl, &body, &len); kore_log(LOG_INFO, "got %zu bytes of cert data", len); acme_keymgr_key_req(order->domain, body, len, KORE_ACME_INSTALL_CERT); acme_request_cleanup(&req); order->state = ACME_ORDER_STATE_COMPLETE; } static void acme_order_request_csr(struct acme_order *order) { if (order->flags & ACME_ORDER_CSR_REQUESTED) return; kore_log(LOG_INFO, "[%s] requesting CSR", order->domain); order->flags |= ACME_ORDER_CSR_REQUESTED; acme_keymgr_key_req(order->domain, NULL, 0, KORE_ACME_CSR_REQUEST); } static void acme_order_csr_response(struct kore_msg *msg, const void *data) { const struct kore_x509_msg *req; struct kore_json_item *json; struct acme_order *order; char *b64, *url; if (!kore_worker_keymgr_response_verify(msg, data, NULL)) return; req = (const struct kore_x509_msg *)data; LIST_FOREACH(order, &orders, list) { if (!strcmp(order->domain, req->domain)) break; } if (order == NULL) { kore_log(LOG_NOTICE, "[%s] csr received but no order active", req->domain); return; } url = kore_strdup(order->final); b64 = acme_base64url(req->data, req->data_len); json = kore_json_create_object(NULL, NULL); kore_json_create_string(json, "csr", b64); acme_sign_submit(json, url, url, acme_generic_submit); kore_json_item_free(json); kore_free(b64); } static void acme_order_auth_deactivate(struct acme_order *order) { struct acme_request req; struct acme_auth *auth; LIST_FOREACH(auth, &order->auth, list) { acme_request_prepare(&req, HTTP_METHOD_GET, auth->url, NULL, 0); if (!acme_request_run(&req)) { kore_log(LOG_NOTICE, "[%s:auth] failed to deactivate %s", order->domain, auth->url); } else { kore_log(LOG_NOTICE, "[%s:auth] deactivated %s", order->domain, auth->url); } acme_request_cleanup(&req); } } static void acme_order_auth_log_error(struct acme_order *order) { struct acme_auth *auth; LIST_FOREACH(auth, &order->auth, list) { if (auth->challenge->status == ACME_STATUS_PENDING || auth->challenge->status == ACME_STATUS_VALID || auth->challenge->status == ACME_STATUS_PROCESSING) continue; kore_log(LOG_INFO, "[%s:auth:challenge] %s = %s (%s)", order->domain, auth->challenge->type, auth->challenge->error_type, auth->challenge->error_detail); } } static int acme_order_auth_process(struct acme_order *order, struct acme_auth *auth) { int ret; if (auth == NULL) return (KORE_RESULT_OK); ret = KORE_RESULT_ERROR; kore_log(LOG_INFO, "[%s] processing authentication", order->domain); switch (auth->status) { case ACME_STATUS_PENDING: ret = auth->challenge->process(order, auth->challenge); break; case ACME_STATUS_VALID: case ACME_STATUS_PROCESSING: ret = KORE_RESULT_OK; break; case ACME_STATUS_INVALID: kore_log(LOG_NOTICE, "[%s:auth] authorization invalid", order->domain); break; case ACME_STATUS_EXPIRED: kore_log(LOG_NOTICE, "[%s:auth] authorization expired", order->domain); break; case ACME_STATUS_REVOKED: kore_log(LOG_NOTICE, "[%s:auth] authorization revoked", order->domain); break; default: kore_log(LOG_NOTICE, "[%s:auth] invalid auth status %d", order->domain, auth->status); break; } if (ret == KORE_RESULT_OK) order->curauth = LIST_NEXT(order->curauth, list); return (ret); } static void acme_order_auth_update(struct acme_order *order, struct acme_auth *auth) { acme_sign_submit(NULL, auth->url, auth, acme_order_auth_update_submit); } static void acme_order_auth_update_submit(struct acme_sign_op *op, struct kore_buf *payload) { const char *p; struct acme_request req; size_t len; struct kore_json json; const u_int8_t *body; struct acme_auth *auth; struct acme_order *order; struct acme_challenge *challenge; int ret, stval; struct kore_json_item *status, *type, *url, *token; struct kore_json_item *array, *object, *err, *detail; ret = KORE_RESULT_ERROR; memset(&json, 0, sizeof(json)); auth = op->udata; order = auth->order; op->udata = NULL; acme_request_prepare(&req, HTTP_METHOD_POST, auth->url, payload->data, payload->offset); if (!acme_request_run(&req)) goto cleanup; if (req.curl.http.status != HTTP_STATUS_OK) { kore_log(LOG_NOTICE, "[%s:auth] request to '%s' failed: got %ld - expected 200", order->domain, auth->url, req.curl.http.status); goto cleanup; } kore_curl_response_as_bytes(&req.curl, &body, &len); kore_json_init(&json, body, len); if (!kore_json_parse(&json)) { kore_log(LOG_NOTICE, "[%s:auth] failed to parse payload from ACME server (%s)", order->domain, kore_json_strerror()); goto cleanup; } kore_log(LOG_INFO, "[%s:auth] %s updated", order->domain, auth->url); if ((status = kore_json_find_string(json.root, "status")) == NULL) { kore_log(LOG_NOTICE, "[%s:auth] payload has no 'status' string", order->domain); goto cleanup; } if ((array = kore_json_find_array(json.root, "challenges")) == NULL) { kore_log(LOG_NOTICE, "[%s:auth] payload has no 'challenges' array", order->domain); goto cleanup; } if (TAILQ_EMPTY(&array->data.items)) { kore_log(LOG_NOTICE, "[%s:auth] no challenges URLs in challenge array", order->domain); goto cleanup; } if ((stval = acme_status_type(status->data.string)) == -1) { kore_log(LOG_NOTICE, "[%s] auth has invalid status", order->domain); goto cleanup; } auth->status = stval; TAILQ_FOREACH(object, &array->data.items, list) { if (object->type != KORE_JSON_TYPE_OBJECT) continue; if ((type = kore_json_find_string(object, "type")) == NULL) { kore_log(LOG_NOTICE, "[%s:auth:challenge] no type", order->domain); continue; } /* * We only support tls-alpn-01 for now, we ignore the rest. */ if (strcmp(type->data.string, "tls-alpn-01")) continue; url = kore_json_find_string(object, "url"); token = kore_json_find_string(object, "token"); status = kore_json_find_string(object, "status"); if (url == NULL || token == NULL || status == NULL) { kore_log(LOG_NOTICE, "[%s:auth:challenge] missing members", order->domain); continue; } if (strlen(token->data.string) > ACME_CHALLENGE_TOKEN_MAXLEN) { kore_log(LOG_NOTICE, "[%s:auth:challenge] invalid token length", order->domain); continue; } for (p = token->data.string; *p != '\0'; p++) { if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_' || *p == '-') continue; break; } if (*p != '\0') { kore_log(LOG_NOTICE, "[%s:auth:challenge] invalid token", order->domain); continue; } if ((stval = acme_status_type(status->data.string)) == -1) { kore_log(LOG_NOTICE, "[%s:auth:challenge] invalid challenge status", order->domain); continue; } if (auth->challenge == NULL) { challenge = kore_calloc(1, sizeof(*challenge)); challenge->url = kore_strdup(url->data.string); challenge->process = acme_challenge_tls_alpn_01; challenge->token = kore_strdup(token->data.string); challenge->type = kore_strdup(type->data.string); auth->challenge = challenge; } else { challenge = auth->challenge; } challenge->status = stval; if (challenge->status == ACME_STATUS_INVALID && (err = kore_json_find_object(object, "error")) != NULL) { type = kore_json_find_string(err, "type"); detail = kore_json_find_string(err, "detail"); if (type == NULL || detail == NULL) { kore_log(LOG_NOTICE, "[%s:auth:challenge] error missing fields", order->domain); } else { kore_free(challenge->error_type); kore_free(challenge->error_detail); challenge->error_type = kore_strdup(type->data.string); challenge->error_detail = kore_strdup(detail->data.string); } } break; } if (auth->challenge == NULL) { kore_log(LOG_NOTICE, "[%s:auth] no supported challenges found", order->domain); goto cleanup; } ret = KORE_RESULT_OK; cleanup: if (ret != KORE_RESULT_OK) { order->state = ACME_ORDER_STATE_CANCELLED; } else { order->auths--; if (order->auths == 0) { kore_log(LOG_INFO, "[%s:auth] authentications done", order->domain); order->state = ACME_ORDER_STATE_RUNNING; } } kore_json_cleanup(&json); acme_request_cleanup(&req); } static int acme_challenge_tls_alpn_01(struct acme_order *order, struct acme_challenge *challenge) { int ret; ret = KORE_RESULT_RETRY; switch (challenge->status) { case ACME_STATUS_PENDING: acme_challenge_tls_alpn_01_create(order, challenge); break; case ACME_STATUS_PROCESSING: kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] processing", order->domain); break; case ACME_STATUS_VALID: kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] valid", order->domain); ret = KORE_RESULT_OK; break; default: kore_log(LOG_NOTICE, "[%s:auth:challenge:tls-alpn-01] invalid (%d)", order->domain, challenge->status); ret = KORE_RESULT_ERROR; break; } return (ret); } static void acme_challenge_tls_alpn_01_create(struct acme_order *order, struct acme_challenge *challenge) { struct kore_buf auth; char *thumb; u_int8_t digest[SHA256_DIGEST_LENGTH]; if (challenge->flags & ACME_FLAG_CHALLENGE_CREATED) { kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] pending keymgr", order->domain); return; } challenge->flags |= ACME_FLAG_CHALLENGE_CREATED; kore_log(LOG_INFO, "[%s:auth:challenge:tls-alpn-01] requested from keymgr", order->domain); thumb = acme_thumbprint_component(); kore_buf_init(&auth, 128); kore_buf_appendf(&auth, "%s.%s", challenge->token, thumb); (void)SHA256(auth.data, auth.offset, digest); kore_buf_cleanup(&auth); kore_free(thumb); acme_keymgr_key_req(order->domain, digest, sizeof(digest), KORE_ACME_CHALLENGE_CERT); /* XXX - this maybe too fast, keymgr may not have had time. */ acme_challenge_respond(order, challenge->url, "tls-alpn-01"); } static void acme_challenge_respond(struct acme_order *order, const char *url, const char *name) { struct kore_json_item *json; char *copy; kore_log(LOG_INFO, "[%s:auth:challenge:%s] submitting challenge", order->domain, name); copy = kore_strdup(url); json = kore_json_create_object(NULL, NULL); acme_sign_submit(json, url, copy, acme_generic_submit); kore_json_item_free(json); } static void acme_generic_submit(struct acme_sign_op *op, struct kore_buf *payload) { struct acme_request req; acme_request_prepare(&req, HTTP_METHOD_POST, op->udata, payload->data, payload->offset); if (!acme_request_run(&req)) goto cleanup; if (req.curl.http.status != HTTP_STATUS_OK) { kore_log(LOG_NOTICE, "request to '%s' failed: status %ld - body '%s'", req.curl.url, req.curl.http.status, kore_curl_response_as_string(&req.curl)); goto cleanup; } kore_log(LOG_INFO, "submitted %zu bytes to %s", payload->offset, req.curl.url); cleanup: acme_request_cleanup(&req); } static void acme_request_prepare(struct acme_request *req, int method, const char *url, const void *data, size_t len) { memset(req, 0, sizeof(*req)); if (!kore_curl_init(&req->curl, url, KORE_CURL_SYNC)) fatal("failed to initialize request to '%s'", url); /* Override default timeout. */ curl_easy_setopt(req->curl.handle, CURLOPT_TIMEOUT, acme_request_timeout); kore_curl_http_setup(&req->curl, method, data, len); kore_curl_http_set_header(&req->curl, "content-type", "application/jose+json"); } static void acme_request_json(struct kore_buf *buf, const char *payload, const char *protected, const char *sig) { struct kore_json_item *json; json = kore_json_create_object(NULL, NULL); kore_json_create_string(json, "signature", sig); kore_json_create_string(json, "payload", payload); kore_json_create_string(json, "protected", protected); kore_json_item_tobuf(json, buf); kore_json_item_free(json); } static int acme_request_run(struct acme_request *req) { size_t len; struct kore_json json; const u_int8_t *body; struct kore_json_item *detail; kore_curl_run(&req->curl); if (!kore_curl_success(&req->curl)) { kore_log(LOG_NOTICE, "request to '%s' failed: %s", req->curl.url, kore_curl_strerror(&req->curl)); return (KORE_RESULT_ERROR); } if (req->curl.http.status == HTTP_STATUS_BAD_REQUEST) { kore_curl_response_as_bytes(&req->curl, &body, &len); kore_json_init(&json, body, len); if (!kore_json_parse(&json)) { detail = NULL; } else { detail = kore_json_find_string(json.root, "detail"); } if (detail != NULL) { kore_log(LOG_NOTICE, "request to '%s' failed with 400 - detail: %s", req->curl.url, detail->data.string); } else { kore_log(LOG_NOTICE, "request to '%s' failed with 400 - body: %.*s", req->curl.url, (int)len, (const char *)body); } kore_json_cleanup(&json); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static void acme_request_cleanup(struct acme_request *req) { kore_curl_cleanup(&req->curl); } static void acme_directory_set(struct kore_json *json, const char *name, char **out) { struct kore_json_item *item; if ((item = kore_json_find_string(json->root, name)) == NULL) { kore_log(LOG_NOTICE, "directory has missing '%s' URI", name); return; } *out = kore_strdup(item->data.string); } static void acme_sign_submit(struct kore_json_item *json, const char *url, void *udata, void (*cb)(struct acme_sign_op *, struct kore_buf *)) { struct acme_sign_op *op; struct kore_buf buf; char *nonce; if ((nonce = acme_nonce_fetch()) == NULL) { kore_log(LOG_ERR, "failed to fetch nonce from servers"); return; } kore_buf_init(&buf, 1024); if (json != NULL) kore_json_item_tobuf(json, &buf); op = kore_calloc(1, sizeof(*op)); LIST_INSERT_HEAD(&signops, op, list); op->cb = cb; op->udata = udata; op->nonce = nonce; op->id = signop_id++; op->payload = acme_base64url(buf.data, buf.offset); op->protected = acme_protected_component(op->nonce, url); op->t = kore_timer_add(acme_sign_expire, 30000, op, KORE_TIMER_ONESHOT); kore_buf_reset(&buf); kore_buf_append(&buf, &op->id, sizeof(op->id)); kore_buf_appendf(&buf, "%s.%s", op->protected, op->payload); kore_msg_send(KORE_WORKER_KEYMGR, KORE_ACME_SIGN, buf.data, buf.offset); kore_buf_cleanup(&buf); } static void acme_sign_expire(void *udata, u_int64_t now) { struct acme_sign_op *op = udata; kore_log(LOG_NOTICE, "signop %u expired (no answer)", op->id); LIST_REMOVE(op, list); kore_free(op->protected); kore_free(op->payload); kore_free(op->udata); kore_free(op->nonce); kore_free(op); } static void acme_sign_result(struct kore_msg *msg, const void *data) { u_int32_t id; struct kore_buf buf; struct acme_sign_op *op; char *sig; const u_int8_t *ptr; if (msg->length < sizeof(id)) fatal("%s: invalid length (%zu)", __func__, msg->length); ptr = data; memcpy(&id, ptr, sizeof(id)); ptr += sizeof(id); msg->length -= sizeof(id); LIST_FOREACH(op, &signops, list) { if (op->id == id) break; } if (op == NULL) { kore_log(LOG_NOTICE, "received KORE_ACME_SIGN_RESULT for unknown op: %u", id); return; } kore_timer_remove(op->t); LIST_REMOVE(op, list); sig = kore_malloc(msg->length + 1); memcpy(sig, ptr, msg->length); sig[msg->length] = '\0'; kore_buf_init(&buf, 1024); acme_request_json(&buf, op->payload, op->protected, sig); op->cb(op, &buf); kore_free(op->protected); kore_free(op->payload); kore_free(op->udata); kore_free(op->nonce); kore_free(op); kore_free(sig); kore_buf_cleanup(&buf); } static char * acme_protected_component(const char *nonce, const char *url) { char *b64; struct kore_buf payload; struct kore_json_item *root, *jwk; root = kore_json_create_object(NULL, NULL); kore_json_create_string(root, "url", url); kore_json_create_string(root, "alg", "RS256"); kore_json_create_string(root, "nonce", nonce); if (account_id == NULL) { jwk = kore_json_create_object(root, "jwk"); kore_json_create_string(jwk, "kty", "RSA"); kore_json_create_string(jwk, "e", rsakey_e); kore_json_create_string(jwk, "n", rsakey_n); } else { kore_json_create_string(root, "kid", account_id); } kore_buf_init(&payload, 128); kore_json_item_tobuf(root, &payload); b64 = acme_base64url(payload.data, payload.offset); kore_json_item_free(root); kore_buf_cleanup(&payload); return (b64); } static char * acme_thumbprint_component(void) { struct kore_json_item *json; struct kore_buf payload; u_int8_t digest[SHA256_DIGEST_LENGTH]; json = kore_json_create_object(NULL, NULL); /* Order matters here, see RFC7638. */ kore_json_create_string(json, "e", rsakey_e); kore_json_create_string(json, "kty", "RSA"); kore_json_create_string(json, "n", rsakey_n); kore_buf_init(&payload, 128); kore_json_item_tobuf(json, &payload); (void)SHA256(payload.data, payload.offset, digest); kore_json_item_free(json); kore_buf_cleanup(&payload); return (acme_base64url(digest, sizeof(digest))); } static char * acme_base64url(const void *data, size_t len) { char *b64; if (!kore_base64url_encode(data, len, &b64, KORE_BASE64_RAW)) { fatal("%s: failed to encode base64url data of %zu bytes", __func__, len); } return (b64); } static void acme_keymgr_key_req(const char *domain, const void *data, size_t len, int msg) { struct kore_keyreq req; struct kore_buf buf; memset(&req, 0, sizeof(req)); req.data_len = len; if (kore_strlcpy(req.domain, domain, sizeof(req.domain)) >= sizeof(req.domain)) fatal("%s: domain truncated", __func__); kore_buf_init(&buf, sizeof(req) + len); kore_buf_append(&buf, &req, sizeof(req)); if (data != NULL) kore_buf_append(&buf, data, len); kore_msg_send(KORE_WORKER_KEYMGR, msg, buf.data, buf.offset); kore_buf_cleanup(&buf); } static int acme_status_type(const char *status) { int type; if (!strcmp(status, "pending")) { type = ACME_STATUS_PENDING; } else if (!strcmp(status, "processing")) { type = ACME_STATUS_PROCESSING; } else if (!strcmp(status, "valid")) { type = ACME_STATUS_VALID; } else if (!strcmp(status, "invalid")) { type = ACME_STATUS_INVALID; } else if (!strcmp(status, "ready")) { type = ACME_STATUS_READY; } else if (!strcmp(status, "expired")) { type = ACME_STATUS_EXPIRED; } else if (!strcmp(status, "revoked")) { type = ACME_STATUS_REVOKED; } else { type = -1; } return (type); } static void acme_rsakey_exp(struct kore_msg *msg, const void *data) { kore_free(rsakey_e); rsakey_e = kore_calloc(1, msg->length + 1); memcpy(rsakey_e, data, msg->length); rsakey_e[msg->length] = '\0'; } static void acme_rsakey_mod(struct kore_msg *msg, const void *data) { kore_free(rsakey_n); rsakey_n = kore_calloc(1, msg->length + 1); memcpy(rsakey_n, data, msg->length); rsakey_n[msg->length] = '\0'; } kore-4.2.3/src/auth.c000066400000000000000000000074051430066302400143600ustar00rootroot00000000000000/* * Copyright (c) 2014 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "kore.h" #include "http.h" TAILQ_HEAD(, kore_auth) auth_list; void kore_auth_init(void) { TAILQ_INIT(&auth_list); } int kore_auth_new(const char *name) { struct kore_auth *auth; if (kore_auth_lookup(name) != NULL) return (KORE_RESULT_ERROR); auth = kore_malloc(sizeof(*auth)); auth->type = 0; auth->value = NULL; auth->redirect = NULL; auth->validator = NULL; auth->name = kore_strdup(name); TAILQ_INSERT_TAIL(&auth_list, auth, list); return (KORE_RESULT_OK); } int kore_auth_run(struct http_request *req, struct kore_auth *auth) { int r; switch (auth->type) { case KORE_AUTH_TYPE_COOKIE: r = kore_auth_cookie(req, auth); break; case KORE_AUTH_TYPE_HEADER: r = kore_auth_header(req, auth); break; case KORE_AUTH_TYPE_REQUEST: r = kore_auth_request(req, auth); break; default: kore_log(LOG_NOTICE, "unknown auth type %d", auth->type); return (KORE_RESULT_ERROR); } switch (r) { case KORE_RESULT_OK: req->flags |= HTTP_REQUEST_AUTHED; /* FALLTHROUGH */ case KORE_RESULT_RETRY: return (r); default: break; } /* Authentication types of "request" send their own HTTP responses. */ if (auth->type == KORE_AUTH_TYPE_REQUEST) return (r); if (auth->redirect == NULL) { http_response(req, HTTP_STATUS_FORBIDDEN, NULL, 0); return (KORE_RESULT_ERROR); } http_response_header(req, "location", auth->redirect); http_response(req, HTTP_STATUS_FOUND, NULL, 0); return (KORE_RESULT_ERROR); } int kore_auth_cookie(struct http_request *req, struct kore_auth *auth) { const char *hdr; int i, v; size_t len, slen; char *value, *c, *cookie, *cookies[HTTP_MAX_COOKIES]; if (!http_request_header(req, "cookie", &hdr)) return (KORE_RESULT_ERROR); cookie = kore_strdup(hdr); slen = strlen(auth->value); v = kore_split_string(cookie, ";", cookies, HTTP_MAX_COOKIES); for (i = 0; i < v; i++) { for (c = cookies[i]; isspace(*(unsigned char *)c); c++) ; len = MIN(slen, strlen(cookies[i])); if (!strncmp(c, auth->value, len)) break; } if (i == v) { kore_free(cookie); return (KORE_RESULT_ERROR); } c = cookies[i]; if ((value = strchr(c, '=')) == NULL) { kore_free(cookie); return (KORE_RESULT_ERROR); } i = kore_validator_check(req, auth->validator, ++value); kore_free(cookie); return (i); } int kore_auth_header(struct http_request *req, struct kore_auth *auth) { const char *header; if (!http_request_header(req, auth->value, &header)) return (KORE_RESULT_ERROR); return (kore_validator_check(req, auth->validator, header)); } int kore_auth_request(struct http_request *req, struct kore_auth *auth) { int ret; req->flags |= HTTP_VALIDATOR_IS_REQUEST; ret = kore_validator_check(req, auth->validator, req); req->flags &= ~HTTP_VALIDATOR_IS_REQUEST; return (ret); } struct kore_auth * kore_auth_lookup(const char *name) { struct kore_auth *auth; TAILQ_FOREACH(auth, &auth_list, list) { if (!strcmp(auth->name, name)) return (auth); } return (NULL); } kore-4.2.3/src/bsd.c000066400000000000000000000147041430066302400141670ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #if defined(__FreeBSD_version) #include #endif #include #include #include "kore.h" #if defined(KORE_USE_PGSQL) #include "pgsql.h" #endif #if defined(KORE_USE_TASKS) #include "tasks.h" #endif static int kfd = -1; static int scheduled = 0; static struct kevent *events = NULL; static u_int32_t event_count = 0; #if defined(KORE_USE_PLATFORM_PLEDGE) static char pledges[256] = { "stdio rpath inet" }; #endif void kore_platform_init(void) { long n; size_t len = sizeof(n); int mib[] = { CTL_HW, HW_NCPU }; if (sysctl(mib, 2, &n, &len, NULL, 0) == -1) { cpu_count = 1; } else { cpu_count = (u_int16_t)n; } } void kore_platform_worker_setcpu(struct kore_worker *kw) { #if defined(__FreeBSD_version) cpuset_t cpuset; CPU_ZERO(&cpuset); CPU_SET(kw->cpu, &cpuset); if (cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(cpuset), &cpuset) == -1) { fatal("failed: %s", errno_s); } #endif /* __FreeBSD_version */ } void kore_platform_event_init(void) { if (kfd != -1) close(kfd); if (events != NULL) kore_free(events); if ((kfd = kqueue()) == -1) fatal("kqueue(): %s", errno_s); event_count = (worker_max_connections * 2) + nlisteners; events = kore_calloc(event_count, sizeof(struct kevent)); } void kore_platform_event_cleanup(void) { if (kfd != -1) { close(kfd); kfd = -1; } if (events != NULL) { kore_free(events); events = NULL; } } void kore_platform_event_wait(u_int64_t timer) { u_int32_t r; struct kore_event *evt; int n, i; struct timespec timeo, *ts; if (timer == KORE_WAIT_INFINITE) { ts = NULL; } else { timeo.tv_sec = timer / 1000; timeo.tv_nsec = (timer % 1000) * 1000000; ts = &timeo; } n = kevent(kfd, NULL, 0, events, event_count, ts); if (n == -1) { if (errno == EINTR) return; fatal("kevent(): %s", errno_s); } for (i = 0; i < n; i++) { evt = (struct kore_event *)events[i].udata; if (evt == NULL) fatal("evt == NULL"); r = 0; if (events[i].filter == EVFILT_READ) evt->flags |= KORE_EVENT_READ; if (events[i].filter == EVFILT_WRITE) evt->flags |= KORE_EVENT_WRITE; if (events[i].flags & EV_EOF || events[i].flags & EV_ERROR) r = 1; evt->handle(evt, r); } } void kore_platform_event_all(int fd, void *c) { kore_platform_event_schedule(fd, EVFILT_READ, EV_ADD | EV_CLEAR, c); kore_platform_event_schedule(fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, c); } void kore_platform_event_level_all(int fd, void *c) { kore_platform_event_schedule(fd, EVFILT_READ, EV_ADD, c); kore_platform_event_schedule(fd, EVFILT_WRITE, EV_ADD, c); } void kore_platform_event_level_read(int fd, void *c) { kore_platform_event_schedule(fd, EVFILT_READ, EV_ADD, c); } void kore_platform_event_schedule(int fd, int type, int flags, void *data) { struct kevent event[1]; EV_SET(&event[0], fd, type, flags, 0, 0, data); if (kevent(kfd, event, 1, NULL, 0, NULL) == -1 && errno != ENOENT) fatal("kevent: %s", errno_s); } void kore_platform_enable_accept(void) { struct listener *l; struct kore_server *srv; int flags; if (scheduled == 0) { scheduled = 1; flags = EV_ADD | EV_ENABLE; } else { flags = EV_ENABLE; } LIST_FOREACH(srv, &kore_servers, list) { LIST_FOREACH(l, &srv->listeners, list) { kore_platform_event_schedule(l->fd, EVFILT_READ, flags, l); } } } void kore_platform_disable_accept(void) { struct listener *l; struct kore_server *srv; LIST_FOREACH(srv, &kore_servers, list) { LIST_FOREACH(l, &srv->listeners, list) { kore_platform_event_schedule(l->fd, EVFILT_READ, EV_DISABLE, l); } } } void kore_platform_schedule_read(int fd, void *data) { kore_platform_event_schedule(fd, EVFILT_READ, EV_ADD | EV_CLEAR, data); } void kore_platform_schedule_write(int fd, void *data) { kore_platform_event_schedule(fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, data); } void kore_platform_disable_read(int fd) { kore_platform_event_schedule(fd, EVFILT_READ, EV_DELETE, NULL); } void kore_platform_disable_write(int fd) { kore_platform_event_schedule(fd, EVFILT_WRITE, EV_DELETE, NULL); } void kore_platform_proctitle(const char *title) { #ifdef __MACH__ kore_proctitle(title); #else setproctitle("%s", title); #endif } #if defined(KORE_USE_PLATFORM_SENDFILE) int kore_platform_sendfile(struct connection *c, struct netbuf *nb) { int ret; off_t len, smin; smin = nb->fd_len - nb->fd_off; len = MIN(SENDFILE_PAYLOAD_MAX, smin); #if defined(__MACH__) ret = sendfile(nb->file_ref->fd, c->fd, nb->fd_off, &len, NULL, 0); #else ret = sendfile(nb->file_ref->fd, c->fd, nb->fd_off, len, NULL, &len, 0); #endif if (ret == -1) { if (errno == EAGAIN) { nb->fd_off += len; c->evt.flags &= ~KORE_EVENT_WRITE; return (KORE_RESULT_OK); } if (errno == EINTR) { nb->fd_off += len; return (KORE_RESULT_OK); } return (KORE_RESULT_ERROR); } nb->fd_off += len; if (len == 0 || nb->fd_off == nb->fd_len) { net_remove_netbuf(c, nb); c->snb = NULL; } return (KORE_RESULT_OK); } #endif void kore_platform_sandbox(void) { #if defined(KORE_USE_PLATFORM_PLEDGE) kore_platform_pledge(); #endif } #if defined(KORE_USE_PLATFORM_PLEDGE) void kore_platform_pledge(void) { if (worker->id == KORE_WORKER_KEYMGR || worker->id == KORE_WORKER_ACME) return; if (pledge(pledges, NULL) == -1) fatal("failed to pledge process"); } void kore_platform_add_pledge(const char *pledge) { size_t len; len = strlcat(pledges, " ", sizeof(pledges)); if (len >= sizeof(pledges)) fatal("truncation on pledges"); len = strlcat(pledges, pledge, sizeof(pledges)); if (len >= sizeof(pledges)) fatal("truncation on pledges (%s)", pledge); } #endif kore-4.2.3/src/buf.c000066400000000000000000000071751430066302400141770ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "kore.h" struct kore_buf * kore_buf_alloc(size_t initial) { struct kore_buf *buf; buf = kore_malloc(sizeof(*buf)); kore_buf_init(buf, initial); buf->flags = KORE_BUF_OWNER_API; return (buf); } void kore_buf_init(struct kore_buf *buf, size_t initial) { if (initial > 0) buf->data = kore_malloc(initial); else buf->data = NULL; buf->length = initial; buf->offset = 0; buf->flags = 0; } void kore_buf_cleanup(struct kore_buf *buf) { kore_free(buf->data); buf->data = NULL; buf->offset = 0; buf->length = 0; } void kore_buf_free(struct kore_buf *buf) { kore_buf_cleanup(buf); if (buf->flags & KORE_BUF_OWNER_API) kore_free(buf); } void kore_buf_append(struct kore_buf *buf, const void *data, size_t len) { if ((buf->offset + len) < len) fatal("overflow in kore_buf_append"); if ((buf->offset + len) > buf->length) { buf->length += len; buf->data = kore_realloc(buf->data, buf->length); } memcpy((buf->data + buf->offset), data, len); buf->offset += len; } void kore_buf_appendv(struct kore_buf *buf, const char *fmt, va_list args) { int l; va_list copy; char *b, sb[BUFSIZ]; va_copy(copy, args); l = vsnprintf(sb, sizeof(sb), fmt, args); if (l == -1) fatal("kore_buf_appendv(): vsnprintf error"); if ((size_t)l >= sizeof(sb)) { l = vasprintf(&b, fmt, copy); if (l == -1) fatal("kore_buf_appendv(): error or truncation"); } else { b = sb; } kore_buf_append(buf, b, l); if (b != sb) free(b); va_end(copy); } void kore_buf_appendf(struct kore_buf *buf, const char *fmt, ...) { va_list args; va_start(args, fmt); kore_buf_appendv(buf, fmt, args); va_end(args); } char * kore_buf_stringify(struct kore_buf *buf, size_t *len) { char c; if (len != NULL) *len = buf->offset; c = '\0'; kore_buf_append(buf, &c, sizeof(c)); return ((char *)buf->data); } u_int8_t * kore_buf_release(struct kore_buf *buf, size_t *len) { u_int8_t *p; p = buf->data; *len = buf->offset; buf->data = NULL; kore_buf_free(buf); return (p); } void kore_buf_replace_string(struct kore_buf *b, const char *src, const void *dst, size_t len) { char *key, *end, *tmp, *p; size_t blen, off, off2, nlen, klen; off = 0; klen = strlen(src); for (;;) { blen = b->offset; nlen = blen + len; p = (char *)b->data; key = kore_mem_find(p + off, b->offset - off, src, klen); if (key == NULL) break; end = key + klen; off = key - p; off2 = ((char *)(b->data + b->offset) - end); tmp = kore_malloc(nlen); memcpy(tmp, p, off); if (dst != NULL) memcpy((tmp + off), dst, len); memcpy((tmp + off + len), end, off2); kore_free(b->data); b->data = (u_int8_t *)tmp; b->offset = off + len + off2; b->length = nlen; off = off + len; } } void kore_buf_reset(struct kore_buf *buf) { buf->offset = 0; } kore-4.2.3/src/cli.c000066400000000000000000001557661430066302400142040ustar00rootroot00000000000000/* * Copyright (c) 2014-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #if !defined(KODEV_MINIMAL) #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define errno_s strerror(errno) #define ssl_errno_s ERR_error_string(ERR_get_error(), NULL) #define LD_FLAGS_MAX 300 #define CFLAGS_MAX 300 #define CXXFLAGS_MAX CFLAGS_MAX #define BUILD_NOBUILD 0 #define BUILD_C 1 #define BUILD_CXX 2 struct cli_buf { u_int8_t *data; size_t length; size_t offset; }; struct mime_type { char *ext; char *type; TAILQ_ENTRY(mime_type) list; }; TAILQ_HEAD(mime_list, mime_type); struct buildopt { char *name; char *kore_source; char *kore_flavor; int flavor_nohttp; int single_binary; struct cli_buf *cflags; struct cli_buf *cxxflags; struct cli_buf *ldflags; TAILQ_ENTRY(buildopt) list; }; TAILQ_HEAD(buildopt_list, buildopt); struct cmd { const char *name; const char *descr; void (*cb)(int, char **); }; struct filegen { void (*cb)(void); }; struct cfile { struct stat st; int build; char *name; char *fpath; char *opath; TAILQ_ENTRY(cfile) list; }; TAILQ_HEAD(cfile_list, cfile); static struct cli_buf *cli_buf_alloc(size_t); static void cli_buf_free(struct cli_buf *); static char *cli_buf_stringify(struct cli_buf *, size_t *); static void cli_buf_append(struct cli_buf *, const void *, size_t); static void cli_buf_appendf(struct cli_buf *, const char *, ...) __attribute__((format (printf, 2, 3))); static void cli_buf_appendv(struct cli_buf *, const char *, va_list) __attribute__((format (printf, 2, 0))); static void *cli_malloc(size_t); static char *cli_strdup(const char *); static void *cli_realloc(void *, size_t); static char *cli_text_trim(char *, size_t); static char *cli_read_line(FILE *, char *, size_t); static long long cli_strtonum(const char *, long long, long long); static int cli_split_string(char *, const char *, char **, size_t); static void usage(void) __attribute__((noreturn)); static void fatal(const char *, ...) __attribute__((noreturn)) __attribute__((format (printf, 1, 2))); static void cli_file_close(int); static void cli_run_kore(void); static void cli_run_kore_python(void); static void cli_compile_kore(void *); static void cli_link_application(void *); static void cli_compile_source_file(void *); static void cli_mkdir(const char *, int); static int cli_dir_exists(const char *); static int cli_file_exists(const char *); static void cli_cleanup_files(const char *); static void cli_build_cflags(struct buildopt *); static void cli_build_cxxflags(struct buildopt *); static void cli_build_ldflags(struct buildopt *); static void cli_file_read(int, char **, size_t *); static void cli_file_writef(int, const char *, ...) __attribute__((format (printf, 2, 3))); static void cli_file_open(const char *, int, int *); static void cli_file_remove(char *, struct dirent *); static void cli_build_asset(char *, struct dirent *); static void cli_file_write(int, const void *, size_t); static int cli_vasprintf(char **, const char *, ...) __attribute__((format (printf, 2, 3))); static void cli_spawn_proc(void (*cb)(void *), void *); static void cli_write_asset(const char *, const char *, struct buildopt *); static void cli_register_kore_file(char *, struct dirent *); static void cli_register_source_file(char *, struct dirent *); static int cli_file_requires_build(struct stat *, const char *); static void cli_find_files(const char *, void (*cb)(char *, struct dirent *)); static void cli_add_source_file(char *, char *, char *, struct stat *, int); static struct buildopt *cli_buildopt_default(void); static struct buildopt *cli_buildopt_new(const char *); static struct buildopt *cli_buildopt_find(const char *); static void cli_buildopt_cleanup(void); static void cli_buildopt_parse(const char *); static void cli_buildopt_cflags(struct buildopt *, const char *); static void cli_buildopt_cxxflags(struct buildopt *, const char *); static void cli_buildopt_ldflags(struct buildopt *, const char *); static void cli_buildopt_single_binary(struct buildopt *, const char *); static void cli_buildopt_kore_source(struct buildopt *, const char *); static void cli_buildopt_kore_flavor(struct buildopt *, const char *); static void cli_buildopt_mime(struct buildopt *, const char *); static void cli_build_flags_common(struct buildopt *, struct cli_buf *); static void cli_flavor_load(void); static void cli_flavor_change(const char *); static void cli_kore_load_file(const char *, struct buildopt *, char **, size_t *); static void cli_run(int, char **); static void cli_help(int, char **); static void cli_info(int, char **); static void cli_build(int, char **); static void cli_clean(int, char **); static void cli_source(int, char **); static void cli_reload(int, char **); static void cli_flavor(int, char **); static void cli_cflags(int, char **); static void cli_ldflags(int, char **); static void cli_genasset(int, char **); static void cli_genasset_help(void); #if !defined(KODEV_MINIMAL) static void cli_create(int, char **); static void cli_create_help(void); static void file_create_src(void); static void file_create_config(void); static void file_create_gitignore(void); static void file_create_python_src(void); static void cli_generate_certs(void); static void cli_file_create(const char *, const char *, size_t); #endif static struct cmd cmds[] = { { "help", "this help text", cli_help }, { "run", "run an application (-fnr implied)", cli_run }, { "gen", "generate asset file for compilation", cli_genasset }, { "reload", "reload the application (SIGHUP)", cli_reload }, { "info", "show info on kore on this system", cli_info }, { "build", "build an application", cli_build }, { "clean", "cleanup the build files", cli_clean }, { "source", "print the path to kore sources", cli_source }, #if !defined(KODEV_MINIMAL) { "create", "create a new application skeleton", cli_create }, #endif { "flavor", "switch between build flavors", cli_flavor }, { "cflags", "show kore CFLAGS", cli_cflags }, { "ldflags", "show kore LDFLAGS", cli_ldflags }, { NULL, NULL, NULL } }; #if !defined(KODEV_MINIMAL) static struct filegen gen_files[] = { { file_create_src }, { file_create_config }, { file_create_gitignore }, { NULL } }; static const char *gen_dirs[] = { "src", "cert", "conf", "assets", NULL }; static const char *python_gen_dirs[] = { "cert", NULL }; static struct filegen python_gen_files[] = { { file_create_python_src }, { file_create_gitignore }, { NULL } }; static const char *http_serveable_function = "int\n" "asset_serve_%s_%s(struct http_request *req)\n" "{\n" " http_serveable(req, asset_%s_%s, asset_len_%s_%s,\n" " asset_sha256_%s_%s, \"%s\");\n" " return (KORE_RESULT_OK);\n" "}\n"; static const char *src_data = "#include \n" "#include \n" "\n" "int\t\tpage(struct http_request *);\n" "\n" "int\n" "page(struct http_request *req)\n" "{\n" "\thttp_response(req, 200, NULL, 0);\n" "\treturn (KORE_RESULT_OK);\n" "}\n"; static const char *config_data = "# %s configuration\n" "\n" "server tls {\n" "\tbind 127.0.0.1 8888\n" "}\n" "\n" "load\t\t./%s.so\n" "\n" "domain * {\n" "\tattach\t\ttls\n" "\n" "\tcertfile\tcert/server.pem\n" "\tcertkey\t\tcert/key.pem\n" "\n" "\troute / {\n" "\t\thandler page\n" "\t}\n" "\n" "}\n"; static const char *build_data = "# %s build config\n" "# You can switch flavors using: kodev flavor [newflavor]\n" "\n" "# Set to yes if you wish to produce a single binary instead\n" "# of a dynamic library. If you set this to yes you must also\n" "# set kore_source together with kore_flavor.\n" "#single_binary=no\n" "#kore_source=/home/joris/src/kore\n" "#kore_flavor=\n" "\n" "# The flags below are shared between flavors\n" "cflags=-Wall -Wmissing-declarations -Wshadow\n" "cflags=-Wstrict-prototypes -Wmissing-prototypes\n" "cflags=-Wpointer-arith -Wcast-qual -Wsign-compare\n" "\n" "cxxflags=-Wall -Wmissing-declarations -Wshadow\n" "cxxflags=-Wpointer-arith -Wcast-qual -Wsign-compare\n" "\n" "# Mime types for assets served via the builtin asset_serve_*\n" "#mime_add=txt:text/plain; charset=utf-8\n" "#mime_add=png:image/png\n" "#mime_add=html:text/html; charset=utf-8\n" "\n" "dev {\n" " # These flags are added to the shared ones when\n" " # you build the \"dev\" flavor.\n" " cflags=-g\n" " cxxflags=-g\n" "}\n" "\n" "#prod {\n" "# You can specify additional flags here which are only\n" "# included if you build with the \"prod\" flavor.\n" "#}\n"; static const char *python_init_data = "from .app import koreapp\n"; static const char *python_app_data = "import kore\n" "\n" "class KoreApp:\n" " def configure(self, args):\n" " kore.config.deployment = \"development\"\n" " kore.server(\"default\", ip=\"127.0.0.1\", port=\"8888\")\n" "\n" " d = kore.domain(\"*\",\n" " attach=\"default\",\n" " key=\"cert/key.pem\",\n" " cert=\"cert/server.pem\",\n" " )\n" "\n" " d.route(\"/\", self.index, methods=[\"get\"])\n" "\n" " async def index(self, req):\n" " req.response(200, b'')\n" "\n" "koreapp = KoreApp()"; static const char *gitignore = "*.o\n.flavor\n.objs\n%s.so\nassets.h\ncert\n"; #endif /* !KODEV_MINIMAL */ static int s_fd = -1; static char *appl = NULL; static int run_after = 0; static char *compiler_c = "cc"; static char *compiler_cpp = "c++"; static char *compiler_ld = "cc"; static const char *prefix = PREFIX; static struct mime_list mime_types; static struct cfile_list source_files; static struct buildopt_list build_options; static int source_files_count; static int cxx_files_count; static struct cmd *command = NULL; static int cflags_count = 0; static int genasset_cmd = 0; static int cxxflags_count = 0; static int ldflags_count = 0; static char *flavor = NULL; static char *out_dir = "."; static char *object_dir = ".objs"; static char *cflags[CFLAGS_MAX]; static char *cxxflags[CXXFLAGS_MAX]; static char *ldflags[LD_FLAGS_MAX]; static void usage(void) { int i; fprintf(stderr, "Usage: kodev [command]\n"); #if defined(KODEV_MINIMAL) fprintf(stderr, "minimal (only build commands supported)\n"); #endif fprintf(stderr, "\nAvailable commands:\n"); for (i = 0; cmds[i].name != NULL; i++) printf("\t%s\t%s\n", cmds[i].name, cmds[i].descr); fprintf(stderr, "\nFind more information on https://kore.io\n"); exit(1); } int main(int argc, char **argv) { int i; char *env; if (argc < 2) usage(); argc--; argv++; if ((env = getenv("KORE_PREFIX")) != NULL) prefix = env; if ((env = getenv("KORE_OBJDIR")) != NULL) object_dir = env; if ((env = getenv("KODEV_OUTPUT")) != NULL) out_dir = env; (void)umask(S_IWGRP | S_IWOTH); for (i = 0; cmds[i].name != NULL; i++) { if (!strcmp(argv[0], cmds[i].name)) { if (strcmp(argv[0], "create")) { argc--; argv++; } command = &cmds[i]; cmds[i].cb(argc, argv); break; } } if (cmds[i].name == NULL) { fprintf(stderr, "unknown command: %s\n", argv[0]); usage(); } return (0); } static void cli_help(int argc, char **argv) { usage(); } #if !defined(KODEV_MINIMAL) static void cli_create_help(void) { printf("Usage: kodev create [-p] [name]\n"); printf("Synopsis:\n"); printf(" Create a new application skeleton directory structure.\n"); printf("\n"); printf(" Optional flags:\n"); printf("\t-p = generate a python application skeleton\n"); exit(1); } static void cli_create(int argc, char **argv) { char *fpath; const char **dirs; struct filegen *files; int i, ch, python; python = 0; while ((ch = getopt(argc, argv, "hp")) != -1) { switch (ch) { case 'h': cli_create_help(); break; case 'p': python = 1; break; default: cli_create_help(); break; } } argc -= optind; argv += optind; if (argc != 1) cli_create_help(); appl = argv[0]; cli_mkdir(appl, 0755); if (python) { dirs = python_gen_dirs; files = python_gen_files; } else { dirs = gen_dirs; files = gen_files; } for (i = 0; dirs[i] != NULL; i++) { (void)cli_vasprintf(&fpath, "%s/%s", appl, dirs[i]); cli_mkdir(fpath, 0755); free(fpath); } for (i = 0; files[i].cb != NULL; i++) files[i].cb(); if (chdir(appl) == -1) fatal("chdir(%s): %s", appl, errno_s); cli_generate_certs(); printf("%s created successfully!\n", appl); printf("WARNING: DO NOT USE THE GENERATED CERTIFICATE IN PRODUCTION\n"); } #endif static void cli_flavor(int argc, char **argv) { struct buildopt *bopt; char pwd[MAXPATHLEN], *conf; if (getcwd(pwd, sizeof(pwd)) == NULL) fatal("could not get cwd: %s", errno_s); appl = basename(pwd); (void)cli_vasprintf(&conf, "conf/%s.conf", appl); if (!cli_dir_exists("conf") || !cli_file_exists(conf)) fatal("%s doesn't appear to be a kore app", appl); free(conf); TAILQ_INIT(&build_options); TAILQ_INIT(&mime_types); (void)cli_buildopt_new("_default"); cli_buildopt_parse("conf/build.conf"); if (argc == 0) { cli_flavor_load(); TAILQ_FOREACH(bopt, &build_options, list) { if (!strcmp(bopt->name, "_default")) continue; if (!strcmp(bopt->name, flavor)) { printf("* %s\n", bopt->name); } else { printf(" %s\n", bopt->name); } } } else { cli_flavor_change(argv[0]); printf("changed build flavor to: %s\n", argv[0]); } cli_buildopt_cleanup(); } static void cli_build(int argc, char **argv) { #if !defined(KODEV_MINIMAL) int l; char *data; #endif struct dirent dp; struct cfile *cf; struct buildopt *bopt; struct timeval times[2]; char *build_path; char *vsrc, *vobj; int requires_relink; char *sofile, *config; char *assets_path, *p, *src_path; char pwd[PATH_MAX], *assets_header; if (getcwd(pwd, sizeof(pwd)) == NULL) fatal("could not get cwd: %s", errno_s); appl = cli_strdup(basename(pwd)); if ((p = getenv("CC")) != NULL) { compiler_c = p; compiler_ld = p; } if ((p = getenv("CXX")) != NULL) { compiler_cpp = p; compiler_ld = p; } source_files_count = 0; cxx_files_count = 0; TAILQ_INIT(&source_files); TAILQ_INIT(&build_options); TAILQ_INIT(&mime_types); (void)cli_vasprintf(&src_path, "src"); (void)cli_vasprintf(&assets_path, "assets"); (void)cli_vasprintf(&config, "conf/%s.conf", appl); (void)cli_vasprintf(&build_path, "conf/build.conf"); (void)cli_vasprintf(&assets_header, "%s/assets.h", object_dir); if (!cli_dir_exists(src_path) || !cli_file_exists(config)) fatal("%s doesn't appear to be a kore app", appl); cli_flavor_load(); bopt = cli_buildopt_new("_default"); #if !defined(KODEV_MINIMAL) if (!cli_file_exists(build_path)) { l = cli_vasprintf(&data, build_data, appl); cli_file_create("conf/build.conf", data, l); free(data); } #endif cli_find_files(src_path, cli_register_source_file); free(src_path); cli_buildopt_parse(build_path); free(build_path); if (!cli_dir_exists(object_dir)) cli_mkdir(object_dir, 0755); if (bopt->single_binary) { if (bopt->kore_source == NULL) fatal("single_binary set but not kore_source"); printf("building kore (%s)\n", bopt->kore_source); cli_spawn_proc(cli_compile_kore, bopt); (void)cli_vasprintf(&src_path, "%s/src", bopt->kore_source); cli_find_files(src_path, cli_register_kore_file); free(src_path); (void)cli_vasprintf(&vsrc, "%s/version.c", object_dir); (void)cli_vasprintf(&vobj, "%s/version.o", object_dir); cli_add_source_file("version.c", vsrc, vobj, NULL, BUILD_NOBUILD); } printf("building %s (%s)\n", appl, flavor); cli_build_cflags(bopt); cli_build_cxxflags(bopt); cli_build_ldflags(bopt); (void)unlink(assets_header); /* Generate the assets. */ cli_file_open(assets_header, O_CREAT | O_TRUNC | O_WRONLY, &s_fd); cli_file_writef(s_fd, "#ifndef __H_KORE_ASSETS_H\n"); cli_file_writef(s_fd, "#define __H_KORE_ASSETS_H\n"); if (cli_dir_exists(assets_path)) cli_find_files(assets_path, cli_build_asset); if (bopt->single_binary) { memset(&dp, 0, sizeof(dp)); dp.d_type = DT_REG; printf("adding config %s\n", config); (void)snprintf(dp.d_name, sizeof(dp.d_name), "builtin_kore.conf"); cli_build_asset(config, &dp); } cli_file_writef(s_fd, "\n#endif\n"); cli_file_close(s_fd); free(assets_path); free(config); if (cxx_files_count > 0) compiler_ld = compiler_cpp; requires_relink = 0; TAILQ_FOREACH(cf, &source_files, list) { if (cf->build == BUILD_NOBUILD) continue; printf("compiling %s\n", cf->name); cli_spawn_proc(cli_compile_source_file, cf); times[0].tv_usec = 0; times[0].tv_sec = cf->st.st_mtime; times[1] = times[0]; if (utimes(cf->opath, times) == -1) printf("utime(%s): %s\n", cf->opath, errno_s); requires_relink++; } free(assets_header); #if !defined(KODEV_MINIMAL) if (bopt->kore_flavor == NULL || !strstr(bopt->kore_flavor, "NOTLS=1")) { if (!cli_dir_exists("cert")) { cli_mkdir("cert", 0700); cli_generate_certs(); } } #endif if (bopt->single_binary) { requires_relink++; (void)cli_vasprintf(&sofile, "%s/%s", out_dir, appl); } else { (void)cli_vasprintf(&sofile, "%s/%s.so", out_dir, appl); } if (!cli_file_exists(sofile) && source_files_count > 0) requires_relink++; free(sofile); if (requires_relink) { cli_spawn_proc(cli_link_application, bopt); printf("%s built successfully!\n", appl); } else { printf("nothing to be done!\n"); } if (run_after == 0) cli_buildopt_cleanup(); } static void cli_source(int argc, char **argv) { printf("%s/share/kore/\n", prefix); } static void cli_clean(int argc, char **argv) { struct buildopt *bopt; char pwd[PATH_MAX], *bin; if (cli_dir_exists(object_dir)) cli_cleanup_files(object_dir); if (getcwd(pwd, sizeof(pwd)) == NULL) fatal("could not get cwd: %s", errno_s); appl = basename(pwd); TAILQ_INIT(&mime_types); TAILQ_INIT(&build_options); cli_flavor_load(); bopt = cli_buildopt_new("_default"); cli_buildopt_parse("conf/build.conf"); if (bopt->single_binary) (void)cli_vasprintf(&bin, "%s/%s", out_dir, appl); else (void)cli_vasprintf(&bin, "%s/%s.so", out_dir, appl); if (unlink(bin) == -1 && errno != ENOENT) printf("couldn't unlink %s: %s", bin, errno_s); free(bin); } static void cli_run(int argc, char **argv) { if (cli_file_exists("__init__.py")) { cli_run_kore_python(); return; } run_after = 1; cli_build(argc, argv); /* * We are exec()'ing kore again, while we could technically set * the right cli options manually and just continue running. */ cli_run_kore(); } static void cli_reload(int argc, char **argv) { int fd; size_t len; pid_t pid; char *buf; cli_file_open("kore.pid", O_RDONLY, &fd); cli_file_read(fd, &buf, &len); cli_file_close(fd); if (len == 0) fatal("reload: pid file is empty"); buf[len - 1] = '\0'; pid = cli_strtonum(buf, 0, UINT_MAX); if (kill(pid, SIGHUP) == -1) fatal("failed to reload: %s", errno_s); printf("reloaded application\n"); } static void cli_info(int argc, char **argv) { size_t len; struct buildopt *bopt; char *features; TAILQ_INIT(&mime_types); TAILQ_INIT(&build_options); cli_flavor_load(); bopt = cli_buildopt_new("_default"); cli_buildopt_parse("conf/build.conf"); printf("active flavor\t %s\n", flavor); printf("output type \t %s\n", (bopt->single_binary) ? "binary" : "dso"); if (bopt->single_binary) { printf("kore features\t %s\n", bopt->kore_flavor); printf("kore source \t %s\n", bopt->kore_source); } else { cli_kore_load_file("features", bopt, &features, &len); printf("kore binary \t %s/bin/kore\n", prefix); printf("kore features\t %.*s\n", (int)len, features); free(features); } } static void cli_cflags(int argc, char **argv) { struct cli_buf *buf; buf = cli_buf_alloc(128); cli_build_flags_common(NULL, buf); printf("%.*s\n", (int)buf->offset, buf->data); cli_buf_free(buf); } static void cli_ldflags(int argc, char **argv) { char *p; size_t len; cli_kore_load_file("linker", NULL, &p, &len); printf("%.*s ", (int)len, p); #if defined(__MACH__) printf("-dynamiclib -undefined suppress -flat_namespace "); #else printf("-shared "); #endif printf("\n"); free(p); } static void cli_genasset(int argc, char **argv) { struct stat st; struct dirent dp; char *hdr; genasset_cmd = 1; TAILQ_INIT(&build_options); (void)cli_buildopt_new("_default"); if (getenv("KORE_OBJDIR") == NULL) object_dir = out_dir; if (argv[0] == NULL) cli_genasset_help(); (void)cli_vasprintf(&hdr, "%s/assets.h", out_dir); (void)unlink(hdr); cli_file_open(hdr, O_CREAT | O_TRUNC | O_WRONLY, &s_fd); cli_file_writef(s_fd, "#ifndef __H_KORE_ASSETS_H\n"); cli_file_writef(s_fd, "#define __H_KORE_ASSETS_H\n"); if (stat(argv[0], &st) == -1) fatal("%s: %s", argv[0], errno_s); if (S_ISDIR(st.st_mode)) { if (cli_dir_exists(argv[0])) cli_find_files(argv[0], cli_build_asset); } else if (S_ISREG(st.st_mode)) { memset(&dp, 0, sizeof(dp)); dp.d_type = DT_REG; (void)snprintf(dp.d_name, sizeof(dp.d_name), "%s", basename(argv[0])); cli_build_asset(argv[0], &dp); } else { fatal("%s is not a directory or regular file", argv[0]); } cli_file_writef(s_fd, "\n#endif\n"); cli_file_close(s_fd); } static void cli_genasset_help(void) { printf("Usage: kodev genasset [source]\n"); printf("Synopsis:\n"); printf(" Generates asset file(s) to be used for compilation.\n"); printf(" The source can be a single file or directory.\n"); printf("\n"); printf("This command honors the KODEV_OUTPUT environment variable.\n"); printf("This command honors the KORE_OBJDIR environment variable.\n"); exit(1); } #if !defined(KODEV_MINIMAL) static void file_create_python_src(void) { char *name; (void)cli_vasprintf(&name, "%s/__init__.py", appl); cli_file_create(name, python_init_data, strlen(python_init_data)); free(name); (void)cli_vasprintf(&name, "%s/app.py", appl); cli_file_create(name, python_app_data, strlen(python_app_data)); free(name); } static void file_create_src(void) { char *name; (void)cli_vasprintf(&name, "%s/src/%s.c", appl, appl); cli_file_create(name, src_data, strlen(src_data)); free(name); } static void file_create_config(void) { int l; char *name, *data; (void)cli_vasprintf(&name, "%s/conf/%s.conf", appl, appl); l = cli_vasprintf(&data, config_data, appl, appl); cli_file_create(name, data, l); free(name); free(data); (void)cli_vasprintf(&name, "%s/conf/build.conf", appl); l = cli_vasprintf(&data, build_data, appl); cli_file_create(name, data, l); free(name); free(data); } static void file_create_gitignore(void) { int l; char *name, *data; (void)cli_vasprintf(&name, "%s/.gitignore", appl); l = cli_vasprintf(&data, gitignore, appl); cli_file_create(name, data, l); free(name); free(data); } #endif static void cli_mkdir(const char *fpath, int mode) { if (mkdir(fpath, mode) == -1) fatal("cli_mkdir(%s): %s", fpath, errno_s); } static int cli_file_exists(const char *fpath) { struct stat st; if (stat(fpath, &st) == -1) return (0); if (!S_ISREG(st.st_mode)) return (0); return (1); } static int cli_file_requires_build(struct stat *fst, const char *opath) { struct stat ost; if (stat(opath, &ost) == -1) { if (errno == ENOENT) return (1); fatal("stat(%s): %s", opath, errno_s); } return (fst->st_mtime != ost.st_mtime); } static int cli_dir_exists(const char *fpath) { struct stat st; if (stat(fpath, &st) == -1) return (0); if (!S_ISDIR(st.st_mode)) return (0); return (1); } static void cli_file_open(const char *fpath, int flags, int *fd) { if ((*fd = open(fpath, flags, 0644)) == -1) fatal("cli_file_open(%s): %s", fpath, errno_s); } static void cli_file_read(int fd, char **buf, size_t *len) { struct stat st; char *p; ssize_t ret; size_t offset, bytes; if (fstat(fd, &st) == -1) fatal("fstat(): %s", errno_s); if (st.st_size > USHRT_MAX) fatal("cli_file_read: way too big"); offset = 0; bytes = st.st_size; p = cli_malloc(bytes); while (offset != bytes) { ret = read(fd, p + offset, bytes - offset); if (ret == -1) { if (errno == EINTR) continue; fatal("read(): %s", errno_s); } if (ret == 0) fatal("unexpected EOF"); offset += (size_t)ret; } *buf = p; *len = bytes; } static void cli_file_close(int fd) { if (close(fd) == -1) printf("warning: close() %s\n", errno_s); } static void cli_file_writef(int fd, const char *fmt, ...) { int l; char *buf; va_list args; va_start(args, fmt); l = vasprintf(&buf, fmt, args); va_end(args); if (l == -1) fatal("cli_file_writef"); cli_file_write(fd, buf, l); free(buf); } static void cli_file_write(int fd, const void *buf, size_t len) { ssize_t r; const u_int8_t *d; size_t written; d = buf; written = 0; while (written != len) { r = write(fd, d + written, len - written); if (r == -1) { if (errno == EINTR) continue; fatal("cli_file_write: %s", errno_s); } written += r; } } #if !defined(KODEV_MINIMAL) static void cli_file_create(const char *name, const char *data, size_t len) { int fd; cli_file_open(name, O_CREAT | O_TRUNC | O_WRONLY, &fd); cli_file_write(fd, data, len); cli_file_close(fd); printf("created %s\n", name); } #endif static void cli_write_asset(const char *n, const char *e, struct buildopt *bopt) { cli_file_writef(s_fd, "extern const u_int8_t asset_%s_%s[];\n", n, e); cli_file_writef(s_fd, "extern const u_int32_t asset_len_%s_%s;\n", n, e); cli_file_writef(s_fd, "extern const time_t asset_mtime_%s_%s;\n", n, e); #if !defined(KODEV_MINIMAL) cli_file_writef(s_fd, "extern const char *asset_sha256_%s_%s;\n", n, e); #endif if (bopt->flavor_nohttp == 0) { cli_file_writef(s_fd, "int asset_serve_%s_%s(struct http_request *);\n", n, e); } } static void cli_build_asset(char *fpath, struct dirent *dp) { u_int8_t *d; struct stat st; #if !defined(KODEV_MINIMAL) SHA256_CTX sctx; int i, len; struct mime_type *mime; const char *mime_type; u_int8_t digest[SHA256_DIGEST_LENGTH]; char hash[(SHA256_DIGEST_LENGTH * 2) + 1]; #endif off_t off; void *base; struct buildopt *bopt; int in, out; char *cpath, *ext, *opath, *p, *name; bopt = cli_buildopt_default(); /* Ignore hidden files and some editor files */ if (dp->d_name[0] == '.' || strrchr(dp->d_name, '~') || strrchr(dp->d_name, '#')) { return; } name = cli_strdup(dp->d_name); /* Grab the extension as we're using it in the symbol name. */ if ((ext = strrchr(name, '.')) == NULL) fatal("couldn't find ext in %s", name); /* Replace dots, spaces, etc etc with underscores. */ for (p = name; *p != '\0'; p++) { if (*p == '.' || isspace((unsigned char)*p) || *p == '-') *p = '_'; } /* Grab inode information. */ if (stat(fpath, &st) == -1) fatal("stat: %s %s", fpath, errno_s); /* If this file was empty, skip it. */ if (st.st_size == 0) { printf("skipping empty asset %s\n", name); free(name); return; } (void)cli_vasprintf(&opath, "%s/%s.o", object_dir, name); (void)cli_vasprintf(&cpath, "%s/%s.c", object_dir, name); /* Check if the file needs to be built. */ if (!cli_file_requires_build(&st, opath)) { *(ext)++ = '\0'; cli_write_asset(name, ext, bopt); *ext = '_'; cli_add_source_file(name, cpath, opath, &st, BUILD_NOBUILD); free(name); return; } /* Open the file we're converting. */ cli_file_open(fpath, O_RDONLY, &in); /* mmap our in file. */ if ((base = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, in, 0)) == MAP_FAILED) fatal("mmap: %s %s", fpath, errno_s); /* Create the c file where we will write too. */ cli_file_open(cpath, O_CREAT | O_TRUNC | O_WRONLY, &out); /* No longer need name so cut off the extension. */ printf("building asset %s\n", dp->d_name); *(ext)++ = '\0'; /* Start generating the file. */ cli_file_writef(out, "/* Auto generated */\n"); cli_file_writef(out, "#include \n\n"); cli_file_writef(out, "#include \n"); cli_file_writef(out, "#include \n\n"); cli_file_writef(out, "#include \"assets.h\"\n\n"); /* Write the file data as a byte array. */ cli_file_writef(out, "const u_int8_t asset_%s_%s[] = {\n", name, ext); d = base; for (off = 0; off < st.st_size; off++) cli_file_writef(out, "0x%02x,", *d++); /* * Always NUL-terminate the asset, even if this NUL is not included in * the actual length. This way assets can be cast to char * without * any additional thinking for the developer. */ cli_file_writef(out, "0x00"); #if !defined(KODEV_MINIMAL) /* Calculate the SHA256 digest of the contents. */ (void)SHA256_Init(&sctx); (void)SHA256_Update(&sctx, base, st.st_size); (void)SHA256_Final(digest, &sctx); for (i = 0; i < (int)sizeof(digest); i++) { len = snprintf(hash + (i * 2), sizeof(hash) - (i * 2), "%02x", digest[i]); if (len == -1 || (size_t)len >= sizeof(hash)) fatal("failed to convert SHA256 digest to hex"); } mime = NULL; TAILQ_FOREACH(mime, &mime_types, list) { if (!strcasecmp(mime->ext, ext)) break; } if (mime != NULL) mime_type = mime->type; else mime_type = "text/plain"; #endif /* Add the meta data. */ cli_file_writef(out, "};\n\n"); cli_file_writef(out, "const u_int32_t asset_len_%s_%s = %" PRIu32 ";\n", name, ext, (u_int32_t)st.st_size); cli_file_writef(out, "const time_t asset_mtime_%s_%s = %" PRId64 ";\n", name, ext, (int64_t)st.st_mtime); #if !defined(KODEV_MINIMAL) if (bopt->flavor_nohttp == 0) { cli_file_writef(out, "const char *asset_sha256_%s_%s = \"\\\"%s\\\"\";\n", name, ext, hash); cli_file_writef(out, http_serveable_function, name, ext, name, ext, name, ext, name, ext, mime_type); } #endif /* Write the file symbols into assets.h so they can be used. */ cli_write_asset(name, ext, bopt); /* Cleanup static file source. */ if (munmap(base, st.st_size) == -1) fatal("munmap: %s %s", fpath, errno_s); /* Cleanup fds */ cli_file_close(in); cli_file_close(out); /* Restore the original name */ *--ext = '.'; /* Register the .c file now (cpath is free'd later). */ if (genasset_cmd == 0) cli_add_source_file(name, cpath, opath, &st, BUILD_C); free(name); } static void cli_add_source_file(char *name, char *fpath, char *opath, struct stat *st, int build) { struct cfile *cf; source_files_count++; cf = cli_malloc(sizeof(*cf)); if (st != NULL) cf->st = *st; else memset(&cf->st, 0, sizeof(cf->st)); cf->build = build; cf->fpath = fpath; cf->opath = opath; cf->name = cli_strdup(name); TAILQ_INSERT_TAIL(&source_files, cf, list); } static void cli_register_source_file(char *fpath, struct dirent *dp) { struct stat st; char *ext, *opath; int build; if ((ext = strrchr(fpath, '.')) == NULL || (strcmp(ext, ".c") && strcmp(ext, ".cpp"))) return; if (stat(fpath, &st) == -1) fatal("stat(%s): %s", fpath, errno_s); if (!strcmp(ext, ".cpp")) cxx_files_count++; (void)cli_vasprintf(&opath, "%s/%s.o", object_dir, dp->d_name); if (!cli_file_requires_build(&st, opath)) { build = BUILD_NOBUILD; } else if (!strcmp(ext, ".cpp")) { build = BUILD_CXX; } else { build = BUILD_C; } cli_add_source_file(dp->d_name, fpath, opath, &st, build); } static void cli_register_kore_file(char *fpath, struct dirent *dp) { struct stat st, ost; char *opath, *ext, *fname; if ((ext = strrchr(fpath, '.')) == NULL || strcmp(ext, ".c")) return; if (stat(fpath, &st) == -1) fatal("stat(%s): %s", fpath, errno_s); *ext = '\0'; if ((fname = basename(fpath)) == NULL) fatal("basename failed"); (void)cli_vasprintf(&opath, "%s/%s.o", object_dir, fname); /* Silently ignore non existing object files for kore source files. */ if (stat(opath, &ost) == -1) { free(opath); return; } cli_add_source_file(dp->d_name, fpath, opath, &st, BUILD_NOBUILD); } static void cli_file_remove(char *fpath, struct dirent *dp) { if (unlink(fpath) == -1) fprintf(stderr, "couldn't unlink %s: %s", fpath, errno_s); } static void cli_find_files(const char *path, void (*cb)(char *, struct dirent *)) { DIR *d; struct stat st; struct dirent *dp; char *fpath; if ((d = opendir(path)) == NULL) fatal("cli_find_files: opendir(%s): %s", path, errno_s); while ((dp = readdir(d)) != NULL) { if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; (void)cli_vasprintf(&fpath, "%s/%s", path, dp->d_name); if (stat(fpath, &st) == -1) { fprintf(stderr, "stat(%s): %s\n", fpath, errno_s); free(fpath); continue; } if (S_ISDIR(st.st_mode)) { cli_find_files(fpath, cb); free(fpath); } else if (S_ISREG(st.st_mode)) { cb(fpath, dp); } else { fprintf(stderr, "ignoring %s\n", fpath); free(fpath); } } closedir(d); } #if !defined(KODEV_MINIMAL) static void cli_generate_certs(void) { BIGNUM *e; FILE *fp; time_t now; X509_NAME *name; EVP_PKEY *pkey; X509 *x509; RSA *kpair; char issuer[64]; /* Create new certificate. */ if ((x509 = X509_new()) == NULL) fatal("X509_new(): %s", ssl_errno_s); /* Generate version 3. */ if (!X509_set_version(x509, 2)) fatal("X509_set_version(): %s", ssl_errno_s); /* Generate RSA keys. */ if ((pkey = EVP_PKEY_new()) == NULL) fatal("EVP_PKEY_new(): %s", ssl_errno_s); if ((kpair = RSA_new()) == NULL) fatal("RSA_new(): %s", ssl_errno_s); if ((e = BN_new()) == NULL) fatal("BN_new(): %s", ssl_errno_s); if (!BN_set_word(e, 65537)) fatal("BN_set_word(): %s", ssl_errno_s); if (!RSA_generate_key_ex(kpair, 2048, e, NULL)) fatal("RSA_generate_key_ex(): %s", ssl_errno_s); BN_free(e); if (!EVP_PKEY_assign_RSA(pkey, kpair)) fatal("EVP_PKEY_assign_RSA(): %s", ssl_errno_s); /* Set serial number to current timestamp. */ time(&now); if (!ASN1_INTEGER_set(X509_get_serialNumber(x509), now)) fatal("ASN1_INTEGER_set(): %s", ssl_errno_s); /* Not before and not after dates. */ if (!X509_gmtime_adj(X509_get_notBefore(x509), 0)) fatal("X509_gmtime_adj(): %s", ssl_errno_s); if (!X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60 * 24 * 3000)) fatal("X509_gmtime_adj(): %s", ssl_errno_s); /* Attach the pkey to the certificate. */ if (!X509_set_pubkey(x509, pkey)) fatal("X509_set_pubkey(): %s", ssl_errno_s); /* Set certificate information. */ if ((name = X509_get_subject_name(x509)) == NULL) fatal("X509_get_subject_name(): %s", ssl_errno_s); (void)snprintf(issuer, sizeof(issuer), "kore autogen: %s", appl); if (!X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char *)"SE", -1, -1, 0)) fatal("X509_NAME_add_entry_by_txt(): C %s", ssl_errno_s); if (!X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char *)issuer, -1, -1, 0)) fatal("X509_NAME_add_entry_by_txt(): O %s", ssl_errno_s); if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)"localhost", -1, -1, 0)) fatal("X509_NAME_add_entry_by_txt(): CN %s", ssl_errno_s); if (!X509_set_issuer_name(x509, name)) fatal("X509_set_issuer_name(): %s", ssl_errno_s); if (!X509_sign(x509, pkey, EVP_sha256())) fatal("X509_sign(): %s", ssl_errno_s); if ((fp = fopen("cert/key.pem", "w")) == NULL) fatal("fopen(cert/key.pem): %s", errno_s); if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) fatal("PEM_write_PrivateKey(): %s", ssl_errno_s); fclose(fp); if ((fp = fopen("cert/server.pem", "w")) == NULL) fatal("fopen(cert/server.pem): %s", errno_s); if (!PEM_write_X509(fp, x509)) fatal("PEM_write_X509(%s)", errno_s); fclose(fp); EVP_PKEY_free(pkey); X509_free(x509); } #endif static void cli_compile_source_file(void *arg) { struct cfile *cf; int idx, i; char **flags; char *compiler; int flags_count; char *args[34 + CFLAGS_MAX]; cf = arg; switch (cf->build) { case BUILD_C: compiler = compiler_c; flags = cflags; flags_count = cflags_count; break; case BUILD_CXX: compiler = compiler_cpp; flags = cxxflags; flags_count = cxxflags_count; break; default: fatal("cli_compile_file: unexpected file type: %d", cf->build); break; } idx = 0; args[idx++] = compiler; for (i = 0; i < flags_count; i++) args[idx++] = flags[i]; args[idx++] = "-I"; args[idx++] = object_dir; args[idx++] = "-c"; args[idx++] = cf->fpath; args[idx++] = "-o"; args[idx++] = cf->opath; args[idx] = NULL; execvp(compiler, args); fatal("failed to start '%s': %s", compiler, errno_s); } static void cli_link_application(void *arg) { struct cfile *cf; struct buildopt *bopt; int idx, i; char *output; char *args[source_files_count + 11 + LD_FLAGS_MAX]; bopt = arg; if (bopt->single_binary) (void)cli_vasprintf(&output, "%s/%s", out_dir, appl); else (void)cli_vasprintf(&output, "%s/%s.so", out_dir, appl); idx = 0; args[idx++] = compiler_ld; TAILQ_FOREACH(cf, &source_files, list) args[idx++] = cf->opath; for (i = 0; i < ldflags_count; i++) args[idx++] = ldflags[i]; args[idx++] = "-o"; args[idx++] = output; args[idx] = NULL; execvp(compiler_ld, args); fatal("failed to start '%s': %s", compiler_ld, errno_s); } static void cli_compile_kore(void *arg) { struct buildopt *bopt = arg; int idx, i, fcnt; char pwd[MAXPATHLEN], *obj, *args[20], *flavors[7]; if (object_dir[0] != '/') { if (getcwd(pwd, sizeof(pwd)) == NULL) fatal("could not get cwd: %s", errno_s); (void)cli_vasprintf(&obj, "OBJDIR=%s/%s", pwd, object_dir); } else { (void)cli_vasprintf(&obj, "OBJDIR=%s", object_dir); } if (putenv(obj) != 0) fatal("cannot set OBJDIR for building kore"); fcnt = cli_split_string(bopt->kore_flavor, " ", flavors, 7); #if defined(OpenBSD) || defined(__FreeBSD_version) || \ defined(NetBSD) || defined(__DragonFly_version) args[0] = "gmake"; #else args[0] = "make"; #endif args[1] = "-s"; args[2] = "-C"; args[3] = bopt->kore_source; args[4] = "objects"; idx = 5; for (i = 0; i < fcnt; i++) { printf("using flavor %s\n", flavors[i]); args[idx++] = flavors[i]; } args[idx++] = "KORE_SINGLE_BINARY=1"; args[idx] = NULL; execvp(args[0], args); fatal("failed to start '%s': %s", args[0], errno_s); } static void cli_run_kore_python(void) { char *args[5], *cmd; char pwd[MAXPATHLEN]; (void)cli_vasprintf(&cmd, "%s/bin/kore", prefix); if (getcwd(pwd, sizeof(pwd)) == NULL) fatal("could not get cwd: %s", errno_s); args[0] = cmd; args[1] = pwd; args[2] = NULL; execvp(args[0], args); fatal("failed to start '%s': %s", args[0], errno_s); } static void cli_run_kore(void) { struct buildopt *bopt; char *args[4], *cpath, *cmd, *flags; bopt = cli_buildopt_default(); if (bopt->single_binary) { cpath = NULL; flags = "-fnr"; (void)cli_vasprintf(&cmd, "./%s", appl); } else { flags = "-fnrc"; (void)cli_vasprintf(&cmd, "%s/bin/kore", prefix); (void)cli_vasprintf(&cpath, "conf/%s.conf", appl); } args[0] = cmd; args[1] = flags; if (cpath != NULL) { args[2] = cpath; args[3] = NULL; } else { args[2] = NULL; } execvp(args[0], args); fatal("failed to start '%s': %s", args[0], errno_s); } static void cli_buildopt_parse(const char *path) { FILE *fp; const char *env; struct buildopt *bopt; char buf[BUFSIZ], *p, *t; if ((fp = fopen(path, "r")) == NULL) fatal("cli_buildopt_parse: fopen(%s): %s", path, errno_s); bopt = NULL; while ((p = cli_read_line(fp, buf, sizeof(buf))) != NULL) { if (strlen(p) == 0) continue; if (bopt != NULL && !strcmp(p, "}")) { bopt = NULL; continue; } if (bopt == NULL) { if ((t = strchr(p, '=')) != NULL) goto parse_option; if ((t = strchr(p, ' ')) == NULL) fatal("unexpected '%s'", p); *(t)++ = '\0'; if (strcmp(t, "{")) fatal("expected '{', got '%s'", t); bopt = cli_buildopt_new(p); continue; } if ((t = strchr(p, '=')) == NULL) { printf("bad buildopt line: '%s'\n", p); continue; } parse_option: *(t)++ = '\0'; p = cli_text_trim(p, strlen(p)); t = cli_text_trim(t, strlen(t)); if (!strcasecmp(p, "cflags")) { cli_buildopt_cflags(bopt, t); } else if (!strcasecmp(p, "cxxflags")) { cli_buildopt_cxxflags(bopt, t); } else if (!strcasecmp(p, "ldflags")) { cli_buildopt_ldflags(bopt, t); } else if (!strcasecmp(p, "single_binary")) { cli_buildopt_single_binary(bopt, t); } else if (!strcasecmp(p, "kore_source")) { cli_buildopt_kore_source(bopt, t); } else if (!strcasecmp(p, "kore_flavor")) { cli_buildopt_kore_flavor(bopt, t); } else if (!strcasecmp(p, "mime_add")) { cli_buildopt_mime(bopt, t); } else { printf("ignoring unknown option '%s'\n", p); } } fclose(fp); if ((env = getenv("KORE_SOURCE")) != NULL) cli_buildopt_kore_source(NULL, env); if ((env = getenv("KORE_FLAVOR")) != NULL) cli_buildopt_kore_flavor(NULL, env); } static struct buildopt * cli_buildopt_new(const char *name) { struct buildopt *bopt; bopt = cli_malloc(sizeof(*bopt)); bopt->cflags = NULL; bopt->cxxflags = NULL; bopt->ldflags = NULL; bopt->flavor_nohttp = 0; bopt->single_binary = 0; bopt->kore_flavor = NULL; bopt->name = cli_strdup(name); (void)cli_vasprintf(&bopt->kore_source, "%s/share/kore/", prefix); TAILQ_INSERT_TAIL(&build_options, bopt, list); return (bopt); } static struct buildopt * cli_buildopt_find(const char *name) { struct buildopt *bopt; TAILQ_FOREACH(bopt, &build_options, list) { if (!strcmp(bopt->name, name)) return (bopt); } return (NULL); } static struct buildopt * cli_buildopt_default(void) { struct buildopt *bopt; if ((bopt = cli_buildopt_find("_default")) == NULL) fatal("no _default buildopt options"); return (bopt); } static void cli_buildopt_cleanup(void) { struct buildopt *bopt, *next; struct mime_type *mime, *mnext; for (bopt = TAILQ_FIRST(&build_options); bopt != NULL; bopt = next) { next = TAILQ_NEXT(bopt, list); TAILQ_REMOVE(&build_options, bopt, list); if (bopt->cflags != NULL) cli_buf_free(bopt->cflags); if (bopt->cxxflags != NULL) cli_buf_free(bopt->cxxflags); if (bopt->ldflags != NULL) cli_buf_free(bopt->ldflags); if (bopt->kore_source != NULL) free(bopt->kore_source); if (bopt->kore_flavor != NULL) free(bopt->kore_flavor); free(bopt); } for (mime = TAILQ_FIRST(&mime_types); mime != NULL; mime = mnext) { mnext = TAILQ_NEXT(mime, list); TAILQ_REMOVE(&mime_types, mime, list); free(mime->type); free(mime->ext); free(mime); } } static void cli_buildopt_cflags(struct buildopt *bopt, const char *string) { if (bopt == NULL) bopt = cli_buildopt_default(); if (bopt->cflags == NULL) bopt->cflags = cli_buf_alloc(128); cli_buf_appendf(bopt->cflags, "%s ", string); } static void cli_buildopt_cxxflags(struct buildopt *bopt, const char *string) { if (bopt == NULL) bopt = cli_buildopt_default(); if (bopt->cxxflags == NULL) bopt->cxxflags = cli_buf_alloc(128); cli_buf_appendf(bopt->cxxflags, "%s ", string); } static void cli_buildopt_ldflags(struct buildopt *bopt, const char *string) { if (bopt == NULL) bopt = cli_buildopt_default(); if (bopt->ldflags == NULL) bopt->ldflags = cli_buf_alloc(128); cli_buf_appendf(bopt->ldflags, "%s ", string); } static void cli_buildopt_single_binary(struct buildopt *bopt, const char *string) { if (bopt == NULL) bopt = cli_buildopt_default(); else fatal("single_binary only supported in global context"); if (!strcmp(string, "yes")) bopt->single_binary = 1; else bopt->single_binary = 0; } static void cli_buildopt_kore_source(struct buildopt *bopt, const char *string) { if (bopt == NULL) bopt = cli_buildopt_default(); else fatal("kore_source only supported in global context"); if (bopt->kore_source != NULL) free(bopt->kore_source); bopt->kore_source = cli_strdup(string); } static void cli_buildopt_kore_flavor(struct buildopt *bopt, const char *string) { int cnt, i; char *p, *copy, *flavors[10]; if (bopt == NULL) bopt = cli_buildopt_default(); else fatal("kore_flavor only supported in global context"); if (bopt->kore_flavor != NULL) free(bopt->kore_flavor); copy = cli_strdup(string); cnt = cli_split_string(copy, " ", flavors, 10); for (i = 0; i < cnt; i++) { if ((p = strchr(flavors[i], '=')) == NULL) fatal("invalid flavor %s", string); *p = '\0'; if (!strcmp(flavors[i], "NOHTTP")) bopt->flavor_nohttp = 1; } bopt->kore_flavor = cli_strdup(string); free(copy); } static void cli_buildopt_mime(struct buildopt *bopt, const char *ext) { struct mime_type *mime; char *type; if (bopt == NULL) bopt = cli_buildopt_default(); else fatal("mime_add only supported in global context"); if ((type = strchr(ext, ':')) == NULL) fatal("no type given in %s", ext); *(type)++ = '\0'; TAILQ_FOREACH(mime, &mime_types, list) { if (!strcmp(mime->ext, ext)) fatal("duplicate extension %s found", ext); } mime = cli_malloc(sizeof(*mime)); mime->ext = cli_strdup(ext); mime->type = cli_strdup(type); TAILQ_INSERT_TAIL(&mime_types, mime, list); } static void cli_build_flags_common(struct buildopt *bopt, struct cli_buf *buf) { size_t len; char *data; cli_buf_appendf(buf, "-fPIC "); if (bopt != NULL) cli_buf_appendf(buf, "-Isrc -Isrc/includes "); if (bopt == NULL || bopt->single_binary == 0) cli_buf_appendf(buf, "-I%s/include ", prefix); else cli_buf_appendf(buf, "-I%s/include ", bopt->kore_source); if (bopt == NULL || bopt->single_binary == 0) { cli_kore_load_file("features", bopt, &data, &len); cli_buf_append(buf, data, len); cli_buf_appendf(buf, " "); free(data); } } static void cli_build_cflags(struct buildopt *bopt) { size_t len; struct buildopt *obopt; char *string, *buf, *env; if ((obopt = cli_buildopt_find(flavor)) == NULL) fatal("no such build flavor: %s", flavor); if (bopt->cflags == NULL) bopt->cflags = cli_buf_alloc(128); cli_build_flags_common(bopt, bopt->cflags); if (obopt != NULL && obopt->cflags != NULL) { cli_buf_append(bopt->cflags, obopt->cflags->data, obopt->cflags->offset); } if (bopt->single_binary) { cli_kore_load_file("features", bopt, &buf, &len); cli_buf_append(bopt->cflags, buf, len); cli_buf_appendf(bopt->cflags, " "); free(buf); } if ((env = getenv("CFLAGS")) != NULL) cli_buf_appendf(bopt->cflags, "%s", env); string = cli_buf_stringify(bopt->cflags, NULL); printf("CFLAGS=%s\n", string); cflags_count = cli_split_string(string, " ", cflags, CFLAGS_MAX); } static void cli_build_cxxflags(struct buildopt *bopt) { struct buildopt *obopt; char *string, *env; if ((obopt = cli_buildopt_find(flavor)) == NULL) fatal("no such build flavor: %s", flavor); if (bopt->cxxflags == NULL) bopt->cxxflags = cli_buf_alloc(128); cli_build_flags_common(bopt, bopt->cxxflags); if (obopt != NULL && obopt->cxxflags != NULL) { cli_buf_append(bopt->cxxflags, obopt->cxxflags->data, obopt->cxxflags->offset); } if ((env = getenv("CXXFLAGS")) != NULL) cli_buf_appendf(bopt->cxxflags, "%s", env); string = cli_buf_stringify(bopt->cxxflags, NULL); if (cxx_files_count > 0) printf("CXXFLAGS=%s\n", string); cxxflags_count = cli_split_string(string, " ", cxxflags, CXXFLAGS_MAX); } static void cli_build_ldflags(struct buildopt *bopt) { int fd; size_t len; struct buildopt *obopt; char *string, *buf, *env, *path; if ((obopt = cli_buildopt_find(flavor)) == NULL) fatal("no such build flavor: %s", flavor); if (bopt->ldflags == NULL) bopt->ldflags = cli_buf_alloc(128); if (bopt->single_binary == 0) { #if defined(__MACH__) cli_buf_appendf(bopt->ldflags, "-dynamiclib -undefined suppress -flat_namespace "); #else cli_buf_appendf(bopt->ldflags, "-shared "); #endif } else { (void)cli_vasprintf(&path, "%s/ldflags", object_dir); cli_file_open(path, O_RDONLY, &fd); cli_file_read(fd, &buf, &len); cli_file_close(fd); if (len == 0) fatal("ldflags is empty"); len--; cli_buf_append(bopt->ldflags, buf, len); cli_buf_appendf(bopt->ldflags, " "); free(buf); } if (obopt != NULL && obopt->ldflags != NULL) { cli_buf_append(bopt->ldflags, obopt->ldflags->data, obopt->ldflags->offset); } if ((env = getenv("LDFLAGS")) != NULL) cli_buf_appendf(bopt->ldflags, "%s", env); string = cli_buf_stringify(bopt->ldflags, NULL); printf("LDFLAGS=%s\n", string); ldflags_count = cli_split_string(string, " ", ldflags, LD_FLAGS_MAX); } static void cli_flavor_load(void) { FILE *fp; char buf[BUFSIZ], pwd[MAXPATHLEN], *p, *conf, *env; if ((env = getenv("KORE_BUILD_FLAVOR")) != NULL) { flavor = cli_strdup(env); return; } if (getcwd(pwd, sizeof(pwd)) == NULL) fatal("could not get cwd: %s", errno_s); appl = basename(pwd); if (appl == NULL) fatal("basename: %s", errno_s); appl = cli_strdup(appl); (void)cli_vasprintf(&conf, "conf/%s.conf", appl); if (!cli_dir_exists("conf") || !cli_file_exists(conf)) fatal("%s doesn't appear to be a kore app", appl); free(conf); if ((fp = fopen(".flavor", "r")) == NULL) { flavor = cli_strdup("dev"); return; } if (fgets(buf, sizeof(buf), fp) == NULL) fatal("failed to read flavor from file"); if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; flavor = cli_strdup(buf); (void)fclose(fp); } static void cli_kore_load_file(const char *name, struct buildopt *bopt, char **out, size_t *outlen) { int fd; size_t len; char *path, *data; if (bopt != NULL && bopt->single_binary) { (void)cli_vasprintf(&path, "%s/%s", object_dir, name); } else { (void)cli_vasprintf(&path, "%s/share/kore/%s", prefix, name); } cli_file_open(path, O_RDONLY, &fd); cli_file_read(fd, &data, &len); cli_file_close(fd); free(path); if (len == 0) fatal("%s is empty", name); len--; *out = data; *outlen = len; } static void cli_flavor_change(const char *name) { FILE *fp; int ret; struct buildopt *bopt; if ((bopt = cli_buildopt_find(name)) == NULL) fatal("no such flavor: %s", name); if ((fp = fopen(".flavor.tmp", "w")) == NULL) fatal("failed to open temporary file to save flavor"); ret = fprintf(fp, "%s\n", name); if (ret == -1 || (size_t)ret != (strlen(name) + 1)) fatal("failed to write new build flavor"); (void)fclose(fp); if (rename(".flavor.tmp", ".flavor") == -1) fatal("failed to replace build flavor"); cli_clean(0, NULL); } static void cli_spawn_proc(void (*cb)(void *), void *arg) { pid_t pid; int status; pid = fork(); switch (pid) { case -1: fatal("cli_compile_cfile: fork() %s", errno_s); /* NOTREACHED */ case 0: cb(arg); fatal("cli_spawn_proc: %s", errno_s); /* NOTREACHED */ default: break; } if (waitpid(pid, &status, 0) == -1) fatal("couldn't wait for child %d", pid); if (WEXITSTATUS(status) || WTERMSIG(status) || WCOREDUMP(status)) fatal("subprocess trouble, check output"); } static int cli_vasprintf(char **out, const char *fmt, ...) { int l; va_list args; va_start(args, fmt); l = vasprintf(out, fmt, args); va_end(args); if (l == -1) fatal("cli_vasprintf"); return (l); } static void cli_cleanup_files(const char *spath) { cli_find_files(spath, cli_file_remove); if (rmdir(spath) == -1 && errno != ENOENT) printf("couldn't rmdir %s\n", spath); } static void * cli_malloc(size_t len) { void *ptr; if ((ptr = calloc(1, len)) == NULL) fatal("calloc: %s", errno_s); return (ptr); } static void * cli_realloc(void *ptr, size_t len) { void *nptr; if ((nptr = realloc(ptr, len)) == NULL) fatal("realloc: %s", errno_s); return (nptr); } static char * cli_strdup(const char *string) { char *copy; if ((copy = strdup(string)) == NULL) fatal("strdup: %s", errno_s); return (copy); } struct cli_buf * cli_buf_alloc(size_t initial) { struct cli_buf *buf; buf = cli_malloc(sizeof(*buf)); if (initial > 0) buf->data = cli_malloc(initial); else buf->data = NULL; buf->length = initial; buf->offset = 0; return (buf); } void cli_buf_free(struct cli_buf *buf) { free(buf->data); buf->data = NULL; buf->offset = 0; buf->length = 0; free(buf); } void cli_buf_append(struct cli_buf *buf, const void *d, size_t len) { if ((buf->offset + len) < len) fatal("overflow in cli_buf_append"); if ((buf->offset + len) > buf->length) { buf->length += len; buf->data = cli_realloc(buf->data, buf->length); } memcpy((buf->data + buf->offset), d, len); buf->offset += len; } void cli_buf_appendv(struct cli_buf *buf, const char *fmt, va_list args) { int l; va_list copy; char *b, sb[BUFSIZ]; va_copy(copy, args); l = vsnprintf(sb, sizeof(sb), fmt, args); if (l == -1) fatal("cli_buf_appendv(): vsnprintf error"); if ((size_t)l >= sizeof(sb)) { l = vasprintf(&b, fmt, copy); if (l == -1) fatal("cli_buf_appendv(): error or truncation"); } else { b = sb; } cli_buf_append(buf, b, l); if (b != sb) free(b); va_end(copy); } void cli_buf_appendf(struct cli_buf *buf, const char *fmt, ...) { va_list args; va_start(args, fmt); cli_buf_appendv(buf, fmt, args); va_end(args); } char * cli_buf_stringify(struct cli_buf *buf, size_t *len) { char c; if (len != NULL) *len = buf->offset; c = '\0'; cli_buf_append(buf, &c, sizeof(c)); return ((char *)buf->data); } static int cli_split_string(char *input, const char *delim, char **out, size_t ele) { int count; char **ap; if (ele == 0) return (0); count = 0; for (ap = out; ap < &out[ele - 1] && (*ap = strsep(&input, delim)) != NULL;) { if (**ap != '\0') { ap++; count++; } } *ap = NULL; return (count); } static char * cli_read_line(FILE *fp, char *in, size_t len) { char *p, *t; if (fgets(in, len, fp) == NULL) return (NULL); p = in; in[strcspn(in, "\n")] = '\0'; while (isspace(*(unsigned char *)p)) p++; if (p[0] == '#' || p[0] == '\0') { p[0] = '\0'; return (p); } for (t = p; *t != '\0'; t++) { if (*t == '\t') *t = ' '; } return (p); } static char * cli_text_trim(char *string, size_t len) { char *end; if (len == 0) return (string); end = (string + len) - 1; while (isspace(*(unsigned char *)string) && string < end) string++; while (isspace(*(unsigned char *)end) && end > string) *(end)-- = '\0'; return (string); } static long long cli_strtonum(const char *str, long long min, long long max) { long long l; char *ep; if (min > max) fatal("cli_strtonum: min > max"); errno = 0; l = strtoll(str, &ep, 10); if (errno != 0 || str == ep || *ep != '\0') fatal("strtoll(): %s", errno_s); if (l < min) fatal("cli_strtonum: value < min"); if (l > max) fatal("cli_strtonum: value > max"); return (l); } static void fatal(const char *fmt, ...) { va_list args; char buf[2048]; va_start(args, fmt); (void)vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); if (command != NULL) printf("kore %s: %s\n", command->name, buf); else printf("kore: %s\n", buf); exit(1); } kore-4.2.3/src/config.c000066400000000000000000001402411430066302400146600ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "kore.h" #include "http.h" #if defined(KORE_USE_PGSQL) #include "pgsql.h" #endif #if defined(KORE_USE_TASKS) #include "tasks.h" #endif #if defined(KORE_USE_PYTHON) #include "python_api.h" #endif #if defined(KORE_USE_CURL) #include "curl.h" #endif #if defined(KORE_USE_ACME) #include "acme.h" #endif #if defined(__linux__) #include "seccomp.h" #endif /* XXX - This is becoming a clusterfuck. Fix it. */ static int configure_load(char *); static char *configure_resolve_var(char *); static void configure_check_var(char **, const char *, const char *); #if defined(KORE_SINGLE_BINARY) static FILE *config_file_write(void); extern u_int8_t asset_builtin_kore_conf[]; extern u_int32_t asset_len_builtin_kore_conf; #elif defined(KORE_USE_PYTHON) static int configure_file(char *); #endif #if defined(KORE_USE_ACME) static int configure_acme(char *); static int configure_acme_email(char *); static int configure_acme_provider(char *); #endif static int configure_tls(char *); static int configure_server(char *); static int configure_include(char *); static int configure_bind(char *); static int configure_bind_unix(char *); static int configure_attach(char *); static int configure_domain(char *); static int configure_privsep(char *); static int configure_logfile(char *); static int configure_workers(char *); static int configure_pidfile(char *); static int configure_rlimit_nofiles(char *); static int configure_max_connections(char *); static int configure_accept_threshold(char *); static int configure_death_policy(char *); static int configure_set_affinity(char *); static int configure_socket_backlog(char *); static int configure_privsep_skip(char *); static int configure_privsep_root(char *); static int configure_privsep_runas(char *); #if defined(KORE_USE_PLATFORM_PLEDGE) static int configure_add_pledge(char *); #endif static int configure_rand_file(char *); static int configure_certfile(char *); static int configure_certkey(char *); static int configure_tls_version(char *); static int configure_tls_cipher(char *); static int configure_tls_dhparam(char *); static int configure_client_verify(char *); static int configure_client_verify_depth(char *); #if !defined(KORE_NO_HTTP) static int configure_route(char *); static int configure_route_methods(char *); static int configure_route_handler(char *); static int configure_route_on_free(char *); static int configure_route_on_headers(char *); static int configure_route_authenticate(char *); static int configure_route_on_body_chunk(char *); static int configure_filemap(char *); static int configure_return(char *); static int configure_redirect(char *); static int configure_static_handler(char *); static int configure_dynamic_handler(char *); static int configure_accesslog(char *); static int configure_http_header_max(char *); static int configure_http_header_timeout(char *); static int configure_http_body_max(char *); static int configure_http_body_timeout(char *); static int configure_filemap_ext(char *); static int configure_filemap_index(char *); static int configure_http_media_type(char *); static int configure_http_hsts_enable(char *); static int configure_http_keepalive_time(char *); static int configure_http_request_ms(char *); static int configure_http_request_limit(char *); static int configure_http_body_disk_offload(char *); static int configure_http_body_disk_path(char *); static int configure_http_server_version(char *); static int configure_http_pretty_error(char *); static int configure_validator(char *); static int configure_validate(char *); static int configure_authentication(char *); static int configure_authentication_uri(char *); static int configure_authentication_type(char *); static int configure_authentication_value(char *); static int configure_authentication_validator(char *); static int configure_websocket_maxframe(char *); static int configure_websocket_timeout(char *); #endif #if defined(KORE_USE_PGSQL) static int configure_pgsql_conn_max(char *); static int configure_pgsql_queue_limit(char *); #endif #if defined(KORE_USE_TASKS) static int configure_task_threads(char *); #endif #if defined(KORE_USE_PYTHON) static int configure_deployment(char *); static int configure_python_path(char *); static int configure_python_import(char *); #endif #if defined(KORE_USE_CURL) static int configure_curl_timeout(char *); static int configure_curl_recv_max(char *); #endif #if defined(__linux__) static int configure_seccomp_tracing(char *); #endif static struct { const char *name; int (*configure)(char *); } config_directives[] = { { "tls", configure_tls }, #if defined(KORE_USE_ACME) { "acme", configure_acme }, #endif { "bind", configure_bind }, { "load", configure_load }, { "domain", configure_domain }, { "privsep", configure_privsep }, { "server", configure_server }, { "attach", configure_attach }, { "certkey", configure_certkey }, { "certfile", configure_certfile }, { "include", configure_include }, { "unix", configure_bind_unix }, { "skip", configure_privsep_skip }, { "root", configure_privsep_root }, { "runas", configure_privsep_runas }, { "client_verify", configure_client_verify }, { "client_verify_depth", configure_client_verify_depth }, #if defined(KORE_USE_PYTHON) { "python_path", configure_python_path }, { "python_import", configure_python_import }, #endif #if !defined(KORE_NO_HTTP) { "route", configure_route }, { "handler", configure_route_handler }, { "on_headers", configure_route_on_headers }, { "on_body_chunk", configure_route_on_body_chunk }, { "on_free", configure_route_on_free }, { "methods", configure_route_methods }, { "authenticate", configure_route_authenticate }, { "filemap", configure_filemap }, { "redirect", configure_redirect }, { "return", configure_return }, { "static", configure_static_handler }, { "dynamic", configure_dynamic_handler }, { "accesslog", configure_accesslog }, { "validator", configure_validator }, { "validate", configure_validate }, { "authentication", configure_authentication }, { "authentication_uri", configure_authentication_uri }, { "authentication_type", configure_authentication_type }, { "authentication_value", configure_authentication_value }, { "authentication_validator", configure_authentication_validator }, #endif { NULL, NULL }, }; static struct { const char *name; int (*configure)(char *); } config_settings[] = { { "logfile", configure_logfile }, { "workers", configure_workers }, { "worker_max_connections", configure_max_connections }, { "worker_rlimit_nofiles", configure_rlimit_nofiles }, { "worker_accept_threshold", configure_accept_threshold }, { "worker_death_policy", configure_death_policy }, { "worker_set_affinity", configure_set_affinity }, { "pidfile", configure_pidfile }, { "socket_backlog", configure_socket_backlog }, { "tls_version", configure_tls_version }, { "tls_cipher", configure_tls_cipher }, { "tls_dhparam", configure_tls_dhparam }, { "rand_file", configure_rand_file }, #if defined(KORE_USE_ACME) { "acme_email", configure_acme_email }, { "acme_provider", configure_acme_provider }, #endif #if defined(KORE_USE_PLATFORM_PLEDGE) { "pledge", configure_add_pledge }, #endif #if defined(__linux__) { "seccomp_tracing", configure_seccomp_tracing }, #endif #if !defined(KORE_NO_HTTP) { "filemap_ext", configure_filemap_ext }, { "filemap_index", configure_filemap_index }, { "http_media_type", configure_http_media_type }, { "http_header_max", configure_http_header_max }, { "http_header_timeout", configure_http_header_timeout }, { "http_body_max", configure_http_body_max }, { "http_body_timeout", configure_http_body_timeout }, { "http_hsts_enable", configure_http_hsts_enable }, { "http_keepalive_time", configure_http_keepalive_time }, { "http_request_ms", configure_http_request_ms }, { "http_request_limit", configure_http_request_limit }, { "http_body_disk_offload", configure_http_body_disk_offload }, { "http_body_disk_path", configure_http_body_disk_path }, { "http_server_version", configure_http_server_version }, { "http_pretty_error", configure_http_pretty_error }, { "websocket_maxframe", configure_websocket_maxframe }, { "websocket_timeout", configure_websocket_timeout }, #endif #if defined(KORE_USE_PYTHON) { "deployment", configure_deployment }, #endif #if defined(KORE_USE_PGSQL) { "pgsql_conn_max", configure_pgsql_conn_max }, { "pgsql_queue_limit", configure_pgsql_queue_limit }, #endif #if defined(KORE_USE_TASKS) { "task_threads", configure_task_threads }, #endif #if defined(KORE_USE_CURL) { "curl_timeout", configure_curl_timeout }, { "curl_recv_max", configure_curl_recv_max }, #endif #if !defined(KORE_SINGLE_BINARY) && defined(KORE_USE_PYTHON) { "file", configure_file }, #endif { NULL, NULL }, }; static int finalized = 0; #if !defined(KORE_SINGLE_BINARY) char *config_file = NULL; #endif #if !defined(KORE_NO_HTTP) static struct kore_auth *current_auth = NULL; static struct kore_route *current_route = NULL; #endif extern const char *__progname; static struct kore_domain *current_domain = NULL; static struct kore_server *current_server = NULL; static struct kore_privsep *current_privsep = NULL; void kore_parse_config(void) { FILE *fp; struct passwd *pwd; char path[PATH_MAX]; if (finalized) return; fp = NULL; #if !defined(KORE_SINGLE_BINARY) if (config_file != NULL) { if ((fp = fopen(config_file, "r")) == NULL) { fatal("configuration given cannot be opened: %s", config_file); } } #else fp = config_file_write(); #endif if (fp != NULL) { kore_parse_config_file(fp); (void)fclose(fp); } kore_tls_dh_check(); if (!kore_module_loaded()) fatal("no application module was loaded"); if (worker_privsep.root == NULL) { if (getcwd(path, sizeof(path)) == NULL) fatal("getcwd: %s", errno_s); worker_privsep.root = kore_strdup(path); if (!kore_quiet) kore_log(LOG_NOTICE, "privsep: no root path set"); } if (worker_privsep.runas == NULL) { if ((pwd = getpwuid(getuid())) == NULL) fatal("getpwuid: %s", errno_s); worker_privsep.runas = kore_strdup(pwd->pw_name); if (!kore_quiet) kore_log(LOG_NOTICE, "privsep: no runas user set"); endpwent(); } configure_check_var(&keymgr_privsep.runas, worker_privsep.runas, "privsep: no keymgr runas set"); #if defined(KORE_USE_ACME) configure_check_var(&acme_privsep.runas, worker_privsep.runas, "privsep: no acme runas set"); #endif configure_check_var(&keymgr_privsep.root, worker_privsep.root, "privsep: no keymgr root set"); #if defined(KORE_USE_ACME) configure_check_var(&acme_privsep.root, worker_privsep.root, "privsep: no acme root set"); #endif if (skip_chroot) { worker_privsep.skip_chroot = 1; keymgr_privsep.skip_chroot = 1; #if defined(KORE_USE_ACME) acme_privsep.skip_chroot = 1; #endif } if (skip_runas) { worker_privsep.skip_runas = 1; keymgr_privsep.skip_runas = 1; #if defined(KORE_USE_ACME) acme_privsep.skip_runas = 1; #endif } if (skip_runas && !kore_quiet) kore_log(LOG_NOTICE, "privsep: skipping all runas options"); if (skip_chroot && !kore_quiet) kore_log(LOG_NOTICE, "privsep: skipping all chroot options"); finalized = 1; } void kore_parse_config_file(FILE *fp) { int i, lineno; char buf[BUFSIZ], *p, *t, *v; lineno = 1; while ((p = kore_read_line(fp, buf, sizeof(buf))) != NULL) { if (strlen(p) == 0) { lineno++; continue; } if (!strcmp(p, "}") && current_privsep != NULL) { lineno++; current_privsep = NULL; continue; } if (!strcmp(p, "}") && current_server != NULL) { lineno++; kore_server_finalize(current_server); current_server = NULL; continue; } #if !defined(KORE_NO_HTTP) if (!strcmp(p, "}") && current_route != NULL) { lineno++; current_route = NULL; continue; } if (!strcmp(p, "}") && current_auth != NULL) { if (current_auth->validator == NULL) { fatal("no authentication validator for %s", current_auth->name); } lineno++; current_auth = NULL; continue; } #endif if (!strcmp(p, "}") && current_domain != NULL) { if (current_domain->server == NULL) { fatal("domain '%s' not attached to server", current_domain->domain); } if (current_domain->server->tls == 1) { #if defined(KORE_USE_ACME) if (current_domain->acme) { lineno++; current_domain = NULL; continue; } #endif if (current_domain->certfile == NULL || current_domain->certkey == NULL) { fatal("incomplete TLS setup for '%s'", current_domain->domain); } } current_domain = NULL; } if (!strcmp(p, "}")) { lineno++; continue; } if ((t = strchr(p, ' ')) == NULL) { kore_log(LOG_NOTICE, "ignoring \"%s\" on line %d", p, lineno++); continue; } *(t)++ = '\0'; p = kore_text_trim(p, strlen(p)); t = kore_text_trim(t, strlen(t)); if (strlen(p) == 0 || strlen(t) == 0) { kore_log(LOG_NOTICE, "ignoring \"%s\" on line %d", p, lineno++); continue; } for (i = 0; config_directives[i].name != NULL; i++) { if (!strcmp(config_directives[i].name, p)) { if ((v = configure_resolve_var(t)) == NULL) fatal("variable %s does not exist", t); if (config_directives[i].configure(v)) break; fatal("configuration error on line %d", lineno); /* NOTREACHED */ } } if (config_directives[i].name != NULL) { lineno++; continue; } for (i = 0; config_settings[i].name != NULL; i++) { if (!strcmp(config_settings[i].name, p)) { if ((v = configure_resolve_var(t)) == NULL) fatal("variable %s does not exist", t); if (config_settings[i].configure(v)) break; fatal("configuration error on line %d", lineno); /* NOTREACHED */ } } if (config_settings[i].name == NULL) { kore_log(LOG_NOTICE, "ignoring \"%s\" on line %d", p, lineno); } lineno++; } } #if defined(KORE_USE_PYTHON) int kore_configure_setting(const char *name, char *value) { int i; if (finalized) return (KORE_RESULT_ERROR); for (i = 0; config_settings[i].name != NULL; i++) { if (!strcmp(config_settings[i].name, name)) { if (config_settings[i].configure(value)) return (KORE_RESULT_OK); fatal("bad value '%s' for '%s'", value, name); } } kore_log(LOG_NOTICE, "ignoring unknown kore.config.%s setting", name); return (KORE_RESULT_OK); } #endif static void configure_check_var(char **var, const char *other, const char *logmsg) { if (*var == NULL) { if (!kore_quiet) kore_log(LOG_NOTICE, "%s", logmsg); *var = kore_strdup(other); } } static char * configure_resolve_var(char *var) { char *v; if (var[0] == '$') { if ((v = getenv(&var[1])) == NULL) return (NULL); } else { v = var; } return (v); } static int configure_include(char *path) { FILE *fp; if ((fp = fopen(path, "r")) == NULL) fatal("failed to open include '%s'", path); kore_parse_config_file(fp); (void)fclose(fp); return (KORE_RESULT_OK); } static int configure_server(char *options) { struct kore_server *srv; char *argv[3]; if (current_server != NULL) { kore_log(LOG_ERR, "nested server contexts are not allowed"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 3); if (argv[0] == NULL || argv[1] == NULL) { kore_log(LOG_ERR, "server context invalid"); return (KORE_RESULT_ERROR); } if (strcmp(argv[1], "{")) { kore_log(LOG_ERR, "server context not opened correctly"); return (KORE_RESULT_ERROR); } if ((srv = kore_server_lookup(argv[0])) != NULL) { kore_log(LOG_ERR, "server with name '%s' exists", srv->name); return (KORE_RESULT_ERROR); } current_server = kore_server_create(argv[0]); return (KORE_RESULT_OK); } static int configure_tls(char *yesno) { if (!kore_tls_supported()) { current_server->tls = 0; if (!strcmp(yesno, "yes")) { kore_log(LOG_ERR, "TLS not supported in this build"); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } if (current_server == NULL) { kore_log(LOG_ERR, "tls keyword not inside a server context"); return (KORE_RESULT_ERROR); } if (!strcmp(yesno, "no")) { current_server->tls = 0; } else if (!strcmp(yesno, "yes")) { current_server->tls = 1; } else { kore_log(LOG_ERR, "invalid '%s' for yes|no tls option", yesno); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } #if defined(KORE_USE_ACME) static int configure_acme(char *yesno) { if (current_domain == NULL) { kore_log(LOG_ERR, "acme keyword not inside a domain context"); return (KORE_RESULT_ERROR); } if (strchr(current_domain->domain, '*')) { kore_log(LOG_ERR, "wildcards not supported due to lack of dns-01"); return (KORE_RESULT_ERROR); } if (!strcmp(yesno, "no")) { current_domain->acme = 0; } else if (!strcmp(yesno, "yes")) { current_domain->acme = 1; /* Override keyfile and certfile locations. */ kore_free(current_domain->certkey); kore_free(current_domain->certfile); kore_acme_get_paths(current_domain->domain, ¤t_domain->certkey, ¤t_domain->certfile); acme_domains++; } else { kore_log(LOG_ERR, "invalid '%s' for yes|no acme option", yesno); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_acme_email(char *email) { kore_free(acme_email); acme_email = kore_strdup(email); return (KORE_RESULT_OK); } static int configure_acme_provider(char *provider) { kore_free(acme_provider); acme_provider = kore_strdup(provider); return (KORE_RESULT_OK); } #endif static int configure_bind(char *options) { char *argv[4]; if (current_server == NULL) { kore_log(LOG_ERR, "bind keyword not inside a server context"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 4); if (argv[0] == NULL || argv[1] == NULL) return (KORE_RESULT_ERROR); return (kore_server_bind(current_server, argv[0], argv[1], argv[2])); } static int configure_bind_unix(char *options) { char *argv[3]; if (current_server == NULL) { kore_log(LOG_ERR, "bind_unix keyword not inside a server context"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 3); if (argv[0] == NULL) return (KORE_RESULT_ERROR); return (kore_server_bind_unix(current_server, argv[0], argv[1])); } static int configure_load(char *options) { char *argv[3]; kore_split_string(options, " ", argv, 3); if (argv[0] == NULL) return (KORE_RESULT_ERROR); kore_module_load(argv[0], argv[1], KORE_MODULE_NATIVE); return (KORE_RESULT_OK); } #if defined(KORE_SINGLE_BINARY) static FILE * config_file_write(void) { FILE *fp; ssize_t ret; int fd, len; char fpath[MAXPATHLEN]; len = snprintf(fpath, sizeof(fpath), "%s/%s.XXXXXX", KORE_TMPDIR, __progname); if (len == -1 || (size_t)len >= sizeof(fpath)) fatal("failed to create temporary path"); if ((fd = mkstemp(fpath)) == -1) fatal("mkstemp(%s): %s", fpath, errno_s); (void)unlink(fpath); for (;;) { ret = write(fd, asset_builtin_kore_conf, asset_len_builtin_kore_conf); if (ret == -1) { if (errno == EINTR) continue; fatal("failed to write temporary config: %s", errno_s); } if ((size_t)ret != asset_len_builtin_kore_conf) fatal("failed to write temporary config"); break; } if ((fp = fdopen(fd, "w+")) == NULL) fatal("fdopen(): %s", errno_s); rewind(fp); return (fp); } #elif defined(KORE_USE_PYTHON) static int configure_file(char *file) { free(config_file); if ((config_file = strdup(file)) == NULL) fatal("strdup"); return (KORE_RESULT_OK); } #endif static int configure_tls_version(char *version) { int ver; if (!strcmp(version, "1.3")) { ver = KORE_TLS_VERSION_1_3; } else if (!strcmp(version, "1.2")) { ver = KORE_TLS_VERSION_1_2; } else if (!strcmp(version, "both")) { ver = KORE_TLS_VERSION_BOTH; } else { kore_log(LOG_ERR, "unknown value for tls_version: %s (use 1.3, 1.2, both)", version); return (KORE_RESULT_ERROR); } kore_tls_version_set(ver); return (KORE_RESULT_OK); } static int configure_tls_cipher(char *cipherlist) { return (kore_tls_ciphersuite_set(cipherlist)); } static int configure_tls_dhparam(char *path) { return (kore_tls_dh_load(path)); } static int configure_client_verify_depth(char *value) { int err, depth; if (current_domain == NULL) { kore_log(LOG_ERR, "client_verify_depth keyword not in domain context"); return (KORE_RESULT_ERROR); } depth = kore_strtonum(value, 10, 0, INT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad client_verify_depth value: %s", value); return (KORE_RESULT_ERROR); } current_domain->x509_verify_depth = depth; return (KORE_RESULT_OK); } static int configure_client_verify(char *options) { char *argv[3]; if (current_domain == NULL) { kore_log(LOG_ERR, "client_verify keyword not in domain context"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 3); if (argv[0] == NULL) { kore_log(LOG_ERR, "client_verify is missing a parameter"); return (KORE_RESULT_ERROR); } if (current_domain->cafile != NULL) { kore_log(LOG_ERR, "client_verify already set for '%s'", current_domain->domain); return (KORE_RESULT_ERROR); } current_domain->cafile = kore_strdup(argv[0]); if (argv[1] != NULL) current_domain->crlfile = kore_strdup(argv[1]); return (KORE_RESULT_OK); } static int configure_rand_file(char *path) { if (kore_rand_file != NULL) kore_free(kore_rand_file); kore_rand_file = kore_strdup(path); return (KORE_RESULT_OK); } static int configure_certfile(char *path) { if (current_domain == NULL) { kore_log(LOG_ERR, "certfile keyword not specified in domain context"); return (KORE_RESULT_ERROR); } kore_free(current_domain->certfile); current_domain->certfile = kore_strdup(path); return (KORE_RESULT_OK); } static int configure_certkey(char *path) { if (current_domain == NULL) { kore_log(LOG_ERR, "certkey keyword not specified in domain context"); return (KORE_RESULT_ERROR); } kore_free(current_domain->certkey); current_domain->certkey = kore_strdup(path); return (KORE_RESULT_OK); } static int configure_privsep(char *options) { char *argv[3]; if (current_privsep != NULL) { kore_log(LOG_ERR, "nested privsep contexts are not allowed"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 3); if (argv[0] == NULL || argv[1] == NULL) { kore_log(LOG_ERR, "invalid privsep context"); return (KORE_RESULT_ERROR); } if (strcmp(argv[1], "{")) { kore_log(LOG_ERR, "privsep context not opened correctly"); return (KORE_RESULT_ERROR); } if (!strcmp(argv[0], "worker")) { current_privsep = &worker_privsep; } else if (!strcmp(argv[0], "keymgr")) { current_privsep = &keymgr_privsep; #if defined(KORE_USE_ACME) } else if (!strcmp(argv[0], "acme")) { current_privsep = &acme_privsep; #endif } else { kore_log(LOG_ERR, "unknown privsep context: %s", argv[0]); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_privsep_runas(char *user) { if (current_privsep == NULL) { kore_log(LOG_ERR, "runas keyword not in privsep context"); return (KORE_RESULT_ERROR); } if (current_privsep->runas != NULL) kore_free(current_privsep->runas); current_privsep->runas = kore_strdup(user); return (KORE_RESULT_OK); } static int configure_privsep_root(char *root) { if (current_privsep == NULL) { kore_log(LOG_ERR, "root keyword not in privsep context"); return (KORE_RESULT_ERROR); } if (current_privsep->root != NULL) kore_free(current_privsep->root); current_privsep->root = kore_strdup(root); return (KORE_RESULT_OK); } static int configure_privsep_skip(char *option) { if (current_privsep == NULL) { kore_log(LOG_ERR, "skip keyword not in privsep context"); return (KORE_RESULT_ERROR); } if (!strcmp(option, "chroot")) { current_privsep->skip_chroot = 1; } else { kore_log(LOG_ERR, "unknown skip option '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_domain(char *options) { char *argv[3]; if (current_domain != NULL) { kore_log(LOG_ERR, "nested domain contexts are not allowed"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 3); if (argv[0] == NULL || argv[1] == NULL) { kore_log(LOG_ERR, "invalid domain context"); return (KORE_RESULT_ERROR); } if (strcmp(argv[1], "{")) { kore_log(LOG_ERR, "domain context not opened correctly"); return (KORE_RESULT_ERROR); } if (strlen(argv[0]) >= KORE_DOMAINNAME_LEN - 1) { kore_log(LOG_ERR, "domain name '%s' too long", argv[0]); return (KORE_RESULT_ERROR); } current_domain = kore_domain_new(argv[0]); return (KORE_RESULT_OK); } static int configure_attach(char *name) { struct kore_server *srv; if (current_domain == NULL) { kore_log(LOG_ERR, "attach keyword not in domain context"); return (KORE_RESULT_ERROR); } if (current_domain->server != NULL) { kore_log(LOG_ERR, "domain '%s' already attached to server", current_domain->domain); return (KORE_RESULT_ERROR); } if ((srv = kore_server_lookup(name)) == NULL) { kore_log(LOG_ERR, "server '%s' does not exist", name); return (KORE_RESULT_ERROR); } if (!kore_domain_attach(current_domain, srv)) { kore_log(LOG_ERR, "failed to attach '%s' to '%s'", current_domain->domain, name); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } #if !defined(KORE_NO_HTTP) static int configure_static_handler(char *options) { kore_log(LOG_NOTICE, "static keyword removed, use route instead"); return (KORE_RESULT_ERROR); } static int configure_dynamic_handler(char *options) { kore_log(LOG_NOTICE, "dynamic keyword removed, use route instead"); return (KORE_RESULT_ERROR); } static int configure_route(char *options) { struct kore_route *rt; int type; char *argv[4]; if (current_domain == NULL) { kore_log(LOG_ERR, "route keyword not in domain context"); return (KORE_RESULT_ERROR); } if (current_route != NULL) { kore_log(LOG_ERR, "nested route contexts not allowed"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 4); if (argv[1] == NULL || strcmp(argv[1], "{")) { kore_log(LOG_ERR, "invalid route context"); return (KORE_RESULT_ERROR); } if (*argv[0] == '/') type = HANDLER_TYPE_STATIC; else type = HANDLER_TYPE_DYNAMIC; if ((rt = kore_route_create(current_domain, argv[0], type)) == NULL) { kore_log(LOG_ERR, "failed to create route handler for '%s'", argv[0]); return (KORE_RESULT_ERROR); } current_route = rt; return (KORE_RESULT_OK); } static int configure_route_handler(char *name) { if (current_route == NULL) { kore_log(LOG_ERR, "handler keyword not inside of route context"); return (KORE_RESULT_ERROR); } kore_route_callback(current_route, name); return (KORE_RESULT_OK); } static int configure_route_on_headers(char *name) { if (current_route == NULL) { kore_log(LOG_ERR, "on_header keyword not inside of route context"); return (KORE_RESULT_ERROR); } if ((current_route->on_headers = kore_runtime_getcall(name)) == NULL) { kore_log(LOG_ERR, "on_headers callback '%s' for '%s' not found", name, current_route->path); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_route_on_body_chunk(char *name) { if (current_route == NULL) { kore_log(LOG_ERR, "on_body_chunk keyword not inside of route context"); return (KORE_RESULT_ERROR); } current_route->on_body_chunk = kore_runtime_getcall(name); if (current_route->on_body_chunk == NULL) { kore_log(LOG_ERR, "on_body_chunk callback '%s' for '%s' not found", name, current_route->path); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_route_on_free(char *name) { if (current_route == NULL) { kore_log(LOG_ERR, "on_free keyword not inside of route context"); return (KORE_RESULT_ERROR); } if ((current_route->on_free = kore_runtime_getcall(name)) == NULL) { kore_log(LOG_ERR, "on_free callback '%s' for '%s' not found", name, current_route->path); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_route_authenticate(char *name) { if (current_route == NULL) { kore_log(LOG_ERR, "authenticate keyword not inside of route context"); return (KORE_RESULT_ERROR); } current_route->auth = kore_auth_lookup(name); if (current_route->auth == NULL) { kore_log(LOG_ERR, "no such authentication '%s' for '%s' found", name, current_route->path); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_route_methods(char *options) { int i, cnt; char *argv[10]; if (current_route == NULL) { kore_log(LOG_ERR, "methods keyword not inside of route context"); return (KORE_RESULT_ERROR); } cnt = kore_split_string(options, " ", argv, 10); if (cnt < 1) { kore_log(LOG_ERR, "bad methods option '%s', missing methods", options); return (KORE_RESULT_ERROR); } current_route->methods = 0; for (i = 0; i < cnt; i++) { if (!strcasecmp(argv[i], "post")) { current_route->methods |= HTTP_METHOD_POST; } else if (!strcasecmp(argv[i], "get")) { current_route->methods |= HTTP_METHOD_GET; } else if (!strcasecmp(argv[i], "put")) { current_route->methods |= HTTP_METHOD_PUT; } else if (!strcasecmp(argv[i], "delete")) { current_route->methods |= HTTP_METHOD_DELETE; } else if (!strcasecmp(argv[i], "head")) { current_route->methods |= HTTP_METHOD_HEAD; } else if (!strcasecmp(argv[i], "patch")) { current_route->methods |= HTTP_METHOD_PATCH; } else { kore_log(LOG_ERR, "unknown method: %s in method for %s", argv[i], current_route->path); return (KORE_RESULT_ERROR); } } return (KORE_RESULT_OK); } static int configure_return(char *options) { char *argv[3]; int elm, status, err; if (current_domain == NULL) { kore_log(LOG_ERR, "return keyword not in domain context"); return (KORE_RESULT_ERROR); } elm = kore_split_string(options, " ", argv, 3); if (elm != 2) { kore_log(LOG_ERR, "missing parameters for return"); return (KORE_RESULT_ERROR); } status = kore_strtonum(argv[1], 10, 400, 600, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "invalid status code on return (%s)", argv[1]); return (KORE_RESULT_ERROR); } if (!http_redirect_add(current_domain, argv[0], status, NULL)) { kore_log(LOG_ERR, "invalid regex on return path"); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_redirect(char *options) { char *argv[4]; int elm, status, err; if (current_domain == NULL) { kore_log(LOG_ERR, "redirect keyword not in domain context"); return (KORE_RESULT_ERROR); } elm = kore_split_string(options, " ", argv, 4); if (elm != 3) { kore_log(LOG_ERR, "missing parameters for redirect"); return (KORE_RESULT_ERROR); } status = kore_strtonum(argv[1], 10, 300, 399, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "invalid status code on redirect (%s)", argv[1]); return (KORE_RESULT_ERROR); } if (!http_redirect_add(current_domain, argv[0], status, argv[2])) { kore_log(LOG_ERR, "invalid regex on redirect path"); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_filemap(char *options) { char *argv[4]; if (current_domain == NULL) { kore_log(LOG_ERR, "filemap keyword not in domain context"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 4); if (argv[0] == NULL || argv[1] == NULL) { kore_log(LOG_ERR, "missing parameters for filemap"); return (KORE_RESULT_ERROR); } if (kore_filemap_create(current_domain, argv[1], argv[0], argv[2]) == NULL) { kore_log(LOG_ERR, "cannot create filemap for %s", argv[1]); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_accesslog(char *path) { if (current_domain == NULL) { kore_log(LOG_ERR, "accesslog not specified in domain context"); return (KORE_RESULT_ERROR); } if (current_domain->accesslog != -1) { kore_log(LOG_ERR, "domain '%s' already has an open accesslog", current_domain->domain); return (KORE_RESULT_ERROR); } current_domain->accesslog = open(path, O_CREAT | O_APPEND | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (current_domain->accesslog == -1) { kore_log(LOG_ERR, "accesslog open(%s): %s", path, errno_s); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_filemap_ext(char *ext) { kore_free(kore_filemap_ext); kore_filemap_ext = kore_strdup(ext); return (KORE_RESULT_OK); } static int configure_filemap_index(char *index) { kore_free(kore_filemap_index); kore_filemap_index = kore_strdup(index); return (KORE_RESULT_OK); } static int configure_http_media_type(char *type) { int i; char *extensions, *ext[10]; extensions = strchr(type, ' '); if (extensions == NULL) { kore_log(LOG_ERR, "bad http_media_type value '%s'", type); return (KORE_RESULT_ERROR); } *(extensions)++ = '\0'; kore_split_string(extensions, " \t", ext, 10); for (i = 0; ext[i] != NULL; i++) { if (!http_media_register(ext[i], type)) { kore_log(LOG_ERR, "duplicate extension found '%s'", ext[i]); return (KORE_RESULT_ERROR); } } if (i == 0) { kore_log(LOG_ERR, "missing extensions in '%s'", type); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_header_max(char *option) { int err; http_header_max = kore_strtonum(option, 10, 1, 65535, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_header_max value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_header_timeout(char *option) { int err; http_header_timeout = kore_strtonum(option, 10, 1, 65535, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_header_timeout value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_body_max(char *option) { int err; http_body_max = kore_strtonum(option, 10, 0, LONG_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_body_max value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_body_timeout(char *option) { int err; http_body_timeout = kore_strtonum(option, 10, 1, 65535, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_body_timeout value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_body_disk_offload(char *option) { int err; http_body_disk_offload = kore_strtonum(option, 10, 0, LONG_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_body_disk_offload value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_body_disk_path(char *path) { if (strcmp(http_body_disk_path, HTTP_BODY_DISK_PATH)) kore_free(http_body_disk_path); http_body_disk_path = kore_strdup(path); return (KORE_RESULT_OK); } static int configure_http_server_version(char *version) { http_server_version(version); return (KORE_RESULT_OK); } static int configure_http_pretty_error(char *yesno) { if (!strcmp(yesno, "no")) { http_pretty_error = 0; } else if (!strcmp(yesno, "yes")) { http_pretty_error = 1; } else { kore_log(LOG_ERR, "invalid '%s' for yes|no http_pretty_error option", yesno); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_hsts_enable(char *option) { int err; http_hsts_enable = kore_strtonum(option, 10, 0, LONG_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_hsts_enable value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_keepalive_time(char *option) { int err; http_keepalive_time = kore_strtonum(option, 10, 0, USHRT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_keepalive_time value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_request_ms(char *option) { int err; http_request_ms = kore_strtonum(option, 10, 0, UINT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_request_ms value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_http_request_limit(char *option) { int err; http_request_limit = kore_strtonum(option, 10, 0, UINT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad http_request_limit value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_validator(char *name) { u_int8_t type; char *tname, *value; if ((tname = strchr(name, ' ')) == NULL) { kore_log(LOG_ERR, "missing validator name"); return (KORE_RESULT_ERROR); } *(tname)++ = '\0'; tname = kore_text_trim(tname, strlen(tname)); if ((value = strchr(tname, ' ')) == NULL) { kore_log(LOG_ERR, "missing validator value"); return (KORE_RESULT_ERROR); } *(value)++ = '\0'; value = kore_text_trim(value, strlen(value)); if (!strcmp(tname, "regex")) { type = KORE_VALIDATOR_TYPE_REGEX; } else if (!strcmp(tname, "function")) { type = KORE_VALIDATOR_TYPE_FUNCTION; } else { kore_log(LOG_ERR, "bad type '%s' for validator '%s'", tname, name); return (KORE_RESULT_ERROR); } if (!kore_validator_add(name, type, value)) { kore_log(LOG_ERR, "bad validator specified for '%s'", name); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_validate(char *options) { struct kore_validator *val; struct kore_route_params *param; char *method, *argv[4]; int flags, http_method; if (kore_split_string(options, " ", argv, 4) != 3) { kore_log(LOG_ERR, "validate keyword needs 3 args: method param validator"); return (KORE_RESULT_ERROR); } flags = 0; if ((method = strchr(argv[0], ':')) != NULL) { *(method)++ = '\0'; if (!strcasecmp(argv[0], "qs")) { flags = KORE_PARAMS_QUERY_STRING; } else { kore_log(LOG_ERR, "unknown validate method prefix '%s' for '%s'", argv[0], current_route->path); return (KORE_RESULT_ERROR); } } else { method = argv[0]; } if ((val = kore_validator_lookup(argv[2])) == NULL) { kore_log(LOG_ERR, "unknown validator '%s'", argv[2]); return (KORE_RESULT_ERROR); } if (!strcasecmp(method, "post")) { http_method = HTTP_METHOD_POST; } else if (!strcasecmp(method, "get")) { http_method = HTTP_METHOD_GET; /* Let params get /foo {} imply qs:get automatically. */ flags |= KORE_PARAMS_QUERY_STRING; } else if (!strcasecmp(method, "put")) { http_method = HTTP_METHOD_PUT; } else if (!strcasecmp(method, "delete")) { http_method = HTTP_METHOD_DELETE; } else if (!strcasecmp(method, "head")) { http_method = HTTP_METHOD_HEAD; } else if (!strcasecmp(method, "patch")) { http_method = HTTP_METHOD_PATCH; } else { kore_log(LOG_ERR, "unknown method: %s in validator for %s", method, current_route->path); return (KORE_RESULT_ERROR); } if (!(current_route->methods & http_method)) { kore_log(LOG_ERR, "method '%s' not enabled for route '%s'", method, current_route->path); return (KORE_RESULT_ERROR); } param = kore_calloc(1, sizeof(*param)); param->flags = flags; param->validator = val; param->method = http_method; param->name = kore_strdup(argv[1]); TAILQ_INSERT_TAIL(¤t_route->params, param, list); return (KORE_RESULT_OK); } static int configure_authentication(char *options) { char *argv[3]; if (current_auth != NULL) { kore_log(LOG_ERR, "previous authentication block not closed"); return (KORE_RESULT_ERROR); } kore_split_string(options, " ", argv, 3); if (argv[1] == NULL) { kore_log(LOG_ERR, "missing name for authentication block"); return (KORE_RESULT_ERROR); } if (strcmp(argv[1], "{")) { kore_log(LOG_ERR, "missing { for authentication block"); return (KORE_RESULT_ERROR); } if (!kore_auth_new(argv[0])) return (KORE_RESULT_ERROR); current_auth = kore_auth_lookup(argv[0]); return (KORE_RESULT_OK); } static int configure_authentication_type(char *option) { if (current_auth == NULL) { kore_log(LOG_ERR, "authentication_type keyword not in correct context"); return (KORE_RESULT_ERROR); } if (!strcmp(option, "cookie")) { current_auth->type = KORE_AUTH_TYPE_COOKIE; } else if (!strcmp(option, "header")) { current_auth->type = KORE_AUTH_TYPE_HEADER; } else if (!strcmp(option, "request")) { current_auth->type = KORE_AUTH_TYPE_REQUEST; } else { kore_log(LOG_ERR, "unknown authentication type '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_authentication_value(char *option) { if (current_auth == NULL) { kore_log(LOG_ERR, "authentication_value keyword not in correct context"); return (KORE_RESULT_ERROR); } if (current_auth->value != NULL) kore_free(current_auth->value); current_auth->value = kore_strdup(option); return (KORE_RESULT_OK); } static int configure_authentication_validator(char *validator) { struct kore_validator *val; if (current_auth == NULL) { kore_log(LOG_ERR, "authentication_validator not in correct context"); return (KORE_RESULT_ERROR); } if ((val = kore_validator_lookup(validator)) == NULL) { kore_log(LOG_ERR, "authentication validator '%s' not found", validator); return (KORE_RESULT_ERROR); } current_auth->validator = val; return (KORE_RESULT_OK); } static int configure_authentication_uri(char *uri) { if (current_auth == NULL) { kore_log(LOG_ERR, "authentication_uri keyword not in correct context"); return (KORE_RESULT_ERROR); } if (current_auth->redirect != NULL) kore_free(current_auth->redirect); current_auth->redirect = kore_strdup(uri); return (KORE_RESULT_OK); } static int configure_websocket_maxframe(char *option) { int err; kore_websocket_maxframe = kore_strtonum64(option, 1, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad kore_websocket_maxframe value '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_websocket_timeout(char *option) { int err; kore_websocket_timeout = kore_strtonum64(option, 1, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad kore_websocket_timeout value '%s'", option); return (KORE_RESULT_ERROR); } kore_websocket_timeout = kore_websocket_timeout * 1000; return (KORE_RESULT_OK); } #endif /* !KORE_NO_HTTP */ static int configure_logfile(char *path) { kore_log_file(path); return (KORE_RESULT_OK); } static int configure_workers(char *option) { int err; worker_count = kore_strtonum(option, 10, 1, KORE_WORKER_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for worker '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_pidfile(char *path) { if (strcmp(kore_pidfile, KORE_PIDFILE_DEFAULT)) kore_free(kore_pidfile); kore_pidfile = kore_strdup(path); return (KORE_RESULT_OK); } static int configure_max_connections(char *option) { int err; worker_max_connections = kore_strtonum(option, 10, 1, UINT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for worker_max_connections '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_rlimit_nofiles(char *option) { int err; worker_rlimit_nofiles = kore_strtonum(option, 10, 1, UINT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for worker_rlimit_nofiles '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_accept_threshold(char *option) { int err; worker_accept_threshold = kore_strtonum(option, 0, 1, UINT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for worker_accept_threshold '%s'\n", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_death_policy(char *option) { if (!strcmp(option, "restart")) { worker_policy = KORE_WORKER_POLICY_RESTART; } else if (!strcmp(option, "terminate")) { worker_policy = KORE_WORKER_POLICY_TERMINATE; } else { kore_log(LOG_ERR, "bad value for worker_death_policy '%s'\n", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_set_affinity(char *option) { int err; worker_set_affinity = kore_strtonum(option, 10, 0, 1, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for worker_set_affinity '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_socket_backlog(char *option) { int err; kore_socket_backlog = kore_strtonum(option, 10, 0, UINT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad socket_backlog value: '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } #if defined(KORE_USE_PGSQL) static int configure_pgsql_conn_max(char *option) { int err; pgsql_conn_max = kore_strtonum(option, 10, 0, USHRT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for pgsql_conn_max '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_pgsql_queue_limit(char *option) { int err; pgsql_queue_limit = kore_strtonum(option, 10, 0, UINT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for pgsql_queue_limit '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } #endif #if defined(KORE_USE_TASKS) static int configure_task_threads(char *option) { int err; kore_task_threads = kore_strtonum(option, 10, 0, UCHAR_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad value for task_threads: '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } #endif #if defined(KORE_USE_PYTHON) static int configure_deployment(char *value) { if (!strcmp(value, "docker")) { kore_foreground = 1; skip_runas = 0; skip_chroot = 0; } else if (!strcmp(value, "dev") || !strcmp(value, "development")) { kore_foreground = 1; skip_runas = 1; skip_chroot = 1; } else if (!strcmp(value, "production")) { kore_foreground = 0; skip_runas = 0; skip_chroot = 0; } else { kore_log(LOG_NOTICE, "kore.config.deployment: bad value '%s'", value); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_python_path(char *path) { kore_python_path(path); return (KORE_RESULT_OK); } static int configure_python_import(char *module) { char *argv[3]; kore_split_string(module, " ", argv, 3); if (argv[0] == NULL) return (KORE_RESULT_ERROR); kore_module_load(argv[0], argv[1], KORE_MODULE_PYTHON); return (KORE_RESULT_OK); } #endif #if defined(KORE_USE_PLATFORM_PLEDGE) static int configure_add_pledge(char *pledge) { kore_platform_add_pledge(pledge); return (KORE_RESULT_OK); } #endif #if defined(KORE_USE_CURL) static int configure_curl_recv_max(char *option) { int err; kore_curl_recv_max = kore_strtonum64(option, 1, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad curl_recv_max value '%s'\n", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int configure_curl_timeout(char *option) { int err; kore_curl_timeout = kore_strtonum(option, 10, 0, USHRT_MAX, &err); if (err != KORE_RESULT_OK) { kore_log(LOG_ERR, "bad kore_curl_timeout value: '%s'", option); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } #endif #if defined(__linux__) static int configure_seccomp_tracing(char *opt) { if (!strcmp(opt, "yes")) { kore_seccomp_tracing = 1; } else if (!strcmp(opt, "no")) { kore_seccomp_tracing = 0; } else { kore_log(LOG_ERR, "bad seccomp_tracing value '%s' (expected yes|no)\n", opt); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } #endif kore-4.2.3/src/connection.c000066400000000000000000000206601430066302400155540ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "kore.h" #include "http.h" struct kore_pool connection_pool; struct connection_list connections; struct connection_list disconnected; void kore_connection_init(void) { u_int32_t elm; TAILQ_INIT(&connections); TAILQ_INIT(&disconnected); /* Add some overhead so we don't rollover for internal items. */ elm = worker_max_connections + 10; kore_pool_init(&connection_pool, "connection_pool", sizeof(struct connection), elm); } void kore_connection_cleanup(void) { /* Drop all connections */ kore_connection_prune(KORE_CONNECTION_PRUNE_ALL); kore_pool_cleanup(&connection_pool); } struct connection * kore_connection_new(void *owner) { struct connection *c; c = kore_pool_get(&connection_pool); c->flags = 0; c->rnb = NULL; c->snb = NULL; c->owner = owner; c->handle = NULL; c->tls = NULL; c->tls_cert = NULL; c->tls_reneg = 0; c->tls_sni = NULL; c->disconnect = NULL; c->hdlr_extra = NULL; c->proto = CONN_PROTO_UNKNOWN; c->idle_timer.start = 0; c->idle_timer.length = KORE_IDLE_TIMER_MAX; c->evt.type = KORE_TYPE_CONNECTION; c->evt.handle = kore_connection_event; #if !defined(KORE_NO_HTTP) c->ws_connect = NULL; c->ws_message = NULL; c->ws_disconnect = NULL; c->http_start = kore_time_ms(); c->http_timeout = http_header_timeout * 1000; TAILQ_INIT(&(c->http_requests)); #endif TAILQ_INIT(&(c->send_queue)); return (c); } int kore_connection_accept(struct listener *listener, struct connection **out) { struct connection *c; struct sockaddr *s; socklen_t len; *out = NULL; c = kore_connection_new(listener); c->family = listener->family; switch (c->family) { case AF_INET: len = sizeof(struct sockaddr_in); s = (struct sockaddr *)&(c->addr.ipv4); break; case AF_INET6: len = sizeof(struct sockaddr_in6); s = (struct sockaddr *)&(c->addr.ipv6); break; case AF_UNIX: len = sizeof(struct sockaddr_un); s = (struct sockaddr *)&(c->addr.sun); break; default: fatal("unknown family type %d", c->family); } if ((c->fd = accept(listener->fd, s, &len)) == -1) { kore_pool_put(&connection_pool, c); return (KORE_RESULT_ERROR); } if (!kore_connection_nonblock(c->fd, listener->family != AF_UNIX)) { close(c->fd); kore_pool_put(&connection_pool, c); return (KORE_RESULT_ERROR); } if (fcntl(c->fd, F_SETFD, FD_CLOEXEC) == -1) { close(c->fd); kore_pool_put(&connection_pool, c); return (KORE_RESULT_ERROR); } c->handle = kore_connection_handle; TAILQ_INSERT_TAIL(&connections, c, list); if (listener->server->tls) { c->state = CONN_STATE_TLS_SHAKE; c->write = kore_tls_write; c->read = kore_tls_read; } else { c->state = CONN_STATE_ESTABLISHED; c->write = net_write; c->read = net_read; if (listener->connect != NULL) { kore_runtime_connect(listener->connect, c); } else { #if !defined(KORE_NO_HTTP) c->proto = CONN_PROTO_HTTP; if (http_keepalive_time != 0) { c->idle_timer.length = http_keepalive_time * 1000; } net_recv_queue(c, http_header_max, NETBUF_CALL_CB_ALWAYS, http_header_recv); #endif } } kore_connection_start_idletimer(c); worker_active_connections++; *out = c; return (KORE_RESULT_OK); } void kore_connection_check_timeout(u_int64_t now) { struct connection *c, *next; for (c = TAILQ_FIRST(&connections); c != NULL; c = next) { next = TAILQ_NEXT(c, list); if (c->proto == CONN_PROTO_MSG) continue; #if !defined(KORE_NO_HTTP) if (c->state == CONN_STATE_ESTABLISHED && c->proto == CONN_PROTO_HTTP) { if (!http_check_timeout(c, now)) continue; if (!TAILQ_EMPTY(&c->http_requests)) continue; } #endif if (c->flags & CONN_IDLE_TIMER_ACT) kore_connection_check_idletimer(now, c); } } void kore_connection_prune(int all) { struct connection *c, *cnext; if (all) { for (c = TAILQ_FIRST(&connections); c != NULL; c = cnext) { cnext = TAILQ_NEXT(c, list); net_send_flush(c); kore_connection_disconnect(c); } } for (c = TAILQ_FIRST(&disconnected); c != NULL; c = cnext) { cnext = TAILQ_NEXT(c, list); TAILQ_REMOVE(&disconnected, c, list); kore_connection_remove(c); } } void kore_connection_disconnect(struct connection *c) { if (c->state != CONN_STATE_DISCONNECTING) { c->state = CONN_STATE_DISCONNECTING; if (c->disconnect) c->disconnect(c); TAILQ_REMOVE(&connections, c, list); TAILQ_INSERT_TAIL(&disconnected, c, list); } } void kore_connection_event(void *arg, int error) { struct connection *c = arg; if (error) { kore_connection_disconnect(c); return; } if (!c->handle(c)) kore_connection_disconnect(c); } int kore_connection_handle(struct connection *c) { struct listener *listener; kore_connection_stop_idletimer(c); switch (c->state) { case CONN_STATE_TLS_SHAKE: switch (kore_tls_connection_accept(c)) { case KORE_RESULT_OK: break; case KORE_RESULT_RETRY: return (KORE_RESULT_OK); default: return (KORE_RESULT_ERROR); } if (c->owner != NULL) { listener = (struct listener *)c->owner; if (listener->connect != NULL) { kore_runtime_connect(listener->connect, c); kore_connection_start_idletimer(c); return (KORE_RESULT_OK); } } #if !defined(KORE_NO_HTTP) c->proto = CONN_PROTO_HTTP; if (http_keepalive_time != 0) { c->idle_timer.length = http_keepalive_time * 1000; } net_recv_queue(c, http_header_max, NETBUF_CALL_CB_ALWAYS, http_header_recv); #endif c->state = CONN_STATE_ESTABLISHED; /* FALLTHROUGH */ case CONN_STATE_ESTABLISHED: if (c->evt.flags & KORE_EVENT_READ) { if (!net_recv_flush(c)) return (KORE_RESULT_ERROR); } if (c->evt.flags & KORE_EVENT_WRITE) { if (!net_send_flush(c)) return (KORE_RESULT_ERROR); } break; case CONN_STATE_DISCONNECTING: break; default: break; } kore_connection_start_idletimer(c); return (KORE_RESULT_OK); } void kore_connection_remove(struct connection *c) { struct netbuf *nb, *next; #if !defined(KORE_NO_HTTP) struct http_request *req, *rnext; #endif kore_tls_connection_cleanup(c); close(c->fd); if (c->hdlr_extra != NULL) kore_free(c->hdlr_extra); #if !defined(KORE_NO_HTTP) for (req = TAILQ_FIRST(&(c->http_requests)); req != NULL; req = rnext) { rnext = TAILQ_NEXT(req, olist); TAILQ_REMOVE(&(c->http_requests), req, olist); req->owner = NULL; req->flags |= HTTP_REQUEST_DELETE; http_request_wakeup(req); } kore_free(c->ws_connect); kore_free(c->ws_message); kore_free(c->ws_disconnect); #endif for (nb = TAILQ_FIRST(&(c->send_queue)); nb != NULL; nb = next) { next = TAILQ_NEXT(nb, list); nb->flags &= ~NETBUF_MUST_RESEND; net_remove_netbuf(c, nb); } if (c->rnb != NULL) { kore_free(c->rnb->buf); kore_pool_put(&nb_pool, c->rnb); } kore_pool_put(&connection_pool, c); worker_active_connections--; } void kore_connection_check_idletimer(u_int64_t now, struct connection *c) { u_int64_t d; if (now > c->idle_timer.start) d = now - c->idle_timer.start; else d = 0; if (d >= c->idle_timer.length) kore_connection_disconnect(c); } void kore_connection_start_idletimer(struct connection *c) { c->flags |= CONN_IDLE_TIMER_ACT; c->idle_timer.start = kore_time_ms(); } void kore_connection_stop_idletimer(struct connection *c) { c->flags &= ~CONN_IDLE_TIMER_ACT; c->idle_timer.start = 0; } int kore_connection_nonblock(int fd, int nodelay) { int flags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) return (KORE_RESULT_ERROR); flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) return (KORE_RESULT_ERROR); if (nodelay) { if (!kore_sockopt(fd, IPPROTO_TCP, TCP_NODELAY)) { kore_log(LOG_NOTICE, "failed to set TCP_NODELAY on %d", fd); } } return (KORE_RESULT_OK); } kore-4.2.3/src/curl.c000066400000000000000000000413251430066302400143630ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "kore.h" #include "http.h" #include "curl.h" #if defined(__linux__) #include "seccomp.h" static struct sock_filter filter_curl[] = { /* Allow sockets and libcurl to call connect. */ KORE_SYSCALL_ALLOW(bind), KORE_SYSCALL_ALLOW(ioctl), KORE_SYSCALL_ALLOW(connect), KORE_SYSCALL_ALLOW(getsockopt), KORE_SYSCALL_ALLOW(getsockname), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_NETLINK), /* Threading related. */ KORE_SYSCALL_ALLOW(clone), KORE_SYSCALL_ALLOW(set_robust_list), /* Other */ KORE_SYSCALL_ALLOW(uname), KORE_SYSCALL_ALLOW(ioctl), KORE_SYSCALL_ALLOW(madvise), KORE_SYSCALL_ALLOW(recvmsg), KORE_SYSCALL_ALLOW(sendmmsg), KORE_SYSCALL_ALLOW(faccessat), KORE_SYSCALL_ALLOW(newfstatat), KORE_SYSCALL_ALLOW(getpeername), }; #endif #define FD_CACHE_BUCKETS 2048 struct fd_cache { struct kore_event evt; int fd; int scheduled; LIST_ENTRY(fd_cache) list; }; struct curl_run { int eof; struct fd_cache *fdc; TAILQ_ENTRY(curl_run) list; }; static void curl_process(void); static void curl_event_handle(void *, int); static void curl_timeout(void *, u_int64_t); static int curl_timer(CURLM *, long, void *); static void curl_run_handle(struct curl_run *); static void curl_run_schedule(struct fd_cache *, int); static int curl_socket(CURL *, curl_socket_t, int, void *, void *); static struct fd_cache *fd_cache_get(int); static TAILQ_HEAD(, curl_run) runlist; static struct kore_pool run_pool; static int running = 0; static CURLM *multi = NULL; static struct kore_timer *timer = NULL; static struct kore_pool fd_cache_pool; static char user_agent[64]; static int timeout_immediate = 0; static LIST_HEAD(, fd_cache) cache[FD_CACHE_BUCKETS]; u_int16_t kore_curl_timeout = KORE_CURL_TIMEOUT; u_int64_t kore_curl_recv_max = KORE_CURL_RECV_MAX; void kore_curl_sysinit(void) { CURLMcode res; int i, len; if (curl_global_init(CURL_GLOBAL_ALL)) fatal("failed to initialize libcurl"); if ((multi = curl_multi_init()) == NULL) fatal("curl_multi_init(): failed"); /* XXX - make configurable? */ curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, 500); if ((res = curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, curl_socket)) != CURLM_OK) fatal("curl_multi_setopt: %s", curl_multi_strerror(res)); if ((res = curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, curl_timer)) != CURLM_OK) fatal("curl_multi_setopt: %s", curl_multi_strerror(res)); for (i = 0; i < FD_CACHE_BUCKETS; i++) LIST_INIT(&cache[i]); TAILQ_INIT(&runlist); kore_pool_init(&fd_cache_pool, "fd_cache_pool", 100, sizeof(struct fd_cache)); kore_pool_init(&run_pool, "run_pool", 100, sizeof(struct curl_run)); len = snprintf(user_agent, sizeof(user_agent), "kore/%s", kore_version); if (len == -1 || (size_t)len >= sizeof(user_agent)) fatal("user-agent string too long"); #if defined(__linux__) kore_seccomp_filter("curl", filter_curl, KORE_FILTER_LEN(filter_curl)); #endif #if defined(KORE_USE_PLATFORM_PLEDGE) kore_platform_add_pledge("dns"); #endif } int kore_curl_init(struct kore_curl *client, const char *url, int flags) { CURL *handle; if ((flags & KORE_CURL_ASYNC) && (flags & KORE_CURL_SYNC)) { (void)kore_strlcpy(client->errbuf, "invalid flags", sizeof(client->errbuf)); return (KORE_RESULT_ERROR); } memset(client, 0, sizeof(*client)); TAILQ_INIT(&client->http.resp_hdrs); if ((handle = curl_easy_init()) == NULL) { (void)kore_strlcpy(client->errbuf, "failed to setup curl", sizeof(client->errbuf)); return (KORE_RESULT_ERROR); } curl_easy_setopt(handle, CURLOPT_WRITEDATA, &client->response); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, kore_curl_tobuf); curl_easy_setopt(handle, CURLOPT_URL, url); curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(handle, CURLOPT_PRIVATE, client); curl_easy_setopt(handle, CURLOPT_USERAGENT, user_agent); curl_easy_setopt(handle, CURLOPT_TIMEOUT, kore_curl_timeout); curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, client->errbuf); client->flags = flags; client->handle = handle; client->url = kore_strdup(url); client->type = KORE_CURL_TYPE_CUSTOM; return (KORE_RESULT_OK); } void kore_curl_cleanup(struct kore_curl *client) { struct http_header *hdr, *next; kore_free(client->url); if (client->flags & KORE_CURL_FLAG_BOUND) LIST_REMOVE(client, list); if (client->handle != NULL) { curl_multi_remove_handle(multi, client->handle); curl_easy_cleanup(client->handle); } if (client->http.hdrlist != NULL) curl_slist_free_all(client->http.hdrlist); if (client->response != NULL) kore_buf_free(client->response); if (client->http.headers != NULL) kore_buf_free(client->http.headers); if (client->http.tosend != NULL) kore_buf_free(client->http.tosend); for (hdr = TAILQ_FIRST(&client->http.resp_hdrs); hdr != NULL; hdr = next) { next = TAILQ_NEXT(hdr, list); TAILQ_REMOVE(&client->http.resp_hdrs, hdr, list); kore_pool_put(&http_header_pool, hdr); } } void kore_curl_do_timeout(void) { while (timeout_immediate) { curl_timeout(NULL, kore_time_ms()); if (running == 0) curl_timer(multi, -1, NULL); } } void kore_curl_run_scheduled(void) { struct curl_run *run; while ((run = TAILQ_FIRST(&runlist))) { TAILQ_REMOVE(&runlist, run, list); curl_run_handle(run); kore_pool_put(&run_pool, run); } curl_process(); } size_t kore_curl_tobuf(char *ptr, size_t size, size_t nmemb, void *udata) { size_t len; struct kore_buf **buf, *b; if (SIZE_MAX / nmemb < size) fatal("%s: %zu * %zu overflow", __func__, nmemb, size); buf = udata; len = size * nmemb; if (*buf == NULL) *buf = kore_buf_alloc(len); b = *buf; if (b->offset + len < b->offset) fatal("%s: %zu+%zu overflows", __func__, b->offset, len); if ((b->offset + len) > kore_curl_recv_max) { kore_log(LOG_ERR, "received too large transfer (%zu > %" PRIu64 ")", b->offset + len, kore_curl_recv_max); return (0); } kore_buf_append(b, ptr, len); return (len); } size_t kore_curl_frombuf(char *ptr, size_t size, size_t nmemb, void *udata) { size_t len; struct kore_buf *buf; if (SIZE_MAX / nmemb < size) fatal("%s: %zu * %zu overflow", __func__, nmemb, size); buf = udata; len = size * nmemb; if (buf->offset == buf->length) return (0); if (buf->offset + len < buf->offset) fatal("%s: %zu+%zu overflows", __func__, buf->offset, len); if ((buf->offset + len) < buf->length) { memcpy(ptr, buf->data + buf->offset, len); } else { len = buf->length - buf->offset; memcpy(ptr, buf->data + buf->offset, len); } buf->offset += len; return (len); } void kore_curl_bind_request(struct kore_curl *client, struct http_request *req) { if (client->cb != NULL) fatal("%s: already bound to callback", __func__); client->req = req; http_request_sleep(req); client->flags |= KORE_CURL_FLAG_BOUND; LIST_INSERT_HEAD(&req->chandles, client, list); } void kore_curl_bind_callback(struct kore_curl *client, void (*cb)(struct kore_curl *, void *), void *arg) { if (client->req != NULL) fatal("%s: already bound to request", __func__); client->cb = cb; client->arg = arg; } void kore_curl_run(struct kore_curl *client) { if (client->flags & KORE_CURL_ASYNC) { curl_multi_add_handle(multi, client->handle); return; } client->result = curl_easy_perform(client->handle); curl_easy_getinfo(client->handle, CURLINFO_RESPONSE_CODE, &client->http.status); curl_easy_cleanup(client->handle); client->handle = NULL; } int kore_curl_success(struct kore_curl *client) { return (client->result == CURLE_OK); } const char * kore_curl_strerror(struct kore_curl *client) { const char *err; if (client->errbuf[0] != '\0') err = &client->errbuf[0]; else err = curl_easy_strerror(client->result); return (err); } void kore_curl_logerror(struct kore_curl *client) { kore_log(LOG_NOTICE, "curl error: %s -> %s", client->url, kore_curl_strerror(client)); } void kore_curl_response_as_bytes(struct kore_curl *client, const u_int8_t **body, size_t *len) { if (client->response == NULL) { *len = 0; *body = NULL; } else { *len = client->response->offset; *body = client->response->data; } } char * kore_curl_response_as_string(struct kore_curl *client) { kore_buf_stringify(client->response, NULL); return ((char *)client->response->data); } void kore_curl_http_setup(struct kore_curl *client, int method, const void *data, size_t len) { const char *mname; int has_body; if (client->handle == NULL) fatal("%s: called without setup", __func__); mname = NULL; has_body = 1; client->type = KORE_CURL_TYPE_HTTP_CLIENT; curl_easy_setopt(client->handle, CURLOPT_HEADERDATA, &client->http.headers); curl_easy_setopt(client->handle, CURLOPT_HEADERFUNCTION, kore_curl_tobuf); kore_curl_http_set_header(client, "expect", ""); switch (method) { case HTTP_METHOD_GET: case HTTP_METHOD_OPTIONS: break; case HTTP_METHOD_HEAD: curl_easy_setopt(client->handle, CURLOPT_NOBODY, 1); break; case HTTP_METHOD_PUT: has_body = 1; curl_easy_setopt(client->handle, CURLOPT_UPLOAD, 1); break; case HTTP_METHOD_PATCH: case HTTP_METHOD_DELETE: mname = http_method_text(method); /* fallthrough */ case HTTP_METHOD_POST: has_body = 1; curl_easy_setopt(client->handle, CURLOPT_POST, 1); break; default: fatal("%s: unknown method %d", __func__, method); } if (has_body && data != NULL && len > 0) { client->http.tosend = kore_buf_alloc(len); kore_buf_append(client->http.tosend, data, len); kore_buf_reset(client->http.tosend); curl_easy_setopt(client->handle, CURLOPT_READDATA, client->http.tosend); curl_easy_setopt(client->handle, CURLOPT_READFUNCTION, kore_curl_frombuf); } if (has_body) { if (method == HTTP_METHOD_PUT) { curl_easy_setopt(client->handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)len); } else { curl_easy_setopt(client->handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)len); } } else { if (data != NULL || len != 0) { fatal("%s: %d should not have a body", __func__, method); } } if (mname != NULL) curl_easy_setopt(client->handle, CURLOPT_CUSTOMREQUEST, mname); } void kore_curl_http_set_header(struct kore_curl *client, const char *header, const char *value) { struct kore_buf buf; const char *hdr; kore_buf_init(&buf, 512); if (value != NULL || *value != '\0') { kore_buf_appendf(&buf, "%s: %s", header, value); } else { kore_buf_appendf(&buf, "%s:", header); } hdr = kore_buf_stringify(&buf, NULL); client->http.hdrlist = curl_slist_append(client->http.hdrlist, hdr); kore_buf_cleanup(&buf); curl_easy_setopt(client->handle, CURLOPT_HTTPHEADER, client->http.hdrlist); } int kore_curl_http_get_header(struct kore_curl *client, const char *header, const char **out) { struct http_header *hdr; if (!(client->flags & KORE_CURL_FLAG_HTTP_PARSED_HEADERS)) kore_curl_http_parse_headers(client); TAILQ_FOREACH(hdr, &(client->http.resp_hdrs), list) { if (!strcasecmp(hdr->header, header)) { *out = hdr->value; return (KORE_RESULT_OK); } } return (KORE_RESULT_ERROR); } void kore_curl_http_parse_headers(struct kore_curl *client) { struct http_header *hdr; int i, cnt; char *value, *hbuf, *headers[HTTP_REQ_HEADER_MAX]; if (client->flags & KORE_CURL_FLAG_HTTP_PARSED_HEADERS) fatal("%s: headers already parsed", __func__); client->flags |= KORE_CURL_FLAG_HTTP_PARSED_HEADERS; if (client->http.headers == NULL) return; hbuf = kore_buf_stringify(client->http.headers, NULL); cnt = kore_split_string(hbuf, "\r\n", headers, HTTP_REQ_HEADER_MAX); for (i = 0; i < cnt; i++) { if ((value = http_validate_header(headers[i])) == NULL) continue; if (*value == '\0') continue; hdr = kore_pool_get(&http_header_pool); hdr->header = headers[i]; hdr->value = value; TAILQ_INSERT_TAIL(&(client->http.resp_hdrs), hdr, list); } } static int curl_socket(CURL *easy, curl_socket_t fd, int action, void *arg, void *sock) { CURLcode res; struct fd_cache *fdc; struct kore_curl *client; client = NULL; res = curl_easy_getinfo(easy, CURLINFO_PRIVATE, (char **)&client); if (res != CURLE_OK) fatal("curl_easy_getinfo: %s", curl_easy_strerror(res)); if (client == NULL) fatal("%s: failed to get client context", __func__); fdc = fd_cache_get(fd); switch (action) { case CURL_POLL_NONE: break; case CURL_POLL_IN: if (fdc->scheduled) { kore_platform_disable_read(fd); #if !defined(__linux__) kore_platform_disable_write(fd); #endif } fdc->scheduled = 1; kore_platform_event_level_read(fd, fdc); break; case CURL_POLL_OUT: case CURL_POLL_INOUT: if (fdc->scheduled) { kore_platform_disable_read(fd); #if !defined(__linux__) kore_platform_disable_write(fd); #endif } fdc->scheduled = 1; kore_platform_event_level_all(fd, fdc); break; case CURL_POLL_REMOVE: if (fdc->scheduled) { fdc->evt.flags = 0; fdc->scheduled = 0; kore_platform_disable_read(fd); #if !defined(__linux__) kore_platform_disable_write(fd); #endif } break; default: fatal("unknown action value: %d", action); } if (action != CURL_POLL_NONE && action != CURL_POLL_REMOVE) curl_run_schedule(fdc, 0); return (CURLM_OK); } static void curl_process(void) { CURLcode res; CURLMsg *msg; CURL *handle; struct kore_curl *client; int pending; pending = 0; while ((msg = curl_multi_info_read(multi, &pending)) != NULL) { if (msg->msg != CURLMSG_DONE) continue; handle = msg->easy_handle; res = curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char **)&client); if (res != CURLE_OK) fatal("curl_easy_getinfo: %s", curl_easy_strerror(res)); if (client == NULL) fatal("%s: failed to get client context", __func__); client->result = msg->data.result; if (client->type == KORE_CURL_TYPE_HTTP_CLIENT) { curl_easy_getinfo(client->handle, CURLINFO_RESPONSE_CODE, &client->http.status); } curl_multi_remove_handle(multi, client->handle); curl_easy_cleanup(client->handle); client->handle = NULL; if (client->req != NULL) http_request_wakeup(client->req); else if (client->cb != NULL) client->cb(client, client->arg); } } static void curl_timeout(void *uarg, u_int64_t now) { CURLMcode res; timer = NULL; res = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running); if (res != CURLM_OK) fatal("curl_multi_socket_action: %s", curl_multi_strerror(res)); curl_process(); } static int curl_timer(CURLM *mctx, long timeout, void *arg) { timeout_immediate = 0; if (timeout < 0) { if (timer != NULL) { kore_timer_remove(timer); timer = NULL; } return (CURLM_OK); } if (timer != NULL) { kore_timer_remove(timer); timer = NULL; } if (timeout == 0) { timeout_immediate = 1; return (CURLM_OK); } timer = kore_timer_add(curl_timeout, timeout, mctx, KORE_TIMER_ONESHOT); return (CURLM_OK); } static void curl_run_schedule(struct fd_cache *fdc, int eof) { struct curl_run *run; run = kore_pool_get(&run_pool); run->fdc = fdc; run->eof = eof; TAILQ_INSERT_TAIL(&runlist, run, list); } static void curl_event_handle(void *arg, int eof) { curl_run_schedule(arg, eof); } static void curl_run_handle(struct curl_run *run) { CURLMcode res; int flags; struct fd_cache *fdc = run->fdc; flags = 0; if (fdc->evt.flags & KORE_EVENT_READ) flags |= CURL_CSELECT_IN; if (fdc->evt.flags & KORE_EVENT_WRITE) flags |= CURL_CSELECT_OUT; if (run->eof) flags |= CURL_CSELECT_ERR; res = curl_multi_socket_action(multi, fdc->fd, flags, &running); if (res != CURLM_OK) fatal("curl_multi_socket_action: %s", curl_multi_strerror(res)); } static struct fd_cache * fd_cache_get(int fd) { struct fd_cache *fdc; int bucket; bucket = fd % FD_CACHE_BUCKETS; LIST_FOREACH(fdc, &cache[bucket], list) { if (fdc->fd == fd) return (fdc); } fdc = kore_pool_get(&fd_cache_pool); fdc->fd = fd; fdc->scheduled = 0; fdc->evt.flags = 0; fdc->evt.handle = curl_event_handle; fdc->evt.type = KORE_TYPE_CURL_HANDLE; LIST_INSERT_HEAD(&cache[bucket], fdc, list); return (fdc); } kore-4.2.3/src/domain.c000066400000000000000000000106711430066302400146650ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "kore.h" #if !defined(KORE_NO_HTTP) #include "http.h" #endif #if defined(KORE_USE_ACME) #include "acme.h" #endif #define KORE_DOMAIN_CACHE 16 static u_int16_t domain_id = 0; struct kore_domain *primary_dom = NULL; static struct kore_domain *cached[KORE_DOMAIN_CACHE]; void kore_domain_init(void) { int i; for (i = 0; i < KORE_DOMAIN_CACHE; i++) cached[i] = NULL; } void kore_domain_cleanup(void) { } struct kore_domain * kore_domain_new(const char *domain) { struct kore_domain *dom; dom = kore_calloc(1, sizeof(*dom)); dom->id = domain_id++; dom->accesslog = -1; dom->x509_verify_depth = 1; dom->domain = kore_strdup(domain); #if !defined(KORE_NO_HTTP) TAILQ_INIT(&dom->routes); TAILQ_INIT(&dom->redirects); #endif if (dom->id < KORE_DOMAIN_CACHE) { if (cached[dom->id] != NULL) fatal("non free domain cache slot"); cached[dom->id] = dom; } if (primary_dom == NULL) primary_dom = dom; return (dom); } int kore_domain_attach(struct kore_domain *dom, struct kore_server *server) { struct kore_domain *d; if (dom->server != NULL) return (KORE_RESULT_ERROR); TAILQ_FOREACH(d, &server->domains, list) { if (!strcmp(d->domain, dom->domain)) return (KORE_RESULT_ERROR); } dom->server = server; TAILQ_INSERT_TAIL(&server->domains, dom, list); /* The primary domain should be attached to a TLS context. */ if (server->tls == 0 && dom == primary_dom) primary_dom = NULL; return (KORE_RESULT_OK); } void kore_domain_free(struct kore_domain *dom) { #if !defined(KORE_NO_HTTP) struct kore_route *rt; struct http_redirect *rdr; #endif if (dom == NULL) return; if (primary_dom == dom) primary_dom = NULL; TAILQ_REMOVE(&dom->server->domains, dom, list); if (dom->domain != NULL) kore_free(dom->domain); kore_tls_domain_cleanup(dom); kore_free(dom->cafile); kore_free(dom->certkey); kore_free(dom->certfile); kore_free(dom->crlfile); #if !defined(KORE_NO_HTTP) /* Drop all handlers associated with this domain */ while ((rt = TAILQ_FIRST(&dom->routes)) != NULL) { TAILQ_REMOVE(&dom->routes, rt, list); kore_route_free(rt); } while ((rdr = TAILQ_FIRST(&(dom->redirects))) != NULL) { TAILQ_REMOVE(&(dom->redirects), rdr, list); regfree(&rdr->rctx); kore_free(rdr->target); kore_free(rdr); } #endif kore_free(dom); } void kore_domain_callback(void (*cb)(struct kore_domain *)) { struct kore_server *srv; struct kore_domain *dom; LIST_FOREACH(srv, &kore_servers, list) { TAILQ_FOREACH(dom, &srv->domains, list) { cb(dom); } } } struct kore_domain * kore_domain_lookup(struct kore_server *srv, const char *domain) { struct kore_domain *dom; TAILQ_FOREACH(dom, &srv->domains, list) { if (!strcmp(dom->domain, domain)) return (dom); if (!fnmatch(dom->domain, domain, FNM_CASEFOLD)) return (dom); } return (NULL); } struct kore_domain * kore_domain_byid(u_int16_t id) { struct kore_server *srv; struct kore_domain *dom; if (id < KORE_DOMAIN_CACHE) return (cached[id]); LIST_FOREACH(srv, &kore_servers, list) { TAILQ_FOREACH(dom, &srv->domains, list) { if (dom->id == id) return (dom); } } return (NULL); } /* * Called by the worker processes to close the file descriptor towards * the accesslog as they do not need it locally. */ void kore_domain_closelogs(void) { struct kore_server *srv; struct kore_domain *dom; LIST_FOREACH(srv, &kore_servers, list) { TAILQ_FOREACH(dom, &srv->domains, list) { if (dom->accesslog != -1) { (void)close(dom->accesslog); /* * Turn into flag to indicate accesslogs * are active. */ dom->accesslog = 1; } else { dom->accesslog = 0; } } } } kore-4.2.3/src/filemap.c000066400000000000000000000171131430066302400150310ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "kore.h" #if !defined(KORE_NO_HTTP) #include "http.h" struct filemap_entry { char *root; size_t root_len; struct kore_domain *domain; char *ondisk; size_t ondisk_len; TAILQ_ENTRY(filemap_entry) list; }; int filemap_resolve(struct http_request *); static void filemap_serve(struct http_request *, const struct filemap_entry *); static TAILQ_HEAD(, filemap_entry) maps; char *kore_filemap_ext = NULL; char *kore_filemap_index = NULL; void kore_filemap_init(void) { TAILQ_INIT(&maps); } struct kore_route * kore_filemap_create(struct kore_domain *dom, const char *path, const char *root, const char *auth) { size_t sz; struct stat st; int len; struct kore_route *rt; struct filemap_entry *entry; char regex[1024], fpath[PATH_MAX]; sz = strlen(root); if (sz == 0) return (NULL); if (root[0] != '/' || root[sz - 1] != '/') return (NULL); if (worker_privsep.root != NULL) { len = snprintf(fpath, sizeof(fpath), "%s/%s", worker_privsep.root, path); if (len == -1 || (size_t)len >= sizeof(fpath)) fatal("kore_filemap_create: failed to concat paths"); } else { if (kore_strlcpy(fpath, path, sizeof(fpath)) >= sizeof(fpath)) fatal("kore_filemap_create: failed to copy path"); } if (stat(fpath, &st) == -1) { kore_log(LOG_ERR, "%s: failed to stat '%s': %s", __func__, fpath, errno_s); return (NULL); } len = snprintf(regex, sizeof(regex), "^%s.*$", root); if (len == -1 || (size_t)len >= sizeof(regex)) fatal("kore_filemap_create: buffer too small"); if ((rt = kore_route_create(dom, regex, HANDLER_TYPE_DYNAMIC)) == NULL) return (NULL); if (auth != NULL) { rt->auth = kore_auth_lookup(auth); if (rt->auth == NULL) { fatal("filemap for '%s' has unknown auth '%s'", path, auth); } } kore_route_callback(rt, "filemap_resolve"); rt->methods = HTTP_METHOD_GET | HTTP_METHOD_HEAD; entry = kore_calloc(1, sizeof(*entry)); entry->domain = dom; entry->root_len = sz; entry->root = kore_strdup(root); /* * Resolve the ondisk component inside the workers to make sure * realpath() resolves the correct path (they maybe chrooted). */ entry->ondisk_len = strlen(path); entry->ondisk = kore_strdup(path); TAILQ_INSERT_TAIL(&maps, entry, list); return (rt); } void kore_filemap_resolve_paths(void) { struct filemap_entry *entry; char rpath[PATH_MAX]; TAILQ_FOREACH(entry, &maps, list) { if (realpath(entry->ondisk, rpath) == NULL) fatal("realpath(%s): %s", entry->ondisk, errno_s); kore_free(entry->ondisk); entry->ondisk_len = strlen(rpath); entry->ondisk = kore_strdup(rpath); } } int filemap_resolve(struct http_request *req) { size_t best_len; struct filemap_entry *entry, *best; if (req->method != HTTP_METHOD_GET && req->method != HTTP_METHOD_HEAD) { http_response_header(req, "allow", "get, head"); http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0); return (KORE_RESULT_OK); } best = NULL; best_len = 0; TAILQ_FOREACH(entry, &maps, list) { if (entry->domain != req->rt->dom) continue; if (!strncmp(entry->root, req->path, entry->root_len)) { if (best == NULL || entry->root_len > best_len) { best = entry; best_len = entry->root_len; continue; } } } if (best == NULL) { http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0); return (KORE_RESULT_OK); } filemap_serve(req, best); return (KORE_RESULT_OK); } static void filemap_serve(struct http_request *req, const struct filemap_entry *map) { struct stat st; struct connection *c; struct kore_fileref *ref; struct kore_server *srv; const char *path; int len, fd, index; char fpath[PATH_MAX], rpath[PATH_MAX]; path = req->path + map->root_len; len = snprintf(fpath, sizeof(fpath), "%s/%s", map->ondisk, path); if (len == -1 || (size_t)len >= sizeof(fpath)) { http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return; } if (!http_argument_urldecode(fpath)) { http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0); return; } index = 0; lookup: if (realpath(fpath, rpath) == NULL) { if (errno == ENOENT) { if (index == 0 && kore_filemap_ext != NULL) { len = snprintf(fpath, sizeof(fpath), "%s/%s%s", map->ondisk, path, kore_filemap_ext); if (len == -1 || (size_t)len >= sizeof(fpath)) { http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return; } index++; goto lookup; } } http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0); return; } if (strncmp(rpath, fpath, map->ondisk_len)) { http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0); return; } c = req->owner; srv = c->owner->server; if ((ref = kore_fileref_get(rpath, srv->tls)) == NULL) { if ((fd = open(fpath, O_RDONLY | O_NOFOLLOW)) == -1) { switch (errno) { case ENOENT: if (index || kore_filemap_ext == NULL) { req->status = HTTP_STATUS_NOT_FOUND; } else { len = snprintf(fpath, sizeof(fpath), "%s/%s%s", map->ondisk, path, kore_filemap_ext); if (len == -1 || (size_t)len >= sizeof(fpath)) { http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return; } index++; goto lookup; } break; case EPERM: case EACCES: req->status = HTTP_STATUS_FORBIDDEN; break; default: req->status = HTTP_STATUS_INTERNAL_ERROR; break; } http_response(req, req->status, NULL, 0); return; } if (fstat(fd, &st) == -1) { http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); goto cleanup; } if (S_ISREG(st.st_mode)) { if (st.st_size <= 0) { http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0); goto cleanup; } /* kore_fileref_create() takes ownership of the fd. */ ref = kore_fileref_create(srv, fpath, fd, st.st_size, &st.st_mtim); if (ref == NULL) { http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); } else { fd = -1; } } else if (S_ISDIR(st.st_mode) && index == 0) { close(fd); if (req->path[strlen(req->path) - 1] != '/') { (void)snprintf(fpath, sizeof(fpath), "%s/", req->path); http_response_header(req, "location", fpath); http_response(req, HTTP_STATUS_FOUND, NULL, 0); return; } len = snprintf(fpath, sizeof(fpath), "%s/%s%s", map->ondisk, path, kore_filemap_index != NULL ? kore_filemap_index : "index.html"); if (len == -1 || (size_t)len >= sizeof(fpath)) { http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return; } index++; goto lookup; } else { http_response(req, HTTP_STATUS_NOT_FOUND, NULL, 0); } } if (ref != NULL) { http_response_fileref(req, HTTP_STATUS_OK, ref); fd = -1; } cleanup: if (fd != -1) close(fd); } #endif kore-4.2.3/src/fileref.c000066400000000000000000000132561430066302400150340ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "kore.h" /* cached filerefs expire after 30 seconds of inactivity. */ #define FILEREF_EXPIRATION (1000 * 30) static void fileref_timer_prime(void); static void fileref_drop(struct kore_fileref *); static void fileref_soft_remove(struct kore_fileref *); static void fileref_expiration_check(void *, u_int64_t); static TAILQ_HEAD(, kore_fileref) refs; static struct kore_pool ref_pool; static struct kore_timer *ref_timer = NULL; void kore_fileref_init(void) { TAILQ_INIT(&refs); kore_pool_init(&ref_pool, "ref_pool", sizeof(struct kore_fileref), 100); } struct kore_fileref * kore_fileref_create(struct kore_server *srv, const char *path, int fd, off_t size, struct timespec *ts) { struct kore_fileref *ref; fileref_timer_prime(); if ((ref = kore_fileref_get(path, srv->tls)) != NULL) return (ref); ref = kore_pool_get(&ref_pool); ref->cnt = 1; ref->flags = 0; ref->size = size; ref->ontls = srv->tls; ref->path = kore_strdup(path); ref->mtime_sec = ts->tv_sec; ref->mtime = ((u_int64_t)(ts->tv_sec * 1000 + (ts->tv_nsec / 1000000))); #if !defined(KORE_USE_PLATFORM_SENDFILE) if ((uintmax_t)size> SIZE_MAX) { kore_pool_put(&ref_pool, ref); return (NULL); } ref->base = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE, fd, 0); if (ref->base == MAP_FAILED) fatal("net_send_file: mmap failed: %s", errno_s); if (madvise(ref->base, (size_t)size, MADV_SEQUENTIAL) == -1) fatal("net_send_file: madvise: %s", errno_s); close(fd); #else if (srv->tls == 0) { ref->fd = fd; } else { if ((uintmax_t)size > SIZE_MAX) { kore_pool_put(&ref_pool, ref); return (NULL); } ref->base = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE, fd, 0); if (ref->base == MAP_FAILED) fatal("net_send_file: mmap failed: %s", errno_s); if (madvise(ref->base, (size_t)size, MADV_SEQUENTIAL) == -1) fatal("net_send_file: madvise: %s", errno_s); close(fd); } #endif #if defined(FILEREF_DEBUG) kore_log(LOG_DEBUG, "ref:%p created", (void *)ref); #endif TAILQ_INSERT_TAIL(&refs, ref, list); return (ref); } /* * Caller must call kore_fileref_release() after kore_fileref_get() even * if they don't end up using the ref. */ struct kore_fileref * kore_fileref_get(const char *path, int ontls) { struct stat st; struct kore_fileref *ref; u_int64_t mtime; TAILQ_FOREACH(ref, &refs, list) { if (!strcmp(ref->path, path) && ref->ontls == ontls) { if (stat(ref->path, &st) == -1) { if (errno != ENOENT) { kore_log(LOG_ERR, "stat(%s): %s", ref->path, errno_s); } fileref_soft_remove(ref); return (NULL); } mtime = ((u_int64_t)(st.st_mtim.tv_sec * 1000 + (st.st_mtim.tv_nsec / 1000000))); if (ref->mtime != mtime) { fileref_soft_remove(ref); return (NULL); } ref->cnt++; #if defined(FILEREF_DEBUG) kore_log(LOG_DEBUG, "ref:%p cnt:%d", (void *)ref, ref->cnt); #endif TAILQ_REMOVE(&refs, ref, list); TAILQ_INSERT_HEAD(&refs, ref, list); return (ref); } } return (NULL); } void kore_fileref_release(struct kore_fileref *ref) { ref->cnt--; #if defined(FILEREF_DEBUG) kore_log(LOG_DEBUG, "ref:%p released cnt:%d", (void *)ref, ref->cnt); #endif if (ref->cnt < 0) { fatal("kore_fileref_release: cnt < 0 (%p:%d)", (void *)ref, ref->cnt); } if (ref->cnt == 0) { if (ref->flags & KORE_FILEREF_SOFT_REMOVED) fileref_drop(ref); else ref->expiration = kore_time_ms() + FILEREF_EXPIRATION; } } static void fileref_timer_prime(void) { if (ref_timer != NULL) return; ref_timer = kore_timer_add(fileref_expiration_check, 10000, NULL, 0); } static void fileref_soft_remove(struct kore_fileref *ref) { if (ref->flags & KORE_FILEREF_SOFT_REMOVED) fatal("fileref_soft_remove: %p already removed", (void *)ref); #if defined(FILEREF_DEBUG) kore_log(LOG_DEBUG, "ref:%p softremoved", (void *)ref); #endif TAILQ_REMOVE(&refs, ref, list); ref->flags |= KORE_FILEREF_SOFT_REMOVED; if (ref->cnt == 0) fileref_drop(ref); } static void fileref_expiration_check(void *arg, u_int64_t now) { struct kore_fileref *ref, *next; for (ref = TAILQ_FIRST(&refs); ref != NULL; ref = next) { next = TAILQ_NEXT(ref, list); if (ref->cnt != 0) continue; if (ref->expiration > now) continue; #if defined(FILEREF_DEBUG) kore_log(LOG_DEBUG, "ref:%p expired, removing", (void *)ref); #endif fileref_drop(ref); } if (TAILQ_EMPTY(&refs)) { /* remove the timer. */ ref_timer->flags |= KORE_TIMER_ONESHOT; ref_timer = NULL; } } static void fileref_drop(struct kore_fileref *ref) { #if defined(FILEREF_DEBUG) kore_log(LOG_DEBUG, "ref:%p dropped", (void *)ref); #endif if (!(ref->flags & KORE_FILEREF_SOFT_REMOVED)) TAILQ_REMOVE(&refs, ref, list); kore_free(ref->path); #if !defined(KORE_USE_PLATFORM_SENDFILE) (void)munmap(ref->base, ref->size); #else close(ref->fd); #endif kore_pool_put(&ref_pool, ref); } kore-4.2.3/src/http.c000066400000000000000000001634351430066302400144040ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include "kore.h" #include "http.h" #if defined(KORE_USE_PYTHON) #include "python_api.h" #endif #if defined(KORE_USE_PGSQL) #include "pgsql.h" #endif #if defined(KORE_USE_TASKS) #include "tasks.h" #endif #if defined(KORE_USE_CURL) #include "curl.h" #endif static struct { const char *ext; const char *type; } builtin_media[] = { { "gif", "image/gif" }, { "png", "image/png" }, { "jpeg", "image/jpeg" }, { "jpg", "image/jpeg" }, { "zip", "application/zip" }, { "pdf", "application/pdf" }, { "json", "application/json" }, { "js", "application/javascript" }, { "htm", "text/html" }, { "txt", "text/plain" }, { "css", "text/css" }, { "html", "text/html" }, { NULL, NULL }, }; #define HTTP_MAP_LIMIT 127 /* * token = 1* * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ static const char http_token[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, '!' , 0x00, '#' , '$' , '%' , '&' , '\'', 0x00, 0x00, '*' , '+' , 0x00, '-' , '.' , 0x00, '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' , 0x00, 0x00, 0x00, '^' , '_' , '`' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , 0x00, '|' , 0x00, '~' , 0x00 }; /* * field-content = */ static const char http_field_content[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ' ' , '!' , '"' , '#' , '$' , '%' , '&' , '\'', '(' , ')' , '*' , '+' , ',' , '-' , '.' , '/' , '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , ':' , ';' , '<' , '=' , '>' , '?' , '@' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' , '[' , '\\', ']' , '^' , '_' , '`' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' , '{' , '|' , '}' , '~' , 0x00 }; /* * Fixed "pretty" HTTP error HTML page. */ static const char *pretty_error_fmt = "\n\n\t%d %s" "\n\n\t" "

%d %s

\n" "\n\n"; static int http_body_recv(struct netbuf *); static int http_release_buffer(struct netbuf *); static void http_error_response(struct connection *, int); static int http_data_convert(void *, void **, void *, int); static void http_write_response_cookie(struct http_cookie *); static int http_body_update(struct http_request *, const void *, size_t); static void http_argument_add(struct http_request *, char *, char *, int, int); static int http_check_redirect(struct http_request *, struct kore_domain *); static void http_response_normal(struct http_request *, struct connection *, int, const void *, size_t); static void multipart_add_field(struct http_request *, struct kore_buf *, char *, const char *, const int); static void multipart_file_add(struct http_request *, struct kore_buf *, const char *, const char *, const char *, const int); static int multipart_find_data(struct kore_buf *, struct kore_buf *, size_t *, struct http_request *, const void *, size_t); static int multipart_parse_headers(struct http_request *, struct kore_buf *, struct kore_buf *, const char *, const int); static struct http_request *http_request_new(struct connection *, const char *, const char *, char *, const char *); static struct kore_buf *header_buf; static struct kore_buf *ckhdr_buf; static char http_version[64]; static u_int16_t http_version_len; static TAILQ_HEAD(, http_request) http_requests; static TAILQ_HEAD(, http_request) http_requests_sleeping; static LIST_HEAD(, http_media_type) http_media_types; static struct kore_pool http_request_pool; static struct kore_pool http_cookie_pool; static struct kore_pool http_body_path; static struct kore_pool http_rlq_pool; struct kore_pool http_header_pool; int http_pretty_error = 0; u_int32_t http_request_count = 0; u_int32_t http_request_ms = HTTP_REQUEST_MS; u_int16_t http_body_timeout = HTTP_BODY_TIMEOUT; u_int32_t http_request_limit = HTTP_REQUEST_LIMIT; u_int64_t http_hsts_enable = HTTP_HSTS_ENABLE; u_int16_t http_header_max = HTTP_HEADER_MAX_LEN; u_int16_t http_keepalive_time = HTTP_KEEPALIVE_TIME; u_int16_t http_header_timeout = HTTP_HEADER_TIMEOUT; size_t http_body_max = HTTP_BODY_MAX_LEN; char *http_body_disk_path = HTTP_BODY_DISK_PATH; u_int64_t http_body_disk_offload = HTTP_BODY_DISK_OFFLOAD; void http_parent_init(void) { LIST_INIT(&http_media_types); } void http_init(void) { int prealloc, l, i; TAILQ_INIT(&http_requests); TAILQ_INIT(&http_requests_sleeping); header_buf = kore_buf_alloc(HTTP_HEADER_BUFSIZE); ckhdr_buf = kore_buf_alloc(HTTP_COOKIE_BUFSIZE); if (!http_version_len) { l = snprintf(http_version, sizeof(http_version), "server: kore (%s)\r\n", kore_version); if (l == -1 || (size_t)l >= sizeof(http_version)) fatal("http_init(): http_version buffer too small"); http_version_len = l; } prealloc = MIN((worker_max_connections / 10), 1000); kore_pool_init(&http_request_pool, "http_request_pool", sizeof(struct http_request), http_request_limit); kore_pool_init(&http_header_pool, "http_header_pool", sizeof(struct http_header), prealloc * HTTP_REQ_HEADER_MAX); kore_pool_init(&http_cookie_pool, "http_cookie_pool", sizeof(struct http_cookie), prealloc * HTTP_MAX_COOKIES); kore_pool_init(&http_rlq_pool, "http_rlq_pool", sizeof(struct http_runlock_queue), http_request_limit); kore_pool_init(&http_body_path, "http_body_path", HTTP_BODY_PATH_MAX, prealloc); for (i = 0; builtin_media[i].ext != NULL; i++) { if (!http_media_register(builtin_media[i].ext, builtin_media[i].type)) { fatal("duplicate media type for %s", builtin_media[i].ext); } } } void http_cleanup(void) { if (header_buf != NULL) { kore_buf_free(header_buf); header_buf = NULL; } if (ckhdr_buf != NULL) { kore_buf_free(ckhdr_buf); ckhdr_buf = NULL; } kore_pool_cleanup(&http_request_pool); kore_pool_cleanup(&http_header_pool); kore_pool_cleanup(&http_body_path); } void http_server_version(const char *version) { int l; l = snprintf(http_version, sizeof(http_version), "server: %s\r\n", version); if (l == -1 || (size_t)l >= sizeof(http_version)) fatal("http_server_version(): http_version buffer too small"); http_version_len = l; } int http_check_timeout(struct connection *c, u_int64_t now) { u_int64_t d; if (c->http_timeout == 0) return (KORE_RESULT_OK); if (now > c->http_start) d = now - c->http_start; else d = 0; if (d >= c->http_timeout) { http_error_response(c, HTTP_STATUS_REQUEST_TIMEOUT); kore_connection_disconnect(c); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } void http_request_sleep(struct http_request *req) { if (!(req->flags & HTTP_REQUEST_SLEEPING)) { req->flags |= HTTP_REQUEST_SLEEPING; TAILQ_REMOVE(&http_requests, req, list); TAILQ_INSERT_TAIL(&http_requests_sleeping, req, list); } } void http_request_wakeup(struct http_request *req) { if (req->flags & HTTP_REQUEST_SLEEPING) { req->flags &= ~HTTP_REQUEST_SLEEPING; TAILQ_REMOVE(&http_requests_sleeping, req, list); TAILQ_INSERT_TAIL(&http_requests, req, list); } } void http_process(void) { u_int64_t total; struct http_request *req, *next; total = 0; for (req = TAILQ_FIRST(&http_requests); req != NULL; req = next) { if (total >= http_request_ms) break; next = TAILQ_NEXT(req, list); if (req->flags & HTTP_REQUEST_DELETE) { http_request_free(req); continue; } /* Sleeping requests should be in http_requests_sleeping. */ if (req->flags & HTTP_REQUEST_SLEEPING) fatal("http_process: sleeping request on list"); if (!(req->flags & HTTP_REQUEST_COMPLETE)) continue; http_process_request(req); total += req->ms; if (req->flags & HTTP_REQUEST_DELETE) http_request_free(req); } } void http_process_request(struct http_request *req) { int r; if (req->flags & HTTP_REQUEST_DELETE || req->rt == NULL) return; req->start = kore_time_ms(); if (req->rt->auth != NULL && !(req->flags & HTTP_REQUEST_AUTHED)) r = kore_auth_run(req, req->rt->auth); else r = KORE_RESULT_OK; switch (r) { case KORE_RESULT_OK: r = kore_runtime_http_request(req->rt->rcall, req); break; case KORE_RESULT_RETRY: break; case KORE_RESULT_ERROR: /* * Set r to KORE_RESULT_OK so we can properly * flush the result from kore_auth_run(). */ r = KORE_RESULT_OK; break; default: fatal("kore_auth() returned unknown %d", r); } req->end = kore_time_ms(); req->ms = req->end - req->start; req->total += req->ms; switch (r) { case KORE_RESULT_OK: r = net_send_flush(req->owner); if (r == KORE_RESULT_ERROR) kore_connection_disconnect(req->owner); break; case KORE_RESULT_ERROR: kore_connection_disconnect(req->owner); break; case KORE_RESULT_RETRY: return; default: fatal("A page handler returned an unknown result: %d", r); } if (req->rt->dom->accesslog) kore_accesslog(req); req->flags |= HTTP_REQUEST_DELETE; } void http_response_header(struct http_request *req, const char *header, const char *value) { struct http_header *hdr; hdr = NULL; TAILQ_FOREACH(hdr, &req->resp_headers, list) { if (!strcasecmp(hdr->header, header)) { TAILQ_REMOVE(&req->resp_headers, hdr, list); kore_free(hdr->header); kore_free(hdr->value); break; } } if (hdr == NULL) hdr = kore_pool_get(&http_header_pool); hdr->header = kore_strdup(header); hdr->value = kore_strdup(value); TAILQ_INSERT_TAIL(&(req->resp_headers), hdr, list); } void http_request_free(struct http_request *req) { #if defined(KORE_USE_TASKS) struct kore_task *t, *nt; int pending_tasks; #endif #if defined(KORE_USE_PGSQL) struct kore_pgsql *pgsql; #endif #if defined(KORE_USE_CURL) struct kore_curl *client; #endif struct http_file *f, *fnext; struct http_arg *q, *qnext; struct http_header *hdr, *next; struct http_cookie *ck, *cknext; if (req->rt != NULL && req->rt->on_free != NULL) kore_runtime_http_request_free(req->rt->on_free, req); if (req->runlock != NULL) { LIST_REMOVE(req->runlock, list); req->runlock = NULL; } #if defined(KORE_USE_TASKS) pending_tasks = 0; for (t = LIST_FIRST(&(req->tasks)); t != NULL; t = nt) { nt = LIST_NEXT(t, rlist); if (!kore_task_finished(t)) { pending_tasks++; } else { kore_task_destroy(t); } } if (pending_tasks) return; #endif #if defined(KORE_USE_PYTHON) if (req->py_coro != NULL) { kore_python_coro_delete(req->py_coro); req->py_coro = NULL; } if (req->py_validator != NULL) { kore_python_coro_delete(req->py_validator); req->py_validator = NULL; } Py_XDECREF(req->py_req); #endif #if defined(KORE_USE_PGSQL) while (!LIST_EMPTY(&(req->pgsqls))) { pgsql = LIST_FIRST(&(req->pgsqls)); kore_pgsql_cleanup(pgsql); } #endif #if defined(KORE_USE_CURL) while (!LIST_EMPTY(&req->chandles)) { client = LIST_FIRST(&req->chandles); kore_curl_cleanup(client); } #endif kore_free(req->headers); req->host = NULL; req->path = NULL; req->headers = NULL; TAILQ_REMOVE(&http_requests, req, list); if (req->owner != NULL) TAILQ_REMOVE(&(req->owner->http_requests), req, olist); for (hdr = TAILQ_FIRST(&(req->resp_headers)); hdr != NULL; hdr = next) { next = TAILQ_NEXT(hdr, list); TAILQ_REMOVE(&(req->resp_headers), hdr, list); kore_free(hdr->header); kore_free(hdr->value); kore_pool_put(&http_header_pool, hdr); } for (hdr = TAILQ_FIRST(&(req->req_headers)); hdr != NULL; hdr = next) { next = TAILQ_NEXT(hdr, list); TAILQ_REMOVE(&(req->req_headers), hdr, list); kore_pool_put(&http_header_pool, hdr); } for (ck = TAILQ_FIRST(&(req->resp_cookies)); ck != NULL; ck = cknext) { cknext = TAILQ_NEXT(ck, list); TAILQ_REMOVE(&(req->resp_cookies), ck, list); kore_free(ck->name); kore_free(ck->value); kore_free(ck->path); kore_free(ck->domain); kore_pool_put(&http_cookie_pool, ck); } for (ck = TAILQ_FIRST(&(req->req_cookies)); ck != NULL; ck = cknext) { cknext = TAILQ_NEXT(ck, list); TAILQ_REMOVE(&(req->req_cookies), ck, list); kore_free(ck->name); kore_free(ck->value); kore_pool_put(&http_cookie_pool, ck); } for (q = TAILQ_FIRST(&(req->arguments)); q != NULL; q = qnext) { qnext = TAILQ_NEXT(q, list); TAILQ_REMOVE(&(req->arguments), q, list); kore_free(q->name); kore_free(q->s_value); kore_free(q); } for (f = TAILQ_FIRST(&(req->files)); f != NULL; f = fnext) { fnext = TAILQ_NEXT(f, list); TAILQ_REMOVE(&(req->files), f, list); kore_free(f->filename); kore_free(f->name); kore_free(f); } if (req->http_body != NULL) kore_buf_free(req->http_body); if (req->http_body_fd != -1) (void)close(req->http_body_fd); if (req->http_body_path != NULL) { if (unlink(req->http_body_path) == -1 && errno != ENOENT) { kore_log(LOG_NOTICE, "failed to unlink %s: %s", req->http_body_path, errno_s); } kore_pool_put(&http_body_path, req->http_body_path); } if (req->hdlr_extra != NULL && !(req->flags & HTTP_REQUEST_RETAIN_EXTRA)) kore_free(req->hdlr_extra); kore_pool_put(&http_request_pool, req); http_request_count--; } void http_serveable(struct http_request *req, const void *data, size_t len, const char *etag, const char *type) { const char *match; if (req->method != HTTP_METHOD_GET) { http_response_header(req, "allow", "get"); http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0); return; } if (http_request_header(req, "if-none-match", &match)) { if (!strcmp(match, etag)) { http_response(req, HTTP_STATUS_NOT_MODIFIED, NULL, 0); return; } } http_response_header(req, "etag", etag); http_response_header(req, "content-type", type); http_response(req, HTTP_STATUS_OK, data, len); } void http_response(struct http_request *req, int code, const void *d, size_t l) { if (req->owner == NULL) return; req->status = code; switch (req->owner->proto) { case CONN_PROTO_HTTP: case CONN_PROTO_WEBSOCKET: http_response_normal(req, req->owner, code, d, l); break; default: fatal("%s: bad proto %d", __func__, req->owner->proto); /* NOTREACHED. */ } } void http_response_close(struct http_request *req, int code, const void *d, size_t l) { if (req->owner == NULL) return; req->status = code; req->owner->flags |= CONN_CLOSE_EMPTY; switch (req->owner->proto) { case CONN_PROTO_HTTP: case CONN_PROTO_WEBSOCKET: http_response_normal(req, req->owner, code, d, l); break; default: fatal("%s: bad proto %d", __func__, req->owner->proto); /* NOTREACHED. */ } } void http_response_json(struct http_request *req, int status, struct kore_json_item *json) { struct kore_buf *buf; if (req->owner == NULL) return; buf = kore_buf_alloc(1024); kore_json_item_tobuf(json, buf); kore_json_item_free(json); req->status = status; http_response_header(req, "content-type", "application/json"); switch (req->owner->proto) { case CONN_PROTO_HTTP: http_response_stream(req, status, buf->data, buf->offset, http_release_buffer, buf); break; default: fatal("%s: bad proto %d", __func__, req->owner->proto); /* NOTREACHED. */ } } void http_response_stream(struct http_request *req, int status, void *base, size_t len, int (*cb)(struct netbuf *), void *arg) { struct netbuf *nb; if (req->owner == NULL) return; req->status = status; switch (req->owner->proto) { case CONN_PROTO_HTTP: http_response_normal(req, req->owner, status, NULL, len); break; default: fatal("%s: bad proto %d", __func__, req->owner->proto); /* NOTREACHED. */ } net_send_stream(req->owner, base, len, cb, &nb); nb->extra = arg; if (req->method == HTTP_METHOD_HEAD) { nb->s_off = nb->b_len; net_remove_netbuf(req->owner, nb); } } void http_response_fileref(struct http_request *req, int status, struct kore_fileref *ref) { struct tm *tm; time_t mtime; char tbuf[128]; const char *media_type, *modified; if (req->owner == NULL) return; media_type = http_media_type(ref->path); if (media_type != NULL) http_response_header(req, "content-type", media_type); if (http_request_header(req, "if-modified-since", &modified)) { mtime = kore_date_to_time(modified); if (mtime == ref->mtime_sec) { kore_fileref_release(ref); http_response(req, HTTP_STATUS_NOT_MODIFIED, NULL, 0); return; } } if ((tm = gmtime(&ref->mtime_sec)) != NULL) { if (strftime(tbuf, sizeof(tbuf), "%a, %d %b %Y %H:%M:%S GMT", tm) > 0) { http_response_header(req, "last-modified", tbuf); } } req->status = status; switch (req->owner->proto) { case CONN_PROTO_HTTP: http_response_normal(req, req->owner, status, NULL, ref->size); break; default: fatal("http_response_fd() bad proto %d", req->owner->proto); /* NOTREACHED. */ } if (req->method != HTTP_METHOD_HEAD) net_send_fileref(req->owner, ref); else kore_fileref_release(ref); } int http_request_header(struct http_request *req, const char *header, const char **out) { struct http_header *hdr; TAILQ_FOREACH(hdr, &(req->req_headers), list) { if (!strcasecmp(hdr->header, header)) { *out = hdr->value; return (KORE_RESULT_OK); } } if (!strcasecmp(header, "host")) { *out = req->host; return (KORE_RESULT_OK); } return (KORE_RESULT_ERROR); } int http_request_header_get(struct http_request *req, const char *header, void **out, void *nout, int type) { struct http_header *hdr; if (type == HTTP_ARG_TYPE_STRING) fatal("%s: cannot be called with type string", __func__); TAILQ_FOREACH(hdr, &req->req_headers, list) { if (strcasecmp(hdr->header, header)) continue; if (http_data_convert(hdr->value, out, nout, type)) return (KORE_RESULT_OK); return (KORE_RESULT_ERROR); } return (KORE_RESULT_ERROR); } int http_request_cookie(struct http_request *req, const char *cookie, char **out) { struct http_cookie *ck; TAILQ_FOREACH(ck, &(req->req_cookies), list) { if (!strcasecmp(ck->name, cookie)) { *out = ck->value; return (KORE_RESULT_OK); } } return (KORE_RESULT_ERROR); } int http_header_recv(struct netbuf *nb) { struct connection *c; size_t len; struct http_header *hdr; struct http_request *req; u_int8_t *end_headers; int h, i, v, skip, l; char *headers[HTTP_REQ_HEADER_MAX]; char *value, *host, *request[4], *hbuf; c = nb->owner; if (nb->b_len < 4) return (KORE_RESULT_OK); if (!isalpha(nb->buf[0])) { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_ERROR); } skip = 4; end_headers = kore_mem_find(nb->buf, nb->s_off, "\r\n\r\n", 4); if (end_headers == NULL) { end_headers = kore_mem_find(nb->buf, nb->s_off, "\n\n", 2); if (end_headers == NULL) return (KORE_RESULT_OK); skip = 2; } *end_headers = '\0'; end_headers += skip; len = end_headers - nb->buf; hbuf = (char *)nb->buf; h = kore_split_string(hbuf, "\r\n", headers, HTTP_REQ_HEADER_MAX); if (h < 2) { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_OK); } v = kore_split_string(headers[0], " ", request, 4); if (v != 3) { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_OK); } skip = 0; host = NULL; for (i = 0; i < h; i++) { if (strncasecmp(headers[i], "host", 4)) continue; if ((host = http_validate_header(headers[i])) == NULL) { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_OK); } if (*host == '\0') { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_OK); } skip = i; break; } if (host == NULL) { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_OK); } req = http_request_new(c, host, request[0], request[1], request[2]); if (req == NULL) return (KORE_RESULT_OK); /* take full ownership of the buffer. */ req->headers = nb->buf; nb->buf = NULL; nb->m_len = 0; for (i = 1; i < h; i++) { if (i == skip) continue; if ((value = http_validate_header(headers[i])) == NULL) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_OK); } if (*value == '\0') { req->flags |= HTTP_REQUEST_DELETE; http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (KORE_RESULT_OK); } hdr = kore_pool_get(&http_header_pool); hdr->header = headers[i]; hdr->value = value; TAILQ_INSERT_TAIL(&(req->req_headers), hdr, list); if (req->agent == NULL && !strcasecmp(hdr->header, "user-agent")) req->agent = hdr->value; if (req->referer == NULL && !strcasecmp(hdr->header, "referer")) req->referer = hdr->value; } if (req->flags & HTTP_REQUEST_EXPECT_BODY) { if (http_body_max == 0) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_METHOD_NOT_ALLOWED); return (KORE_RESULT_OK); } if (!http_request_header_uint64(req, "content-length", &req->content_length)) { if (req->method == HTTP_METHOD_DELETE) { req->flags |= HTTP_REQUEST_COMPLETE; return (KORE_RESULT_OK); } req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_LENGTH_REQUIRED); return (KORE_RESULT_OK); } if (req->content_length == 0) { req->flags |= HTTP_REQUEST_COMPLETE; req->flags &= ~HTTP_REQUEST_EXPECT_BODY; return (KORE_RESULT_OK); } if (req->content_length > http_body_max) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE); return (KORE_RESULT_OK); } req->http_body_length = req->content_length; if (http_body_disk_offload > 0 && req->content_length > http_body_disk_offload) { req->http_body_path = kore_pool_get(&http_body_path); l = snprintf(req->http_body_path, HTTP_BODY_PATH_MAX, "%s/http_body.XXXXXX", http_body_disk_path); if (l == -1 || (size_t)l >= HTTP_BODY_PATH_MAX) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_INTERNAL_ERROR); return (KORE_RESULT_ERROR); } req->http_body = NULL; req->http_body_fd = mkstemp(req->http_body_path); if (req->http_body_fd == -1) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_INTERNAL_ERROR); return (KORE_RESULT_OK); } } else { req->http_body_fd = -1; req->http_body = kore_buf_alloc(req->content_length); } SHA256Init(&req->hashctx); c->http_timeout = http_body_timeout * 1000; if (!http_body_update(req, end_headers, nb->s_off - len)) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_INTERNAL_ERROR); return (KORE_RESULT_OK); } } else { c->http_timeout = 0; } if (req->rt->on_headers != NULL) { if (!kore_runtime_http_request(req->rt->on_headers, req)) { req->flags |= HTTP_REQUEST_DELETE; return (KORE_RESULT_OK); } } return (KORE_RESULT_OK); } int http_argument_get(struct http_request *req, const char *name, void **out, void *nout, int type) { struct http_arg *q; TAILQ_FOREACH(q, &(req->arguments), list) { if (strcmp(q->name, name)) continue; if (http_data_convert(q->s_value, out, nout, type)) return (KORE_RESULT_OK); break; } return (KORE_RESULT_ERROR); } int http_argument_urldecode(char *arg) { u_int8_t v; int err; size_t len; char *p, *in, h[5]; p = arg; in = arg; len = strlen(arg); while (*p != '\0' && p < (arg + len)) { if (*p == '+') *p = ' '; if (*p != '%') { *in++ = *p++; continue; } if ((p + 2) >= (arg + len)) return (KORE_RESULT_ERROR); if (!isxdigit((unsigned char)*(p + 1)) || !isxdigit((unsigned char)*(p + 2))) { *in++ = *p++; continue; } h[0] = '0'; h[1] = 'x'; h[2] = *(p + 1); h[3] = *(p + 2); h[4] = '\0'; v = kore_strtonum(h, 16, 0x0, 0xff, &err); if (err != KORE_RESULT_OK) return (err); if (v <= 0x1f || v == 0x7f) return (KORE_RESULT_ERROR); *in++ = (char)v; p += 3; } *in = '\0'; return (KORE_RESULT_OK); } struct http_file * http_file_lookup(struct http_request *req, const char *name) { struct http_file *f; TAILQ_FOREACH(f, &(req->files), list) { if (!strcmp(f->name, name)) return (f); } return (NULL); } ssize_t http_file_read(struct http_file *file, void *buf, size_t len) { ssize_t ret; size_t toread, off; if (file->length < file->offset) return (-1); if ((file->offset + len) < file->offset) return (-1); if ((file->position + file->offset) < file->position) return (-1); off = file->position + file->offset; toread = MIN(len, (file->length - file->offset)); if (toread == 0) return (0); if (file->req->http_body_fd != -1) { if (lseek(file->req->http_body_fd, off, SEEK_SET) == -1) { kore_log(LOG_ERR, "http_file_read: lseek(%s): %s", file->req->http_body_path, errno_s); return (-1); } for (;;) { ret = read(file->req->http_body_fd, buf, toread); if (ret == -1) { if (errno == EINTR) continue; kore_log(LOG_ERR, "failed to read %s: %s", file->req->http_body_path, errno_s); return (-1); } if (ret == 0) return (0); break; } } else if (file->req->http_body != NULL) { if (off > file->req->http_body->length) return (0); memcpy(buf, file->req->http_body->data + off, toread); ret = toread; } else { kore_log(LOG_ERR, "http_file_read: called without body"); return (-1); } file->offset += (size_t)ret; return (ret); } void http_file_rewind(struct http_file *file) { file->offset = 0; } void http_response_cookie(struct http_request *req, const char *name, const char *val, const char *path, time_t expires, u_int32_t maxage, struct http_cookie **out) { char *p; struct http_cookie *ck; if (name == NULL || val == NULL) fatal("http_response_cookie: invalid parameters"); ck = kore_pool_get(&http_cookie_pool); ck->maxage = maxage; ck->expires = expires; ck->name = kore_strdup(name); ck->value = kore_strdup(val); ck->domain = kore_strdup(req->host); ck->flags = HTTP_COOKIE_HTTPONLY | HTTP_COOKIE_SECURE; if ((p = strrchr(ck->domain, ':')) != NULL) *p = '\0'; if (path != NULL) ck->path = kore_strdup(path); else ck->path = NULL; TAILQ_INSERT_TAIL(&(req->resp_cookies), ck, list); if (out != NULL) *out = ck; } void http_populate_cookies(struct http_request *req) { struct http_cookie *ck; const char *hdr; int i, v, n; char *c, *header, *pair[3]; char *cookies[HTTP_MAX_COOKIES]; if (!http_request_header(req, "cookie", &hdr)) return; header = kore_strdup(hdr); v = kore_split_string(header, ";", cookies, HTTP_MAX_COOKIES); for (i = 0; i < v; i++) { for (c = cookies[i]; isspace(*(unsigned char *)c); c++) ; n = kore_split_string(c, "=", pair, 3); if (n != 2) continue; ck = kore_pool_get(&http_cookie_pool); ck->name = kore_strdup(pair[0]); ck->value = kore_strdup(pair[1]); TAILQ_INSERT_TAIL(&(req->req_cookies), ck, list); } kore_free(header); } void http_populate_post(struct http_request *req) { ssize_t ret; int i, v; struct kore_buf *body; char data[BUFSIZ]; char *args[HTTP_MAX_QUERY_ARGS], *val[3], *string; if (req->method != HTTP_METHOD_POST) return; if (req->http_body != NULL) { body = NULL; req->http_body->offset = req->content_length; string = kore_buf_stringify(req->http_body, NULL); req->http_body_length = 0; req->http_body_offset = 0; } else { body = kore_buf_alloc(128); for (;;) { ret = http_body_read(req, data, sizeof(data)); if (ret == -1) goto out; if (ret == 0) break; kore_buf_append(body, data, ret); } string = kore_buf_stringify(body, NULL); } v = kore_split_string(string, "&", args, HTTP_MAX_QUERY_ARGS); for (i = 0; i < v; i++) { kore_split_string(args[i], "=", val, 3); if (val[0] != NULL && val[1] != NULL) http_argument_add(req, val[0], val[1], 0, 1); } out: if (body != NULL) kore_buf_free(body); } void http_populate_qs(struct http_request *req) { int i, v; char *query, *args[HTTP_MAX_QUERY_ARGS], *val[3]; if (req->query_string == NULL) return; query = kore_strdup(req->query_string); v = kore_split_string(query, "&", args, HTTP_MAX_QUERY_ARGS); for (i = 0; i < v; i++) { kore_split_string(args[i], "=", val, 3); if (val[0] != NULL && val[1] != NULL) http_argument_add(req, val[0], val[1], 1, 1); } kore_free(query); } void http_populate_multipart_form(struct http_request *req) { const char *hdr; int h, blen; struct kore_buf in, out; char *type, *val, *args[3]; char boundary[HTTP_BOUNDARY_MAX]; if (req->method != HTTP_METHOD_POST) return; if (!http_request_header(req, "content-type", &hdr)) return; kore_buf_init(&in, 128); kore_buf_init(&out, 128); type = kore_strdup(hdr); h = kore_split_string(type, ";", args, 3); if (h != 2) goto cleanup; if (strcasecmp(args[0], "multipart/form-data")) goto cleanup; if ((val = strchr(args[1], '=')) == NULL) goto cleanup; val++; blen = snprintf(boundary, sizeof(boundary), "--%s", val); if (blen == -1 || (size_t)blen >= sizeof(boundary)) goto cleanup; if (!multipart_find_data(&in, NULL, NULL, req, boundary, blen)) goto cleanup; for (;;) { if (!multipart_find_data(&in, NULL, NULL, req, "\r\n", 2)) break; if (in.offset < 4 && req->http_body_length == 0) break; if (!multipart_find_data(&in, &out, NULL, req, "\r\n\r\n", 4)) break; if (!multipart_parse_headers(req, &in, &out, boundary, blen)) break; kore_buf_reset(&out); } cleanup: kore_free(type); kore_buf_cleanup(&in); kore_buf_cleanup(&out); } int http_body_rewind(struct http_request *req) { if (req->http_body_fd != -1) { if (lseek(req->http_body_fd, 0, SEEK_SET) == -1) { kore_log(LOG_ERR, "lseek(%s) failed: %s", req->http_body_path, errno_s); return (KORE_RESULT_ERROR); } } else if (req->http_body != NULL) { kore_buf_reset(req->http_body); } req->http_body_offset = 0; req->http_body_length = req->content_length; return (KORE_RESULT_OK); } int http_body_digest(struct http_request *req, char *out, size_t len) { size_t idx; int slen; if (len != HTTP_BODY_DIGEST_STRLEN) { fatal("http_body_digest: bad len:%zu wanted:%u", len, HTTP_BODY_DIGEST_STRLEN); } if (!(req->flags & HTTP_REQUEST_COMPLETE)) return (KORE_RESULT_ERROR); for (idx = 0; idx < sizeof(req->http_body_digest); idx++) { slen = snprintf(out + (idx * 2), len - (idx * 2), "%02x", req->http_body_digest[idx]); if (slen == -1 || (size_t)slen >= len) fatal("failed to create hex string"); } return (KORE_RESULT_OK); } ssize_t http_body_read(struct http_request *req, void *out, size_t len) { ssize_t ret; size_t toread; toread = MIN(req->http_body_length, len); if (toread == 0) return (0); if (req->http_body_fd != -1) { for (;;) { ret = read(req->http_body_fd, out, toread); if (ret == -1) { if (errno == EINTR) continue; kore_log(LOG_ERR, "failed to read %s: %s", req->http_body_path, errno_s); return (-1); } if (ret == 0) return (0); break; } } else if (req->http_body != NULL) { memcpy(out, (req->http_body->data + req->http_body->offset), toread); req->http_body->offset += toread; ret = toread; } else { kore_log(LOG_ERR, "http_body_read: called without body"); return (-1); } req->http_body_length -= (size_t)ret; req->http_body_offset += (size_t)ret; return (ret); } int http_state_run(struct http_state *states, u_int8_t elm, struct http_request *req) { int r, done; done = 0; while (!done) { if (req->fsm_state >= elm) { fatal("http_state_run: fsm_state > elm (%d/%d)", req->fsm_state, elm); } r = states[req->fsm_state].cb(req); switch (r) { case HTTP_STATE_ERROR: return (KORE_RESULT_OK); case HTTP_STATE_RETRY: return (KORE_RESULT_RETRY); case HTTP_STATE_CONTINUE: break; case HTTP_STATE_COMPLETE: done = 1; break; default: fatal("http_state_run: unknown return value %d", r); } } req->fsm_state = 0; return (KORE_RESULT_OK); } int http_state_exists(struct http_request *req) { return (req->hdlr_extra != NULL); } void * http_state_create(struct http_request *req, size_t len) { if (req->hdlr_extra != NULL) fatal("http_state_create: state already exists"); req->state_len = len; req->hdlr_extra = kore_calloc(1, len); return (req->hdlr_extra); } void * http_state_get(struct http_request *req) { return (req->hdlr_extra); } void http_state_cleanup(struct http_request *req) { kore_free(req->hdlr_extra); req->hdlr_extra = NULL; } void http_start_recv(struct connection *c) { c->http_start = kore_time_ms(); c->http_timeout = http_header_timeout * 1000; net_recv_reset(c, http_header_max, http_header_recv); } void http_runlock_init(struct http_runlock *lock) { lock->owner = NULL; LIST_INIT(&lock->queue); } int http_runlock_acquire(struct http_runlock *lock, struct http_request *req) { if (lock->owner != NULL) { if (req->runlock != NULL) fatal("%s: request already waiting on lock", __func__); req->runlock = kore_pool_get(&http_rlq_pool); req->runlock->req = req; LIST_INSERT_HEAD(&lock->queue, req->runlock, list); http_request_sleep(req); return (KORE_RESULT_ERROR); } lock->owner = req; return (KORE_RESULT_OK); } void http_runlock_release(struct http_runlock *lock, struct http_request *req) { struct http_runlock_queue *next; struct http_request *nextreq; if (lock->owner != req) fatal("%s: calling request != owner of runlock", __func__); lock->owner = NULL; if ((next = LIST_FIRST(&lock->queue)) != NULL) { LIST_REMOVE(next, list); nextreq = next->req; nextreq->runlock = NULL; http_request_wakeup(nextreq); kore_pool_put(&http_rlq_pool, next); } } int http_redirect_add(struct kore_domain *dom, const char *path, int status, const char *target) { struct http_redirect *rdr; rdr = kore_calloc(1, sizeof(*rdr)); if (regcomp(&(rdr->rctx), path, REG_EXTENDED)) { kore_free(rdr); return (KORE_RESULT_ERROR); } rdr->status = status; if (target != NULL) rdr->target = kore_strdup(target); else rdr->target = NULL; TAILQ_INSERT_TAIL(&dom->redirects, rdr, list); return (KORE_RESULT_OK); } const char * http_status_text(int status) { const char *r; switch (status) { case HTTP_STATUS_CONTINUE: r = "Continue"; break; case HTTP_STATUS_SWITCHING_PROTOCOLS: r = "Switching Protocols"; break; case HTTP_STATUS_OK: r = "OK"; break; case HTTP_STATUS_CREATED: r = "Created"; break; case HTTP_STATUS_ACCEPTED: r = "Accepted"; break; case HTTP_STATUS_NON_AUTHORITATIVE: r = "Non-Authoritative Information"; break; case HTTP_STATUS_NO_CONTENT: r = "No Content"; break; case HTTP_STATUS_RESET_CONTENT: r = "Reset Content"; break; case HTTP_STATUS_PARTIAL_CONTENT: r = "Partial Content"; break; case HTTP_STATUS_MULTIPLE_CHOICES: r = "Multiple Choices"; break; case HTTP_STATUS_MOVED_PERMANENTLY: r = "Moved Permanently"; break; case HTTP_STATUS_FOUND: r = "Found"; break; case HTTP_STATUS_SEE_OTHER: r = "See Other"; break; case HTTP_STATUS_NOT_MODIFIED: r = "Not Modified"; break; case HTTP_STATUS_USE_PROXY: r = "Use Proxy"; break; case HTTP_STATUS_TEMPORARY_REDIRECT: r = "Temporary Redirect"; break; case HTTP_STATUS_BAD_REQUEST: r = "Bad Request"; break; case HTTP_STATUS_UNAUTHORIZED: r = "Unauthorized"; break; case HTTP_STATUS_PAYMENT_REQUIRED: r = "Payment Required"; break; case HTTP_STATUS_FORBIDDEN: r = "Forbidden"; break; case HTTP_STATUS_NOT_FOUND: r = "Not Found"; break; case HTTP_STATUS_METHOD_NOT_ALLOWED: r = "Method Not Allowed"; break; case HTTP_STATUS_NOT_ACCEPTABLE: r = "Not Acceptable"; break; case HTTP_STATUS_PROXY_AUTH_REQUIRED: r = "Proxy Authentication Required"; break; case HTTP_STATUS_REQUEST_TIMEOUT: r = "Request Time-out"; break; case HTTP_STATUS_CONFLICT: r = "Conflict"; break; case HTTP_STATUS_GONE: r = "Gone"; break; case HTTP_STATUS_LENGTH_REQUIRED: r = "Length Required"; break; case HTTP_STATUS_PRECONDITION_FAILED: r = "Precondition Failed"; break; case HTTP_STATUS_REQUEST_ENTITY_TOO_LARGE: r = "Request Entity Too Large"; break; case HTTP_STATUS_REQUEST_URI_TOO_LARGE: r = "Request-URI Too Large"; break; case HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: r = "Unsupported Media Type"; break; case HTTP_STATUS_REQUEST_RANGE_INVALID: r = "Requested range not satisfiable"; break; case HTTP_STATUS_EXPECTATION_FAILED: r = "Expectation Failed"; break; case HTTP_STATUS_MISDIRECTED_REQUEST: r = "Misdirected Request"; break; case HTTP_STATUS_INTERNAL_ERROR: r = "Internal Server Error"; break; case HTTP_STATUS_NOT_IMPLEMENTED: r = "Not Implemented"; break; case HTTP_STATUS_BAD_GATEWAY: r = "Bad Gateway"; break; case HTTP_STATUS_SERVICE_UNAVAILABLE: r = "Service Unavailable"; break; case HTTP_STATUS_GATEWAY_TIMEOUT: r = "Gateway Time-out"; break; case HTTP_STATUS_BAD_VERSION: r = "HTTP Version not supported"; break; default: r = ""; break; } return (r); } const char * http_method_text(int method) { char *r; switch(method) { case HTTP_METHOD_GET: r = "GET"; break; case HTTP_METHOD_POST: r = "POST"; break; case HTTP_METHOD_PUT: r = "PUT"; break; case HTTP_METHOD_DELETE: r = "DELETE"; break; case HTTP_METHOD_HEAD: r = "HEAD"; break; case HTTP_METHOD_OPTIONS: r = "OPTIONS"; break; case HTTP_METHOD_PATCH: r = "PATCH"; break; default: r = ""; break; } return (r); } int http_method_value(const char *method) { if (!strcasecmp(method, "GET")) return (HTTP_METHOD_GET); if (!strcasecmp(method, "POST")) return (HTTP_METHOD_POST); if (!strcasecmp(method, "PUT")) return (HTTP_METHOD_PUT); if (!strcasecmp(method, "DELETE")) return (HTTP_METHOD_DELETE); if (!strcasecmp(method, "HEAD")) return (HTTP_METHOD_HEAD); if (!strcasecmp(method, "OPTIONS")) return (HTTP_METHOD_OPTIONS); if (!strcasecmp(method, "PATCH")) return (HTTP_METHOD_PATCH); return (0); } int http_media_register(const char *ext, const char *type) { struct http_media_type *media; LIST_FOREACH(media, &http_media_types, list) { if (!strcasecmp(media->ext, ext)) return (KORE_RESULT_ERROR); } media = kore_calloc(1, sizeof(*media)); media->ext = kore_strdup(ext); media->type = kore_strdup(type); LIST_INSERT_HEAD(&http_media_types, media, list); return (KORE_RESULT_OK); } const char * http_media_type(const char *path) { const char *p; struct http_media_type *media; if ((p = strrchr(path, '.')) == NULL) return (NULL); p++; if (*p == '\0') return (NULL); LIST_FOREACH(media, &http_media_types, list) { if (!strcasecmp(media->ext, p)) return (media->type); } return (NULL); } char * http_validate_header(char *header) { u_int8_t idx; char *p, *value; for (p = header; *p != '\0'; p++) { idx = *p; if (idx > HTTP_MAP_LIMIT) return (NULL); if (*p == ':') { *(p)++ = '\0'; break; } if (http_token[idx] == 0x00) return (NULL); } while (isspace(*(unsigned char *)p)) p++; if (*p == '\0') return (NULL); value = p; while (*p != '\0') { idx = *p; if (idx > HTTP_MAP_LIMIT) return (NULL); if (http_field_content[idx] == 0x00) return (NULL); p++; } return (value); } static int http_release_buffer(struct netbuf *nb) { kore_buf_free(nb->extra); return (KORE_RESULT_OK); } static int http_check_redirect(struct http_request *req, struct kore_domain *dom) { int idx; struct http_redirect *rdr; const char *uri; char key[4]; struct kore_buf location; TAILQ_FOREACH(rdr, &dom->redirects, list) { if (!regexec(&(rdr->rctx), req->path, HTTP_CAPTURE_GROUPS, req->cgroups, 0)) break; } if (rdr == NULL) return (KORE_RESULT_ERROR); uri = NULL; kore_buf_init(&location, 128); if (rdr->target) { kore_buf_appendf(&location, "%s", rdr->target); if (req->query_string != NULL) { kore_buf_replace_string(&location, "$qs", req->query_string, strlen(req->query_string)); } /* Starts at 1 to skip the full path. */ for (idx = 1; idx < HTTP_CAPTURE_GROUPS - 1; idx++) { if (req->cgroups[idx].rm_so == -1 || req->cgroups[idx].rm_eo == -1) break; (void)snprintf(key, sizeof(key), "$%d", idx); kore_buf_replace_string(&location, key, req->path + req->cgroups[idx].rm_so, req->cgroups[idx].rm_eo - req->cgroups[idx].rm_so); } uri = kore_buf_stringify(&location, NULL); } if (uri) http_response_header(req, "location", uri); http_response(req, rdr->status, NULL, 0); kore_buf_cleanup(&location); if (dom->accesslog) kore_accesslog(req); return (KORE_RESULT_OK); } static struct http_request * http_request_new(struct connection *c, const char *host, const char *method, char *path, const char *version) { struct kore_domain *dom; struct http_request *req; size_t qsoff; char *p, *hp; int m, flags, exists; if (http_request_count >= http_request_limit) { http_error_response(c, HTTP_STATUS_SERVICE_UNAVAILABLE); return (NULL); } if (strlen(host) >= KORE_DOMAINNAME_LEN - 1) { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (NULL); } if (strlen(path) >= HTTP_URI_LEN - 1) { http_error_response(c, HTTP_STATUS_REQUEST_URI_TOO_LARGE); return (NULL); } if (strcasecmp(version, "http/1.1")) { if (strcasecmp(version, "http/1.0")) { http_error_response(c, HTTP_STATUS_BAD_VERSION); return (NULL); } flags = HTTP_VERSION_1_0; } else { flags = HTTP_VERSION_1_1; } if ((p = strchr(path, '?')) != NULL) { qsoff = p - path; } else { qsoff = 0; } hp = NULL; switch (c->family) { case AF_INET6: if (*host == '[') { if ((hp = strrchr(host, ']')) == NULL) { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (NULL); } hp++; if (*hp == ':') *hp = '\0'; else hp = NULL; } break; default: if ((hp = strrchr(host, ':')) != NULL) *hp = '\0'; break; } if (c->owner->server->tls && c->tls_sni != NULL) { if (strcasecmp(c->tls_sni, host)) { http_error_response(c, HTTP_STATUS_MISDIRECTED_REQUEST); return (NULL); } } if ((dom = kore_domain_lookup(c->owner->server, host)) == NULL) { http_error_response(c, HTTP_STATUS_NOT_FOUND); return (NULL); } if (dom->cafile != NULL && c->tls_cert == NULL) { http_error_response(c, HTTP_STATUS_FORBIDDEN); return (NULL); } if (hp != NULL) *hp = ':'; if (!strcasecmp(method, "get")) { m = HTTP_METHOD_GET; flags |= HTTP_REQUEST_COMPLETE; } else if (!strcasecmp(method, "delete")) { m = HTTP_METHOD_DELETE; flags |= HTTP_REQUEST_EXPECT_BODY; } else if (!strcasecmp(method, "post")) { m = HTTP_METHOD_POST; flags |= HTTP_REQUEST_EXPECT_BODY; } else if (!strcasecmp(method, "put")) { m = HTTP_METHOD_PUT; flags |= HTTP_REQUEST_EXPECT_BODY; } else if (!strcasecmp(method, "head")) { m = HTTP_METHOD_HEAD; flags |= HTTP_REQUEST_COMPLETE; } else if (!strcasecmp(method, "options")) { m = HTTP_METHOD_OPTIONS; flags |= HTTP_REQUEST_COMPLETE; } else if (!strcasecmp(method, "patch")) { m = HTTP_METHOD_PATCH; flags |= HTTP_REQUEST_EXPECT_BODY; } else { http_error_response(c, HTTP_STATUS_BAD_REQUEST); return (NULL); } if (flags & HTTP_VERSION_1_0) { if (m != HTTP_METHOD_GET && m != HTTP_METHOD_POST && m != HTTP_METHOD_HEAD) { http_error_response(c, HTTP_STATUS_METHOD_NOT_ALLOWED); return (NULL); } } req = kore_pool_get(&http_request_pool); req->end = 0; req->total = 0; req->start = 0; req->owner = c; req->status = 0; req->method = m; req->agent = NULL; req->referer = NULL; req->runlock = NULL; req->flags = flags; req->fsm_state = 0; req->http_body = NULL; req->http_body_fd = -1; req->hdlr_extra = NULL; req->content_length = 0; req->query_string = NULL; req->http_body_length = 0; req->http_body_offset = 0; req->http_body_path = NULL; req->host = host; req->path = path; #if defined(KORE_USE_PYTHON) req->py_req = NULL; req->py_coro = NULL; req->py_rqnext = NULL; req->py_validator = NULL; #endif if (qsoff > 0) { req->query_string = path + qsoff; *(req->query_string)++ = '\0'; } else { req->query_string = NULL; } /* Checked further down below if we need to 404. */ exists = kore_route_lookup(req, dom, m, &req->rt); TAILQ_INIT(&(req->resp_headers)); TAILQ_INIT(&(req->req_headers)); TAILQ_INIT(&(req->resp_cookies)); TAILQ_INIT(&(req->req_cookies)); TAILQ_INIT(&(req->arguments)); TAILQ_INIT(&(req->files)); #if defined(KORE_USE_TASKS) LIST_INIT(&(req->tasks)); #endif #if defined(KORE_USE_PGSQL) LIST_INIT(&(req->pgsqls)); #endif http_request_count++; TAILQ_INSERT_HEAD(&http_requests, req, list); TAILQ_INSERT_TAIL(&(c->http_requests), req, olist); if (http_check_redirect(req, dom)) { http_request_free(req); return (NULL); } if (exists == 0) { http_request_free(req); http_error_response(c, HTTP_STATUS_NOT_FOUND); return (NULL); } if (req->rt == NULL) { http_request_free(req); http_error_response(c, HTTP_STATUS_METHOD_NOT_ALLOWED); return (NULL); } return (req); } static int multipart_find_data(struct kore_buf *in, struct kore_buf *out, size_t *olen, struct http_request *req, const void *needle, size_t len) { ssize_t ret; size_t left; u_int8_t *p, first, data[4096]; if (olen != NULL) *olen = 0; first = *(const u_int8_t *)needle; for (;;) { if (in->offset < len) { ret = http_body_read(req, data, sizeof(data)); if (ret == -1) return (KORE_RESULT_ERROR); if (ret == 0) return (KORE_RESULT_ERROR); kore_buf_append(in, data, ret); continue; } p = kore_mem_find(in->data, in->offset, &first, 1); if (p == NULL) { if (out != NULL) kore_buf_append(out, in->data, in->offset); if (olen != NULL) *olen += in->offset; kore_buf_reset(in); continue; } left = in->offset - (p - in->data); if (left < len) { if (out != NULL) kore_buf_append(out, in->data, (p - in->data)); if (olen != NULL) *olen += (p - in->data); memmove(in->data, p, left); in->offset = left; continue; } if (!memcmp(p, needle, len)) { if (out != NULL) kore_buf_append(out, in->data, p - in->data); if (olen != NULL) *olen += (p - in->data); in->offset = left - len; if (in->offset > 0) memmove(in->data, p + len, in->offset); return (KORE_RESULT_OK); } if (out != NULL) kore_buf_append(out, in->data, (p - in->data) + 1); if (olen != NULL) *olen += (p - in->data) + 1; in->offset = left - 1; if (in->offset > 0) memmove(in->data, p + 1, in->offset); } return (KORE_RESULT_ERROR); } static int multipart_parse_headers(struct http_request *req, struct kore_buf *in, struct kore_buf *hbuf, const char *boundary, const int blen) { int h, c, i; char *headers[5], *args[5], *opt[5]; char *d, *val, *name, *fname, *string; string = kore_buf_stringify(hbuf, NULL); h = kore_split_string(string, "\r\n", headers, 5); for (i = 0; i < h; i++) { c = kore_split_string(headers[i], ":", args, 5); if (c != 2) continue; /* Ignore other headers for now. */ if (strcasecmp(args[0], "content-disposition")) continue; for (d = args[1]; isspace(*(unsigned char *)d); d++) ; c = kore_split_string(d, ";", opt, 5); if (c < 2) continue; if (strcasecmp(opt[0], "form-data")) continue; if ((val = strchr(opt[1], '=')) == NULL) continue; if (strlen(val) < 3) continue; val++; kore_strip_chars(val, '"', &name); if (opt[2] == NULL) { multipart_add_field(req, in, name, boundary, blen); kore_free(name); continue; } for (d = opt[2]; isspace(*(unsigned char *)d); d++) ; if (!strncasecmp(d, "filename=", 9)) { if ((val = strchr(d, '=')) == NULL) { kore_free(name); continue; } val++; kore_strip_chars(val, '"', &fname); if (strlen(fname) > 0) { multipart_file_add(req, in, name, fname, boundary, blen); } kore_free(fname); } kore_free(name); } return (KORE_RESULT_OK); } static void multipart_add_field(struct http_request *req, struct kore_buf *in, char *name, const char *boundary, const int blen) { struct kore_buf *data; char *string; data = kore_buf_alloc(128); if (!multipart_find_data(in, data, NULL, req, boundary, blen)) { kore_buf_free(data); return; } if (data->offset < 3) { kore_buf_free(data); return; } data->offset -= 2; string = kore_buf_stringify(data, NULL); http_argument_add(req, name, string, 0, 0); kore_buf_free(data); } static void multipart_file_add(struct http_request *req, struct kore_buf *in, const char *name, const char *fname, const char *boundary, const int blen) { struct http_file *f; size_t position, len; position = req->http_body_offset - in->offset; if (!multipart_find_data(in, NULL, &len, req, boundary, blen)) return; if (len < 3) return; len -= 2; f = kore_malloc(sizeof(struct http_file)); f->req = req; f->offset = 0; f->length = len; f->position = position; f->name = kore_strdup(name); f->filename = kore_strdup(fname); TAILQ_INSERT_TAIL(&(req->files), f, list); } static void http_argument_add(struct http_request *req, char *name, char *value, int qs, int decode) { struct http_arg *q; struct kore_route_params *p; if (decode) { if (!http_argument_urldecode(name)) return; } TAILQ_FOREACH(p, &req->rt->params, list) { if (qs == 1 && !(p->flags & KORE_PARAMS_QUERY_STRING)) continue; if (qs == 0 && (p->flags & KORE_PARAMS_QUERY_STRING)) continue; if (p->method != req->method) continue; if (strcmp(p->name, name)) continue; if (decode) { if (!http_argument_urldecode(value)) return; } if (!kore_validator_check(req, p->validator, value)) break; q = kore_malloc(sizeof(struct http_arg)); q->name = kore_strdup(name); q->s_value = kore_strdup(value); TAILQ_INSERT_TAIL(&(req->arguments), q, list); break; } } static int http_body_recv(struct netbuf *nb) { struct http_request *req = (struct http_request *)nb->extra; return (http_body_update(req, nb->buf, nb->s_off)); } static int http_body_update(struct http_request *req, const void *data, size_t len) { ssize_t ret; u_int64_t bytes_left; SHA256Update(&req->hashctx, data, len); if (req->http_body_fd != -1) { ret = write(req->http_body_fd, data, len); if (ret == -1 || (size_t)ret != len) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_INTERNAL_ERROR); return (KORE_RESULT_ERROR); } } else if (req->http_body != NULL) { kore_buf_append(req->http_body, data, len); } else { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_INTERNAL_ERROR); return (KORE_RESULT_ERROR); } req->content_length -= len; if (req->content_length == 0) { req->owner->rnb->extra = NULL; http_request_wakeup(req); req->flags |= HTTP_REQUEST_COMPLETE; req->flags &= ~HTTP_REQUEST_EXPECT_BODY; req->content_length = req->http_body_length; if (!http_body_rewind(req)) { req->flags |= HTTP_REQUEST_DELETE; http_error_response(req->owner, HTTP_STATUS_INTERNAL_ERROR); return (KORE_RESULT_ERROR); } SHA256Final(req->http_body_digest, &req->hashctx); } else { bytes_left = req->content_length; net_recv_reset(req->owner, MIN(bytes_left, NETBUF_SEND_PAYLOAD_MAX), http_body_recv); req->owner->rnb->extra = req; } if (req->rt->on_body_chunk != NULL && len > 0) { kore_runtime_http_body_chunk(req->rt->on_body_chunk, req, data, len); } return (KORE_RESULT_OK); } static void http_error_response(struct connection *c, int status) { c->flags |= CONN_CLOSE_EMPTY; switch (c->proto) { case CONN_PROTO_HTTP: http_response_normal(NULL, c, status, NULL, 0); break; default: fatal("http_error_response() bad proto %d", c->proto); /* NOTREACHED. */ } if (!net_send_flush(c)) kore_connection_disconnect(c); } static void http_response_normal(struct http_request *req, struct connection *c, int status, const void *d, size_t len) { struct kore_buf buf; struct http_cookie *ck; struct http_header *hdr; char version; const char *conn, *text; int connection_close, send_body; send_body = 1; text = http_status_text(status); kore_buf_reset(header_buf); if (req != NULL) { if (req->flags & HTTP_VERSION_1_0) version = '0'; else version = '1'; } else { version = '1'; } kore_buf_appendf(header_buf, "HTTP/1.%c %d %s\r\n", version, status, text); if (status == 100) { kore_buf_append(header_buf, "\r\n", 2); net_send_queue(c, header_buf->data, header_buf->offset); return; } kore_buf_append(header_buf, http_version, http_version_len); if ((c->flags & CONN_CLOSE_EMPTY) || (req != NULL && (req->flags & HTTP_VERSION_1_0))) { connection_close = 1; } else { connection_close = 0; } if (connection_close == 0 && req != NULL) { if (http_request_header(req, "connection", &conn)) { if ((*conn == 'c' || *conn == 'C') && !strcasecmp(conn, "close")) { connection_close = 1; } } } kore_buf_init(&buf, 1024); /* Note that req CAN be NULL. */ if (req == NULL || req->owner->proto != CONN_PROTO_WEBSOCKET) { if (http_keepalive_time && connection_close == 0) { kore_buf_appendf(header_buf, "connection: keep-alive\r\n"); kore_buf_appendf(header_buf, "keep-alive: timeout=%d\r\n", http_keepalive_time); } else { c->flags |= CONN_CLOSE_EMPTY; kore_buf_appendf(header_buf, "connection: close\r\n"); } } if (c->tls && http_hsts_enable) { kore_buf_appendf(header_buf, "strict-transport-security: "); kore_buf_appendf(header_buf, "max-age=%" PRIu64 "; includeSubDomains\r\n", http_hsts_enable); } if (http_pretty_error && d == NULL && status >= 400) { kore_buf_appendf(&buf, pretty_error_fmt, status, text, status, text); d = buf.data; len = buf.offset; } if (req != NULL) { TAILQ_FOREACH(ck, &(req->resp_cookies), list) http_write_response_cookie(ck); TAILQ_FOREACH(hdr, &(req->resp_headers), list) { kore_buf_appendf(header_buf, "%s: %s\r\n", hdr->header, hdr->value); } if (status != 204 && status >= 200 && !(req->flags & HTTP_REQUEST_NO_CONTENT_LENGTH)) { kore_buf_appendf(header_buf, "content-length: %zu\r\n", len); } } else { if (status != 204 && status >= 200) { kore_buf_appendf(header_buf, "content-length: %zu\r\n", len); } } kore_buf_append(header_buf, "\r\n", 2); net_send_queue(c, header_buf->data, header_buf->offset); if (req != NULL && req->method == HTTP_METHOD_HEAD) send_body = 0; if (d != NULL && send_body) net_send_queue(c, d, len); if (!(c->flags & CONN_CLOSE_EMPTY) && !(c->flags & CONN_IS_BUSY)) http_start_recv(c); if (req != NULL) req->content_length = len; kore_buf_cleanup(&buf); } static void http_write_response_cookie(struct http_cookie *ck) { struct tm tm; char expires[HTTP_DATE_MAXSIZE]; kore_buf_reset(ckhdr_buf); kore_buf_appendf(ckhdr_buf, "%s=%s", ck->name, ck->value); if (ck->path != NULL) kore_buf_appendf(ckhdr_buf, "; Path=%s", ck->path); if (ck->domain != NULL) kore_buf_appendf(ckhdr_buf, "; Domain=%s", ck->domain); if (ck->expires > 0) { if (gmtime_r(&ck->expires, &tm) == NULL) { kore_log(LOG_ERR, "gmtime_r(): %s", errno_s); return; } if (strftime(expires, sizeof(expires), "%a, %d %b %y %H:%M:%S GMT", &tm) == 0) { kore_log(LOG_ERR, "strftime(): %s", errno_s); return; } kore_buf_appendf(ckhdr_buf, "; Expires=%s", expires); } if (ck->maxage > 0) kore_buf_appendf(ckhdr_buf, "; Max-Age=%u", ck->maxage); if (ck->flags & HTTP_COOKIE_HTTPONLY) kore_buf_appendf(ckhdr_buf, "; HttpOnly"); if (ck->flags & HTTP_COOKIE_SECURE) kore_buf_appendf(ckhdr_buf, "; Secure"); kore_buf_appendf(header_buf, "set-cookie: %s\r\n", kore_buf_stringify(ckhdr_buf, NULL)); } static int http_data_convert(void *data, void **out, void *nout, int type) { switch (type) { case HTTP_ARG_TYPE_RAW: case HTTP_ARG_TYPE_STRING: *out = data; return (KORE_RESULT_OK); case HTTP_ARG_TYPE_BYTE: COPY_ARG_TYPE(*(u_int8_t *)data, u_int8_t); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_INT16: COPY_AS_INTTYPE(SHRT_MIN, SHRT_MAX, int16_t); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_UINT16: COPY_AS_INTTYPE(0, USHRT_MAX, u_int16_t); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_INT32: COPY_AS_INTTYPE(INT_MIN, INT_MAX, int32_t); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_UINT32: COPY_AS_INTTYPE(0, UINT_MAX, u_int32_t); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_INT64: COPY_AS_INTTYPE_64(int64_t, 1); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_UINT64: COPY_AS_INTTYPE_64(u_int64_t, 0); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_FLOAT: COPY_ARG_DOUBLE(-FLT_MAX, FLT_MAX, float); return (KORE_RESULT_OK); case HTTP_ARG_TYPE_DOUBLE: COPY_ARG_DOUBLE(-DBL_MAX, DBL_MAX, double); return (KORE_RESULT_OK); default: break; } return (KORE_RESULT_ERROR); } kore-4.2.3/src/json.c000066400000000000000000000457641430066302400144020ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "kore.h" static int json_guess_type(u_int8_t, u_int32_t *); static int json_next(struct kore_json *, u_int8_t *); static int json_peek(struct kore_json *, u_int8_t *); static int json_consume_whitespace(struct kore_json *); static int json_next_byte(struct kore_json *, u_int8_t *, int); static char *json_get_string(struct kore_json *); static int json_parse_array(struct kore_json *, struct kore_json_item *); static int json_parse_object(struct kore_json *, struct kore_json_item *); static int json_parse_string(struct kore_json *, struct kore_json_item *); static int json_parse_number(struct kore_json *, struct kore_json_item *); static int json_parse_literal(struct kore_json *, struct kore_json_item *); static struct kore_json_item *json_item_alloc(int, const char *, struct kore_json_item *); static struct kore_json_item *json_find_item(struct kore_json_item *, char **, u_int32_t, int); static u_int8_t json_null_literal[] = { 'n', 'u', 'l', 'l' }; static u_int8_t json_true_literal[] = { 't', 'r', 'u', 'e' }; static u_int8_t json_false_literal[] = { 'f', 'a', 'l', 's', 'e' }; static int json_errno = 0; static const char *json_errtab[] = { "no error", "invalid JSON object", "invalid JSON array", "invalid JSON string", "invalid JSON number", "invalid JSON literal", "too many nested items", "end of stream while parsing JSON", "invalid JSON", "invalid search query specified", "item not found", "item found, but not expected value" }; void kore_json_init(struct kore_json *json, const void *data, size_t len) { memset(json, 0, sizeof(*json)); json->data = data; json->length = len; kore_buf_init(&json->tmpbuf, 1024); } int kore_json_parse(struct kore_json *json) { u_int8_t ch; u_int32_t type; if (json->root) return (KORE_RESULT_OK); json_errno = 0; if (json_consume_whitespace(json) == -1) { json_errno = KORE_JSON_ERR_INVALID_JSON; return (KORE_RESULT_ERROR); } if (!json_peek(json, &ch)) return (KORE_RESULT_ERROR); if (!json_guess_type(ch, &type)) { json_errno = KORE_JSON_ERR_INVALID_JSON; return (KORE_RESULT_ERROR); } json->root = json_item_alloc(type, NULL, NULL); if (!json->root->parse(json, json->root)) { if (json_errno == 0) json_errno = KORE_JSON_ERR_INVALID_JSON; return (KORE_RESULT_ERROR); } /* Don't allow garbage at the end. */ (void)json_consume_whitespace(json); if (json->offset != json->length) { json_errno = KORE_JSON_ERR_INVALID_JSON; return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } struct kore_json_item * kore_json_find(struct kore_json_item *root, const char *path, u_int32_t type) { struct kore_json_item *item; char *copy; char *tokens[KORE_JSON_DEPTH_MAX + 1]; json_errno = 0; copy = kore_strdup(path); if (!kore_split_string(copy, "/", tokens, KORE_JSON_DEPTH_MAX)) { kore_free(copy); json_errno = KORE_JSON_ERR_INVALID_SEARCH; return (NULL); } item = json_find_item(root, tokens, type, 0); kore_free(copy); if (item == NULL && json_errno == 0) json_errno = KORE_JSON_ERR_INVALID_SEARCH; return (item); } void kore_json_cleanup(struct kore_json *json) { if (json == NULL) return; kore_buf_cleanup(&json->tmpbuf); kore_json_item_free(json->root); } int kore_json_errno(void) { return (json_errno); } const char * kore_json_strerror(void) { if (json_errno >= 0 && json_errno <= KORE_JSON_ERR_LAST) return (json_errtab[json_errno]); return ("unknown JSON error"); } struct kore_json_item * kore_json_create_item(struct kore_json_item *parent, const char *name, u_int32_t type, ...) { const char *p; va_list args; struct kore_json_item *item; item = kore_calloc(1, sizeof(*item)); item->type = type; va_start(args, type); switch (item->type) { case KORE_JSON_TYPE_OBJECT: TAILQ_INIT(&item->data.items); break; case KORE_JSON_TYPE_ARRAY: TAILQ_INIT(&item->data.items); break; case KORE_JSON_TYPE_STRING: p = va_arg(args, const char *); item->data.string = kore_strdup(p); break; case KORE_JSON_TYPE_NUMBER: item->data.number = va_arg(args, double); break; case KORE_JSON_TYPE_INTEGER: item->data.integer = va_arg(args, int64_t); break; case KORE_JSON_TYPE_INTEGER_U64: item->data.u64 = va_arg(args, u_int64_t); break; case KORE_JSON_TYPE_LITERAL: item->data.literal = va_arg(args, int); break; default: fatal("%s: unknown type %d", __func__, item->type); } if (name) item->name = kore_strdup(name); if (parent) { if (parent->type != KORE_JSON_TYPE_OBJECT && parent->type != KORE_JSON_TYPE_ARRAY) { fatal("%s: invalid parent type (%d)", __func__, parent->type); } TAILQ_INSERT_TAIL(&parent->data.items, item, list); } va_end(args); return (item); } void kore_json_item_tobuf(struct kore_json_item *item, struct kore_buf *buf) { struct kore_json_item *nitem; if (item->name) kore_buf_appendf(buf, "\"%s\":", item->name); switch (item->type) { case KORE_JSON_TYPE_OBJECT: kore_buf_appendf(buf, "{"); TAILQ_FOREACH(nitem, &item->data.items, list) { kore_json_item_tobuf(nitem, buf); if (TAILQ_NEXT(nitem, list)) kore_buf_appendf(buf, ","); } kore_buf_appendf(buf, "}"); break; case KORE_JSON_TYPE_ARRAY: kore_buf_appendf(buf, "["); TAILQ_FOREACH(nitem, &item->data.items, list) { kore_json_item_tobuf(nitem, buf); if (TAILQ_NEXT(nitem, list)) kore_buf_appendf(buf, ","); } kore_buf_appendf(buf, "]"); break; case KORE_JSON_TYPE_STRING: kore_buf_appendf(buf, "\"%s\"", item->data.string); break; case KORE_JSON_TYPE_NUMBER: kore_buf_appendf(buf, "%f", item->data.number); break; case KORE_JSON_TYPE_INTEGER: kore_buf_appendf(buf, "%" PRId64, item->data.integer); break; case KORE_JSON_TYPE_INTEGER_U64: kore_buf_appendf(buf, "%" PRIu64, item->data.u64); break; case KORE_JSON_TYPE_LITERAL: switch (item->data.literal) { case KORE_JSON_TRUE: kore_buf_append(buf, json_true_literal, sizeof(json_true_literal)); break; case KORE_JSON_FALSE: kore_buf_append(buf, json_false_literal, sizeof(json_false_literal)); break; case KORE_JSON_NULL: kore_buf_append(buf, json_null_literal, sizeof(json_null_literal)); break; default: fatal("%s: unknown literal %d", __func__, item->data.literal); } break; default: fatal("%s: unknown type %d", __func__, item->type); } } void kore_json_item_attach(struct kore_json_item *parent, struct kore_json_item *item) { if (item->parent != NULL) fatal("%s: item already has parent", __func__); item->parent = parent; if (parent->type != KORE_JSON_TYPE_OBJECT && parent->type != KORE_JSON_TYPE_ARRAY) { fatal("%s: invalid parent type (%d)", __func__, parent->type); } TAILQ_INSERT_TAIL(&parent->data.items, item, list); } static struct kore_json_item * json_find_item(struct kore_json_item *object, char **tokens, u_int32_t type, int pos) { char *p, *str; struct kore_json_item *item, *nitem; int err, idx, spot; if (tokens[pos] == NULL) return (NULL); if (object->type != KORE_JSON_TYPE_OBJECT && object->type != KORE_JSON_TYPE_ARRAY) return (NULL); if ((str = strchr(tokens[pos], '[')) != NULL) { *(str)++ = '\0'; if ((p = strchr(str, ']')) == NULL) return (NULL); *p = '\0'; spot = kore_strtonum(str, 10, 0, USHRT_MAX, &err); if (err != KORE_RESULT_OK) return (NULL); } else { spot = -1; } item = NULL; TAILQ_FOREACH(item, &object->data.items, list) { if (item->name && strcmp(item->name, tokens[pos])) continue; if (item->type == KORE_JSON_TYPE_ARRAY && spot != -1) { idx = 0; nitem = NULL; TAILQ_FOREACH(nitem, &item->data.items, list) { if (idx++ == spot) break; } if (nitem == NULL) { json_errno = KORE_JSON_ERR_NOT_FOUND; return (NULL); } item = nitem; } if (tokens[pos + 1] == NULL) { /* * If an uint64 was required and we find an item * with the same name but marked as an integer check * if it can be represented as a uint64. * * If it can, reduce the type to integer so we match * on it as well. */ if (type == KORE_JSON_TYPE_INTEGER_U64 && item->type == KORE_JSON_TYPE_INTEGER) { if (item->data.integer >= 0) type = KORE_JSON_TYPE_INTEGER; } if (item->type == type) return (item); json_errno = KORE_JSON_ERR_TYPE_MISMATCH; return (NULL); } if (item->type == KORE_JSON_TYPE_OBJECT || item->type == KORE_JSON_TYPE_ARRAY) { item = json_find_item(item, tokens, type, pos + 1); } else { item = NULL; } break; } if (item == NULL && json_errno == 0) json_errno = KORE_JSON_ERR_NOT_FOUND; return (item); } void kore_json_item_free(struct kore_json_item *item) { struct kore_json_item *node; if (item == NULL) return; switch (item->type) { case KORE_JSON_TYPE_OBJECT: case KORE_JSON_TYPE_ARRAY: while ((node = TAILQ_FIRST(&item->data.items)) != NULL) { TAILQ_REMOVE(&item->data.items, node, list); kore_json_item_free(node); } break; case KORE_JSON_TYPE_STRING: kore_free(item->data.string); break; case KORE_JSON_TYPE_NUMBER: case KORE_JSON_TYPE_LITERAL: case KORE_JSON_TYPE_INTEGER: case KORE_JSON_TYPE_INTEGER_U64: break; default: fatal("%s: unknown type %d", __func__, item->type); } kore_free(item->name); kore_free(item); } static struct kore_json_item * json_item_alloc(int type, const char *name, struct kore_json_item *parent) { struct kore_json_item *item; item = kore_calloc(1, sizeof(*item)); item->type = type; item->parent = parent; switch (item->type) { case KORE_JSON_TYPE_OBJECT: TAILQ_INIT(&item->data.items); item->parse = json_parse_object; break; case KORE_JSON_TYPE_ARRAY: TAILQ_INIT(&item->data.items); item->parse = json_parse_array; break; case KORE_JSON_TYPE_STRING: item->parse = json_parse_string; break; case KORE_JSON_TYPE_NUMBER: case KORE_JSON_TYPE_INTEGER: case KORE_JSON_TYPE_INTEGER_U64: item->parse = json_parse_number; break; case KORE_JSON_TYPE_LITERAL: item->parse = json_parse_literal; break; default: fatal("%s: unknown type %d", __func__, item->type); } if (name) item->name = kore_strdup(name); if (parent) { if (parent->type != KORE_JSON_TYPE_OBJECT && parent->type != KORE_JSON_TYPE_ARRAY) { fatal("%s: invalid parent type (%d)", __func__, parent->type); } TAILQ_INSERT_TAIL(&parent->data.items, item, list); } return (item); } static int json_peek(struct kore_json *json, u_int8_t *ch) { return (json_next_byte(json, ch, 1)); } static int json_next(struct kore_json *json, u_int8_t *ch) { return (json_next_byte(json, ch, 0)); } static int json_next_byte(struct kore_json *json, u_int8_t *ch, int peek) { if (json->offset >= json->length) { json_errno = KORE_JSON_ERR_EOF; return (KORE_RESULT_ERROR); } *ch = json->data[json->offset]; if (peek == 0) json->offset++; return (KORE_RESULT_OK); } static int json_consume_whitespace(struct kore_json *json) { u_int8_t ch; for (;;) { if (!json_peek(json, &ch)) return (KORE_RESULT_ERROR); if (ch != ' ' && ch != '\n' && ch != '\r' && ch != '\t') break; json->offset++; } return (KORE_RESULT_OK); } static int json_guess_type(u_int8_t ch, u_int32_t *type) { if (ch == '-' || (ch >= '0' && ch <= '9')) { *type = KORE_JSON_TYPE_NUMBER; return (KORE_RESULT_OK); } switch (ch) { case '{': *type = KORE_JSON_TYPE_OBJECT; break; case '"': *type = KORE_JSON_TYPE_STRING; break; case '[': *type = KORE_JSON_TYPE_ARRAY; break; case 'f': case 'n': case 't': *type = KORE_JSON_TYPE_LITERAL; break; default: return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int json_parse_object(struct kore_json *json, struct kore_json_item *object) { u_int8_t ch; u_int32_t type; char *key; struct kore_json_item *item; int ret, hasnext; if (json->depth++ >= KORE_JSON_DEPTH_MAX) { json_errno = KORE_JSON_ERR_DEPTH; return (KORE_RESULT_ERROR); } key = NULL; hasnext = 0; ret = KORE_RESULT_ERROR; if (!json_next(json, &ch)) goto cleanup; if (ch != '{') goto cleanup; for (;;) { if (!json_consume_whitespace(json)) goto cleanup; if (!json_peek(json, &ch)) goto cleanup; switch (ch) { case '}': if (hasnext) { json_errno = KORE_JSON_ERR_INVALID_JSON; goto cleanup; } json->offset++; ret = KORE_RESULT_OK; goto cleanup; case '"': if ((key = json_get_string(json)) == NULL) goto cleanup; break; default: goto cleanup; } if (!json_consume_whitespace(json)) goto cleanup; if (!json_next(json, &ch)) goto cleanup; if (ch != ':') goto cleanup; if (!json_consume_whitespace(json)) goto cleanup; if (!json_peek(json, &ch)) goto cleanup; if (!json_guess_type(ch, &type)) goto cleanup; item = json_item_alloc(type, key, object); if (!item->parse(json, item)) goto cleanup; key = NULL; if (!json_consume_whitespace(json)) goto cleanup; if (!json_next(json, &ch)) goto cleanup; if (ch == ',') { hasnext = 1; continue; } if (ch == '}') { ret = KORE_RESULT_OK; break; } break; } cleanup: if (ret == KORE_RESULT_ERROR && json_errno == 0) json_errno = KORE_JSON_ERR_INVALID_OBJECT; json->depth--; return (ret); } static int json_parse_array(struct kore_json *json, struct kore_json_item *array) { u_int8_t ch; u_int32_t type; char *key; struct kore_json_item *item; int ret, hasnext; if (json->depth++ >= KORE_JSON_DEPTH_MAX) { json_errno = KORE_JSON_ERR_DEPTH; return (KORE_RESULT_ERROR); } key = NULL; hasnext = 0; ret = KORE_RESULT_ERROR; if (!json_next(json, &ch)) goto cleanup; if (ch != '[') goto cleanup; for (;;) { if (!json_consume_whitespace(json)) goto cleanup; if (!json_peek(json, &ch)) goto cleanup; if (ch == ']') { if (hasnext) { json_errno = KORE_JSON_ERR_INVALID_JSON; goto cleanup; } json->offset++; ret = KORE_RESULT_OK; goto cleanup; } if (!json_guess_type(ch, &type)) goto cleanup; item = json_item_alloc(type, key, array); if (!item->parse(json, item)) goto cleanup; key = NULL; if (!json_consume_whitespace(json)) goto cleanup; if (!json_next(json, &ch)) goto cleanup; if (ch == ',') { hasnext = 1; continue; } if (ch == ']') { ret = KORE_RESULT_OK; break; } break; } cleanup: if (ret == KORE_RESULT_ERROR && json_errno == 0) json_errno = KORE_JSON_ERR_INVALID_ARRAY; json->depth--; return (ret); } static int json_parse_string(struct kore_json *json, struct kore_json_item *string) { char *value; if ((value = json_get_string(json)) == NULL) return (KORE_RESULT_ERROR); string->type = KORE_JSON_TYPE_STRING; string->data.string = kore_strdup(value); return (KORE_RESULT_OK); } static int json_parse_number(struct kore_json *json, struct kore_json_item *number) { u_int8_t ch; int ret; char *str; u_int32_t type; str = NULL; ret = KORE_RESULT_ERROR; kore_buf_reset(&json->tmpbuf); type = KORE_JSON_TYPE_NUMBER | KORE_JSON_TYPE_INTEGER | KORE_JSON_TYPE_INTEGER_U64; for (;;) { if (!json_peek(json, &ch)) break; switch (ch) { case 'e': case 'E': case '.': type = KORE_JSON_TYPE_NUMBER; kore_buf_append(&json->tmpbuf, &ch, sizeof(ch)); json->offset++; continue; case '-': if (json->tmpbuf.offset != 0) goto cleanup; type &= ~KORE_JSON_TYPE_INTEGER_U64; /* FALLTHROUGH */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+': kore_buf_append(&json->tmpbuf, &ch, sizeof(ch)); json->offset++; continue; } break; } if (type & KORE_JSON_TYPE_INTEGER_U64) type = KORE_JSON_TYPE_INTEGER_U64; if (type & KORE_JSON_TYPE_INTEGER) type = KORE_JSON_TYPE_INTEGER; str = kore_buf_stringify(&json->tmpbuf, NULL); switch (type) { case KORE_JSON_TYPE_NUMBER: number->data.number = kore_strtodouble(str, -DBL_MAX, DBL_MAX, &ret); break; case KORE_JSON_TYPE_INTEGER: number->data.integer = (int64_t)kore_strtonum64(str, 1, &ret); break; case KORE_JSON_TYPE_INTEGER_U64: number->data.u64 = kore_strtonum64(str, 0, &ret); if (number->data.u64 <= INT64_MAX) { type = KORE_JSON_TYPE_INTEGER; number->data.integer = number->data.u64; } break; default: goto cleanup; } number->type = type; cleanup: if (ret == KORE_RESULT_ERROR && json_errno == 0) json_errno = KORE_JSON_ERR_INVALID_NUMBER; return (ret); } static int json_parse_literal(struct kore_json *json, struct kore_json_item *literal) { size_t len, idx; int ret, val; u_int8_t ch, *tmpl; ret = KORE_RESULT_ERROR; if (!json_next(json, &ch)) goto cleanup; switch (ch) { case 'f': val = KORE_JSON_FALSE; tmpl = json_false_literal; len = sizeof(json_false_literal) - 1; break; case 'n': val = KORE_JSON_NULL; tmpl = json_null_literal; len = sizeof(json_null_literal) - 1; break; case 't': val = KORE_JSON_TRUE; tmpl = json_true_literal; len = sizeof(json_true_literal) - 1; break; default: goto cleanup; } for (idx = 0; idx < len; idx++) { if (!json_next(json, &ch)) goto cleanup; if (ch != tmpl[idx + 1]) goto cleanup; } literal->data.literal = val; literal->type = KORE_JSON_TYPE_LITERAL; ret = KORE_RESULT_OK; cleanup: if (ret == KORE_RESULT_ERROR && json_errno == 0) json_errno = KORE_JSON_ERR_INVALID_LITERAL; return (ret); } static char * json_get_string(struct kore_json *json) { u_int8_t ch; char *res; res = NULL; if (!json_next(json, &ch)) goto cleanup; if (ch != '"') goto cleanup; kore_buf_reset(&json->tmpbuf); for (;;) { if (!json_next(json, &ch)) goto cleanup; if (ch == '"') break; if (ch <= 0x1f) goto cleanup; if (ch == '\\') { if (!json_next(json, &ch)) goto cleanup; switch (ch) { case '\"': case '\\': case '/': break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'u': default: /* XXX - not supported. */ goto cleanup; } } kore_buf_append(&json->tmpbuf, &ch, sizeof(ch)); } res = kore_buf_stringify(&json->tmpbuf, NULL); cleanup: if (res == NULL && json_errno == 0) json_errno = KORE_JSON_ERR_INVALID_STRING; return (res); } kore-4.2.3/src/jsonrpc.c000066400000000000000000000271241430066302400150750ustar00rootroot00000000000000/* * Copyright (c) 2016 Raphaël Monrouzeau * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "kore.h" #include "http.h" #include "jsonrpc.h" static void init_log(struct jsonrpc_log *log) { log->msg = NULL; log->next = log; log->prev = log; } static void append_log(struct jsonrpc_log *prev, int lvl, char *msg) { struct jsonrpc_log *new = kore_malloc(sizeof(struct jsonrpc_log)); new->lvl = lvl; new->msg = msg; new->prev = prev; new->next = prev->next; prev->next->prev = new; prev->next = new; } static void free_log(struct jsonrpc_log *root) { for (struct jsonrpc_log *it = root->next; it != root; it = it->next) { kore_free(it); } } static void init_request(struct jsonrpc_request *req) { init_log(&req->log); kore_buf_init(&req->buf, 256); req->gen = NULL; req->http = NULL; req->json = NULL; req->id = NULL; req->method = NULL; req->params = NULL; req->log_levels = (1 << LOG_EMERG) | (1 << LOG_ERR) | (1 << LOG_WARNING) | (1 << LOG_NOTICE); req->flags = 0; } void jsonrpc_destroy_request(struct jsonrpc_request *req) { if (req->gen != NULL) { yajl_gen_free(req->gen); req->gen = NULL; } if (req->json != NULL) { yajl_tree_free(req->json); req->json = NULL; } kore_buf_cleanup(&req->buf); free_log(&req->log); } void jsonrpc_log(struct jsonrpc_request *req, int lvl, const char *fmt, ...) { va_list ap; char *msg; size_t start = req->buf.offset; va_start(ap, fmt); kore_buf_appendv(&req->buf, fmt, ap); va_end(ap); msg = kore_buf_stringify(&req->buf, NULL) + start; append_log(&req->log, lvl, msg); } static int read_json_body(struct http_request *http_req, struct jsonrpc_request *req) { char *body_string; ssize_t body_len = 0, chunk_len; u_int8_t chunk_buffer[BUFSIZ]; char error_buffer[1024]; for (;;) { chunk_len = http_body_read(http_req, chunk_buffer, sizeof(chunk_buffer)); if (chunk_len == -1) { jsonrpc_log(req, LOG_CRIT, "Failed to read request body"); return (JSONRPC_SERVER_ERROR); } if (chunk_len == 0) break; if (body_len > SSIZE_MAX - chunk_len) { jsonrpc_log(req, LOG_CRIT, "Request body bigger than the platform accepts"); return (JSONRPC_SERVER_ERROR); } body_len += chunk_len; kore_buf_append(&req->buf, chunk_buffer, chunk_len); } /* Grab our body data as a NUL-terminated string. */ body_string = kore_buf_stringify(&req->buf, NULL); /* Parse the body via yajl now. */ *error_buffer = 0; req->json = yajl_tree_parse(body_string, error_buffer, sizeof(error_buffer)); if (req->json == NULL) { if (strlen(error_buffer)) { jsonrpc_log(req, LOG_ERR, "Invalid json: %s", error_buffer); } else { jsonrpc_log(req, LOG_ERR, "Invalid json"); } return (JSONRPC_PARSE_ERROR); } return (0); } static int parse_json_body(struct jsonrpc_request *req) { static const char *proto_path[] = { "jsonrpc", NULL }; static const char *id_path[] = { "id", NULL }; static const char *method_path[] = { "method", NULL }; static const char *params_path[] = { "params", NULL }; /* Check protocol first. */ yajl_val proto = yajl_tree_get(req->json, proto_path, yajl_t_string); if (proto == NULL) { jsonrpc_log(req, LOG_ERR, "JSON-RPC protocol MUST be indicated and \"2.0\""); return (JSONRPC_PARSE_ERROR); } char *proto_string = YAJL_GET_STRING(proto); if (proto_string == NULL) { jsonrpc_log(req, LOG_ERR, "JSON-RPC protocol MUST be indicated and \"2.0\""); return (JSONRPC_PARSE_ERROR); } if (strcmp("2.0", proto_string) != 0) { jsonrpc_log(req, LOG_ERR, "JSON-RPC protocol MUST be indicated and \"2.0\""); return (JSONRPC_PARSE_ERROR); } /* Check id. */ if ((req->id = yajl_tree_get(req->json, id_path, yajl_t_any)) != NULL) { if (YAJL_IS_NUMBER(req->id)) { if (!YAJL_IS_INTEGER(req->id)) { jsonrpc_log(req, LOG_ERR, "JSON-RPC id SHOULD NOT contain fractional" " parts"); return (JSONRPC_PARSE_ERROR); } } else if (!YAJL_IS_STRING(req->id)) { jsonrpc_log(req, LOG_ERR, "JSON-RPC id MUST contain a String or Number"); return (JSONRPC_PARSE_ERROR); } } /* Check method. */ if ((req->method = YAJL_GET_STRING(yajl_tree_get(req->json, method_path, yajl_t_string))) == NULL) { jsonrpc_log(req, LOG_ERR, "JSON-RPC method MUST exist and be a String"); return (JSONRPC_PARSE_ERROR); } /* Check params. */ req->params = yajl_tree_get(req->json, params_path, yajl_t_any); if (!(req->params == NULL || YAJL_IS_ARRAY(req->params) || YAJL_IS_OBJECT(req->params))) { jsonrpc_log(req, LOG_ERR, "JSON-RPC params MUST be Object or Array"); return (JSONRPC_PARSE_ERROR); } return (0); } int jsonrpc_read_request(struct http_request *http_req, struct jsonrpc_request *req) { int ret; init_request(req); req->http = http_req; if ((ret = read_json_body(http_req, req)) != 0) return (ret); return parse_json_body(req); } static int write_id(yajl_gen gen, yajl_val id) { int status; if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(gen, "id"))) return (status); if (YAJL_IS_NULL(id)) return yajl_gen_null(gen); if (YAJL_IS_NUMBER(id)) { if (YAJL_IS_INTEGER(id)) return yajl_gen_integer(gen, YAJL_GET_INTEGER(id)); return yajl_gen_null(gen); } if (YAJL_IS_STRING(id)) { char *id_str = YAJL_GET_STRING(id); return yajl_gen_string(gen, (unsigned char *)id_str, strlen(id_str)); } return yajl_gen_null(gen); } static int open_response(yajl_gen genctx, yajl_val id) { int status; if (YAJL_GEN_KO(status = yajl_gen_map_open(genctx))) goto failed; if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(genctx, "jsonrpc"))) goto failed; if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(genctx, "2.0"))) goto failed; status = write_id(genctx, id); failed: return (status); } static int close_response(yajl_gen genctx) { int status; if (YAJL_GEN_KO(status = yajl_gen_map_close(genctx))) goto failed; status = yajl_gen_map_close(genctx); failed: return (status); } static int write_log(struct jsonrpc_request *req) { bool wrote_smth = false; int status = 0; for (struct jsonrpc_log *log = req->log.next; log != &req->log; log = log->next) { if (((1 << log->lvl) & req->log_levels) == 0) continue; if (!wrote_smth) { if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "data"))) goto failed; if (YAJL_GEN_KO(status = yajl_gen_array_open(req->gen))) goto failed; yajl_gen_config(req->gen, yajl_gen_validate_utf8, 1); wrote_smth = true; } if (YAJL_GEN_KO(status = yajl_gen_array_open(req->gen))) goto failed; if (YAJL_GEN_KO(status = yajl_gen_integer(req->gen, log->lvl))) goto failed; if (YAJL_GEN_KO(status = yajl_gen_string(req->gen, (unsigned char *)log->msg, strlen(log->msg)))) goto failed; if (YAJL_GEN_KO(status = yajl_gen_array_close(req->gen))) goto failed; } if (wrote_smth) { yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0); status = yajl_gen_array_close(req->gen); } failed: return (status); } static int write_error(struct jsonrpc_request *req, int code, const char *message) { int status; yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0); if (YAJL_GEN_KO(status = open_response(req->gen, req->id))) goto failed; if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "error"))) goto failed; if (YAJL_GEN_KO(status = yajl_gen_map_open(req->gen))) goto failed; if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "code"))) goto failed; if (YAJL_GEN_KO(status = yajl_gen_integer(req->gen, code))) goto failed; if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "message"))) goto failed; yajl_gen_config(req->gen, yajl_gen_validate_utf8, 1); if (YAJL_GEN_KO(status = yajl_gen_string(req->gen, (const unsigned char *)message, strlen(message)))) goto failed; yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0); if (YAJL_GEN_KO(status = write_log(req))) goto failed; status = close_response(req->gen); failed: return (status); } static const char * known_msg(int code) { switch (code) { case JSONRPC_PARSE_ERROR: return (JSONRPC_PARSE_ERROR_MSG); case JSONRPC_INVALID_REQUEST: return (JSONRPC_INVALID_REQUEST_MSG); case JSONRPC_METHOD_NOT_FOUND: return (JSONRPC_METHOD_NOT_FOUND_MSG); case JSONRPC_INVALID_PARAMS: return (JSONRPC_INVALID_PARAMS_MSG); case JSONRPC_INTERNAL_ERROR: return (JSONRPC_INTERNAL_ERROR_MSG); case JSONRPC_SERVER_ERROR: return (JSONRPC_SERVER_ERROR_MSG); case JSONRPC_LIMIT_REACHED: return (JSONRPC_LIMIT_REACHED_MSG); default: return (NULL); } } int jsonrpc_error(struct jsonrpc_request *req, int code, const char *msg) { char *msg_fallback; const unsigned char *body = NULL; size_t body_len = 0; int status; if (req->id == NULL) goto succeeded; if ((req->gen = yajl_gen_alloc(NULL)) == NULL) { kore_log(LOG_ERR, "jsonrpc_error: Failed to allocate yajl gen"); goto failed; } yajl_gen_config(req->gen, yajl_gen_beautify, req->flags & yajl_gen_beautify); if (msg == NULL) msg = known_msg(code); if (msg == NULL) { size_t start = req->buf.offset; kore_buf_appendf(&req->buf, "%d", code); msg_fallback = kore_buf_stringify(&req->buf, NULL) + start; } if (YAJL_GEN_KO(status = write_error(req, code, msg ? msg : msg_fallback))) { kore_log(LOG_ERR, "jsonrpc_error: Failed to yajl gen text [%d]", status); goto failed; } http_response_header(req->http, "content-type", "application/json"); yajl_gen_get_buf(req->gen, &body, &body_len); succeeded: http_response(req->http, HTTP_STATUS_OK, body, body_len); if (req->gen != NULL) yajl_gen_clear(req->gen); jsonrpc_destroy_request(req); return (KORE_RESULT_OK); failed: http_response(req->http, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); jsonrpc_destroy_request(req); return (KORE_RESULT_OK); } int jsonrpc_result(struct jsonrpc_request *req, int (*write_result)(struct jsonrpc_request *, void *), void *ctx) { const unsigned char *body = NULL; size_t body_len = 0; if (req->id == NULL) goto succeeded; if ((req->gen = yajl_gen_alloc(NULL)) == NULL) { kore_log(LOG_ERR, "jsonrpc_result: Failed to allocate yajl gen"); goto failed; } yajl_gen_config(req->gen, yajl_gen_beautify, req->flags & yajl_gen_beautify); yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0); if (YAJL_GEN_KO(open_response(req->gen, req->id))) goto failed; if (YAJL_GEN_KO(YAJL_GEN_CONST_STRING(req->gen, "result"))) goto failed; if (YAJL_GEN_KO(write_result(req, ctx))) goto failed; if (YAJL_GEN_KO(yajl_gen_map_close(req->gen))) goto failed; http_response_header(req->http, "content-type", "application/json"); yajl_gen_get_buf(req->gen, &body, &body_len); succeeded: http_response(req->http, HTTP_STATUS_OK, body, body_len); if (req->gen != NULL) yajl_gen_clear(req->gen); jsonrpc_destroy_request(req); return (KORE_RESULT_OK); failed: http_response(req->http, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); jsonrpc_destroy_request(req); return (KORE_RESULT_OK); } kore-4.2.3/src/keymgr_openssl.c000066400000000000000000001007501430066302400164550ustar00rootroot00000000000000/* * Copyright (c) 2017-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * The kore keymgr process is responsible for managing certificates * and their matching private keys. * * It is the only process in Kore that holds the private keys (the workers * do not have a copy of them in memory). * * When a worker requires the private key for signing it will send a message * to the keymgr with the to-be-signed data (KORE_MSG_KEYMGR_REQ). The keymgr * will perform the signing and respond with a KORE_MSG_KEYMGR_RESP message. * * The keymgr can transparently reload the private keys and certificates * for a configured domain when it receives a SIGUSR1. It it reloads them * it will send the newly loaded certificate chains to the worker processes * which will update their TLS contexts accordingly. * * If ACME is turned on the keymgr will also hold all account and domain * keys and will initiate the process of acquiring new certificates against * the ACME provider that is configured if those certificates do not exist * or are expired (or are expiring soon). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kore.h" #if defined(KORE_USE_ACME) #include "acme.h" #endif #define RAND_TMP_FILE "rnd.tmp" #define RAND_POLL_INTERVAL (1800 * 1000) #define RAND_FILE_SIZE 1024 #if defined(__linux__) #include "seccomp.h" /* The syscalls our keymgr is allowed to perform, only. */ static struct sock_filter filter_keymgr[] = { /* Deny these, but with EACCESS instead of dying. */ KORE_SYSCALL_DENY(ioctl, EACCES), /* Required to deal with private keys and certs. */ #if defined(SYS_open) KORE_SYSCALL_ALLOW(open), #endif KORE_SYSCALL_ALLOW(read), KORE_SYSCALL_ALLOW(lseek), KORE_SYSCALL_ALLOW(write), KORE_SYSCALL_ALLOW(close), #if defined(SYS_stat) KORE_SYSCALL_ALLOW(stat), #endif KORE_SYSCALL_ALLOW(fstat), #if defined(SYS_fstat64) KORE_SYSCALL_ALLOW(fstat64), #endif #if defined(SYS_newfstatat) KORE_SYSCALL_ALLOW(newfstatat), #endif KORE_SYSCALL_ALLOW(futex), KORE_SYSCALL_ALLOW(writev), KORE_SYSCALL_ALLOW(openat), #if defined(SYS_access) KORE_SYSCALL_ALLOW(access), #endif KORE_SYSCALL_ALLOW(faccessat), /* Net related. */ #if defined(SYS_poll) KORE_SYSCALL_ALLOW(poll), #endif #if defined(SYS_send) KORE_SYSCALL_ALLOW(send), #endif KORE_SYSCALL_ALLOW(sendto), #if defined(SYS_recv) KORE_SYSCALL_ALLOW(recv), #endif KORE_SYSCALL_ALLOW(recvfrom), #if defined(SYS_epoll_wait) KORE_SYSCALL_ALLOW(epoll_wait), #endif KORE_SYSCALL_ALLOW(epoll_pwait), /* Process things. */ KORE_SYSCALL_ALLOW(exit), KORE_SYSCALL_ALLOW(kill), KORE_SYSCALL_ALLOW(getuid), KORE_SYSCALL_ALLOW(getpid), #if defined(SYS_arch_prctl) KORE_SYSCALL_ALLOW(arch_prctl), #endif KORE_SYSCALL_ALLOW(exit_group), KORE_SYSCALL_ALLOW(sigaltstack), #if defined(SYS_sigreturn) KORE_SYSCALL_ALLOW(sigreturn), #endif KORE_SYSCALL_ALLOW(rt_sigreturn), KORE_SYSCALL_ALLOW(rt_sigaction), KORE_SYSCALL_ALLOW(rt_sigprocmask), /* Other things. */ KORE_SYSCALL_ALLOW(brk), #if defined(SYS_mmap) KORE_SYSCALL_ALLOW(mmap), #endif #if defined(SYS_mmap2) KORE_SYSCALL_ALLOW(mmap2), #endif #if defined(SYS_madvise) KORE_SYSCALL_ALLOW(madvise), #endif KORE_SYSCALL_ALLOW(munmap), KORE_SYSCALL_ALLOW(clock_gettime), #if defined(__NR_getrandom) KORE_SYSCALL_ALLOW(getrandom), #endif #if defined(KORE_USE_ACME) #if defined(SYS_mkdir) KORE_SYSCALL_ALLOW(mkdir), #endif KORE_SYSCALL_ALLOW(mkdirat), KORE_SYSCALL_ALLOW(umask), #endif }; #endif struct key { KORE_PRIVATE_KEY *pkey; struct kore_domain *dom; TAILQ_ENTRY(key) list; }; char *kore_rand_file = NULL; static TAILQ_HEAD(, key) keys; static int initialized = 0; #if defined(KORE_USE_ACME) #define ACME_ORDER_STATE_INIT 1 #define ACME_ORDER_STATE_SUBMIT 2 #define ACME_X509_EXPIRATION 120 #define ACME_TLS_ALPN_01_OID "1.3.6.1.5.5.7.1.31" #define ACME_RENEWAL_THRESHOLD 5 #define ACME_RENEWAL_TIMER (3600 * 1000) /* UTCTIME in format of YYMMDDHHMMSSZ */ #define ASN1_UTCTIME_LEN 13 /* GENERALIZEDTIME in format of YYYYMMDDHHMMSSZ */ #define ASN1_GENERALIZEDTIME_LEN 15 /* Set to 1 when we receive KORE_ACME_PROC_READY. */ static int acmeproc_ready = 0; /* Renewal timer for all domains under acme control. */ static struct kore_timer *acme_renewal = NULL; /* oid for acme extension. */ static int acme_oid = -1; struct acme_order { int state; struct kore_timer *timer; char *domain; }; static char *keymgr_bignum_base64(const BIGNUM *); static void keymgr_acme_init(void); static void keymgr_acme_renewal(void *, u_int64_t); static void keymgr_acme_check(struct kore_domain *); static void keymgr_acme_sign(struct kore_msg *, const void *); static void keymgr_acme_ready(struct kore_msg *, const void *); static void keymgr_acme_domainkey(struct kore_domain *, struct key *); static void keymgr_acme_order_create(const char *); static void keymgr_acme_order_redo(void *, u_int64_t); static void keymgr_acme_order_start(void *, u_int64_t); static void keymgr_x509_ext(STACK_OF(X509_EXTENSION) *, int, const char *, ...) __attribute__((format (printf, 3, 4))); static void keymgr_acme_csr(const struct kore_keyreq *, struct key *); static void keymgr_acme_install_cert(const void *, size_t, struct key *); static void keymgr_acme_order_failed(const void *, size_t, struct key *); static void keymgr_acme_challenge_cert(const void *, size_t, struct key *); static int keymgr_x509_not_after(X509 *, time_t *); static int keymgr_asn1_convert_utctime(const ASN1_TIME *, time_t *); static int keymgr_asn1_convert_generalizedtime(const void *, size_t, time_t *); #endif /* KORE_USE_ACME */ static void keymgr_reload(void); static void keymgr_load_randfile(void); static void keymgr_save_randfile(void); static struct key *keymgr_load_privatekey(const char *); static void keymgr_load_domain_privatekey(struct kore_domain *); static void keymgr_msg_recv(struct kore_msg *, const void *); static void keymgr_entropy_request(struct kore_msg *, const void *); static void keymgr_certificate_request(struct kore_msg *, const void *); static void keymgr_submit_certificates(struct kore_domain *, u_int16_t); static void keymgr_submit_file(u_int8_t, struct kore_domain *, const char *, u_int16_t, int); static void keymgr_x509_msg(const char *, const void *, size_t, int, int); static void keymgr_rsa_encrypt(struct kore_msg *, const void *, struct key *); static void keymgr_ecdsa_sign(struct kore_msg *, const void *, struct key *); #if defined(__OpenBSD__) #if defined(KORE_USE_ACME) static const char *keymgr_pledges = "stdio rpath wpath cpath"; #else static const char *keymgr_pledges = "stdio rpath"; #endif #endif void kore_keymgr_run(void) { int quit; u_int64_t now, netwait, last_seed; if (kore_keymgr_active == 0) fatalx("%s: called with kore_keymgr_active == 0", __func__); quit = 0; kore_server_closeall(); kore_module_cleanup(); net_init(); kore_timer_init(); kore_connection_init(); kore_platform_event_init(); kore_msg_worker_init(); kore_msg_register(KORE_MSG_KEYMGR_REQ, keymgr_msg_recv); kore_msg_register(KORE_MSG_ENTROPY_REQ, keymgr_entropy_request); kore_msg_register(KORE_MSG_CERTIFICATE_REQ, keymgr_certificate_request); #if defined(__linux__) /* Drop all enabled seccomp filters, and add only ours. */ kore_seccomp_drop(); kore_seccomp_filter("keymgr", filter_keymgr, KORE_FILTER_LEN(filter_keymgr)); #endif #if defined(KORE_USE_PYTHON) kore_msg_unregister(KORE_PYTHON_SEND_OBJ); #endif kore_worker_privsep(); if (kore_rand_file != NULL) { keymgr_load_randfile(); keymgr_save_randfile(); } else if (!kore_quiet) { kore_log(LOG_WARNING, "no rand_file location specified"); } RAND_poll(); last_seed = 0; initialized = 1; keymgr_reload(); #if defined(__OpenBSD__) if (pledge(keymgr_pledges, NULL) == -1) fatalx("failed to pledge keymgr process"); #endif #if defined(KORE_USE_ACME) acme_oid = OBJ_create(ACME_TLS_ALPN_01_OID, "acme", "acmeIdentifier"); X509V3_EXT_add_alias(acme_oid, NID_subject_key_identifier); #endif kore_worker_started(); while (quit != 1) { now = kore_time_ms(); if ((now - last_seed) > RAND_POLL_INTERVAL) { RAND_poll(); last_seed = now; } netwait = kore_timer_next_run(now); kore_platform_event_wait(netwait); if (sig_recv != 0) { switch (sig_recv) { case SIGQUIT: case SIGINT: case SIGTERM: quit = 1; break; case SIGUSR1: keymgr_reload(); break; default: break; } sig_recv = 0; } if (quit) break; now = kore_time_ms(); kore_timer_run(now); kore_connection_prune(KORE_CONNECTION_PRUNE_DISCONNECT); } kore_keymgr_cleanup(1); kore_platform_event_cleanup(); kore_connection_cleanup(); net_cleanup(); } void kore_keymgr_cleanup(int final) { struct key *key, *next; if (initialized == 0) return; for (key = TAILQ_FIRST(&keys); key != NULL; key = next) { next = TAILQ_NEXT(key, list); TAILQ_REMOVE(&keys, key, list); EVP_PKEY_free(key->pkey); kore_free(key); } } static void keymgr_reload(void) { struct kore_server *srv; struct kore_domain *dom; if (!kore_quiet) kore_log(LOG_INFO, "(re)loading certificates, keys and CRLs"); kore_keymgr_cleanup(0); TAILQ_INIT(&keys); #if defined(KORE_USE_ACME) keymgr_acme_init(); #endif kore_domain_callback(keymgr_load_domain_privatekey); /* can't use kore_domain_callback() due to dst parameter. */ LIST_FOREACH(srv, &kore_servers, list) { if (srv->tls == 0) continue; TAILQ_FOREACH(dom, &srv->domains, list) keymgr_submit_certificates(dom, KORE_MSG_WORKER_ALL); } } static void keymgr_submit_certificates(struct kore_domain *dom, u_int16_t dst) { if (access(dom->certfile, R_OK) == -1) { #if defined(KORE_USE_ACME) if (dom->acme && errno == ENOENT) return; #endif fatalx("cannot read '%s' for %s: %s", dom->certfile, dom->domain, errno_s); } keymgr_submit_file(KORE_MSG_CERTIFICATE, dom, dom->certfile, dst, 0); if (dom->crlfile != NULL) keymgr_submit_file(KORE_MSG_CRL, dom, dom->crlfile, dst, 1); } static void keymgr_submit_file(u_int8_t id, struct kore_domain *dom, const char *file, u_int16_t dst, int can_fail) { int fd; struct stat st; u_int8_t *payload; if ((fd = open(file, O_RDONLY)) == -1) { if (errno == ENOENT && can_fail) return; fatalx("open(%s): %s", file, errno_s); } if (fstat(fd, &st) == -1) fatalx("stat(%s): %s", file, errno_s); if (!S_ISREG(st.st_mode)) fatalx("%s is not a file", file); payload = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (payload == MAP_FAILED) fatalx("mmap(): %s", errno_s); keymgr_x509_msg(dom->domain, payload, st.st_size, dst, id); (void)munmap(payload, st.st_size); close(fd); } static void keymgr_load_randfile(void) { int fd; struct stat st; ssize_t ret; size_t total; u_int8_t buf[RAND_FILE_SIZE]; if (kore_rand_file == NULL) return; if ((fd = open(kore_rand_file, O_RDONLY)) == -1) fatalx("open(%s): %s", kore_rand_file, errno_s); if (fstat(fd, &st) == -1) fatalx("stat(%s): %s", kore_rand_file, errno_s); if (!S_ISREG(st.st_mode)) fatalx("%s is not a file", kore_rand_file); if (st.st_size != RAND_FILE_SIZE) fatalx("%s has an invalid size", kore_rand_file); total = 0; while (total != RAND_FILE_SIZE) { ret = read(fd, buf, sizeof(buf)); if (ret == 0) fatalx("EOF on %s", kore_rand_file); if (ret == -1) { if (errno == EINTR) continue; fatalx("read(%s): %s", kore_rand_file, errno_s); } total += (size_t)ret; RAND_seed(buf, (int)ret); OPENSSL_cleanse(buf, sizeof(buf)); } (void)close(fd); if (unlink(kore_rand_file) == -1) { kore_log(LOG_WARNING, "failed to unlink %s: %s", kore_rand_file, errno_s); } } static void keymgr_save_randfile(void) { int fd; struct stat st; ssize_t ret; u_int8_t buf[RAND_FILE_SIZE]; if (kore_rand_file == NULL) return; if (stat(RAND_TMP_FILE, &st) != -1) { kore_log(LOG_WARNING, "removing stale %s file", RAND_TMP_FILE); (void)unlink(RAND_TMP_FILE); } if (RAND_bytes(buf, sizeof(buf)) != 1) { kore_log(LOG_WARNING, "RAND_bytes: %s", ssl_errno_s); goto cleanup; } if ((fd = open(RAND_TMP_FILE, O_CREAT | O_TRUNC | O_WRONLY, 0400)) == -1) { kore_log(LOG_WARNING, "failed to open %s: %s - random data not written", RAND_TMP_FILE, errno_s); goto cleanup; } ret = write(fd, buf, sizeof(buf)); if (ret == -1 || (size_t)ret != sizeof(buf)) { kore_log(LOG_WARNING, "failed to write random data"); (void)close(fd); (void)unlink(RAND_TMP_FILE); goto cleanup; } if (close(fd) == -1) kore_log(LOG_WARNING, "close(%s): %s", RAND_TMP_FILE, errno_s); if (rename(RAND_TMP_FILE, kore_rand_file) == -1) { kore_log(LOG_WARNING, "rename(%s, %s): %s", RAND_TMP_FILE, kore_rand_file, errno_s); (void)unlink(kore_rand_file); (void)unlink(RAND_TMP_FILE); } cleanup: OPENSSL_cleanse(buf, sizeof(buf)); } static void keymgr_load_domain_privatekey(struct kore_domain *dom) { struct key *key; if (dom->server->tls == 0) return; key = keymgr_load_privatekey(dom->certkey); if (key->pkey == NULL) { #if defined(KORE_USE_ACME) if (dom->acme) keymgr_acme_domainkey(dom, key); #endif if (key->pkey == NULL) { fatalx("failed to load private key for '%s' (%s)", dom->domain, errno_s); } } key->dom = dom; if (!kore_quiet) kore_log(LOG_INFO, "loaded private key for '%s'", dom->domain); } static struct key * keymgr_load_privatekey(const char *path) { struct key *key; key = kore_calloc(1, sizeof(*key)); TAILQ_INSERT_TAIL(&keys, key, list); /* Caller should check if pkey was loaded. */ if (path) key->pkey = kore_tls_rsakey_load(path); return (key); } static void keymgr_certificate_request(struct kore_msg *msg, const void *data) { struct kore_server *srv; struct kore_domain *dom; LIST_FOREACH(srv, &kore_servers, list) { if (srv->tls == 0) continue; TAILQ_FOREACH(dom, &srv->domains, list) keymgr_submit_certificates(dom, msg->src); } } static void keymgr_entropy_request(struct kore_msg *msg, const void *data) { u_int8_t buf[RAND_FILE_SIZE]; if (RAND_bytes(buf, sizeof(buf)) != 1) { kore_log(LOG_WARNING, "failed to generate entropy for worker %u: %s", msg->src, ssl_errno_s); return; } /* No cleanse, this stuff is leaked in the kernel path anyway. */ kore_msg_send(msg->src, KORE_MSG_ENTROPY_RESP, buf, sizeof(buf)); } static void keymgr_msg_recv(struct kore_msg *msg, const void *data) { const struct kore_keyreq *req; struct key *key; if (msg->length < sizeof(*req)) return; req = (const struct kore_keyreq *)data; if (msg->length != (sizeof(*req) + req->data_len)) return; if (req->domain[KORE_DOMAINNAME_LEN] != '\0') return; key = NULL; TAILQ_FOREACH(key, &keys, list) { if (key->dom == NULL) continue; if (!strcmp(key->dom->domain, req->domain)) break; } if (key == NULL) return; switch (msg->id) { case KORE_MSG_KEYMGR_REQ: switch (EVP_PKEY_id(key->pkey)) { case EVP_PKEY_RSA: keymgr_rsa_encrypt(msg, data, key); break; case EVP_PKEY_EC: keymgr_ecdsa_sign(msg, data, key); break; default: break; } break; #if defined(KORE_USE_ACME) case KORE_ACME_CSR_REQUEST: keymgr_acme_csr(req, key); break; case KORE_ACME_ORDER_FAILED: keymgr_acme_order_failed(req->data, req->data_len, key); break; case KORE_ACME_CHALLENGE_CERT: keymgr_acme_challenge_cert(req->data, req->data_len, key); break; case KORE_ACME_INSTALL_CERT: keymgr_acme_install_cert(req->data, req->data_len, key); break; #endif } } static void keymgr_rsa_encrypt(struct kore_msg *msg, const void *data, struct key *key) { int ret; RSA *rsa; const struct kore_keyreq *req; size_t keylen; u_int8_t buf[1024]; req = (const struct kore_keyreq *)data; rsa = EVP_PKEY_get0_RSA(key->pkey); keylen = RSA_size(rsa); if (req->data_len > keylen || keylen > sizeof(buf)) return; ret = RSA_private_encrypt(req->data_len, req->data, buf, rsa, req->padding); if (ret != RSA_size(rsa)) return; kore_msg_send(msg->src, KORE_MSG_KEYMGR_RESP, buf, ret); } static void keymgr_ecdsa_sign(struct kore_msg *msg, const void *data, struct key *key) { size_t len; EC_KEY *ec; const struct kore_keyreq *req; unsigned int siglen; u_int8_t sig[1024]; req = (const struct kore_keyreq *)data; ec = EVP_PKEY_get0_EC_KEY(key->pkey); len = ECDSA_size(ec); if (req->data_len > len || len > sizeof(sig)) return; if (ECDSA_sign(EVP_PKEY_NONE, req->data, req->data_len, sig, &siglen, ec) == 0) return; if (siglen > sizeof(sig)) return; kore_msg_send(msg->src, KORE_MSG_KEYMGR_RESP, sig, siglen); } static void keymgr_x509_msg(const char *domain, const void *data, size_t len, int target, int msg) { struct kore_buf buf; struct kore_x509_msg hdr; memset(&hdr, 0, sizeof(hdr)); hdr.data_len = len; if (kore_strlcpy(hdr.domain, domain, sizeof(hdr.domain)) >= sizeof(hdr.domain)) fatalx("%s: domain truncated", __func__); kore_buf_init(&buf, sizeof(hdr) + len); kore_buf_append(&buf, &hdr, sizeof(hdr)); kore_buf_append(&buf, data, len); kore_msg_send(target, msg, buf.data, buf.offset); kore_buf_cleanup(&buf); } #if defined(KORE_USE_ACME) static void keymgr_acme_init(void) { RSA *rsa; struct key *key; char *e, *n; int needsreg; const BIGNUM *be, *bn; if (acme_provider == NULL) return; if (mkdir(KORE_ACME_CERTDIR, 0700) == -1) { if (errno != EEXIST) fatalx("mkdir(%s): %s", KORE_ACME_CERTDIR, errno_s); } umask(S_IWGRP | S_IWOTH | S_IRGRP | S_IROTH); needsreg = 0; acmeproc_ready = 0; key = keymgr_load_privatekey(KORE_ACME_ACCOUNT_KEY); if (acme_renewal != NULL) kore_timer_remove(acme_renewal); acme_renewal = kore_timer_add(keymgr_acme_renewal, ACME_RENEWAL_TIMER, NULL, 0); if (key->pkey == NULL) { kore_log(LOG_INFO, "generating new ACME account key"); key->pkey = kore_tls_rsakey_generate(KORE_ACME_ACCOUNT_KEY); needsreg = 1; } else { kore_log(LOG_INFO, "loaded existing ACME account key"); } rsa = EVP_PKEY_get0_RSA(key->pkey); RSA_get0_key(rsa, &bn, &be, NULL); e = keymgr_bignum_base64(be); n = keymgr_bignum_base64(bn); kore_msg_send(KORE_WORKER_ACME, KORE_ACME_RSAKEY_E, e, strlen(e)); kore_msg_send(KORE_WORKER_ACME, KORE_ACME_RSAKEY_N, n, strlen(n)); kore_free(e); kore_free(n); if (needsreg) { kore_msg_send(KORE_WORKER_ACME, KORE_ACME_ACCOUNT_CREATE, NULL, 0); } else { kore_msg_send(KORE_WORKER_ACME, KORE_ACME_ACCOUNT_RESOLVE, NULL, 0); } kore_msg_register(KORE_ACME_SIGN, keymgr_acme_sign); kore_msg_register(KORE_ACME_CSR_REQUEST, keymgr_msg_recv); kore_msg_register(KORE_ACME_PROC_READY, keymgr_acme_ready); kore_msg_register(KORE_ACME_ORDER_FAILED, keymgr_msg_recv); kore_msg_register(KORE_ACME_INSTALL_CERT, keymgr_msg_recv); kore_msg_register(KORE_ACME_CHALLENGE_CERT, keymgr_msg_recv); } static void keymgr_acme_domainkey(struct kore_domain *dom, struct key *key) { char *p; kore_log(LOG_INFO, "generated new domain key for %s", dom->domain); if ((p = strrchr(dom->certkey, '/')) == NULL) fatalx("invalid certkey path '%s'", dom->certkey); *p = '\0'; if (mkdir(dom->certkey, 0700) == -1) { if (errno != EEXIST) fatalx("mkdir(%s): %s", dom->certkey, errno_s); } *p = '/'; key->pkey = kore_tls_rsakey_generate(dom->certkey); } static void keymgr_acme_order_create(const char *domain) { struct acme_order *order; order = kore_calloc(1, sizeof(*order)); order->state = ACME_ORDER_STATE_INIT; order->domain = kore_strdup(domain); order->timer = kore_timer_add(keymgr_acme_order_start, 1000, order, KORE_TIMER_ONESHOT); } static void keymgr_acme_order_redo(void *udata, u_int64_t now) { struct kore_domain *dom = udata; kore_log(LOG_INFO, "[%s] redoing order", dom->domain); keymgr_acme_order_create(dom->domain); } static void keymgr_acme_order_start(void *udata, u_int64_t now) { struct acme_order *order = udata; switch (order->state) { case ACME_ORDER_STATE_INIT: if (acmeproc_ready == 0) break; order->state = ACME_ORDER_STATE_SUBMIT; /* fallthrough */ case ACME_ORDER_STATE_SUBMIT: kore_msg_send(KORE_WORKER_ACME, KORE_ACME_ORDER_CREATE, order->domain, strlen(order->domain)); kore_free(order->domain); kore_free(order); order = NULL; break; default: fatalx("%s: unknown order state %d", __func__, order->state); } if (order != NULL) { order->timer = kore_timer_add(keymgr_acme_order_start, 5000, order, KORE_TIMER_ONESHOT); } } static void keymgr_acme_ready(struct kore_msg *msg, const void *data) { acmeproc_ready = 1; kore_log(LOG_INFO, "acme process ready to receive orders"); keymgr_acme_renewal(NULL, kore_time_ms()); } static void keymgr_acme_check(struct kore_domain *dom) { FILE *fp; int days; X509 *x509; time_t expires, now; if (dom->acme == 0) return; if (access(dom->certfile, R_OK) == -1) { if (errno == ENOENT) { keymgr_acme_order_create(dom->domain); return; } kore_log(LOG_ERR, "access(%s): %s", dom->certfile, errno_s); return; } if ((fp = fopen(dom->certfile, "r")) == NULL) { kore_log(LOG_ERR, "fopen(%s): %s", dom->certfile, errno_s); return; } if ((x509 = PEM_read_X509(fp, NULL, NULL, NULL)) == NULL) { fclose(fp); kore_log(LOG_ERR, "PEM_read_X509: %s", ssl_errno_s); return; } fclose(fp); if (!keymgr_x509_not_after(x509, &expires)) { X509_free(x509); return; } time(&now); days = (expires - now) / 86400; kore_log(LOG_INFO, "%s certificate expires in %d days", dom->domain, days); if (days <= ACME_RENEWAL_THRESHOLD) { kore_log(LOG_INFO, "%s renewing certificate", dom->domain); keymgr_acme_order_create(dom->domain); } X509_free(x509); } static void keymgr_acme_renewal(void *udata, u_int64_t now) { kore_domain_callback(keymgr_acme_check); } static void keymgr_acme_sign(struct kore_msg *msg, const void *data) { u_int32_t id; struct kore_buf buf; const u_int8_t *ptr; u_int8_t *sig; EVP_MD_CTX *ctx; struct key *key; char *b64; unsigned int siglen; TAILQ_FOREACH(key, &keys, list) { if (key->dom == NULL) break; } if (key == NULL) fatalx("%s: missing key", __func__); if (msg->length < sizeof(id)) fatalx("%s: invalid length (%zu)", __func__, msg->length); ptr = data; memcpy(&id, ptr, sizeof(id)); ptr += sizeof(id); msg->length -= sizeof(id); sig = kore_calloc(1, EVP_PKEY_size(key->pkey)); if ((ctx = EVP_MD_CTX_create()) == NULL) fatalx("EVP_MD_CTX_create: %s", ssl_errno_s); if (!EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) fatalx("EVP_SignInit_ex: %s", ssl_errno_s); if (!EVP_SignUpdate(ctx, ptr, msg->length)) fatalx("EVP_SignUpdate: %s", ssl_errno_s); if (!EVP_SignFinal(ctx, sig, &siglen, key->pkey)) fatalx("EVP_SignFinal: %s", ssl_errno_s); if (!kore_base64url_encode(sig, siglen, &b64, KORE_BASE64_RAW)) fatalx("%s: failed to b64url encode signed data", __func__); kore_buf_init(&buf, siglen + sizeof(id)); kore_buf_append(&buf, &id, sizeof(id)); kore_buf_append(&buf, b64, strlen(b64)); kore_msg_send(KORE_WORKER_ACME, KORE_ACME_SIGN_RESULT, buf.data, buf.offset); EVP_MD_CTX_destroy(ctx); kore_free(sig); kore_free(b64); kore_buf_cleanup(&buf); } static void keymgr_acme_install_cert(const void *data, size_t len, struct key *key) { int fd; ssize_t ret; fd = open(key->dom->certfile, O_CREAT | O_TRUNC | O_WRONLY, 0700); if (fd == -1) fatalx("open(%s): %s", key->dom->certfile, errno_s); kore_log(LOG_INFO, "writing %zu bytes of data", len); for (;;) { ret = write(fd, data, len); if (ret == -1) { if (errno == EINTR) continue; fatalx("write(%s): %s", key->dom->certfile, errno_s); } break; } if ((size_t)ret != len) { fatalx("incorrect write on %s (%zd/%zu)", key->dom->certfile, ret, len); } if (close(fd) == -1) { kore_log(LOG_NOTICE, "close error on '%s' (%s)", key->dom->certfile, errno_s); } keymgr_submit_certificates(key->dom, KORE_MSG_WORKER_ALL); keymgr_x509_msg(key->dom->domain, NULL, 0, KORE_MSG_WORKER_ALL, KORE_ACME_CHALLENGE_CLEAR_CERT); } static void keymgr_acme_order_failed(const void *data, size_t len, struct key *key) { u_int32_t retry; if (len != sizeof(retry)) { kore_log(LOG_ERR, "%s: invalid payload (%zu)", __func__, len); return; } memcpy(&retry, data, len); kore_timer_add(keymgr_acme_order_redo, retry, key->dom, KORE_TIMER_ONESHOT); } static void keymgr_acme_challenge_cert(const void *data, size_t len, struct key *key) { STACK_OF(X509_EXTENSION) *sk; size_t idx; time_t now; X509_EXTENSION *ext; X509_NAME *name; X509 *x509; const u_int8_t *digest; int slen, i; u_int8_t *cert, *uptr; char hex[(SHA256_DIGEST_LENGTH * 2) + 1]; kore_log(LOG_INFO, "[%s] generating tls-alpn-01 challenge cert", key->dom->domain); if (len != SHA256_DIGEST_LENGTH) fatalx("invalid digest length of %zu bytes", len); digest = data; for (idx = 0; idx < SHA256_DIGEST_LENGTH; idx++) { slen = snprintf(hex + (idx * 2), sizeof(hex) - (idx * 2), "%02x", digest[idx]); if (slen == -1 || (size_t)slen >= sizeof(hex)) fatalx("failed to convert digest to hex"); } if ((x509 = X509_new()) == NULL) fatalx("X509_new(): %s", ssl_errno_s); if (!X509_set_version(x509, 2)) fatalx("X509_set_version(): %s", ssl_errno_s); time(&now); if (!ASN1_INTEGER_set(X509_get_serialNumber(x509), now)) fatalx("ASN1_INTEGER_set(): %s", ssl_errno_s); if (!X509_gmtime_adj(X509_get_notBefore(x509), 0)) fatalx("X509_gmtime_adj(): %s", ssl_errno_s); if (!X509_gmtime_adj(X509_get_notAfter(x509), ACME_X509_EXPIRATION)) fatalx("X509_gmtime_adj(): %s", ssl_errno_s); if (!X509_set_pubkey(x509, key->pkey)) fatalx("X509_set_pubkey(): %s", ssl_errno_s); if ((name = X509_get_subject_name(x509)) == NULL) fatalx("X509_get_subject_name(): %s", ssl_errno_s); if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)key->dom->domain, -1, -1, 0)) fatalx("X509_NAME_add_entry_by_txt(): CN %s", ssl_errno_s); if (!X509_set_issuer_name(x509, name)) fatalx("X509_set_issuer_name(): %s", ssl_errno_s); sk = sk_X509_EXTENSION_new_null(); keymgr_x509_ext(sk, acme_oid, "critical,%s", hex); keymgr_x509_ext(sk, NID_subject_alt_name, "DNS:%s", key->dom->domain); for (i = 0; i < sk_X509_EXTENSION_num(sk); i++) { ext = sk_X509_EXTENSION_value(sk, i); if (!X509_add_ext(x509, ext, 0)) fatalx("X509_add_ext(): %s", ssl_errno_s); } if (!X509_sign(x509, key->pkey, EVP_sha256())) fatalx("X509_sign(): %s", ssl_errno_s); if ((slen = i2d_X509(x509, NULL)) <= 0) fatalx("i2d_X509: %s", ssl_errno_s); cert = kore_calloc(1, slen); uptr = cert; if (i2d_X509(x509, &uptr) <= 0) fatalx("i2d_X509: %s", ssl_errno_s); keymgr_x509_msg(key->dom->domain, cert, slen, KORE_MSG_WORKER_ALL, KORE_ACME_CHALLENGE_SET_CERT); kore_free(cert); X509_free(x509); sk_X509_EXTENSION_pop_free(sk, X509_EXTENSION_free); } static void keymgr_acme_csr(const struct kore_keyreq *req, struct key *key) { int len; STACK_OF(X509_EXTENSION) *sk; X509_REQ *csr; X509_NAME *name; u_int8_t *data, *uptr; kore_log(LOG_INFO, "[%s] creating CSR", req->domain); if ((csr = X509_REQ_new()) == NULL) fatalx("X509_REQ_new: %s", ssl_errno_s); if (!X509_REQ_set_version(csr, 3)) fatalx("X509_REQ_set_version(): %s", ssl_errno_s); if (!X509_REQ_set_pubkey(csr, key->pkey)) fatalx("X509_REQ_set_pubkey(): %s", ssl_errno_s); if ((name = X509_REQ_get_subject_name(csr)) == NULL) fatalx("X509_REQ_get_subject_name(): %s", ssl_errno_s); if (!X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)key->dom->domain, -1, -1, 0)) fatalx("X509_NAME_add_entry_by_txt(): %s", ssl_errno_s); sk = sk_X509_EXTENSION_new_null(); keymgr_x509_ext(sk, NID_subject_alt_name, "DNS:%s", key->dom->domain); if (!X509_REQ_add_extensions(csr, sk)) fatalx("X509_REQ_add_extensions(): %s", ssl_errno_s); if (!X509_REQ_sign(csr, key->pkey, EVP_sha256())) fatalx("X509_REQ_sign(): %s", ssl_errno_s); if ((len = i2d_X509_REQ(csr, NULL)) <= 0) fatalx("i2d_X509_REQ: %s", ssl_errno_s); data = kore_calloc(1, len); uptr = data; if (i2d_X509_REQ(csr, &uptr) <= 0) fatalx("i2d_X509_REQ: %s", ssl_errno_s); keymgr_x509_msg(key->dom->domain, data, len, KORE_WORKER_ACME, KORE_ACME_CSR_RESPONSE); kore_free(data); X509_REQ_free(csr); sk_X509_EXTENSION_pop_free(sk, X509_EXTENSION_free); } static void keymgr_x509_ext(STACK_OF(X509_EXTENSION) *sk, int extnid, const char *fmt, ...) { int len; va_list args; X509_EXTENSION *ext; char buf[1024]; va_start(args, fmt); len = vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); if (len == -1 || (size_t)len >= sizeof(buf)) fatalx("failed to create buffer for extension %d", extnid); if ((ext = X509V3_EXT_conf_nid(NULL, NULL, extnid, buf)) == NULL) { fatalx("X509V3_EXT_conf_nid(%d, %s): %s", extnid, buf, ssl_errno_s); } sk_X509_EXTENSION_push(sk, ext); } static char * keymgr_bignum_base64(const BIGNUM *bn) { int len; void *buf; char *encoded; len = BN_num_bytes(bn); buf = kore_calloc(1, len); if (BN_bn2bin(bn, buf) != len) fatalx("BN_bn2bin: %s", ssl_errno_s); if (!kore_base64url_encode(buf, len, &encoded, KORE_BASE64_RAW)) fatalx("failed to base64 encode BIGNUM"); return (encoded); } static int keymgr_x509_not_after(X509 *x509, time_t *out) { const ASN1_TIME *na; int ret; ret = KORE_RESULT_ERROR; if ((na = X509_get_notAfter(x509)) == NULL) { kore_log(LOG_ERR, "no notAfter date in x509"); return (KORE_RESULT_ERROR); } switch (na->type) { case V_ASN1_UTCTIME: ret = keymgr_asn1_convert_utctime(na, out); break; case V_ASN1_GENERALIZEDTIME: ret = keymgr_asn1_convert_generalizedtime(na->data, na->length, out); break; default: kore_log(LOG_ERR, "invalid notAfter type (%d)", na->type); break; } return (ret); } static int keymgr_asn1_convert_utctime(const ASN1_TIME *na, time_t *out) { int len, year; char buf[ASN1_GENERALIZEDTIME_LEN + 1]; if (na->length != ASN1_UTCTIME_LEN) { kore_log(LOG_ERR, "invalid UTCTIME: too short (%d)", na->length); return (KORE_RESULT_ERROR); } if (!isdigit(na->data[0]) || !isdigit(na->data[1])) { kore_log(LOG_ERR, "invalid UTCTIME: YY are not digits"); return (KORE_RESULT_ERROR); } year = (na->data[0] - '0') * 10 + (na->data[1] - '0'); /* RFC 5280 says years >= 50 are interpreted as 19YY */ if (year >= 50) year = 1900 + year; else year = 2000 + year; /* Convert it to GENERALIZEDTIME format and call that parser. */ len = snprintf(buf, sizeof(buf), "%04d%.*s", year, na->length - 2, (const char *)na->data+ 2); if (len == -1 || (size_t)len >= sizeof(buf)) { kore_log(LOG_ERR, "invalid UTCTIME: failed to convert"); return (KORE_RESULT_ERROR); } return (keymgr_asn1_convert_generalizedtime(buf, len, out)); } static int keymgr_asn1_convert_generalizedtime(const void *ptr, size_t len, time_t *out) { size_t i; struct tm tm; const u_int8_t *buf; if (len != ASN1_GENERALIZEDTIME_LEN) { kore_log(LOG_ERR, "invalid GENERALIZEDTIME: too short (%zu)", len); return (KORE_RESULT_ERROR); } buf = ptr; for (i = 0; i < len - 1; i++) { if (!isdigit(buf[i])) { kore_log(LOG_ERR, "invalid GENERALIZEDTIME: invalid bytes"); return (KORE_RESULT_ERROR); } } /* RFC 5280 states that Zulu time must be used (Z). */ if (buf[i] != 'Z') { kore_log(LOG_ERR, "invalid GENERALIZEDTIME: not Zulu time"); return (KORE_RESULT_ERROR); } memset(&tm, 0, sizeof(tm)); tm.tm_year = (buf[0] - '0') * 1000 + (buf[1] - '0') * 100 + (buf[2] - '0') * 10 + (buf[3] - '0'); tm.tm_mon = (buf[4] - '0') * 10 + (buf[5] - '0'); tm.tm_mday = (buf[6] - '0') * 10 + (buf[7] - '0'); tm.tm_hour = (buf[8] - '0') * 10 + (buf[9] - '0'); tm.tm_min = (buf[10] - '0') * 10 + (buf[11] - '0'); tm.tm_sec = (buf[12] - '0') * 10 + (buf[13] - '0'); tm.tm_mon = tm.tm_mon - 1; tm.tm_year = tm.tm_year - 1900; *out = mktime(&tm); return (KORE_RESULT_OK); } #endif kore-4.2.3/src/kore.c000066400000000000000000000472441430066302400143640ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "kore.h" #include "hooks.h" #if !defined(KORE_NO_HTTP) #include "http.h" #endif #if defined(KORE_USE_CURL) #include "curl.h" #endif #if defined(KORE_USE_PGSQL) #include "pgsql.h" #endif #if defined(KORE_USE_PYTHON) #include "python_api.h" #endif #if defined(KORE_USE_ACME) #include "acme.h" #endif volatile sig_atomic_t sig_recv; struct kore_server_list kore_servers; u_int8_t nlisteners; int kore_argc = 0; pid_t kore_pid = -1; u_int16_t cpu_count = 1; int kore_quiet = 0; int skip_runas = 0; int skip_chroot = 0; u_int8_t worker_count = 0; char **kore_argv = NULL; int kore_foreground = 0; char *kore_progname = NULL; u_int32_t kore_socket_backlog = 5000; int kore_quit = KORE_QUIT_NONE; char *kore_pidfile = KORE_PIDFILE_DEFAULT; struct kore_privsep worker_privsep; extern char **environ; extern char *__progname; static size_t proctitle_maxlen = 0; static void usage(void); static void version(void); static void kore_write_kore_pid(void); static void kore_proctitle_setup(void); static void kore_server_shutdown(void); static void kore_server_start(int, char *[]); static void kore_call_parent_configure(int, char **); #if !defined(KORE_SINGLE_BINARY) && defined(KORE_USE_PYTHON) static const char *parent_config_hook = KORE_PYTHON_CONFIG_HOOK; static const char *parent_teardown_hook = KORE_PYTHON_TEARDOWN_HOOK; #else static const char *parent_config_hook = KORE_CONFIG_HOOK; static const char *parent_teardown_hook = KORE_TEARDOWN_HOOK; #if defined(KORE_SINGLE_BINARY) static const char *parent_daemonized_hook = KORE_DAEMONIZED_HOOK; #endif #endif static void usage(void) { #if defined(KORE_USE_PYTHON) printf("Usage: %s [options] [app | app.py]\n", __progname); #else printf("Usage: %s [options]\n", __progname); #endif printf("\n"); printf("Available options:\n"); #if !defined(KORE_SINGLE_BINARY) printf("\t-c\tconfiguration to use\n"); #endif #if defined(KORE_DEBUG) printf("\t-d\trun with debug on\n"); #endif printf("\t-f\tstart in foreground\n"); printf("\t-h\tthis help text\n"); printf("\t-n\tdo not chroot on any worker\n"); printf("\t-q\tonly log errors\n"); printf("\t-r\tdo not change user on any worker\n"); printf("\t-v\tdisplay %s build information\n", __progname); printf("\nFind more information on https://kore.io\n"); exit(1); } static void version(void) { printf("%s ", kore_version); #if defined(KORE_NO_HTTP) printf("no-http "); #endif #if defined(KORE_USE_CURL) printf("curl-%s ", LIBCURL_VERSION); #endif #if defined(KORE_USE_PGSQL) printf("pgsql "); #endif #if defined(KORE_USE_TASKS) printf("tasks "); #endif #if defined(KORE_DEBUG) printf("debug "); #endif #if defined(KORE_USE_PYTHON) printf("python-%s ", PY_VERSION); #endif #if defined(KORE_USE_ACME) printf("acme "); #endif if (!kore_tls_supported()) printf("notls "); printf("\n"); exit(0); } int main(int argc, char *argv[]) { struct kore_runtime_call *rcall; #if !defined(KORE_SINGLE_BINARY) && defined(KORE_USE_PYTHON) struct stat st; #endif kore_argc = argc; kore_argv = argv; #if !defined(KORE_SINGLE_BINARY) kore_default_getopt(argc, argv); #endif kore_mem_init(); kore_msg_init(); kore_log_init(); kore_progname = kore_strdup(argv[0]); kore_proctitle_setup(); #if !defined(KORE_SINGLE_BINARY) argc -= optind; argv += optind; #endif #if !defined(KORE_SINGLE_BINARY) && defined(KORE_USE_PYTHON) if (argc > 0) { kore_pymodule = argv[0]; argc--; argv++; } else { kore_pymodule = NULL; } if (kore_pymodule) { if (lstat(kore_pymodule, &st) == -1) { fatal("failed to stat '%s': %s", kore_pymodule, errno_s); } if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) fatal("%s: not a directory or file", kore_pymodule); } #endif kore_pid = getpid(); nlisteners = 0; LIST_INIT(&kore_servers); kore_platform_init(); #if !defined(KORE_NO_HTTP) http_parent_init(); #if defined(KORE_USE_CURL) kore_curl_sysinit(); #endif #if defined(KORE_USE_PGSQL) kore_pgsql_sys_init(); #endif kore_auth_init(); kore_validator_init(); kore_filemap_init(); #endif #if defined(KORE_USE_ACME) kore_acme_init(); #endif kore_domain_init(); kore_module_init(); kore_tls_init(); #if !defined(KORE_SINGLE_BINARY) && !defined(KORE_USE_PYTHON) if (config_file == NULL) usage(); #endif kore_module_load(NULL, NULL, KORE_MODULE_NATIVE); #if defined(KORE_USE_PYTHON) kore_python_init(); #if !defined(KORE_SINGLE_BINARY) if (kore_pymodule) { kore_module_load(kore_pymodule, NULL, KORE_MODULE_PYTHON); if (S_ISDIR(st.st_mode) && chdir(kore_pymodule) == -1) fatal("chdir(%s): %s", kore_pymodule, errno_s); } else { /* swap back to non-python hooks. */ parent_config_hook = KORE_CONFIG_HOOK; parent_teardown_hook = KORE_TEARDOWN_HOOK; } #endif #endif #if defined(KORE_SINGLE_BINARY) kore_call_parent_configure(argc, argv); #endif #if defined(KORE_USE_PYTHON) && !defined(KORE_SINGLE_BINARY) if (kore_pymodule) kore_call_parent_configure(argc, argv); #endif kore_parse_config(); #if !defined(KORE_SINGLE_BINARY) free(config_file); #endif #if !defined(KORE_NO_HTTP) if (http_body_disk_offload > 0) { if (mkdir(http_body_disk_path, 0700) == -1 && errno != EEXIST) { printf("can't create http_body_disk_path '%s': %s\n", http_body_disk_path, errno_s); return (KORE_RESULT_ERROR); } } #endif kore_signal_setup(); kore_server_start(argc, argv); kore_server_shutdown(); rcall = kore_runtime_getcall(parent_teardown_hook); if (rcall != NULL) { kore_runtime_execute(rcall); kore_free(rcall); } if (unlink(kore_pidfile) == -1 && errno != ENOENT) kore_log(LOG_NOTICE, "failed to remove pidfile (%s)", errno_s); kore_server_cleanup(); if (!kore_quiet) kore_log(LOG_INFO, "goodbye"); #if defined(KORE_USE_PYTHON) kore_python_cleanup(); #endif kore_mem_cleanup(); return (kore_quit); } void kore_default_getopt(int argc, char **argv) { int ch; #if !defined(KORE_SINGLE_BINARY) while ((ch = getopt(argc, argv, "c:fhnqrv")) != -1) { #else while ((ch = getopt(argc, argv, "fhnqrv")) != -1) { #endif switch (ch) { #if !defined(KORE_SINGLE_BINARY) case 'c': free(config_file); if ((config_file = strdup(optarg)) == NULL) fatal("strdup"); break; #endif case 'f': kore_foreground = 1; break; case 'h': usage(); break; case 'n': skip_chroot = 1; break; case 'q': kore_quiet = 1; break; case 'r': skip_runas = 1; break; case 'v': version(); break; default: usage(); } } } int kore_server_bind(struct kore_server *srv, const char *ip, const char *port, const char *ccb) { int r; struct listener *l; struct addrinfo hints, *results; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = 0; r = getaddrinfo(ip, port, &hints, &results); if (r != 0) fatal("getaddrinfo(%s): %s", ip, gai_strerror(r)); l = kore_listener_create(srv); l->host = kore_strdup(ip); l->port = kore_strdup(port); if (!kore_listener_init(l, results->ai_family, ccb)) { freeaddrinfo(results); return (KORE_RESULT_ERROR); } if (bind(l->fd, results->ai_addr, results->ai_addrlen) == -1) { kore_listener_free(l); freeaddrinfo(results); kore_log(LOG_ERR, "bind(): %s", errno_s); return (KORE_RESULT_ERROR); } freeaddrinfo(results); if (listen(l->fd, kore_socket_backlog) == -1) { kore_listener_free(l); kore_log(LOG_ERR, "listen(): %s", errno_s); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } int kore_server_bind_unix(struct kore_server *srv, const char *path, const char *ccb) { struct listener *l; int len; struct sockaddr_un sun; socklen_t socklen; memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; len = snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path); if (len == -1 || (size_t)len >= sizeof(sun.sun_path)) { kore_log(LOG_ERR, "unix socket path '%s' too long", path); return (KORE_RESULT_ERROR); } #if defined(__linux__) if (sun.sun_path[0] == '@') sun.sun_path[0] = '\0'; socklen = sizeof(sun.sun_family) + len; #else socklen = sizeof(sun); #endif l = kore_listener_create(srv); l->host = kore_strdup(path); if (!kore_listener_init(l, AF_UNIX, ccb)) return (KORE_RESULT_ERROR); if (sun.sun_path[0] != '\0') { if (unlink(sun.sun_path) == -1 && errno != ENOENT) { kore_log(LOG_ERR, "unlink: %s: %s", sun.sun_path, errno_s); kore_listener_free(l); return (KORE_RESULT_ERROR); } } if (bind(l->fd, (struct sockaddr *)&sun, socklen) == -1) { kore_log(LOG_ERR, "bind: %s", errno_s); kore_listener_free(l); return (KORE_RESULT_ERROR); } if (listen(l->fd, kore_socket_backlog) == -1) { kore_log(LOG_ERR, "listen(): %s", errno_s); kore_listener_free(l); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } struct kore_server * kore_server_create(const char *name) { struct kore_server *srv; srv = kore_calloc(1, sizeof(struct kore_server)); srv->name = kore_strdup(name); if (kore_tls_supported()) srv->tls = 1; else srv->tls = 0; TAILQ_INIT(&srv->domains); LIST_INIT(&srv->listeners); LIST_INSERT_HEAD(&kore_servers, srv, list); return (srv); } void kore_server_finalize(struct kore_server *srv) { struct listener *l; const char *proto; if (kore_quiet) return; LIST_FOREACH(l, &srv->listeners, list) { if (srv->tls) proto = "https"; else proto = "http"; if (l->family == AF_UNIX) { kore_log(LOG_INFO, "%s serving %s on %s", srv->name, proto, l->host); } else { kore_log(LOG_INFO, "%s serving %s on %s:%s", srv->name, proto, l->host, l->port); } } } struct listener * kore_listener_create(struct kore_server *server) { struct listener *l; l = kore_calloc(1, sizeof(struct listener)); nlisteners++; LIST_INSERT_HEAD(&server->listeners, l, list); l->server = server; l->fd = -1; l->evt.type = KORE_TYPE_LISTENER; l->evt.handle = kore_listener_accept; return (l); } struct kore_server * kore_server_lookup(const char *name) { struct kore_server *srv; LIST_FOREACH(srv, &kore_servers, list) { if (!strcmp(srv->name, name)) return (srv); } return (NULL); } int kore_listener_init(struct listener *l, int family, const char *ccb) { switch (family) { case AF_INET: case AF_INET6: case AF_UNIX: break; default: fatal("unknown address family %d", family); } l->family = family; if ((l->fd = socket(family, SOCK_STREAM, 0)) == -1) { kore_listener_free(l); kore_log(LOG_ERR, "socket(): %s", errno_s); return (KORE_RESULT_ERROR); } if (fcntl(l->fd, F_SETFD, FD_CLOEXEC) == -1) { kore_listener_free(l); kore_log(LOG_ERR, "fcntl(): %s", errno_s); return (KORE_RESULT_ERROR); } if (!kore_connection_nonblock(l->fd, family != AF_UNIX)) { kore_listener_free(l); kore_log(LOG_ERR, "kore_connection_nonblock(): %s", errno_s); return (KORE_RESULT_ERROR); } if (!kore_sockopt(l->fd, SOL_SOCKET, SO_REUSEADDR)) { kore_listener_free(l); return (KORE_RESULT_ERROR); } if (ccb != NULL) { if ((l->connect = kore_runtime_getcall(ccb)) == NULL) { kore_log(LOG_ERR, "no such callback: '%s'", ccb); kore_listener_free(l); return (KORE_RESULT_ERROR); } } else { l->connect = NULL; } return (KORE_RESULT_OK); } void kore_server_free(struct kore_server *srv) { struct listener *l; struct kore_domain *dom; LIST_REMOVE(srv, list); while ((dom = TAILQ_FIRST(&srv->domains)) != NULL) kore_domain_free(dom); while ((l = LIST_FIRST(&srv->listeners)) != NULL) kore_listener_free(l); kore_free(srv->name); kore_free(srv); } void kore_listener_free(struct listener *l) { int rm; LIST_REMOVE(l, list); if (l->fd != -1) close(l->fd); rm = 0; #if defined(__linux__) if (worker == NULL && l->family == AF_UNIX && l->host[0] != '@') rm++; #else if (worker == NULL && l->family == AF_UNIX) rm++; #endif if (rm) { if (unlink(l->host) == -1) { kore_log(LOG_NOTICE, "failed to remove unix socket %s (%s)", l->host, errno_s); } } kore_free(l->host); kore_free(l->port); kore_free(l); } void kore_listener_accept(void *arg, int error) { struct connection *c; struct listener *l = arg; u_int32_t accepted; if (error) fatal("error on listening socket"); if (!(l->evt.flags & KORE_EVENT_READ)) return; accepted = 0; while (worker_active_connections < worker_max_connections) { if (worker_accept_threshold != 0 && accepted >= worker_accept_threshold) { kore_worker_make_busy(); break; } if (!kore_connection_accept(l, &c)) break; if (c == NULL) break; accepted++; kore_platform_event_all(c->fd, c); } } int kore_sockopt(int fd, int what, int opt) { int on; on = 1; if (setsockopt(fd, what, opt, (const char *)&on, sizeof(on)) == -1) { kore_log(LOG_ERR, "setsockopt(): %s", errno_s); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } void kore_signal_setup(void) { kore_signal_trap(SIGHUP); kore_signal_trap(SIGQUIT); kore_signal_trap(SIGTERM); kore_signal_trap(SIGUSR1); kore_signal_trap(SIGCHLD); if (kore_foreground) kore_signal_trap(SIGINT); else (void)signal(SIGINT, SIG_IGN); (void)signal(SIGPIPE, SIG_IGN); } void kore_signal_trap(int sig) { struct sigaction sa; sig_recv = 0; memset(&sa, 0, sizeof(sa)); sa.sa_handler = kore_signal; if (sigfillset(&sa.sa_mask) == -1) fatal("sigfillset: %s", errno_s); if (sigaction(sig, &sa, NULL) == -1) fatal("sigaction: %s", errno_s); } void kore_server_closeall(void) { struct listener *l; struct kore_server *srv; LIST_FOREACH(srv, &kore_servers, list) { LIST_FOREACH(l, &srv->listeners, list) l->fd = -1; } } void kore_server_cleanup(void) { struct kore_server *srv; while ((srv = LIST_FIRST(&kore_servers)) != NULL) kore_server_free(srv); } void kore_signal(int sig) { sig_recv = sig; } void kore_shutdown(void) { if (worker != NULL) { kore_msg_send(KORE_MSG_PARENT, KORE_MSG_SHUTDOWN, NULL, 0); return; } fatal("kore_shutdown: called from parent"); } void kore_proctitle(const char *title) { int len; kore_argv[1] = NULL; len = snprintf(kore_argv[0], proctitle_maxlen, "%s %s", basename(kore_progname), title); if (len == -1 || (size_t)len >= proctitle_maxlen) fatal("proctitle '%s' too large", title); memset(kore_argv[0] + len, 0, proctitle_maxlen - len); } static void kore_proctitle_setup(void) { int i; char *p; proctitle_maxlen = 0; for (i = 0; environ[i] != NULL; i++) { if ((p = strdup(environ[i])) == NULL) fatal("strdup"); proctitle_maxlen += strlen(environ[i]) + 1; environ[i] = p; } for (i = 0; kore_argv[i] != NULL; i++) proctitle_maxlen += strlen(kore_argv[i]) + 1; } static void kore_server_start(int argc, char *argv[]) { u_int32_t tmp; struct kore_server *srv; u_int64_t netwait; int last_sig; #if !defined(KORE_NO_HTTP) int alog; struct kore_domain *dom; #endif #if defined(KORE_SINGLE_BINARY) struct kore_runtime_call *rcall; #endif if (!kore_quiet) { kore_log(LOG_INFO, "%s %s starting, built=%s", __progname, kore_version, kore_build_date); kore_log(LOG_INFO, "built-ins: " #if defined(__linux__) "seccomp " #endif #if defined(KORE_USE_PGSQL) "pgsql " #endif #if defined(KORE_USE_TASKS) "tasks " #endif #if defined(KORE_USE_JSONRPC) "jsonrpc " #endif #if defined(KORE_USE_PYTHON) "python " #endif #if defined(KORE_USE_ACME) "acme " #endif #if defined(KORE_USE_CURL) "curl " #endif ); } if (kore_foreground == 0) { if (daemon(1, 0) == -1) fatal("cannot daemon(): %s", errno_s); #if defined(KORE_SINGLE_BINARY) rcall = kore_runtime_getcall(parent_daemonized_hook); if (rcall != NULL) { kore_runtime_execute(rcall); kore_free(rcall); } #endif } kore_pid = getpid(); kore_write_kore_pid(); #if !defined(KORE_SINGLE_BINARY) && !defined(KORE_USE_PYTHON) kore_call_parent_configure(argc, argv); #endif #if defined(KORE_USE_PYTHON) && !defined(KORE_SINGLE_BINARY) if (kore_pymodule == NULL) kore_call_parent_configure(argc, argv); #endif #if defined(KORE_USE_PYTHON) kore_python_routes_resolve(); #endif /* Check if keymgr will be active. */ if (kore_tls_supported()) { LIST_FOREACH(srv, &kore_servers, list) { if (srv->tls) { kore_keymgr_active = 1; break; } } } else { kore_keymgr_active = 0; } kore_platform_proctitle("[parent]"); if (!kore_worker_init()) { kore_log(LOG_ERR, "last worker log lines:"); kore_log(LOG_ERR, "====================================="); net_init(); kore_connection_init(); kore_platform_event_init(); kore_msg_parent_init(); kore_platform_event_wait(10); kore_worker_dispatch_signal(SIGQUIT); kore_log(LOG_ERR, "====================================="); return; } /* Set worker_max_connections for kore_connection_init(). */ tmp = worker_max_connections; worker_max_connections = worker_count; net_init(); kore_connection_init(); kore_platform_event_init(); kore_msg_parent_init(); worker_max_connections = tmp; kore_timer_init(); #if !defined(KORE_NO_HTTP) alog = 0; LIST_FOREACH(srv, &kore_servers, list) { TAILQ_FOREACH(dom, &srv->domains, list) { if (dom->accesslog != -1) alog = 1; } } if (alog) { kore_timer_add(kore_accesslog_run, 100, NULL, 0); kore_log(LOG_INFO, "accesslog vacuum is enabled"); } #endif #if defined(KORE_USE_PYTHON) kore_msg_unregister(KORE_PYTHON_SEND_OBJ); #endif while (kore_quit == KORE_QUIT_NONE) { last_sig = sig_recv; if (last_sig != 0) { switch (last_sig) { case SIGHUP: kore_worker_dispatch_signal(last_sig); kore_module_reload(0); break; case SIGINT: case SIGQUIT: case SIGTERM: kore_quit = KORE_QUIT_NORMAL; kore_worker_dispatch_signal(last_sig); continue; case SIGUSR1: kore_worker_dispatch_signal(last_sig); break; case SIGCHLD: kore_worker_reap(); break; default: break; } if (sig_recv == last_sig) sig_recv = 0; else continue; } netwait = kore_timer_next_run(kore_time_ms()); kore_platform_event_wait(netwait); kore_connection_prune(KORE_CONNECTION_PRUNE_DISCONNECT); kore_timer_run(kore_time_ms()); kore_worker_reap(); } kore_worker_dispatch_signal(SIGQUIT); } static void kore_server_shutdown(void) { if (!kore_quiet) kore_log(LOG_INFO, "server shutting down"); kore_worker_shutdown(); #if !defined(KORE_NO_HTTP) kore_accesslog_gather(NULL, kore_time_ms(), 1); #endif kore_platform_event_cleanup(); kore_connection_cleanup(); kore_domain_cleanup(); kore_tls_cleanup(); net_cleanup(); } static void kore_write_kore_pid(void) { FILE *fp; if ((fp = fopen(kore_pidfile, "w+")) == NULL) { printf("warning: couldn't write pid to %s (%s)\n", kore_pidfile, errno_s); } else { fprintf(fp, "%d\n", kore_pid); fclose(fp); } } static void kore_call_parent_configure(int argc, char **argv) { struct kore_runtime_call *rcall; rcall = kore_runtime_getcall(parent_config_hook); if (rcall != NULL) { kore_runtime_configure(rcall, argc, argv); kore_free(rcall); } } kore-4.2.3/src/linux.c000066400000000000000000000124131430066302400145510ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "kore.h" #include "seccomp.h" #if defined(KORE_USE_PGSQL) #include "pgsql.h" #endif #if defined(KORE_USE_TASKS) #include "tasks.h" #endif static int efd = -1; static u_int32_t event_count = 0; static struct epoll_event *events = NULL; void kore_platform_init(void) { long n; kore_seccomp_init(); if ((n = sysconf(_SC_NPROCESSORS_ONLN)) == -1) { cpu_count = 1; } else { cpu_count = (u_int16_t)n; } } void kore_platform_worker_setcpu(struct kore_worker *kw) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(kw->cpu, &cpuset); if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) == -1) kore_log(LOG_WARNING, "kore_worker_setcpu(): %s", errno_s); } void kore_platform_event_init(void) { if (efd != -1) close(efd); if (events != NULL) kore_free(events); if ((efd = epoll_create(10000)) == -1) fatal("epoll_create(): %s", errno_s); event_count = worker_max_connections + nlisteners; events = kore_calloc(event_count, sizeof(struct epoll_event)); } void kore_platform_event_cleanup(void) { if (efd != -1) { close(efd); efd = -1; } if (events != NULL) { kore_free(events); events = NULL; } } void kore_platform_event_wait(u_int64_t timer) { u_int32_t r; struct kore_event *evt; int n, i, timeo; if (timer == KORE_WAIT_INFINITE) timeo = -1; else timeo = timer; n = epoll_wait(efd, events, event_count, timeo); if (n == -1) { if (errno == EINTR) return; fatal("epoll_wait(): %s", errno_s); } r = 0; for (i = 0; i < n; i++) { if (events[i].data.ptr == NULL) fatal("events[%d].data.ptr == NULL", i); r = 0; evt = (struct kore_event *)events[i].data.ptr; if (events[i].events & EPOLLIN) evt->flags |= KORE_EVENT_READ; if (events[i].events & EPOLLOUT) evt->flags |= KORE_EVENT_WRITE; if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP || events[i].events & EPOLLRDHUP) r = 1; evt->handle(events[i].data.ptr, r); } } void kore_platform_event_level_all(int fd, void *c) { kore_platform_event_schedule(fd, EPOLLIN | EPOLLOUT | EPOLLRDHUP, 0, c); } void kore_platform_event_level_read(int fd, void *c) { kore_platform_event_schedule(fd, EPOLLIN | EPOLLRDHUP, 0, c); } void kore_platform_event_all(int fd, void *c) { kore_platform_event_schedule(fd, EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET, 0, c); } void kore_platform_event_schedule(int fd, int type, int flags, void *udata) { struct epoll_event evt; evt.events = type; evt.data.ptr = udata; if (epoll_ctl(efd, EPOLL_CTL_ADD, fd, &evt) == -1) { if (errno == EEXIST) { if (epoll_ctl(efd, EPOLL_CTL_MOD, fd, &evt) == -1) fatal("epoll_ctl() MOD: %s", errno_s); } else { fatal("epoll_ctl() ADD: %s", errno_s); } } } void kore_platform_schedule_read(int fd, void *data) { kore_platform_event_schedule(fd, EPOLLIN | EPOLLET, 0, data); } void kore_platform_schedule_write(int fd, void *data) { kore_platform_event_schedule(fd, EPOLLOUT | EPOLLET, 0, data); } void kore_platform_disable_read(int fd) { if (epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL) == -1) fatal("kore_platform_disable_read: %s", errno_s); } void kore_platform_enable_accept(void) { struct listener *l; struct kore_server *srv; LIST_FOREACH(srv, &kore_servers, list) { LIST_FOREACH(l, &srv->listeners, list) kore_platform_event_schedule(l->fd, EPOLLIN, 0, l); } } void kore_platform_disable_accept(void) { struct listener *l; struct kore_server *srv; LIST_FOREACH(srv, &kore_servers, list) { LIST_FOREACH(l, &srv->listeners, list) { if (epoll_ctl(efd, EPOLL_CTL_DEL, l->fd, NULL) == -1) { fatal("kore_platform_disable_accept: %s", errno_s); } } } } void kore_platform_proctitle(const char *title) { kore_proctitle(title); } #if defined(KORE_USE_PLATFORM_SENDFILE) int kore_platform_sendfile(struct connection *c, struct netbuf *nb) { off_t smin; ssize_t sent; size_t len, prevoff; prevoff = nb->fd_off; smin = nb->fd_len - nb->fd_off; len = MIN(SENDFILE_PAYLOAD_MAX, smin); resend: sent = sendfile(c->fd, nb->file_ref->fd, &nb->fd_off, len); if (sent == -1) { if (errno == EAGAIN) { c->evt.flags &= ~KORE_EVENT_WRITE; return (KORE_RESULT_OK); } return (KORE_RESULT_ERROR); } if (nb->fd_off - prevoff != (size_t)len) goto resend; if (sent == 0 || nb->fd_off == nb->fd_len) { net_remove_netbuf(c, nb); c->snb = NULL; } return (KORE_RESULT_OK); } #endif void kore_platform_sandbox(void) { kore_seccomp_enable(); } kore-4.2.3/src/log.c000066400000000000000000000066711430066302400142040ustar00rootroot00000000000000/* * Copyright (c) 2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "kore.h" struct kore_wlog { int prio; u_int16_t wid; size_t loglen; char logmsg[]; }; static void log_print(int, const char *, ...) __attribute__((format (printf, 2, 3))); static void log_from_worker(struct kore_msg *, const void *); static FILE *fp = NULL; void kore_log_init(void) { #if defined(KORE_SINGLE_BINARY) extern const char *__progname; const char *name = kore_strdup(__progname); #else const char *name = "kore"; #endif fp = stdout; if (!kore_foreground) openlog(name, LOG_NDELAY | LOG_PID, LOG_DAEMON); kore_msg_register(KORE_MSG_WORKER_LOG, log_from_worker); } void kore_log_file(const char *path) { if ((fp = fopen(path, "a")) == NULL) { fp = stdout; fatal("fopen(%s): %s", path, errno_s); } } void kore_log(int prio, const char *fmt, ...) { va_list args; const char *str; struct kore_wlog wlog; struct kore_buf buf, pkt; kore_buf_init(&buf, 128); va_start(args, fmt); kore_buf_appendv(&buf, fmt, args); va_end(args); if (worker != NULL) { kore_buf_init(&pkt, sizeof(wlog) + buf.offset); memset(&wlog, 0, sizeof(wlog)); wlog.prio = prio; wlog.wid = worker->id; wlog.loglen = buf.offset; kore_buf_append(&pkt, &wlog, sizeof(wlog)); kore_buf_append(&pkt, buf.data, buf.offset); kore_msg_send(KORE_MSG_PARENT, KORE_MSG_WORKER_LOG, pkt.data, pkt.offset); kore_buf_cleanup(&pkt); } else { str = kore_buf_stringify(&buf, NULL); if (kore_foreground || fp != stdout) log_print(prio, "[parent]: %s\n", str); else syslog(prio, "[parent]: %s", str); } kore_buf_cleanup(&buf); } static void log_from_worker(struct kore_msg *msg, const void *data) { const char *name; const struct kore_wlog *wlog; if (msg->length < sizeof(*wlog)) { kore_log(LOG_NOTICE, "too short worker log received (%zu < %zu)", msg->length, sizeof(*wlog)); return; } wlog = data; name = kore_worker_name(wlog->wid); if (kore_foreground || fp != stdout) { log_print(wlog->prio, "%s: %.*s\n", name, (int)wlog->loglen, wlog->logmsg); } else { syslog(wlog->prio, "%s: %.*s", name, (int)wlog->loglen, wlog->logmsg); } } static void log_print(int prio, const char *fmt, ...) { struct tm *t; struct timespec ts; va_list args; char tbuf[32]; va_start(args, fmt); switch (prio) { case LOG_ERR: case LOG_WARNING: case LOG_NOTICE: case LOG_INFO: case LOG_DEBUG: break; } (void)clock_gettime(CLOCK_REALTIME, &ts); t = gmtime(&ts.tv_sec); if (strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", t) > 0) fprintf(fp, "%s.%03ld UTC ", tbuf, ts.tv_nsec / 1000000); vfprintf(fp, fmt, args); fflush(fp); va_end(args); } kore-4.2.3/src/mem.c000066400000000000000000000137161430066302400141770ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "kore.h" #define KORE_MEM_BLOCKS 11 #define KORE_MEM_BLOCK_SIZE_MAX 8192 #define KORE_MEM_BLOCK_PREALLOC 128 #define KORE_MEM_ALIGN 16 #define KORE_MEM_MAGIC 0xd0d0 #define KORE_MEM_TAGGED 0x0001 struct memsize { size_t len; size_t magic; } __attribute__((packed)); struct meminfo { u_int16_t flags; u_int16_t magic; } __attribute__((packed)); struct memblock { struct kore_pool pool; }; struct tag { void *ptr; u_int32_t id; TAILQ_ENTRY(tag) list; }; static inline struct memsize *memsize(void *); static inline struct meminfo *meminfo(void *); static size_t memblock_index(size_t); static TAILQ_HEAD(, tag) tags; static struct kore_pool tag_pool; static struct memblock blocks[KORE_MEM_BLOCKS]; void kore_mem_init(void) { int i, len; char name[32]; u_int32_t size, elm, mlen; size = 8; TAILQ_INIT(&tags); kore_pool_init(&tag_pool, "tag_pool", sizeof(struct tag), 100); for (i = 0; i < KORE_MEM_BLOCKS; i++) { len = snprintf(name, sizeof(name), "block-%u", size); if (len == -1 || (size_t)len >= sizeof(name)) fatal("kore_mem_init: snprintf"); elm = (KORE_MEM_BLOCK_PREALLOC * 1024) / size; mlen = sizeof(struct memsize) + size + sizeof(struct meminfo) + KORE_MEM_ALIGN; mlen = mlen & ~(KORE_MEM_ALIGN - 1); kore_pool_init(&blocks[i].pool, name, mlen, elm); size = size << 1; } } void kore_mem_cleanup(void) { int i; for (i = 0; i < KORE_MEM_BLOCKS; i++) { kore_pool_cleanup(&blocks[i].pool); } } void * kore_malloc(size_t len) { void *ptr; struct meminfo *mem; struct memsize *size; u_int8_t *addr; size_t mlen, idx; if (len == 0) len = 8; if (len <= KORE_MEM_BLOCK_SIZE_MAX) { idx = memblock_index(len); ptr = kore_pool_get(&blocks[idx].pool); } else { mlen = sizeof(struct memsize) + len + sizeof(struct meminfo); if ((ptr = calloc(1, mlen)) == NULL) fatal("kore_malloc(%zu): %d", len, errno); } size = (struct memsize *)ptr; size->len = len; size->magic = KORE_MEM_MAGIC; addr = (u_int8_t *)ptr + sizeof(struct memsize); mem = (struct meminfo *)(addr + size->len); mem->flags = 0; mem->magic = KORE_MEM_MAGIC; return (addr); } void * kore_realloc(void *ptr, size_t len) { struct memsize *size; void *nptr; if (ptr == NULL) { nptr = kore_malloc(len); } else { size = memsize(ptr); if (len == size->len) return (ptr); nptr = kore_malloc(len); memcpy(nptr, ptr, MIN(len, size->len)); kore_free(ptr); } return (nptr); } void * kore_calloc(size_t memb, size_t len) { void *ptr; size_t total; if (SIZE_MAX / memb < len) fatal("kore_calloc(): memb * len > SIZE_MAX"); total = memb * len; ptr = kore_malloc(total); memset(ptr, 0, total); return (ptr); } void kore_free(void *ptr) { size_t idx; struct meminfo *mem; struct memsize *size; u_int8_t *addr; if (ptr == NULL) return; mem = meminfo(ptr); if (mem->flags & KORE_MEM_TAGGED) { kore_mem_untag(ptr); mem->flags &= ~KORE_MEM_TAGGED; } size = memsize(ptr); addr = (u_int8_t *)ptr - sizeof(struct memsize); if (size->len <= KORE_MEM_BLOCK_SIZE_MAX) { idx = memblock_index(size->len); kore_pool_put(&blocks[idx].pool, addr); } else { free(addr); } } char * kore_strdup(const char *str) { size_t len; char *nstr; len = strlen(str) + 1; nstr = kore_malloc(len); (void)kore_strlcpy(nstr, str, len); return (nstr); } void * kore_malloc_tagged(size_t len, u_int32_t tag) { void *ptr; ptr = kore_malloc(len); kore_mem_tag(ptr, tag); return (ptr); } void kore_mem_tag(void *ptr, u_int32_t id) { struct tag *tag; struct meminfo *mem; if (kore_mem_lookup(id) != NULL) fatal("kore_mem_tag: tag %u taken", id); mem = meminfo(ptr); mem->flags |= KORE_MEM_TAGGED; tag = kore_pool_get(&tag_pool); tag->id = id; tag->ptr = ptr; TAILQ_INSERT_TAIL(&tags, tag, list); } void kore_mem_untag(void *ptr) { struct tag *tag; TAILQ_FOREACH(tag, &tags, list) { if (tag->ptr == ptr) { TAILQ_REMOVE(&tags, tag, list); kore_pool_put(&tag_pool, tag); break; } } } void * kore_mem_lookup(u_int32_t id) { struct tag *tag; TAILQ_FOREACH(tag, &tags, list) { if (tag->id == id) return (tag->ptr); } return (NULL); } /* Best effort to try and let the compiler not optimize this call away. */ void kore_mem_zero(void *ptr, size_t len) { volatile char *p; p = (volatile char *)ptr; if (p != NULL) { while (len-- > 0) *(p)++ = 0x00; } } static size_t memblock_index(size_t len) { size_t mlen, idx; idx = 0; mlen = 8; while (mlen < len) { idx++; mlen = mlen << 1; } if (idx > (KORE_MEM_BLOCKS - 1)) fatal("kore_malloc: idx too high"); return (idx); } static inline struct memsize * memsize(void *ptr) { struct memsize *ms; ms = (struct memsize *)((u_int8_t *)ptr - sizeof(*ms)); if (ms->magic != KORE_MEM_MAGIC) fatal("%s: bad memsize magic (0x%zx)", __func__, ms->magic); return (ms); } static inline struct meminfo * meminfo(void *ptr) { struct memsize *ms; struct meminfo *info; ms = memsize(ptr); info = (struct meminfo *)((u_int8_t *)ptr + ms->len); if (info->magic != KORE_MEM_MAGIC) fatal("%s: bad meminfo magic (0x%x)", __func__, info->magic); return (info); } kore-4.2.3/src/module.c000066400000000000000000000124771430066302400147110ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "kore.h" #if !defined(KORE_NO_HTTP) #include "http.h" #endif #if defined(KORE_USE_PYTHON) #include "python_api.h" #endif static TAILQ_HEAD(, kore_module) modules; static void native_free(struct kore_module *); static void native_load(struct kore_module *); static void native_reload(struct kore_module *); static void *native_getsym(struct kore_module *, const char *); struct kore_module_functions kore_native_module = { .free = native_free, .load = native_load, .getsym = native_getsym, .reload = native_reload, }; void kore_module_init(void) { TAILQ_INIT(&modules); } void kore_module_cleanup(void) { struct kore_module *module, *next; for (module = TAILQ_FIRST(&modules); module != NULL; module = next) { next = TAILQ_NEXT(module, list); TAILQ_REMOVE(&modules, module, list); module->fun->free(module); } } struct kore_module * kore_module_load(const char *path, const char *onload, int type) { struct stat st; struct kore_module *module; module = kore_malloc(sizeof(struct kore_module)); module->ocb = NULL; module->type = type; module->onload = NULL; module->handle = NULL; if (path != NULL) { if (stat(path, &st) == -1) fatal("stat(%s): %s", path, errno_s); module->path = kore_strdup(path); } else { module->path = NULL; } switch (module->type) { case KORE_MODULE_NATIVE: module->fun = &kore_native_module; module->runtime = &kore_native_runtime; break; #if defined(KORE_USE_PYTHON) case KORE_MODULE_PYTHON: module->fun = &kore_python_module; module->runtime = &kore_python_runtime; break; #endif default: fatal("kore_module_load: unknown type %d", type); } module->fun->load(module); TAILQ_INSERT_TAIL(&modules, module, list); if (onload != NULL) { module->onload = kore_strdup(onload); module->ocb = kore_malloc(sizeof(*module->ocb)); module->ocb->runtime = module->runtime; module->ocb->addr = module->fun->getsym(module, onload); if (module->ocb->addr == NULL) { fatal("%s: onload '%s' not present", module->path, onload); } } return (module); } void kore_module_onload(void) { struct kore_module *module; TAILQ_FOREACH(module, &modules, list) { if (module->path == NULL || module->ocb == NULL) continue; kore_runtime_onload(module->ocb, KORE_MODULE_LOAD); } } void kore_module_reload(int cbs) { struct stat st; int ret; struct kore_module *module; TAILQ_FOREACH(module, &modules, list) { if (module->path == NULL) continue; if (stat(module->path, &st) == -1) { kore_log(LOG_NOTICE, "stat(%s): %s, skipping reload", module->path, errno_s); continue; } if (module->ocb != NULL && cbs == 1) { ret = kore_runtime_onload(module->ocb, KORE_MODULE_UNLOAD); if (ret == KORE_RESULT_ERROR) { kore_log(LOG_NOTICE, "%s forced no reloaded", module->path); continue; } } module->fun->reload(module); if (module->onload != NULL) { kore_free(module->ocb); module->ocb = kore_malloc(sizeof(*module->ocb)); module->ocb->runtime = module->runtime; module->ocb->addr = module->fun->getsym(module, module->onload); if (module->ocb->addr == NULL) { fatal("%s: onload '%s' not present", module->path, module->onload); } } if (module->ocb != NULL && cbs == 1) kore_runtime_onload(module->ocb, KORE_MODULE_LOAD); kore_log(LOG_NOTICE, "reloaded '%s' module", module->path); } #if !defined(KORE_NO_HTTP) kore_route_reload(); kore_validator_reload(); #endif } int kore_module_loaded(void) { if (TAILQ_EMPTY(&modules)) return (0); return (1); } void * kore_module_getsym(const char *symbol, struct kore_runtime **runtime) { void *ptr; struct kore_module *module; if (runtime != NULL) *runtime = NULL; TAILQ_FOREACH(module, &modules, list) { ptr = module->fun->getsym(module, symbol); if (ptr != NULL) { if (runtime != NULL) *runtime = module->runtime; return (ptr); } } return (NULL); } static void * native_getsym(struct kore_module *module, const char *symbol) { return (dlsym(module->handle, symbol)); } static void native_free(struct kore_module *module) { kore_free(module->path); (void)dlclose(module->handle); kore_free(module); } static void native_reload(struct kore_module *module) { if (dlclose(module->handle)) fatal("cannot close existing module: %s", dlerror()); module->fun->load(module); } static void native_load(struct kore_module *module) { module->handle = dlopen(module->path, RTLD_NOW | RTLD_GLOBAL); if (module->handle == NULL) fatal("%s: %s", module->path, dlerror()); } kore-4.2.3/src/msg.c000066400000000000000000000157041430066302400142060ustar00rootroot00000000000000/* * Copyright (c) 2015-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "kore.h" #include "http.h" #if defined(KORE_USE_ACME) #include "acme.h" #endif struct msg_type { u_int8_t id; void (*cb)(struct kore_msg *, const void *); TAILQ_ENTRY(msg_type) list; }; static struct msg_type *msg_type_lookup(u_int8_t); static int msg_recv_data(struct netbuf *); static int msg_recv_packet(struct netbuf *); static void msg_disconnected_worker(struct connection *); static void msg_type_shutdown(struct kore_msg *, const void *); #if !defined(KORE_NO_HTTP) static void msg_type_websocket(struct kore_msg *, const void *); #endif static TAILQ_HEAD(, msg_type) msg_types; static size_t cacheidx = 0; static struct connection **conncache = NULL; void kore_msg_init(void) { TAILQ_INIT(&msg_types); } void kore_msg_parent_init(void) { u_int8_t idx; struct kore_worker *kw; for (idx = 0; idx < worker_count; idx++) { kw = kore_worker_data(idx); if (kw->ps != NULL) kore_msg_parent_add(kw); } kore_msg_register(KORE_MSG_FATALX, msg_type_shutdown); kore_msg_register(KORE_MSG_SHUTDOWN, msg_type_shutdown); } void kore_msg_parent_add(struct kore_worker *kw) { kw->msg[0] = kore_connection_new(NULL); kw->msg[0]->fd = kw->pipe[0]; kw->msg[0]->read = net_read; kw->msg[0]->write = net_write; kw->msg[0]->proto = CONN_PROTO_MSG; kw->msg[0]->state = CONN_STATE_ESTABLISHED; kw->msg[0]->hdlr_extra = &kw->id; kw->msg[0]->disconnect = msg_disconnected_worker; kw->msg[0]->handle = kore_connection_handle; conncache = kore_realloc(conncache, (cacheidx + 1) * sizeof(struct connection *)); conncache[cacheidx++] = kw->msg[0]; TAILQ_INSERT_TAIL(&connections, kw->msg[0], list); kore_platform_event_all(kw->msg[0]->fd, kw->msg[0]); net_recv_queue(kw->msg[0], sizeof(struct kore_msg), 0, msg_recv_packet); } void kore_msg_parent_remove(struct kore_worker *kw) { kore_connection_disconnect(kw->msg[0]); kore_connection_prune(KORE_CONNECTION_PRUNE_DISCONNECT); (void)close(kw->pipe[1]); } void kore_msg_worker_init(void) { #if !defined(KORE_NO_HTTP) kore_msg_register(KORE_MSG_WEBSOCKET, msg_type_websocket); #endif worker->msg[1] = kore_connection_new(NULL); worker->msg[1]->fd = worker->pipe[1]; worker->msg[1]->read = net_read; worker->msg[1]->write = net_write; worker->msg[1]->proto = CONN_PROTO_MSG; worker->msg[1]->state = CONN_STATE_ESTABLISHED; worker->msg[1]->handle = kore_connection_handle; worker->msg[1]->evt.flags = KORE_EVENT_WRITE; TAILQ_INSERT_TAIL(&connections, worker->msg[1], list); kore_platform_event_all(worker->msg[1]->fd, worker->msg[1]); net_recv_queue(worker->msg[1], sizeof(struct kore_msg), 0, msg_recv_packet); } void kore_msg_unregister(u_int8_t id) { struct msg_type *type; if ((type = msg_type_lookup(id)) == NULL) return; TAILQ_REMOVE(&msg_types, type, list); kore_free(type); } int kore_msg_register(u_int8_t id, void (*cb)(struct kore_msg *, const void *)) { struct msg_type *type; if (msg_type_lookup(id) != NULL) return (KORE_RESULT_ERROR); type = kore_malloc(sizeof(*type)); type->id = id; type->cb = cb; TAILQ_INSERT_TAIL(&msg_types, type, list); return (KORE_RESULT_OK); } void kore_msg_send(u_int16_t dst, u_int8_t id, const void *data, size_t len) { struct kore_msg m; struct connection *c; struct kore_worker *kw; m.id = id; m.dst = dst; m.length = len; if (worker == NULL) { m.src = KORE_MSG_PARENT; if ((kw = kore_worker_data_byid(dst)) == NULL) { kore_log(LOG_NOTICE, "no such worker by id %u", dst); return; } c = kw->msg[0]; m.dst = kw->id; } else { m.src = worker->id; c = worker->msg[1]; } net_send_queue(c, &m, sizeof(m)); if (data != NULL && len > 0) net_send_queue(c, data, len); net_send_flush(c); } static int msg_recv_packet(struct netbuf *nb) { struct kore_msg *msg = (struct kore_msg *)nb->buf; if (msg->length > 0) { net_recv_expand(nb->owner, msg->length, msg_recv_data); return (KORE_RESULT_OK); } return (msg_recv_data(nb)); } static int msg_recv_data(struct netbuf *nb) { size_t i; struct connection *c; struct msg_type *type; int deliver; u_int16_t dst, destination; struct kore_msg *msg = (struct kore_msg *)nb->buf; if ((type = msg_type_lookup(msg->id)) != NULL) { if (worker == NULL && msg->dst != KORE_MSG_PARENT) fatal("received parent msg for non parent dst"); if (worker != NULL && msg->dst != worker->id) fatal("received message for incorrect worker"); if (msg->length > 0) type->cb(msg, nb->buf + sizeof(*msg)); else type->cb(msg, NULL); } if (worker == NULL && type == NULL) { destination = msg->dst; for (i = 0; i < cacheidx; i++) { c = conncache[i]; if (c->proto != CONN_PROTO_MSG) fatal("connection not a msg connection"); /* * If hdlr_extra is NULL it just means the worker * never started, ignore it. */ if (c->hdlr_extra == NULL) continue; deliver = 1; dst = *(u_int16_t *)c->hdlr_extra; if (destination == KORE_MSG_WORKER_ALL) { if (kore_keymgr_active && dst == 0) deliver = 0; } else { if (dst != destination) deliver = 0; } if (deliver == 0) continue; /* This allows the worker to receive the correct id. */ msg->dst = *(u_int16_t *)c->hdlr_extra; net_send_queue(c, nb->buf, nb->s_off); net_send_flush(c); } } net_recv_reset(nb->owner, sizeof(struct kore_msg), msg_recv_packet); return (KORE_RESULT_OK); } static void msg_disconnected_worker(struct connection *c) { c->hdlr_extra = NULL; } static void msg_type_shutdown(struct kore_msg *msg, const void *data) { if (!kore_quiet) { kore_log(LOG_NOTICE, "shutdown requested by worker %u, going down", msg->src); } if (msg->id == KORE_MSG_FATALX) kore_quit = KORE_QUIT_FATAL; else kore_quit = KORE_QUIT_NORMAL; } #if !defined(KORE_NO_HTTP) static void msg_type_websocket(struct kore_msg *msg, const void *data) { struct connection *c; TAILQ_FOREACH(c, &connections, list) { if (c->proto == CONN_PROTO_WEBSOCKET) { net_send_queue(c, data, msg->length); net_send_flush(c); } } } #endif static struct msg_type * msg_type_lookup(u_int8_t id) { struct msg_type *type; TAILQ_FOREACH(type, &msg_types, list) { if (type->id == id) return (type); } return (NULL); } kore-4.2.3/src/net.c000066400000000000000000000202711430066302400142010ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #if defined(__linux__) #include #elif defined(__MACH__) #include #define htobe64(x) OSSwapHostToBigInt64(x) #define be64toh(x) OSSwapBigToHostInt64(x) #else #include #endif #include "kore.h" struct kore_pool nb_pool; void net_init(void) { u_int32_t elm; /* Add some overhead so we don't roll over for internal items. */ elm = worker_max_connections + 10; kore_pool_init(&nb_pool, "nb_pool", sizeof(struct netbuf), elm); } void net_cleanup(void) { kore_pool_cleanup(&nb_pool); } struct netbuf * net_netbuf_get(void) { struct netbuf *nb; nb = kore_pool_get(&nb_pool); nb->cb = NULL; nb->buf = NULL; nb->owner = NULL; nb->extra = NULL; nb->file_ref = NULL; nb->type = 0; nb->s_off = 0; nb->b_len = 0; nb->m_len = 0; nb->flags = 0; #if defined(KORE_USE_PLATFORM_SENDFILE) nb->fd_off = -1; nb->fd_len = -1; #endif return (nb); } void net_send_queue(struct connection *c, const void *data, size_t len) { const u_int8_t *d; struct netbuf *nb; size_t avail; d = data; nb = TAILQ_LAST(&(c->send_queue), netbuf_head); if (nb != NULL && !(nb->flags & NETBUF_IS_STREAM) && nb->b_len < nb->m_len) { avail = nb->m_len - nb->b_len; if (len < avail) { memcpy(nb->buf + nb->b_len, d, len); nb->b_len += len; return; } else { memcpy(nb->buf + nb->b_len, d, avail); nb->b_len += avail; len -= avail; d += avail; if (len == 0) return; } } nb = net_netbuf_get(); nb->owner = c; nb->b_len = len; nb->type = NETBUF_SEND; if (nb->b_len < NETBUF_SEND_PAYLOAD_MAX) nb->m_len = NETBUF_SEND_PAYLOAD_MAX; else nb->m_len = nb->b_len; nb->buf = kore_malloc(nb->m_len); memcpy(nb->buf, d, nb->b_len); TAILQ_INSERT_TAIL(&(c->send_queue), nb, list); } void net_send_stream(struct connection *c, void *data, size_t len, int (*cb)(struct netbuf *), struct netbuf **out) { struct netbuf *nb; nb = net_netbuf_get(); nb->cb = cb; nb->owner = c; nb->buf = data; nb->b_len = len; nb->m_len = nb->b_len; nb->type = NETBUF_SEND; nb->flags = NETBUF_IS_STREAM; TAILQ_INSERT_TAIL(&(c->send_queue), nb, list); if (out != NULL) *out = nb; } void net_send_fileref(struct connection *c, struct kore_fileref *ref) { struct netbuf *nb; nb = net_netbuf_get(); nb->owner = c; nb->file_ref = ref; nb->type = NETBUF_SEND; nb->flags = NETBUF_IS_FILEREF; #if defined(KORE_USE_PLATFORM_SENDFILE) if (c->owner->server->tls == 0) { nb->fd_off = 0; nb->fd_len = ref->size; } else { nb->buf = ref->base; nb->b_len = ref->size; nb->m_len = nb->b_len; nb->flags |= NETBUF_IS_STREAM; } #else nb->buf = ref->base; nb->b_len = ref->size; nb->m_len = nb->b_len; nb->flags |= NETBUF_IS_STREAM; #endif TAILQ_INSERT_TAIL(&(c->send_queue), nb, list); } void net_recv_reset(struct connection *c, size_t len, int (*cb)(struct netbuf *)) { c->rnb->cb = cb; c->rnb->s_off = 0; c->rnb->b_len = len; if (c->rnb->buf != NULL && c->rnb->b_len <= c->rnb->m_len && c->rnb->m_len < (NETBUF_SEND_PAYLOAD_MAX / 2)) return; kore_free(c->rnb->buf); c->rnb->m_len = len; c->rnb->buf = kore_malloc(c->rnb->m_len); } void net_recv_queue(struct connection *c, size_t len, int flags, int (*cb)(struct netbuf *)) { if (c->rnb != NULL) fatal("net_recv_queue(): called incorrectly"); c->rnb = net_netbuf_get(); c->rnb->cb = cb; c->rnb->owner = c; c->rnb->b_len = len; c->rnb->m_len = len; c->rnb->flags = flags; c->rnb->type = NETBUF_RECV; c->rnb->buf = kore_malloc(c->rnb->b_len); } void net_recv_expand(struct connection *c, size_t len, int (*cb)(struct netbuf *)) { c->rnb->cb = cb; c->rnb->b_len += len; c->rnb->m_len = c->rnb->b_len; c->rnb->buf = kore_realloc(c->rnb->buf, c->rnb->b_len); } int net_send(struct connection *c) { size_t r, len, smin; c->snb = TAILQ_FIRST(&(c->send_queue)); #if defined(KORE_USE_PLATFORM_SENDFILE) if ((c->snb->flags & NETBUF_IS_FILEREF) && !(c->snb->flags & NETBUF_IS_STREAM)) { return (kore_platform_sendfile(c, c->snb)); } #endif if (c->snb->b_len != 0) { smin = c->snb->b_len - c->snb->s_off; len = MIN(NETBUF_SEND_PAYLOAD_MAX, smin); if (!c->write(c, len, &r)) return (KORE_RESULT_ERROR); if (!(c->evt.flags & KORE_EVENT_WRITE)) return (KORE_RESULT_OK); c->snb->s_off += r; c->snb->flags &= ~NETBUF_MUST_RESEND; } if (c->snb->s_off == c->snb->b_len || (c->snb->flags & NETBUF_FORCE_REMOVE)) { net_remove_netbuf(c, c->snb); c->snb = NULL; } return (KORE_RESULT_OK); } int net_send_flush(struct connection *c) { while (!TAILQ_EMPTY(&(c->send_queue)) && (c->evt.flags & KORE_EVENT_WRITE)) { if (!net_send(c)) return (KORE_RESULT_ERROR); } if ((c->flags & CONN_CLOSE_EMPTY) && TAILQ_EMPTY(&(c->send_queue))) { kore_connection_disconnect(c); } return (KORE_RESULT_OK); } int net_recv_flush(struct connection *c) { size_t r; if (c->rnb == NULL) return (KORE_RESULT_OK); while (c->evt.flags & KORE_EVENT_READ) { if (c->rnb->buf == NULL) return (KORE_RESULT_OK); if ((c->rnb->b_len - c->rnb->s_off) == 0) return (KORE_RESULT_OK); if (!c->read(c, &r)) return (KORE_RESULT_ERROR); if (!(c->evt.flags & KORE_EVENT_READ)) break; c->rnb->s_off += r; if (c->rnb->s_off == c->rnb->b_len || (c->rnb->flags & NETBUF_CALL_CB_ALWAYS)) { r = c->rnb->cb(c->rnb); if (r != KORE_RESULT_OK) return (r); } } return (KORE_RESULT_OK); } void net_remove_netbuf(struct connection *c, struct netbuf *nb) { if (nb->type == NETBUF_RECV) fatal("net_remove_netbuf(): cannot remove recv netbuf"); if (nb->flags & NETBUF_MUST_RESEND) { nb->flags |= NETBUF_FORCE_REMOVE; return; } if (!(nb->flags & NETBUF_IS_STREAM)) { kore_free(nb->buf); } else if (nb->cb != NULL) { (void)nb->cb(nb); } if (nb->flags & NETBUF_IS_FILEREF) kore_fileref_release(nb->file_ref); TAILQ_REMOVE(&(c->send_queue), nb, list); kore_pool_put(&nb_pool, nb); } int net_write(struct connection *c, size_t len, size_t *written) { ssize_t r; r = send(c->fd, (c->snb->buf + c->snb->s_off), len, 0); if (r == -1) { switch (errno) { case EINTR: *written = 0; return (KORE_RESULT_OK); case EAGAIN: c->evt.flags &= ~KORE_EVENT_WRITE; return (KORE_RESULT_OK); default: return (KORE_RESULT_ERROR); } } *written = (size_t)r; return (KORE_RESULT_OK); } int net_read(struct connection *c, size_t *bytes) { ssize_t r; r = recv(c->fd, (c->rnb->buf + c->rnb->s_off), (c->rnb->b_len - c->rnb->s_off), 0); if (r == -1) { switch (errno) { case EINTR: *bytes = 0; return (KORE_RESULT_OK); case EAGAIN: c->evt.flags &= ~KORE_EVENT_READ; return (KORE_RESULT_OK); default: return (KORE_RESULT_ERROR); } } if (r == 0) { kore_connection_disconnect(c); c->evt.flags &= ~KORE_EVENT_READ; return (KORE_RESULT_OK); } *bytes = (size_t)r; return (KORE_RESULT_OK); } u_int16_t net_read16(u_int8_t *b) { u_int16_t r; r = *(u_int16_t *)b; return (ntohs(r)); } u_int32_t net_read32(u_int8_t *b) { u_int32_t r; r = *(u_int32_t *)b; return (ntohl(r)); } void net_write16(u_int8_t *p, u_int16_t n) { u_int16_t r; r = htons(n); memcpy(p, &r, sizeof(r)); } void net_write32(u_int8_t *p, u_int32_t n) { u_int32_t r; r = htonl(n); memcpy(p, &r, sizeof(r)); } u_int64_t net_read64(u_int8_t *b) { u_int64_t r; r = *(u_int64_t *)b; return (be64toh(r)); } void net_write64(u_int8_t *p, u_int64_t n) { u_int64_t r; r = htobe64(n); memcpy(p, &r, sizeof(r)); } kore-4.2.3/src/pgsql.c000066400000000000000000000441041430066302400145420ustar00rootroot00000000000000/* * Copyright (c) 2014-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "kore.h" #if !defined(KORE_NO_HTTP) #include "http.h" #endif #include "pgsql.h" #if defined(__linux__) #include "seccomp.h" static struct sock_filter filter_pgsql[] = { /* Allow us to create sockets and call connect. */ KORE_SYSCALL_ALLOW(connect), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX), /* Requires these calls. */ KORE_SYSCALL_ALLOW(getsockopt), KORE_SYSCALL_ALLOW(getsockname), }; #endif struct pgsql_wait { struct kore_pgsql *pgsql; TAILQ_ENTRY(pgsql_wait) list; }; struct pgsql_job { struct kore_pgsql *pgsql; TAILQ_ENTRY(pgsql_job) list; }; #define PGSQL_CONN_MAX 2 #define PGSQL_CONN_FREE 0x01 #define PGSQL_LIST_INSERTED 0x0100 #define PGSQL_QUEUE_LIMIT 1000 static void pgsql_queue_wakeup(void); static void pgsql_cancel(struct kore_pgsql *); static void pgsql_set_error(struct kore_pgsql *, const char *); static void pgsql_queue_add(struct kore_pgsql *); static void pgsql_queue_remove(struct kore_pgsql *); static void pgsql_conn_release(struct kore_pgsql *); static void pgsql_conn_cleanup(struct pgsql_conn *); static void pgsql_read_result(struct kore_pgsql *); static void pgsql_schedule(struct kore_pgsql *); static struct pgsql_conn *pgsql_conn_create(struct kore_pgsql *, struct pgsql_db *); static struct pgsql_conn *pgsql_conn_next(struct kore_pgsql *, struct pgsql_db *); static struct kore_pool pgsql_job_pool; static struct kore_pool pgsql_wait_pool; static TAILQ_HEAD(, pgsql_conn) pgsql_conn_free; static TAILQ_HEAD(, pgsql_wait) pgsql_wait_queue; static LIST_HEAD(, pgsql_db) pgsql_db_conn_strings; u_int32_t pgsql_queue_count = 0; u_int16_t pgsql_conn_max = PGSQL_CONN_MAX; u_int32_t pgsql_queue_limit = PGSQL_QUEUE_LIMIT; void kore_pgsql_sys_init(void) { TAILQ_INIT(&pgsql_conn_free); TAILQ_INIT(&pgsql_wait_queue); LIST_INIT(&pgsql_db_conn_strings); kore_pool_init(&pgsql_job_pool, "pgsql_job_pool", sizeof(struct pgsql_job), 100); kore_pool_init(&pgsql_wait_pool, "pgsql_wait_pool", sizeof(struct pgsql_wait), pgsql_queue_limit); #if defined(__linux__) kore_seccomp_filter("pgsql", filter_pgsql, KORE_FILTER_LEN(filter_pgsql)); #endif } void kore_pgsql_sys_cleanup(void) { struct pgsql_conn *conn, *next; kore_pool_cleanup(&pgsql_job_pool); kore_pool_cleanup(&pgsql_wait_pool); for (conn = TAILQ_FIRST(&pgsql_conn_free); conn != NULL; conn = next) { next = TAILQ_NEXT(conn, list); pgsql_conn_cleanup(conn); } } void kore_pgsql_init(struct kore_pgsql *pgsql) { memset(pgsql, 0, sizeof(*pgsql)); pgsql->state = KORE_PGSQL_STATE_INIT; } int kore_pgsql_setup(struct kore_pgsql *pgsql, const char *dbname, int flags) { struct pgsql_db *db; if ((flags & KORE_PGSQL_ASYNC) && (flags & KORE_PGSQL_SYNC)) { pgsql_set_error(pgsql, "invalid query init parameters"); return (KORE_RESULT_ERROR); } if (flags & KORE_PGSQL_ASYNC) { if (pgsql->req == NULL && pgsql->cb == NULL) { pgsql_set_error(pgsql, "nothing was bound"); return (KORE_RESULT_ERROR); } } db = NULL; pgsql->flags |= flags; LIST_FOREACH(db, &pgsql_db_conn_strings, rlist) { if (!strcmp(db->name, dbname)) break; } if (db == NULL) { pgsql_set_error(pgsql, "no database found"); return (KORE_RESULT_ERROR); } if ((pgsql->conn = pgsql_conn_next(pgsql, db)) == NULL) return (KORE_RESULT_ERROR); if (pgsql->flags & KORE_PGSQL_ASYNC) { pgsql->conn->job = kore_pool_get(&pgsql_job_pool); pgsql->conn->job->pgsql = pgsql; } return (KORE_RESULT_OK); } #if !defined(KORE_NO_HTTP) void kore_pgsql_bind_request(struct kore_pgsql *pgsql, struct http_request *req) { if (pgsql->req != NULL || pgsql->cb != NULL) fatal("kore_pgsql_bind_request: already bound"); pgsql->req = req; pgsql->flags |= PGSQL_LIST_INSERTED; LIST_INSERT_HEAD(&(req->pgsqls), pgsql, rlist); } #endif void kore_pgsql_bind_callback(struct kore_pgsql *pgsql, void (*cb)(struct kore_pgsql *, void *), void *arg) { if (pgsql->req != NULL) fatal("kore_pgsql_bind_callback: already bound"); if (pgsql->cb != NULL) fatal("kore_pgsql_bind_callback: already bound"); pgsql->cb = cb; pgsql->arg = arg; } int kore_pgsql_query(struct kore_pgsql *pgsql, const void *query) { if (pgsql->conn == NULL) { pgsql_set_error(pgsql, "no connection was set before query"); return (KORE_RESULT_ERROR); } if (pgsql->flags & KORE_PGSQL_SYNC) { pgsql->result = PQexec(pgsql->conn->db, query); if ((PQresultStatus(pgsql->result) != PGRES_TUPLES_OK) && (PQresultStatus(pgsql->result) != PGRES_COMMAND_OK)) { pgsql_set_error(pgsql, PQerrorMessage(pgsql->conn->db)); return (KORE_RESULT_ERROR); } pgsql->state = KORE_PGSQL_STATE_DONE; } else { if (!PQsendQuery(pgsql->conn->db, query)) { pgsql_set_error(pgsql, PQerrorMessage(pgsql->conn->db)); return (KORE_RESULT_ERROR); } pgsql_schedule(pgsql); } return (KORE_RESULT_OK); } int kore_pgsql_v_query_params(struct kore_pgsql *pgsql, const void *query, int binary, int count, va_list args) { int i; const char **values; int *lengths, *formats, ret; if (pgsql->conn == NULL) { pgsql_set_error(pgsql, "no connection was set before query"); return (KORE_RESULT_ERROR); } if (count > 0) { lengths = kore_calloc(count, sizeof(int)); formats = kore_calloc(count, sizeof(int)); values = kore_calloc(count, sizeof(char *)); for (i = 0; i < count; i++) { values[i] = va_arg(args, void *); lengths[i] = va_arg(args, int); formats[i] = va_arg(args, int); } } else { lengths = NULL; formats = NULL; values = NULL; } ret = kore_pgsql_query_param_fields(pgsql, query, binary, count, values, lengths, formats); kore_free(values); kore_free(lengths); kore_free(formats); return (ret); } int kore_pgsql_query_param_fields(struct kore_pgsql *pgsql, const void *query, int binary, int count, const char **values, int *lengths, int *formats) { if (pgsql->conn == NULL) { pgsql_set_error(pgsql, "no connection was set before query"); return (KORE_RESULT_ERROR); } if (pgsql->flags & KORE_PGSQL_SYNC) { pgsql->result = PQexecParams(pgsql->conn->db, query, count, NULL, (const char * const *)values, lengths, formats, binary); if ((PQresultStatus(pgsql->result) != PGRES_TUPLES_OK) && (PQresultStatus(pgsql->result) != PGRES_COMMAND_OK)) { pgsql_set_error(pgsql, PQerrorMessage(pgsql->conn->db)); return (KORE_RESULT_ERROR); } pgsql->state = KORE_PGSQL_STATE_DONE; } else { if (!PQsendQueryParams(pgsql->conn->db, query, count, NULL, (const char * const *)values, lengths, formats, binary)) { pgsql_set_error(pgsql, PQerrorMessage(pgsql->conn->db)); return (KORE_RESULT_ERROR); } pgsql_schedule(pgsql); } return (KORE_RESULT_OK); } int kore_pgsql_query_params(struct kore_pgsql *pgsql, const void *query, int binary, int count, ...) { int ret; va_list args; va_start(args, count); ret = kore_pgsql_v_query_params(pgsql, query, binary, count, args); va_end(args); return (ret); } int kore_pgsql_register(const char *dbname, const char *connstring) { struct pgsql_db *pgsqldb; LIST_FOREACH(pgsqldb, &pgsql_db_conn_strings, rlist) { if (!strcmp(pgsqldb->name, dbname)) return (KORE_RESULT_ERROR); } pgsqldb = kore_malloc(sizeof(*pgsqldb)); pgsqldb->name = kore_strdup(dbname); pgsqldb->conn_count = 0; pgsqldb->conn_max = pgsql_conn_max; pgsqldb->conn_string = kore_strdup(connstring); LIST_INSERT_HEAD(&pgsql_db_conn_strings, pgsqldb, rlist); return (KORE_RESULT_OK); } void kore_pgsql_handle(void *c, int err) { struct kore_pgsql *pgsql; struct pgsql_conn *conn = (struct pgsql_conn *)c; if (err) { pgsql_conn_cleanup(conn); return; } if (!(conn->evt.flags & KORE_EVENT_READ)) fatal("%s: read event not set", __func__); pgsql = conn->job->pgsql; pgsql_read_result(pgsql); if (pgsql->state == KORE_PGSQL_STATE_WAIT) { #if !defined(KORE_NO_HTTP) if (pgsql->req != NULL) http_request_sleep(pgsql->req); #endif if (pgsql->cb != NULL) pgsql->cb(pgsql, pgsql->arg); } else { #if !defined(KORE_NO_HTTP) if (pgsql->req != NULL) http_request_wakeup(pgsql->req); #endif if (pgsql->cb != NULL) pgsql->cb(pgsql, pgsql->arg); } } void kore_pgsql_continue(struct kore_pgsql *pgsql) { if (pgsql->error) { kore_free(pgsql->error); pgsql->error = NULL; } if (pgsql->result) { PQclear(pgsql->result); pgsql->result = NULL; } switch (pgsql->state) { case KORE_PGSQL_STATE_INIT: case KORE_PGSQL_STATE_WAIT: break; case KORE_PGSQL_STATE_DONE: #if !defined(KORE_NO_HTTP) if (pgsql->req != NULL) http_request_wakeup(pgsql->req); #endif pgsql_conn_release(pgsql); break; case KORE_PGSQL_STATE_ERROR: case KORE_PGSQL_STATE_RESULT: case KORE_PGSQL_STATE_NOTIFY: kore_pgsql_handle(pgsql->conn, 0); break; default: fatal("unknown pgsql state %d", pgsql->state); } } void kore_pgsql_cleanup(struct kore_pgsql *pgsql) { pgsql_queue_remove(pgsql); if (pgsql->result != NULL) PQclear(pgsql->result); if (pgsql->error != NULL) kore_free(pgsql->error); if (pgsql->conn != NULL) pgsql_conn_release(pgsql); pgsql->result = NULL; pgsql->error = NULL; pgsql->conn = NULL; if (pgsql->flags & PGSQL_LIST_INSERTED) { LIST_REMOVE(pgsql, rlist); pgsql->flags &= ~PGSQL_LIST_INSERTED; } } void kore_pgsql_logerror(struct kore_pgsql *pgsql) { kore_log(LOG_NOTICE, "pgsql error: %s", (pgsql->error) ? pgsql->error : "unknown"); } int kore_pgsql_ntuples(struct kore_pgsql *pgsql) { return (PQntuples(pgsql->result)); } int kore_pgsql_nfields(struct kore_pgsql *pgsql) { return (PQnfields(pgsql->result)); } int kore_pgsql_getlength(struct kore_pgsql *pgsql, int row, int col) { return (PQgetlength(pgsql->result, row, col)); } char * kore_pgsql_fieldname(struct kore_pgsql *pgsql, int field) { return (PQfname(pgsql->result, field)); } char * kore_pgsql_getvalue(struct kore_pgsql *pgsql, int row, int col) { return (PQgetvalue(pgsql->result, row, col)); } int kore_pgsql_column_binary(struct kore_pgsql *pgsql, int col) { return (PQfformat(pgsql->result, col)); } static struct pgsql_conn * pgsql_conn_next(struct kore_pgsql *pgsql, struct pgsql_db *db) { PGTransactionStatusType state; struct pgsql_conn *conn; struct kore_pgsql rollback; rescan: conn = NULL; TAILQ_FOREACH(conn, &pgsql_conn_free, list) { if (!(conn->flags & PGSQL_CONN_FREE)) fatal("got a pgsql connection that was not free?"); if (!strcmp(conn->name, db->name)) break; } if (conn != NULL) { state = PQtransactionStatus(conn->db); if (state == PQTRANS_INERROR) { conn->flags &= ~PGSQL_CONN_FREE; TAILQ_REMOVE(&pgsql_conn_free, conn, list); kore_pgsql_init(&rollback); rollback.conn = conn; rollback.flags = KORE_PGSQL_SYNC; if (!kore_pgsql_query(&rollback, "ROLLBACK")) { kore_pgsql_logerror(&rollback); kore_pgsql_cleanup(&rollback); pgsql_conn_cleanup(conn); } else { kore_pgsql_cleanup(&rollback); } goto rescan; } } if (conn == NULL) { if (db->conn_max != 0 && db->conn_count >= db->conn_max) { if ((pgsql->flags & KORE_PGSQL_ASYNC) && pgsql_queue_count < pgsql_queue_limit) { pgsql_queue_add(pgsql); } else { pgsql_set_error(pgsql, "no available connection"); } return (NULL); } if ((conn = pgsql_conn_create(pgsql, db)) == NULL) return (NULL); } conn->flags &= ~PGSQL_CONN_FREE; TAILQ_REMOVE(&pgsql_conn_free, conn, list); return (conn); } static void pgsql_set_error(struct kore_pgsql *pgsql, const char *msg) { if (pgsql->error != NULL) kore_free(pgsql->error); pgsql->error = kore_strdup(msg); pgsql->state = KORE_PGSQL_STATE_ERROR; } static void pgsql_schedule(struct kore_pgsql *pgsql) { int fd; fd = PQsocket(pgsql->conn->db); if (fd < 0) fatal("PQsocket returned < 0 fd on open connection"); kore_platform_schedule_read(fd, pgsql->conn); pgsql->state = KORE_PGSQL_STATE_WAIT; pgsql->flags |= KORE_PGSQL_SCHEDULED; #if !defined(KORE_NO_HTTP) if (pgsql->req != NULL) http_request_sleep(pgsql->req); #endif if (pgsql->cb != NULL) pgsql->cb(pgsql, pgsql->arg); } static void pgsql_queue_add(struct kore_pgsql *pgsql) { struct pgsql_wait *pgw; #if !defined(KORE_NO_HTTP) if (pgsql->req != NULL) http_request_sleep(pgsql->req); #endif pgw = kore_pool_get(&pgsql_wait_pool); pgw->pgsql = pgsql; pgsql_queue_count++; TAILQ_INSERT_TAIL(&pgsql_wait_queue, pgw, list); } static void pgsql_queue_remove(struct kore_pgsql *pgsql) { struct pgsql_wait *pgw, *next; for (pgw = TAILQ_FIRST(&pgsql_wait_queue); pgw != NULL; pgw = next) { next = TAILQ_NEXT(pgw, list); if (pgw->pgsql != pgsql) continue; pgsql_queue_count--; TAILQ_REMOVE(&pgsql_wait_queue, pgw, list); kore_pool_put(&pgsql_wait_pool, pgw); return; } } static void pgsql_queue_wakeup(void) { struct pgsql_wait *pgw, *next; for (pgw = TAILQ_FIRST(&pgsql_wait_queue); pgw != NULL; pgw = next) { next = TAILQ_NEXT(pgw, list); #if !defined(KORE_NO_HTTP) if (pgw->pgsql->req != NULL) { if (pgw->pgsql->req->flags & HTTP_REQUEST_DELETE) { pgsql_queue_count--; TAILQ_REMOVE(&pgsql_wait_queue, pgw, list); kore_pool_put(&pgsql_wait_pool, pgw); continue; } http_request_wakeup(pgw->pgsql->req); } #endif if (pgw->pgsql->cb != NULL) pgw->pgsql->cb(pgw->pgsql, pgw->pgsql->arg); pgsql_queue_count--; TAILQ_REMOVE(&pgsql_wait_queue, pgw, list); kore_pool_put(&pgsql_wait_pool, pgw); return; } } static struct pgsql_conn * pgsql_conn_create(struct kore_pgsql *pgsql, struct pgsql_db *db) { struct pgsql_conn *conn; if (db == NULL || db->conn_string == NULL) fatal("pgsql_conn_create: no connection string"); db->conn_count++; conn = kore_calloc(1, sizeof(*conn)); conn->job = NULL; conn->flags = PGSQL_CONN_FREE; conn->name = kore_strdup(db->name); TAILQ_INSERT_TAIL(&pgsql_conn_free, conn, list); conn->evt.type = KORE_TYPE_PGSQL_CONN; conn->evt.handle = kore_pgsql_handle; conn->db = PQconnectdb(db->conn_string); if (conn->db == NULL || (PQstatus(conn->db) != CONNECTION_OK)) { pgsql_set_error(pgsql, PQerrorMessage(conn->db)); pgsql_conn_cleanup(conn); return (NULL); } return (conn); } static void pgsql_conn_release(struct kore_pgsql *pgsql) { int fd; PGresult *result; if (pgsql->conn == NULL) return; /* Async query cleanup */ if (pgsql->flags & KORE_PGSQL_ASYNC) { if (pgsql->flags & KORE_PGSQL_SCHEDULED) { fd = PQsocket(pgsql->conn->db); kore_platform_disable_read(fd); if (pgsql->state != KORE_PGSQL_STATE_DONE) pgsql_cancel(pgsql); } kore_pool_put(&pgsql_job_pool, pgsql->conn->job); } /* Drain just in case. */ while ((result = PQgetResult(pgsql->conn->db)) != NULL) PQclear(result); pgsql->conn->job = NULL; pgsql->conn->flags |= PGSQL_CONN_FREE; TAILQ_INSERT_TAIL(&pgsql_conn_free, pgsql->conn, list); pgsql->conn = NULL; pgsql->state = KORE_PGSQL_STATE_COMPLETE; if (pgsql->cb != NULL) pgsql->cb(pgsql, pgsql->arg); pgsql_queue_wakeup(); } static void pgsql_conn_cleanup(struct pgsql_conn *conn) { struct kore_pgsql *pgsql; struct pgsql_db *pgsqldb; if (conn->flags & PGSQL_CONN_FREE) TAILQ_REMOVE(&pgsql_conn_free, conn, list); if (conn->job) { pgsql = conn->job->pgsql; #if !defined(KORE_NO_HTTP) if (pgsql->req != NULL) http_request_wakeup(pgsql->req); #endif pgsql->conn = NULL; pgsql_set_error(pgsql, PQerrorMessage(conn->db)); kore_pool_put(&pgsql_job_pool, conn->job); conn->job = NULL; } if (conn->db != NULL) PQfinish(conn->db); LIST_FOREACH(pgsqldb, &pgsql_db_conn_strings, rlist) { if (!strcmp(pgsqldb->name, conn->name)) { pgsqldb->conn_count--; break; } } kore_free(conn->name); kore_free(conn); } static void pgsql_read_result(struct kore_pgsql *pgsql) { struct pgsql_conn *conn; PGnotify *notify; int saved_errno; conn = pgsql->conn; for (;;) { if (!PQconsumeInput(conn->db)) { pgsql->state = KORE_PGSQL_STATE_ERROR; pgsql->error = kore_strdup(PQerrorMessage(conn->db)); return; } saved_errno = errno; if (PQisBusy(conn->db)) { if (saved_errno != EAGAIN && saved_errno != EWOULDBLOCK) continue; pgsql->state = KORE_PGSQL_STATE_WAIT; conn->evt.flags &= ~KORE_EVENT_READ; return; } break; } while ((notify = PQnotifies(conn->db)) != NULL) { pgsql->state = KORE_PGSQL_STATE_NOTIFY; pgsql->notify.extra = notify->extra; pgsql->notify.channel = notify->relname; if (pgsql->cb != NULL) pgsql->cb(pgsql, pgsql->arg); PQfreemem(notify); } pgsql->result = PQgetResult(conn->db); if (pgsql->result == NULL) { pgsql->state = KORE_PGSQL_STATE_DONE; return; } switch (PQresultStatus(pgsql->result)) { #if PG_VERSION_NUM >= 140000 case PGRES_PIPELINE_SYNC: case PGRES_PIPELINE_ABORTED: #endif case PGRES_COPY_OUT: case PGRES_COPY_IN: case PGRES_NONFATAL_ERROR: case PGRES_COPY_BOTH: break; case PGRES_COMMAND_OK: pgsql->state = KORE_PGSQL_STATE_DONE; break; case PGRES_TUPLES_OK: #if PG_VERSION_NUM >= 90200 case PGRES_SINGLE_TUPLE: #endif pgsql->state = KORE_PGSQL_STATE_RESULT; break; case PGRES_EMPTY_QUERY: case PGRES_BAD_RESPONSE: case PGRES_FATAL_ERROR: pgsql_set_error(pgsql, PQresultErrorMessage(pgsql->result)); break; } } static void pgsql_cancel(struct kore_pgsql *pgsql) { PGcancel *cancel; char buf[256]; if ((cancel = PQgetCancel(pgsql->conn->db)) != NULL) { if (!PQcancel(cancel, buf, sizeof(buf))) kore_log(LOG_ERR, "failed to cancel: %s", buf); PQfreeCancel(cancel); } } kore-4.2.3/src/pool.c000066400000000000000000000111151430066302400143610ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "kore.h" #define POOL_MIN_ELEMENTS 16 #define POOL_ELEMENT_BUSY 0 #define POOL_ELEMENT_FREE 1 #if defined(KORE_USE_TASKS) static void pool_lock(struct kore_pool *); static void pool_unlock(struct kore_pool *); #endif static void pool_region_create(struct kore_pool *, size_t); static void pool_region_destroy(struct kore_pool *); void kore_pool_init(struct kore_pool *pool, const char *name, size_t len, size_t elm) { if (elm < POOL_MIN_ELEMENTS) elm = POOL_MIN_ELEMENTS; if ((pool->name = strdup(name)) == NULL) fatal("kore_pool_init: strdup %s", errno_s); len = (len + (8 - 1)) & ~(8 - 1); pool->lock = 0; pool->elms = 0; pool->inuse = 0; pool->elen = len; pool->growth = elm * 0.25f; pool->slen = pool->elen + sizeof(struct kore_pool_entry); LIST_INIT(&(pool->regions)); LIST_INIT(&(pool->freelist)); pool_region_create(pool, elm); } void kore_pool_cleanup(struct kore_pool *pool) { pool->lock = 0; pool->elms = 0; pool->inuse = 0; pool->elen = 0; pool->slen = 0; free(pool->name); pool->name = NULL; pool_region_destroy(pool); } void * kore_pool_get(struct kore_pool *pool) { u_int8_t *ptr; struct kore_pool_entry *entry; #if defined(KORE_USE_TASKS) pool_lock(pool); #endif if (LIST_EMPTY(&(pool->freelist))) pool_region_create(pool, pool->growth); entry = LIST_FIRST(&(pool->freelist)); if (entry->state != POOL_ELEMENT_FREE) fatal("%s: element %p was not free", pool->name, (void *)entry); LIST_REMOVE(entry, list); entry->state = POOL_ELEMENT_BUSY; ptr = (u_int8_t *)entry + sizeof(struct kore_pool_entry); pool->inuse++; #if defined(KORE_USE_TASKS) pool_unlock(pool); #endif return (ptr); } void kore_pool_put(struct kore_pool *pool, void *ptr) { struct kore_pool_entry *entry; #if defined(KORE_USE_TASKS) pool_lock(pool); #endif entry = (struct kore_pool_entry *) ((u_int8_t *)ptr - sizeof(struct kore_pool_entry)); if (entry->state != POOL_ELEMENT_BUSY) fatal("%s: element %p was not busy", pool->name, ptr); entry->state = POOL_ELEMENT_FREE; LIST_INSERT_HEAD(&(pool->freelist), entry, list); pool->inuse--; #if defined(KORE_USE_TASKS) pool_unlock(pool); #endif } static void pool_region_create(struct kore_pool *pool, size_t elms) { size_t i; u_int8_t *p; struct kore_pool_region *reg; struct kore_pool_entry *entry; if ((reg = calloc(1, sizeof(struct kore_pool_region))) == NULL) fatal("pool_region_create: calloc: %s", errno_s); LIST_INSERT_HEAD(&(pool->regions), reg, list); if (SIZE_MAX / elms < pool->slen) fatal("pool_region_create: overflow"); reg->length = elms * pool->slen; reg->start = mmap(NULL, reg->length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (reg->start == MAP_FAILED) fatal("mmap: %s", errno_s); p = (u_int8_t *)reg->start; for (i = 0; i < elms; i++) { entry = (struct kore_pool_entry *)p; entry->region = reg; entry->state = POOL_ELEMENT_FREE; LIST_INSERT_HEAD(&(pool->freelist), entry, list); p = p + pool->slen; } pool->elms += elms; } static void pool_region_destroy(struct kore_pool *pool) { struct kore_pool_region *reg; /* Take care iterating when modifying list contents */ while (!LIST_EMPTY(&pool->regions)) { reg = LIST_FIRST(&pool->regions); LIST_REMOVE(reg, list); (void)munmap(reg->start, reg->length); free(reg); } /* Freelist references into the regions memory allocations */ LIST_INIT(&pool->freelist); pool->elms = 0; } #if defined(KORE_USE_TASKS) static void pool_lock(struct kore_pool *pool) { for (;;) { if (__sync_bool_compare_and_swap(&pool->lock, 0, 1)) break; } } static void pool_unlock(struct kore_pool *pool) { if (!__sync_bool_compare_and_swap(&pool->lock, 1, 0)) fatal("pool_unlock: failed to release %s", pool->name); } #endif kore-4.2.3/src/python.c000066400000000000000000004507741430066302400147530ustar00rootroot00000000000000/* * Copyright (c) 2016 Stanislav Yudin * Copyright (c) 2017-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kore.h" #include "http.h" #if defined(KORE_USE_PGSQL) #include "pgsql.h" #endif #if defined(KORE_USE_CURL) #include "curl.h" #endif #if defined(KORE_USE_ACME) #include "acme.h" #endif #include "python_api.h" #include "python_methods.h" #if defined(KORE_USE_CURL) #include "python_curlopt.h" #endif #include #if PY_VERSION_HEX < 0x030A0000 typedef enum { PYGEN_RETURN = 0, PYGEN_ERROR = -1, PYGEN_NEXT = 1, } PySendResult; #endif struct reqcall { PyObject *f; TAILQ_ENTRY(reqcall) list; }; TAILQ_HEAD(reqcall_list, reqcall); PyMODINIT_FUNC python_module_init(void); static PyObject *python_import(const char *); static PyObject *pyconnection_alloc(struct connection *); static PyObject *python_callable(PyObject *, const char *); static void python_split_arguments(char *, char **, size_t); static void python_kore_recvobj(struct kore_msg *, const void *); static PyObject *python_cmsg_to_list(struct msghdr *); static const char *python_string_from_dict(PyObject *, const char *); static int python_bool_from_dict(PyObject *, const char *, int *); static int python_long_from_dict(PyObject *, const char *, long *); static int pyhttp_response_sent(struct netbuf *); static PyObject *pyhttp_file_alloc(struct http_file *); static PyObject *pyhttp_request_alloc(const struct http_request *); static struct python_coro *python_coro_create(PyObject *, struct http_request *); static struct kore_domain *python_route_domain_resolve(struct pyroute *); static int python_route_install(struct pyroute *); static int python_route_params(PyObject *, struct kore_route *, const char *, int, int); static int python_route_methods(PyObject *, PyObject *, struct kore_route *); static int python_route_auth(PyObject *, struct kore_route *); static int python_route_hooks(PyObject *, struct kore_route *); static int python_route_hook_set(PyObject *, const char *, struct kore_runtime_call **); static int python_coro_run(struct python_coro *); static void python_coro_wakeup(struct python_coro *); static void python_coro_suspend(struct python_coro *); static void python_coro_trace(const char *, struct python_coro *); static void pysocket_evt_handle(void *, int); static void pysocket_op_timeout(void *, u_int64_t); static PyObject *pysocket_op_create(struct pysocket *, int, const void *, size_t); static struct pysocket *pysocket_alloc(void); static PyObject *pysocket_async_recv(struct pysocket_op *); static PyObject *pysocket_async_send(struct pysocket_op *); static PyObject *pysocket_async_accept(struct pysocket_op *); static PyObject *pysocket_async_connect(struct pysocket_op *); static void pylock_do_release(struct pylock *); static void pytimer_run(void *, u_int64_t); static void pyproc_timeout(void *, u_int64_t); static void pysuspend_wakeup(void *, u_int64_t); static void pygather_reap_coro(struct pygather_op *, struct python_coro *); static int pyhttp_preprocess(struct http_request *); static int pyhttp_iterobj_chunk_sent(struct netbuf *); static int pyhttp_iterobj_next(struct pyhttp_iterobj *); static void pyhttp_iterobj_disconnect(struct connection *); static int pyconnection_x509_cb(void *, int, int, const char *, const void *, size_t, int); #if defined(KORE_USE_PGSQL) static int pykore_pgsql_result(struct pykore_pgsql *); static void pykore_pgsql_callback(struct kore_pgsql *, void *); static int pykore_pgsql_params(struct pykore_pgsql *, PyObject *); static int pykore_pgsql_params(struct pykore_pgsql *, PyObject *); #endif #if defined(KORE_USE_CURL) static void python_curl_http_callback(struct kore_curl *, void *); static void python_curl_handle_callback(struct kore_curl *, void *); static PyObject *pyhttp_client_request(struct pyhttp_client *, int, PyObject *); static PyObject *python_curlopt_set(struct pycurl_data *, long, PyObject *); static int python_curlopt_from_dict(struct pycurl_data *, PyObject *); #endif static void python_append_path(const char *); static void python_push_integer(PyObject *, const char *, long); static void python_push_type(const char *, PyObject *, PyTypeObject *); static int python_validator_check(PyObject *); static int python_runtime_http_request(void *, struct http_request *); static void python_runtime_http_request_free(void *, struct http_request *); static void python_runtime_http_body_chunk(void *, struct http_request *, const void *, size_t); static int python_runtime_validator(void *, struct http_request *, const void *); static void python_runtime_wsmessage(void *, struct connection *, u_int8_t, const void *, size_t); static void python_runtime_execute(void *); static int python_runtime_onload(void *, int); static void python_runtime_signal(void *, int); static void python_runtime_configure(void *, int, char **); static void python_runtime_connect(void *, struct connection *); static void python_module_load(struct kore_module *); static void python_module_free(struct kore_module *); static void python_module_reload(struct kore_module *); static void *python_module_getsym(struct kore_module *, const char *); static void *python_malloc(void *, size_t); static void *python_calloc(void *, size_t, size_t); static void *python_realloc(void *, void *, size_t); static void python_free(void *, void *); struct kore_module_functions kore_python_module = { .free = python_module_free, .load = python_module_load, .getsym = python_module_getsym, .reload = python_module_reload }; struct kore_runtime kore_python_runtime = { KORE_RUNTIME_PYTHON, .http_request = python_runtime_http_request, .http_body_chunk = python_runtime_http_body_chunk, .http_request_free = python_runtime_http_request_free, .validator = python_runtime_validator, .wsconnect = python_runtime_connect, .wsmessage = python_runtime_wsmessage, .wsdisconnect = python_runtime_connect, .onload = python_runtime_onload, .signal = python_runtime_signal, .connect = python_runtime_connect, .execute = python_runtime_execute, .configure = python_runtime_configure, }; static struct { const char *symbol; int value; } python_integers[] = { { "LOG_ERR", LOG_ERR }, { "LOG_INFO", LOG_INFO }, { "LOG_NOTICE", LOG_NOTICE }, { "RESULT_OK", KORE_RESULT_OK }, { "RESULT_RETRY", KORE_RESULT_RETRY }, { "RESULT_ERROR", KORE_RESULT_ERROR }, { "MODULE_LOAD", KORE_MODULE_LOAD }, { "MODULE_UNLOAD", KORE_MODULE_UNLOAD }, { "TIMER_ONESHOT", KORE_TIMER_ONESHOT }, { "CONN_PROTO_HTTP", CONN_PROTO_HTTP }, { "CONN_PROTO_UNKNOWN", CONN_PROTO_UNKNOWN }, { "CONN_PROTO_WEBSOCKET", CONN_PROTO_WEBSOCKET }, { "CONN_STATE_ESTABLISHED", CONN_STATE_ESTABLISHED }, { "HTTP_METHOD_GET", HTTP_METHOD_GET }, { "HTTP_METHOD_PUT", HTTP_METHOD_PUT }, { "HTTP_METHOD_HEAD", HTTP_METHOD_HEAD }, { "HTTP_METHOD_POST", HTTP_METHOD_POST }, { "HTTP_METHOD_DELETE", HTTP_METHOD_DELETE }, { "HTTP_METHOD_OPTIONS", HTTP_METHOD_OPTIONS }, { "HTTP_METHOD_PATCH", HTTP_METHOD_PATCH }, { "WEBSOCKET_OP_TEXT", WEBSOCKET_OP_TEXT }, { "WEBSOCKET_OP_BINARY", WEBSOCKET_OP_BINARY }, { "WEBSOCKET_BROADCAST_LOCAL", WEBSOCKET_BROADCAST_LOCAL }, { "WEBSOCKET_BROADCAST_GLOBAL", WEBSOCKET_BROADCAST_GLOBAL }, { NULL, -1 } }; static PyMemAllocatorEx allocator = { .ctx = NULL, .malloc = python_malloc, .calloc = python_calloc, .realloc = python_realloc, .free = python_free }; #if defined(__linux__) #include "seccomp.h" static struct sock_filter filter_python[] = { /* Required for kore.proc */ #if defined(SYS_dup2) KORE_SYSCALL_ALLOW(dup2), #endif #if defined(SYS_dup3) KORE_SYSCALL_ALLOW(dup3), #endif #if defined(SYS_pipe) KORE_SYSCALL_ALLOW(pipe), #endif #if defined(SYS_pipe2) KORE_SYSCALL_ALLOW(pipe2), #endif KORE_SYSCALL_ALLOW(wait4), KORE_SYSCALL_ALLOW(execve), /* Socket related. */ KORE_SYSCALL_ALLOW(bind), KORE_SYSCALL_ALLOW(listen), KORE_SYSCALL_ALLOW(sendto), KORE_SYSCALL_ALLOW(recvfrom), KORE_SYSCALL_ALLOW(getsockname), KORE_SYSCALL_ALLOW(getpeername), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6), KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX), }; #define PYSECCOMP_ACTION_ALLOW 1 #define PYSECCOMP_ACTION_DENY 2 #define PYSECCOMP_SYSCALL_FILTER 1 #define PYSECCOMP_SYSCALL_ARG 2 #define PYSECCOMP_SYSCALL_MASK 3 #define PYSECCOMP_SYSCALL_FLAG 4 static int pyseccomp_filter_install(struct pyseccomp *, const char *, int, int, int, int); static PyObject *pyseccomp_common_action(struct pyseccomp *, PyObject *, PyObject *, int, int); static struct pyseccomp *py_seccomp = NULL; #endif static TAILQ_HEAD(, pyproc) procs; static TAILQ_HEAD(, pyroute) routes; static struct reqcall_list prereq; static struct kore_pool coro_pool; static struct kore_pool iterobj_pool; static struct kore_pool queue_wait_pool; static struct kore_pool gather_coro_pool; static struct kore_pool queue_object_pool; static struct kore_pool gather_result_pool; static u_int64_t coro_id; static int coro_count; static int coro_tracing; static struct coro_list coro_runnable; static struct coro_list coro_suspended; extern const char *__progname; static PyObject *pickle = NULL; static PyObject *kore_app = NULL; static PyObject *pickle_dumps = NULL; static PyObject *pickle_loads = NULL; static PyObject *python_tracer = NULL; /* XXX */ static struct python_coro *coro_running = NULL; #if !defined(KORE_SINGLE_BINARY) const char *kore_pymodule = NULL; #endif void kore_python_init(void) { struct kore_runtime_call *rcall; coro_id = 0; coro_count = 0; coro_tracing = 0; TAILQ_INIT(&prereq); TAILQ_INIT(&procs); TAILQ_INIT(&routes); TAILQ_INIT(&coro_runnable); TAILQ_INIT(&coro_suspended); kore_pool_init(&coro_pool, "coropool", sizeof(struct python_coro), 100); kore_pool_init(&iterobj_pool, "iterobj_pool", sizeof(struct pyhttp_iterobj), 100); kore_pool_init(&queue_wait_pool, "queue_wait_pool", sizeof(struct pyqueue_waiting), 100); kore_pool_init(&gather_coro_pool, "gather_coro_pool", sizeof(struct pygather_coro), 100); kore_pool_init(&queue_object_pool, "queue_object_pool", sizeof(struct pyqueue_object), 100); kore_pool_init(&gather_result_pool, "gather_result_pool", sizeof(struct pygather_result), 100); PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &allocator); PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &allocator); PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &allocator); #if defined(KORE_DEBUG) PyMem_SetupDebugHooks(); #endif kore_msg_register(KORE_PYTHON_SEND_OBJ, python_kore_recvobj); if (PyImport_AppendInittab("kore", &python_module_init) == -1) fatal("kore_python_init: failed to add new module"); rcall = kore_runtime_getcall("kore_python_preinit"); if (rcall != NULL) { kore_runtime_execute(rcall); kore_free(rcall); } Py_InitializeEx(0); if ((pickle = PyImport_ImportModule("pickle")) == NULL) fatal("failed to import pickle module"); if ((pickle_dumps = PyObject_GetAttrString(pickle, "dumps")) == NULL) fatal("pickle module has no dumps method"); if ((pickle_loads = PyObject_GetAttrString(pickle, "loads")) == NULL) fatal("pickle module has no loads method"); #if defined(__linux__) kore_seccomp_filter("python", filter_python, KORE_FILTER_LEN(filter_python)); #endif #if !defined(KORE_SINGLE_BINARY) if (kore_pymodule) { if (!kore_configure_setting("deployment", "dev")) fatal("failed to set initial deployment"); } #endif } void kore_python_cleanup(void) { if (Py_IsInitialized()) { PyErr_Clear(); Py_Finalize(); } } void kore_python_path(const char *path) { python_append_path(path); } void kore_python_coro_run(void) { struct pygather_op *op; struct python_coro *coro; while ((coro = TAILQ_FIRST(&coro_runnable)) != NULL) { if (coro->state != CORO_STATE_RUNNABLE) fatal("non-runnable coro on coro_runnable"); if (python_coro_run(coro) == KORE_RESULT_OK) { if (coro->gatherop != NULL) { op = coro->gatherop; if (op->coro->request != NULL) http_request_wakeup(op->coro->request); else python_coro_wakeup(op->coro); pygather_reap_coro(op, coro); } else { kore_python_coro_delete(coro); } } } /* * Let Kore do HTTP processing so awoken coroutines run asap without * having to wait for a tick from the event loop. * * Maybe it is more beneficial that we track if something related * to HTTP requests was awoken and only run if true? */ http_process(); #if defined(KORE_USE_CURL) /* * If a coroutine fired off a curl instance, immediately * let it make progress. */ kore_curl_do_timeout(); #endif } void kore_python_coro_delete(void *obj) { struct python_coro *coro; coro = obj; coro_count--; python_coro_trace(coro->killed ? "killed" : "deleted", coro); coro_running = coro; if (coro->lockop != NULL) { coro->lockop->active = 0; TAILQ_REMOVE(&coro->lockop->lock->ops, coro->lockop, list); Py_DECREF((PyObject *)coro->lockop); coro->lockop = NULL; } Py_DECREF(coro->obj); coro_running = NULL; if (coro->state == CORO_STATE_RUNNABLE) TAILQ_REMOVE(&coro_runnable, coro, list); else TAILQ_REMOVE(&coro_suspended, coro, list); kore_free(coro->name); Py_XDECREF(coro->result); kore_pool_put(&coro_pool, coro); } int kore_python_coro_pending(void) { return (!TAILQ_EMPTY(&coro_runnable)); } void kore_python_routes_resolve(void) { struct pyroute *route; while ((route = TAILQ_FIRST(&routes)) != NULL) { TAILQ_REMOVE(&routes, route, list); if (!python_route_install(route)) fatalx("failed to install route for %s", route->path); Py_DECREF((PyObject *)route); } } void kore_python_log_error(const char *function) { const char *sval; PyObject *ret, *repr, *type, *value, *traceback; if (!PyErr_Occurred() || PyErr_ExceptionMatches(PyExc_StopIteration)) return; PyErr_Fetch(&type, &value, &traceback); if (type == NULL || value == NULL) { kore_log(LOG_ERR, "unknown python exception in '%s'", function); return; } if (value == NULL || !PyObject_IsInstance(value, type)) PyErr_NormalizeException(&type, &value, &traceback); /* * If we're in an active coroutine and it was tied to a gather * operation we have to make sure we can use the Exception that * was thrown as the result value so we can propagate it via the * return list of kore.gather(). */ if (coro_running != NULL && coro_running->gatherop != NULL) { PyErr_SetObject(PyExc_StopIteration, value); } else if (python_tracer != NULL) { /* * Call the user-supplied tracer callback. */ ret = PyObject_CallFunctionObjArgs(python_tracer, type, value, traceback, NULL); Py_XDECREF(ret); } else { if ((repr = PyObject_Repr(value)) == NULL) sval = "unknown"; else sval = PyUnicode_AsUTF8(repr); kore_log(LOG_ERR, "uncaught exception %s in '%s'", sval, function); Py_XDECREF(repr); } Py_DECREF(type); Py_DECREF(value); Py_XDECREF(traceback); } void kore_python_proc_reap(void) { struct pyproc *proc; struct python_coro *coro; pid_t child; int status; for (;;) { if ((child = waitpid(-1, &status, WNOHANG)) == -1) { if (errno == ECHILD) return; if (errno == EINTR) continue; kore_log(LOG_NOTICE, "waitpid: %s", errno_s); return; } if (child == 0) return; proc = NULL; TAILQ_FOREACH(proc, &procs, list) { if (proc->pid == child) break; } if (proc == NULL) continue; proc->pid = -1; proc->reaped = 1; proc->status = status; if (proc->timer != NULL) { kore_timer_remove(proc->timer); proc->timer = NULL; } /* * If someone is waiting on proc.reap() then wakeup that * coroutine, otherwise wakeup the coroutine that created * the process. */ if (proc->op != NULL) coro = proc->op->coro; else coro = proc->coro; if (coro->request != NULL) http_request_wakeup(coro->request); else python_coro_wakeup(coro); } } #if defined(__linux__) void kore_python_seccomp_hook(const char *method) { struct kore_runtime *rt; PyObject *func, *result; if ((func = kore_module_getsym(method, &rt)) == NULL) return; if (rt->type != KORE_RUNTIME_PYTHON) return; py_seccomp = PyObject_New(struct pyseccomp, &pyseccomp_type); if (py_seccomp == NULL) fatal("failed to create seccomp object"); py_seccomp->elm = 0; py_seccomp->filters = NULL; result = PyObject_CallFunctionObjArgs(func, (PyObject *)py_seccomp, NULL); kore_python_log_error(method); kore_seccomp_filter("koreapp", py_seccomp->filters, py_seccomp->elm); Py_XDECREF(result); } void kore_python_seccomp_cleanup(void) { Py_XDECREF(py_seccomp); py_seccomp = NULL; } static void pyseccomp_dealloc(struct pyseccomp *seccomp) { kore_free(seccomp->filters); seccomp->elm = 0; seccomp->filters = NULL; } static PyObject * pyseccomp_bpf_stmt(struct pyseccomp *seccomp, PyObject *args) { u_int32_t k; u_int16_t code; size_t len, off; struct sock_filter filter[1]; if (!PyArg_ParseTuple(args, "HI", &code, &k)) return (NULL); filter[0].k = k; filter[0].jt = 0; filter[0].jf = 0; filter[0].code = code; len = sizeof(struct sock_filter); off = seccomp->elm * sizeof(struct sock_filter); seccomp->filters = kore_realloc(seccomp->filters, off + len); memcpy(seccomp->filters + off, filter, len); seccomp->elm += 1; Py_RETURN_NONE; } static PyObject * pyseccomp_allow(struct pyseccomp *seccomp, PyObject *args) { const char *syscall; if (!PyArg_ParseTuple(args, "s", &syscall)) return (NULL); if (!pyseccomp_filter_install(seccomp, syscall, PYSECCOMP_SYSCALL_FILTER, 0, 0, SECCOMP_RET_ALLOW)) return (NULL); Py_RETURN_NONE; } static PyObject * pyseccomp_allow_arg(struct pyseccomp *seccomp, PyObject *args) { return (pyseccomp_common_action(seccomp, args, NULL, PYSECCOMP_SYSCALL_ARG, PYSECCOMP_ACTION_ALLOW)); } static PyObject * pyseccomp_allow_flag(struct pyseccomp *seccomp, PyObject *args) { return (pyseccomp_common_action(seccomp, args, NULL, PYSECCOMP_SYSCALL_FLAG, PYSECCOMP_ACTION_ALLOW)); } static PyObject * pyseccomp_allow_mask(struct pyseccomp *seccomp, PyObject *args) { return (pyseccomp_common_action(seccomp, args, NULL, PYSECCOMP_SYSCALL_MASK, PYSECCOMP_ACTION_ALLOW)); } static PyObject * pyseccomp_deny(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs) { long err; const char *syscall; if (!PyArg_ParseTuple(args, "s", &syscall)) return (NULL); err = EACCES; if (kwargs != NULL) python_long_from_dict(kwargs, "errno", &err); if (!pyseccomp_filter_install(seccomp, syscall, PYSECCOMP_SYSCALL_FILTER, 0, 0, SECCOMP_RET_ERRNO | (int)err)) return (NULL); Py_RETURN_NONE; } static PyObject * pyseccomp_deny_arg(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs) { return (pyseccomp_common_action(seccomp, args, kwargs, PYSECCOMP_SYSCALL_ARG, PYSECCOMP_ACTION_DENY)); } static PyObject * pyseccomp_deny_flag(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs) { return (pyseccomp_common_action(seccomp, args, kwargs, PYSECCOMP_SYSCALL_FLAG, PYSECCOMP_ACTION_DENY)); } static PyObject * pyseccomp_deny_mask(struct pyseccomp *seccomp, PyObject *args, PyObject *kwargs) { return (pyseccomp_common_action(seccomp, args, kwargs, PYSECCOMP_SYSCALL_MASK, PYSECCOMP_ACTION_DENY)); } static PyObject * pyseccomp_common_action(struct pyseccomp *sc, PyObject *args, PyObject *kwargs, int which, int action) { long err; const char *syscall; int arg, val; if (!PyArg_ParseTuple(args, "sii", &syscall, &arg, &val)) return (NULL); switch (action) { case PYSECCOMP_ACTION_ALLOW: action = SECCOMP_RET_ALLOW; break; case PYSECCOMP_ACTION_DENY: err = EACCES; if (kwargs != NULL) python_long_from_dict(kwargs, "errno", &err); action = SECCOMP_RET_ERRNO | (int)err; break; default: fatal("%s: bad action %d", __func__, action); } if (!pyseccomp_filter_install(sc, syscall, which, arg, val, action)) return (NULL); Py_RETURN_NONE; } static int pyseccomp_filter_install(struct pyseccomp *seccomp, const char *syscall, int which, int arg, int val, int action) { struct sock_filter *filter; size_t elm, len, off; switch (which) { case PYSECCOMP_SYSCALL_FILTER: filter = kore_seccomp_syscall_filter(syscall, action); break; case PYSECCOMP_SYSCALL_ARG: filter = kore_seccomp_syscall_arg(syscall, action, arg, val); break; case PYSECCOMP_SYSCALL_MASK: filter = kore_seccomp_syscall_mask(syscall, action, arg, val); break; case PYSECCOMP_SYSCALL_FLAG: filter = kore_seccomp_syscall_flag(syscall, action, arg, val); break; default: fatal("%s: invalid syscall instruction %d", __func__, which); } if (filter == NULL) { PyErr_Format(PyExc_RuntimeError, "system call '%s' does not exist", syscall); return (KORE_RESULT_ERROR); } elm = 0; /* * Find the number of elements in the BPF program, by looking for * the KORE_BPF_GUARD element. */ for (;;) { if (filter[elm].code == USHRT_MAX && filter[elm].jt == UCHAR_MAX && filter[elm].jf == UCHAR_MAX && filter[elm].k == UINT_MAX) break; elm++; } len = elm * sizeof(struct sock_filter); off = seccomp->elm * sizeof(struct sock_filter); seccomp->filters = kore_realloc(seccomp->filters, off + len); memcpy(seccomp->filters + off, filter, len); seccomp->elm += elm; kore_free(filter); return (KORE_RESULT_OK); } #endif static int python_long_from_dict(PyObject *dict, const char *key, long *result) { PyObject *obj; if ((obj = PyDict_GetItemString(dict, key)) == NULL) return (KORE_RESULT_ERROR); if (!PyLong_CheckExact(obj)) return (KORE_RESULT_ERROR); PyErr_Clear(); *result = PyLong_AsLong(obj); if (*result == -1 && PyErr_Occurred()) { PyErr_Clear(); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int python_bool_from_dict(PyObject *dict, const char *key, int *result) { PyObject *obj; if ((obj = PyDict_GetItemString(dict, key)) == NULL) return (KORE_RESULT_ERROR); if (!PyBool_Check(obj)) return (KORE_RESULT_ERROR); *result = (obj == Py_True); return (KORE_RESULT_OK); } static const char * python_string_from_dict(PyObject *dict, const char *key) { PyObject *obj; if ((obj = PyDict_GetItemString(dict, key)) == NULL) return (NULL); if (!PyUnicode_Check(obj)) return (NULL); return (PyUnicode_AsUTF8AndSize(obj, NULL)); } static PyObject * python_cmsg_to_list(struct msghdr *msg) { struct cmsghdr *c; size_t len; Py_ssize_t idx; PyObject *list, *tuple; if ((list = PyList_New(0)) == NULL) return (NULL); idx = 0; for (c = CMSG_FIRSTHDR(msg); c != NULL; c = CMSG_NXTHDR(msg, c)) { len = c->cmsg_len - sizeof(*c); tuple = Py_BuildValue("(Iiiy#)", len, c->cmsg_level, c->cmsg_type, CMSG_DATA(c), len); if (tuple == NULL) { Py_DECREF(list); return (NULL); } /* Steals a reference to tuple. */ if (PyList_Insert(list, idx++, tuple) == -1) { Py_DECREF(tuple); Py_DECREF(list); return (NULL); } } return (list); } static void * python_malloc(void *ctx, size_t len) { return (kore_malloc(len)); } static void * python_calloc(void *ctx, size_t memb, size_t len) { return (kore_calloc(memb, len)); } static void * python_realloc(void *ctx, void *ptr, size_t len) { return (kore_realloc(ptr, len)); } static void python_free(void *ctx, void *ptr) { kore_free(ptr); } static void python_module_free(struct kore_module *module) { kore_free(module->path); Py_DECREF(module->handle); kore_free(module); } static void python_split_arguments(char *args, char **argv, size_t elm) { size_t idx; char *p, *line, *end; if (elm <= 1) fatal("not enough elements (%zu)", elm); idx = 0; line = args; for (p = line; *p != '\0'; p++) { if (idx >= elm - 1) break; if (*p == ' ') { *p = '\0'; if (*line != '\0') argv[idx++] = line; line = p + 1; continue; } if (*p != '"') continue; line = p + 1; if ((end = strchr(line, '"')) == NULL) break; *end = '\0'; argv[idx++] = line; line = end + 1; while (isspace(*(unsigned char *)line)) line++; p = line; } if (idx < elm - 1 && *line != '\0') argv[idx++] = line; argv[idx] = NULL; } static void python_module_reload(struct kore_module *module) { PyObject *handle; PyErr_Clear(); if ((handle = PyImport_ReloadModule(module->handle)) == NULL) { kore_python_log_error("python_module_reload"); return; } Py_DECREF(module->handle); module->handle = handle; } static void python_module_load(struct kore_module *module) { module->handle = python_import(module->path); if (module->handle == NULL) fatal("%s: failed to import module", module->path); } static void * python_module_getsym(struct kore_module *module, const char *symbol) { return (python_callable(module->handle, symbol)); } static struct python_coro * python_coro_create(PyObject *obj, struct http_request *req) { struct python_coro *coro; if (!PyCoro_CheckExact(obj)) fatal("%s: object is not a coroutine", __func__); coro = kore_pool_get(&coro_pool); coro_count++; coro->name = NULL; coro->result = NULL; coro->sockop = NULL; coro->lockop = NULL; coro->gatherop = NULL; coro->exception = NULL; coro->exception_msg = NULL; coro->obj = obj; coro->killed = 0; coro->request = req; coro->id = coro_id++; coro->state = CORO_STATE_RUNNABLE; TAILQ_INSERT_TAIL(&coro_runnable, coro, list); if (coro->request != NULL) http_request_sleep(coro->request); python_coro_trace("created", coro); return (coro); } static int python_coro_run(struct python_coro *coro) { PySendResult res; PyObject *item; PyObject *type, *traceback; if (coro->state != CORO_STATE_RUNNABLE) fatal("non-runnable coro attempted to run"); coro_running = coro; for (;;) { python_coro_trace("running", coro); PyErr_Clear(); #if PY_VERSION_HEX < 0x030A0000 res = PYGEN_RETURN; item = _PyGen_Send((PyGenObject *)coro->obj, NULL); #else /* * Python 3.10.x its PyIter_Send() will return a PYGEN_ERROR * if the coro returned (instead of yielding) and the result * ends up being Py_None. This means the returned item is * NULL but no StopIteration exception has occurred. */ res = PyIter_Send(coro->obj, NULL, &item); #endif if (item == NULL || res == PYGEN_ERROR) { Py_XDECREF(item); if (coro->gatherop == NULL && PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration)) { PyErr_Fetch(&type, &coro->result, &traceback); Py_DECREF(type); Py_XDECREF(traceback); } else if (PyErr_Occurred()) { kore_python_log_error("coroutine"); if (coro->request != NULL) { http_response(coro->request, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); } } coro_running = NULL; return (KORE_RESULT_OK); } #if PY_VERSION_HEX >= 0x030A0000 if (res == PYGEN_RETURN) { coro->result = item; coro_running = NULL; return (KORE_RESULT_OK); } #endif if (item == Py_None) { Py_DECREF(item); break; } Py_DECREF(item); } python_coro_suspend(coro); coro_running = NULL; if (coro->request != NULL) http_request_sleep(coro->request); return (KORE_RESULT_RETRY); } static void python_coro_wakeup(struct python_coro *coro) { if (coro->state != CORO_STATE_SUSPENDED) return; coro->state = CORO_STATE_RUNNABLE; TAILQ_REMOVE(&coro_suspended, coro, list); TAILQ_INSERT_TAIL(&coro_runnable, coro, list); python_coro_trace("wokeup", coro); } static void python_coro_suspend(struct python_coro *coro) { if (coro->state != CORO_STATE_RUNNABLE) return; coro->state = CORO_STATE_SUSPENDED; TAILQ_REMOVE(&coro_runnable, coro, list); TAILQ_INSERT_TAIL(&coro_suspended, coro, list); python_coro_trace("suspended", coro); } static void python_coro_trace(const char *label, struct python_coro *coro) { int line; PyGenObject *gen; PyCodeObject *code; const char *func, *fname, *file; if (coro_tracing == 0) return; gen = (PyGenObject *)coro->obj; if (gen->gi_frame != NULL && gen->gi_frame->f_code != NULL) { code = gen->gi_frame->f_code; func = PyUnicode_AsUTF8AndSize(code->co_name, NULL); file = PyUnicode_AsUTF8AndSize(code->co_filename, NULL); if ((fname = strrchr(file, '/')) == NULL) fname = file; else fname++; } else { func = "unknown"; fname = "unknown"; } if (gen->gi_frame != NULL) line = PyFrame_GetLineNumber(gen->gi_frame); else line = -1; if (coro->name) { kore_log(LOG_NOTICE, "coro '%s' %s <%s> @ [%s:%d]", coro->name, label, func, fname, line); } else { kore_log(LOG_NOTICE, "coro %" PRIu64 " %s <%s> @ [%s:%d]", coro->id, label, func, fname, line); } } static void pyconnection_dealloc(struct pyconnection *pyc) { PyObject_Del((PyObject *)pyc); } static void pyhttp_dealloc(struct pyhttp_request *pyreq) { Py_XDECREF(pyreq->dict); Py_XDECREF(pyreq->data); PyObject_Del((PyObject *)pyreq); } static void pyhttp_file_dealloc(struct pyhttp_file *pyfile) { PyObject_Del((PyObject *)pyfile); } static int python_runtime_http_request(void *addr, struct http_request *req) { int ret, idx, cnt; PyObject *pyret, *args, *callable; PyObject *cargs[HTTP_CAPTURE_GROUPS + 1]; if (req->py_coro != NULL) { python_coro_wakeup(req->py_coro); if (python_coro_run(req->py_coro) == KORE_RESULT_OK) { kore_python_coro_delete(req->py_coro); req->py_coro = NULL; if (req->fsm_state != PYHTTP_STATE_PREPROCESS) return (KORE_RESULT_OK); } return (KORE_RESULT_RETRY); } switch (req->fsm_state) { case PYHTTP_STATE_INIT: req->py_rqnext = TAILQ_FIRST(&prereq); req->fsm_state = PYHTTP_STATE_PREPROCESS; if (req->py_req == NULL) { if ((req->py_req = pyhttp_request_alloc(req)) == NULL) fatal("%s: pyreq alloc failed", __func__); } /* fallthrough */ case PYHTTP_STATE_PREPROCESS: ret = pyhttp_preprocess(req); switch (ret) { case KORE_RESULT_OK: req->fsm_state = PYHTTP_STATE_RUN; break; case KORE_RESULT_RETRY: return (KORE_RESULT_RETRY); case KORE_RESULT_ERROR: return (KORE_RESULT_OK); default: fatal("invalid state pyhttp state %d", req->fsm_state); } /* fallthrough */ case PYHTTP_STATE_RUN: break; } cnt = 0; callable = (PyObject *)addr; /* starts at 1 to skip the full path. */ if (req->rt->type == HANDLER_TYPE_DYNAMIC) { for (idx = 1; idx < HTTP_CAPTURE_GROUPS - 1; idx++) { if (req->cgroups[idx].rm_so == -1 || req->cgroups[idx].rm_eo == -1) break; cargs[cnt] = PyUnicode_FromStringAndSize(req->path + req->cgroups[idx].rm_so, req->cgroups[idx].rm_eo - req->cgroups[idx].rm_so); if (cargs[cnt] == NULL) { while (cnt >= 0) Py_XDECREF(cargs[cnt--]); kore_python_log_error("http request"); http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return (KORE_RESULT_OK); } cnt++; } } cargs[cnt] = NULL; if ((args = PyTuple_New(cnt + 1)) == NULL) fatal("%s: PyTuple_New failed", __func__); Py_INCREF(req->py_req); if (PyTuple_SetItem(args, 0, req->py_req) != 0) fatal("python_runtime_http_request: PyTuple_SetItem failed"); for (idx = 0; cargs[idx] != NULL; idx++) { if (PyTuple_SetItem(args, 1 + idx, cargs[idx]) != 0) fatal("%s: PyTuple_SetItem failed (%d)", __func__, idx); } PyErr_Clear(); pyret = PyObject_Call(callable, args, NULL); Py_DECREF(args); if (pyret == NULL) { kore_python_log_error("python_runtime_http_request"); http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return (KORE_RESULT_OK); } if (PyCoro_CheckExact(pyret)) { req->py_coro = python_coro_create(pyret, req); if (python_coro_run(req->py_coro) == KORE_RESULT_OK) { http_request_wakeup(req); kore_python_coro_delete(req->py_coro); req->py_coro = NULL; return (KORE_RESULT_OK); } return (KORE_RESULT_RETRY); } if (pyret != Py_None) fatal("python_runtime_http_request: unexpected return type"); Py_DECREF(pyret); return (KORE_RESULT_OK); } static void python_runtime_http_request_free(void *addr, struct http_request *req) { PyObject *ret; if (req->py_req == NULL) { if ((req->py_req = pyhttp_request_alloc(req)) == NULL) fatal("%s: pyreq alloc failed", __func__); } PyErr_Clear(); ret = PyObject_CallFunctionObjArgs(addr, req->py_req, NULL); if (ret == NULL) kore_python_log_error("python_runtime_http_request_free"); Py_XDECREF(ret); } static void python_runtime_http_body_chunk(void *addr, struct http_request *req, const void *data, size_t len) { PyObject *args, *ret; if (req->py_req == NULL) { if ((req->py_req = pyhttp_request_alloc(req)) == NULL) fatal("%s: pyreq alloc failed", __func__); } if ((args = Py_BuildValue("(Oy#)", req->py_req, data, len)) == NULL) { kore_python_log_error("python_runtime_http_body_chunk"); return; } PyErr_Clear(); ret = PyObject_Call(addr, args, NULL); if (ret == NULL) kore_python_log_error("python_runtime_http_body_chunk"); Py_XDECREF(ret); Py_DECREF(args); } static int python_runtime_validator(void *addr, struct http_request *req, const void *data) { int ret; struct python_coro *coro; PyObject *pyret, *args, *callable, *arg; if (req->py_req == NULL) { if ((req->py_req = pyhttp_request_alloc(req)) == NULL) fatal("%s: pyreq alloc failed", __func__); } if (req->py_validator != NULL) { coro = req->py_validator; python_coro_wakeup(coro); if (python_coro_run(coro) == KORE_RESULT_OK) { ret = python_validator_check(coro->result); kore_python_coro_delete(coro); req->py_validator = NULL; return (ret); } return (KORE_RESULT_RETRY); } callable = (PyObject *)addr; if (req->flags & HTTP_VALIDATOR_IS_REQUEST) { if ((args = PyTuple_New(1)) == NULL) fatal("%s: PyTuple_New failed", __func__); Py_INCREF(req->py_req); if (PyTuple_SetItem(args, 0, req->py_req) != 0) fatal("%s: PyTuple_SetItem failed", __func__); } else { if ((arg = PyUnicode_FromString(data)) == NULL) fatal("python_runtime_validator: PyUnicode failed"); if ((args = PyTuple_New(2)) == NULL) fatal("%s: PyTuple_New failed", __func__); Py_INCREF(req->py_req); if (PyTuple_SetItem(args, 0, req->py_req) != 0 || PyTuple_SetItem(args, 1, arg) != 0) fatal("%s: PyTuple_SetItem failed", __func__); } PyErr_Clear(); pyret = PyObject_Call(callable, args, NULL); Py_DECREF(args); if (pyret == NULL) { kore_python_log_error("python_runtime_validator"); fatal("failed to execute python call"); } if (PyCoro_CheckExact(pyret)) { coro = python_coro_create(pyret, req); req->py_validator = coro; if (python_coro_run(coro) == KORE_RESULT_OK) { http_request_wakeup(req); ret = python_validator_check(coro->result); kore_python_coro_delete(coro); req->py_validator = NULL; return (ret); } return (KORE_RESULT_RETRY); } ret = python_validator_check(pyret); Py_DECREF(pyret); return (ret); } static int python_validator_check(PyObject *obj) { int ret; if (obj == NULL) return (KORE_RESULT_ERROR); if (!PyBool_Check(obj)) { kore_log(LOG_WARNING, "validator did not return True/False"); ret = KORE_RESULT_ERROR; } if (obj == Py_True) ret = KORE_RESULT_OK; else ret = KORE_RESULT_ERROR; return (ret); } static void python_runtime_wsmessage(void *addr, struct connection *c, u_int8_t op, const void *data, size_t len) { PyObject *callable, *args, *pyret, *pyc, *pyop, *pydata; callable = (PyObject *)addr; if ((pyc = pyconnection_alloc(c)) == NULL) fatal("python_runtime_wsmessage: pyc alloc failed"); if ((pyop = PyLong_FromLong((long)op)) == NULL) fatal("python_runtime_wsmessage: PyLong_FromLong failed"); switch (op) { case WEBSOCKET_OP_TEXT: if ((pydata = PyUnicode_FromStringAndSize(data, len)) == NULL) fatal("wsmessage: PyUnicode_AsUTF8AndSize failed"); break; case WEBSOCKET_OP_BINARY: if ((pydata = PyBytes_FromStringAndSize(data, len)) == NULL) fatal("wsmessage: PyBytes_FromString failed"); break; default: fatal("python_runtime_wsmessage: invalid op"); } if ((args = PyTuple_New(3)) == NULL) fatal("python_runtime_wsmessage: PyTuple_New failed"); if (PyTuple_SetItem(args, 0, pyc) != 0 || PyTuple_SetItem(args, 1, pyop) != 0 || PyTuple_SetItem(args, 2, pydata) != 0) fatal("python_runtime_wsmessage: PyTuple_SetItem failed"); PyErr_Clear(); pyret = PyObject_Call(callable, args, NULL); Py_DECREF(args); if (pyret == NULL) { kore_python_log_error("python_runtime_wsconnect"); fatal("failed to execute python call"); } Py_DECREF(pyret); } static void python_runtime_execute(void *addr) { PyObject *callable, *args, *pyret; callable = (PyObject *)addr; if ((args = PyTuple_New(0)) == NULL) fatal("python_runtime_execute: PyTuple_New failed"); PyErr_Clear(); pyret = PyObject_Call(callable, args, NULL); Py_DECREF(args); if (pyret == NULL) { kore_python_log_error("python_runtime_execute"); fatal("failed to execute python call"); } Py_DECREF(pyret); } static void python_runtime_configure(void *addr, int argc, char **argv) { int i; PyObject *callable, *args, *pyret, *pyarg, *list; callable = (PyObject *)addr; if ((args = PyTuple_New(1)) == NULL) fatal("python_runtime_configure: PyTuple_New failed"); if ((list = PyList_New(argc + 1)) == NULL) fatal("python_runtime_configure: PyList_New failed"); if ((pyarg = PyUnicode_FromString(__progname)) == NULL) fatal("python_runtime_configure: PyUnicode_FromString"); if (PyList_SetItem(list, 0, pyarg) == -1) fatal("python_runtime_configure: PyList_SetItem"); for (i = 0; i < argc; i++) { if ((pyarg = PyUnicode_FromString(argv[i])) == NULL) fatal("python_runtime_configure: PyUnicode_FromString"); if (PyList_SetItem(list, i + 1, pyarg) == -1) fatal("python_runtime_configure: PyList_SetItem"); } if (PyTuple_SetItem(args, 0, list) != 0) fatal("python_runtime_configure: PyTuple_SetItem"); PyErr_Clear(); pyret = PyObject_Call(callable, args, NULL); Py_DECREF(args); if (pyret == NULL) { kore_python_log_error("python_runtime_configure"); fatal("failed to configure your application"); } Py_DECREF(pyret); } static int python_runtime_onload(void *addr, int action) { int ret; PyObject *pyret, *args, *pyact, *callable; callable = (PyObject *)addr; if ((pyact = PyLong_FromLong(action)) == NULL) fatal("python_runtime_onload: PyLong_FromLong failed"); if ((args = PyTuple_New(1)) == NULL) fatal("python_runtime_onload: PyTuple_New failed"); if (PyTuple_SetItem(args, 0, pyact) != 0) fatal("python_runtime_onload: PyTuple_SetItem failed"); PyErr_Clear(); pyret = PyObject_Call(callable, args, NULL); Py_DECREF(args); if (pyret == NULL) { kore_python_log_error("python_runtime_onload"); return (KORE_RESULT_ERROR); } if (!PyLong_Check(pyret)) fatal("python_runtime_onload: unexpected return type"); ret = (int)PyLong_AsLong(pyret); Py_DECREF(pyret); return (ret); } static void python_runtime_connect(void *addr, struct connection *c) { PyObject *pyc, *pyret, *args, *callable; callable = (PyObject *)addr; if ((pyc = pyconnection_alloc(c)) == NULL) fatal("python_runtime_connect: pyc alloc failed"); if ((args = PyTuple_New(1)) == NULL) fatal("python_runtime_connect: PyTuple_New failed"); if (PyTuple_SetItem(args, 0, pyc) != 0) fatal("python_runtime_connect: PyTuple_SetItem failed"); PyErr_Clear(); pyret = PyObject_Call(callable, args, NULL); Py_DECREF(args); if (pyret == NULL) { kore_python_log_error("python_runtime_connect"); kore_connection_disconnect(c); } Py_DECREF(pyret); } static void python_runtime_signal(void *addr, int sig) { PyObject *obj, *ret; if ((obj = Py_BuildValue("i", sig)) == NULL) { kore_python_log_error("python_runtime_signal"); return; } ret = PyObject_CallFunctionObjArgs(addr, obj, NULL); Py_DECREF(obj); Py_XDECREF(ret); } PyMODINIT_FUNC python_module_init(void) { int i; struct pyconfig *config; PyObject *pykore; if ((pykore = PyModule_Create(&pykore_module)) == NULL) fatal("python_module_init: failed to setup pykore module"); python_push_type("pyproc", pykore, &pyproc_type); python_push_type("pylock", pykore, &pylock_type); python_push_type("pytimer", pykore, &pytimer_type); python_push_type("pyqueue", pykore, &pyqueue_type); python_push_type("pyroute", pykore, &pyroute_type); python_push_type("pysocket", pykore, &pysocket_type); python_push_type("pydomain", pykore, &pydomain_type); python_push_type("pyconnection", pykore, &pyconnection_type); #if defined(__linux__) python_push_type("pyseccomp", pykore, &pyseccomp_type); #endif #if defined(KORE_USE_CURL) python_push_type("pycurlhandle", pykore, &pycurl_handle_type); python_push_type("pyhttpclient", pykore, &pyhttp_client_type); for (i = 0; py_curlopt[i].name != NULL; i++) { python_push_integer(pykore, py_curlopt[i].name, py_curlopt[i].value); } #endif python_push_type("pyhttp_file", pykore, &pyhttp_file_type); python_push_type("pyhttp_request", pykore, &pyhttp_request_type); for (i = 0; python_integers[i].symbol != NULL; i++) { python_push_integer(pykore, python_integers[i].symbol, python_integers[i].value); } if ((config = PyObject_New(struct pyconfig, &pyconfig_type)) == NULL) fatal("failed to create config object"); if (PyObject_SetAttrString(pykore, "config", (PyObject *)config) == -1) fatal("failed to add config object"); return (pykore); } static int pyconfig_setattr(PyObject *self, PyObject *attr, PyObject *val) { char *v; int ret; PyObject *repr; const char *name, *value; ret = -1; repr = NULL; if (!PyUnicode_Check(attr)) fatal("setattr: attribute name not a unicode string"); if (PyLong_CheckExact(val)) { if ((repr = PyObject_Repr(val)) == NULL) return (-1); value = PyUnicode_AsUTF8(repr); } else if (PyUnicode_CheckExact(val)) { value = PyUnicode_AsUTF8(val); } else if (PyBool_Check(val)) { if (val == Py_False) value = "False"; else value = "True"; } else { fatal("invalid object, config expects integer, bool or string"); } name = PyUnicode_AsUTF8(attr); v = kore_strdup(value); if (!kore_configure_setting(name, v)) { ret = -1; PyErr_SetString(PyExc_RuntimeError, "configured cannot be changed at runtime"); } else { ret = 0; } kore_free(v); Py_XDECREF(repr); return (ret); } static void python_append_path(const char *path) { PyObject *mpath, *spath; if ((mpath = PyUnicode_FromString(path)) == NULL) fatal("python_append_path: PyUnicode_FromString failed"); if ((spath = PySys_GetObject("path")) == NULL) fatal("python_append_path: PySys_GetObject failed"); PyList_Append(spath, mpath); Py_DECREF(mpath); } static void python_push_type(const char *name, PyObject *module, PyTypeObject *type) { if (PyType_Ready(type) == -1) fatal("python_push_type: failed to ready %s", name); Py_INCREF(type); if (PyModule_AddObject(module, name, (PyObject *)type) == -1) fatal("python_push_type: failed to push %s", name); } static void python_push_integer(PyObject *module, const char *name, long value) { if (PyModule_AddIntConstant(module, name, value) == -1) fatal("python_push_integer: failed to add %s", name); } #if defined(KORE_USE_PGSQL) static PyObject * python_kore_pgsql_register(PyObject *self, PyObject *args) { const char *db, *conninfo; if (!PyArg_ParseTuple(args, "ss", &db, &conninfo)) return (NULL); (void)kore_pgsql_register(db, conninfo); Py_RETURN_TRUE; } #endif static PyObject * python_kore_app(PyObject *self, PyObject *args) { PyObject *obj; if (!PyArg_ParseTuple(args, "O", &obj)) { PyErr_Clear(); if (kore_app == NULL) Py_RETURN_NONE; Py_INCREF(kore_app); return (kore_app); } Py_XDECREF(kore_app); kore_app = obj; Py_INCREF(kore_app); Py_RETURN_TRUE; } static PyObject * python_kore_log(PyObject *self, PyObject *args) { int prio; const char *message; if (!PyArg_ParseTuple(args, "is", &prio, &message)) return (NULL); kore_log(prio, "%s", message); Py_RETURN_TRUE; } static PyObject * python_kore_time(PyObject *self, PyObject *args) { u_int64_t now; now = kore_time_ms(); return (PyLong_FromUnsignedLongLong(now)); } static PyObject * python_kore_server(PyObject *self, PyObject *args, PyObject *kwargs) { struct kore_server *srv; const char *name, *ip, *port, *path; if (kwargs == NULL) { PyErr_SetString(PyExc_RuntimeError, "missing keyword args"); return (NULL); } ip = python_string_from_dict(kwargs, "ip"); path = python_string_from_dict(kwargs, "path"); if (ip == NULL && path == NULL) { PyErr_SetString(PyExc_RuntimeError, "missing ip or path keywords"); return (NULL); } if (ip != NULL && path != NULL) { PyErr_SetString(PyExc_RuntimeError, "ip/path are exclusive"); return (NULL); } name = python_string_from_dict(kwargs, "name"); if (name == NULL) name = "default"; if ((srv = kore_server_lookup(name)) != NULL) { PyErr_Format(PyExc_RuntimeError, "server '%s' already exist", name); return (NULL); } srv = kore_server_create(name); python_bool_from_dict(kwargs, "tls", &srv->tls); if (srv->tls && !kore_tls_supported()) { kore_server_free(srv); PyErr_SetString(PyExc_RuntimeError, "TLS not supported in this Kore build"); return (NULL); } if (ip != NULL) { if ((port = python_string_from_dict(kwargs, "port")) == NULL) { kore_server_free(srv); PyErr_SetString(PyExc_RuntimeError, "missing or invalid 'port' keyword"); return (NULL); } if (!kore_server_bind(srv, ip, port, NULL)) { PyErr_Format(PyExc_RuntimeError, "failed to bind to '%s:%s'", ip, port); return (NULL); } } else { if (!kore_server_bind_unix(srv, path, NULL)) { PyErr_Format(PyExc_RuntimeError, "failed to bind to '%s'", path); return (NULL); } } kore_server_finalize(srv); Py_RETURN_NONE; } static PyObject * python_kore_privsep(PyObject *self, PyObject *args, PyObject *kwargs) { struct kore_privsep *ps; const char *val; PyObject *skip, *obj; Py_ssize_t list_len, idx; if (!PyArg_ParseTuple(args, "s", &val)) return (NULL); if (!strcmp(val, "worker")) { ps = &worker_privsep; } else if (!strcmp(val, "keymgr")) { ps = &keymgr_privsep; #if defined(KORE_USE_ACME) } else if (!strcmp(val, "acme")) { ps = &acme_privsep; #endif } else { PyErr_Format(PyExc_RuntimeError, "unknown privsep process '%s'", val); return (NULL); } if ((val = python_string_from_dict(kwargs, "root")) != NULL) { kore_free(ps->root); ps->root = kore_strdup(val); } if ((val = python_string_from_dict(kwargs, "runas")) != NULL) { kore_free(ps->runas); ps->runas = kore_strdup(val); } if ((skip = PyDict_GetItemString(kwargs, "skip")) != NULL) { if (!PyList_CheckExact(skip)) { PyErr_Format(PyExc_RuntimeError, "privsep skip keyword needs to be a list"); return (NULL); } list_len = PyList_Size(skip); for (idx = 0; idx < list_len; idx++) { if ((obj = PyList_GetItem(skip, idx)) == NULL) return (NULL); if (!PyUnicode_Check(obj)) return (NULL); if ((val = PyUnicode_AsUTF8AndSize(obj, NULL)) == NULL) return (NULL); if (!strcmp(val, "chroot")) { ps->skip_chroot = 1; } else { PyErr_Format(PyExc_RuntimeError, "unknown skip keyword '%s'", val); return (NULL); } } } Py_RETURN_NONE; } static PyObject * python_kore_prerequest(PyObject *self, PyObject *args) { PyObject *f; struct reqcall *rq; if (!PyArg_ParseTuple(args, "O", &f)) return (NULL); rq = kore_calloc(1, sizeof(*rq)); rq->f = f; Py_INCREF(f); TAILQ_INSERT_TAIL(&prereq, rq, list); return (f); } static PyObject * python_kore_task_create(PyObject *self, PyObject *args) { PyObject *obj; struct python_coro *coro; if (!PyArg_ParseTuple(args, "O", &obj)) return (NULL); if (!PyCoro_CheckExact(obj)) fatal("%s: object is not a coroutine", __func__); coro = python_coro_create(obj, NULL); Py_INCREF(obj); return (PyLong_FromUnsignedLongLong(coro->id)); } static PyObject * python_kore_task_id(PyObject *self, PyObject *args) { if (coro_running == NULL) { PyErr_SetString(PyExc_RuntimeError, "no coroutine active"); return (NULL); } return (PyLong_FromUnsignedLongLong(coro_running->id)); } static PyObject * python_kore_task_kill(PyObject *self, PyObject *args) { u_int64_t id; struct python_coro *coro, *active; if (!PyArg_ParseTuple(args, "K", &id)) return (NULL); if (coro_running != NULL && coro_running->id == id) { PyErr_SetString(PyExc_RuntimeError, "refusing to kill active coroutine"); return (NULL); } /* Remember active coro, as delete sets coro_running to NULL. */ active = coro_running; TAILQ_FOREACH(coro, &coro_runnable, list) { if (coro->id == id) { coro->killed++; kore_python_coro_delete(coro); coro_running = active; Py_RETURN_TRUE; } } TAILQ_FOREACH(coro, &coro_suspended, list) { if (coro->id == id) { coro->killed++; kore_python_coro_delete(coro); coro_running = active; Py_RETURN_TRUE; } } Py_RETURN_FALSE; } static PyObject * python_kore_socket_wrap(PyObject *self, PyObject *args) { struct pysocket *sock; PyObject *pysock, *pyfd, *pyfam, *pyproto; sock = NULL; pyfd = NULL; pyfam = NULL; pyproto = NULL; if (!PyArg_ParseTuple(args, "O", &pysock)) return (NULL); if ((pyfd = PyObject_CallMethod(pysock, "fileno", NULL)) == NULL) return (NULL); if ((pyfam = PyObject_GetAttrString(pysock, "family")) == NULL) goto out; if ((pyproto = PyObject_GetAttrString(pysock, "proto")) == NULL) goto out; if ((sock = pysocket_alloc()) == NULL) goto out; sock->socket = pysock; Py_INCREF(sock->socket); sock->fd = (int)PyLong_AsLong(pyfd); sock->family = (int)PyLong_AsLong(pyfam); sock->protocol = (int)PyLong_AsLong(pyproto); memset(&sock->addr, 0, sizeof(sock->addr)); switch (sock->family) { case AF_INET: case AF_UNIX: break; default: PyErr_SetString(PyExc_RuntimeError, "unsupported family"); Py_DECREF((PyObject *)sock); sock = NULL; goto out; } out: Py_XDECREF(pyfd); Py_XDECREF(pyfam); Py_XDECREF(pyproto); return ((PyObject *)sock); } static PyObject * python_kore_queue(PyObject *self, PyObject *args) { struct pyqueue *queue; if ((queue = PyObject_New(struct pyqueue, &pyqueue_type)) == NULL) return (NULL); TAILQ_INIT(&queue->objects); TAILQ_INIT(&queue->waiting); return ((PyObject *)queue); } static PyObject * python_kore_worker(PyObject *self, PyObject *args) { if (worker == NULL) { Py_RETURN_NONE; } return (PyLong_FromLong(worker->id)); } static PyObject * python_kore_tracer(PyObject *self, PyObject *args) { PyObject *obj; if (python_tracer != NULL) { PyErr_SetString(PyExc_RuntimeError, "tracer already set"); return (NULL); } if (!PyArg_ParseTuple(args, "O", &obj)) return (NULL); if (!PyCallable_Check(obj)) { PyErr_SetString(PyExc_RuntimeError, "object not callable"); Py_DECREF(obj); return (NULL); } Py_INCREF(obj); python_tracer = obj; Py_RETURN_TRUE; } static PyObject * python_kore_domain(PyObject *self, PyObject *args, PyObject *kwargs) { #if defined(KORE_USE_ACME) int acme; char *acert, *akey; #endif struct kore_server *srv; long depth; const char *name; struct pydomain *domain; const char *cert, *key, *ca, *attach, *crl; ca = NULL; depth = -1; key = NULL; crl = NULL; cert = NULL; attach = NULL; #if defined(KORE_USE_ACME) acme = 0; #endif if (!PyArg_ParseTuple(args, "s", &name)) return (NULL); if (kwargs != NULL) attach = python_string_from_dict(kwargs, "attach"); if (attach == NULL) attach = "default"; if ((srv = kore_server_lookup(attach)) == NULL) { PyErr_Format(PyExc_RuntimeError, "server '%s' does not exist", attach); return (NULL); } if (srv->tls) { if (kwargs == NULL) { PyErr_Format(PyExc_RuntimeError, "no keywords for TLS enabled domain %s", name); return (NULL); } key = python_string_from_dict(kwargs, "key"); cert = python_string_from_dict(kwargs, "cert"); #if defined(KORE_USE_ACME) python_bool_from_dict(kwargs, "acme", &acme); if (acme) { kore_acme_get_paths(name, &akey, &acert); acme_domains++; key = akey; cert = acert; } #endif if (key == NULL || cert == NULL) { PyErr_Format(PyExc_RuntimeError, "missing key or cert keywords for TLS listener"); return (NULL); } ca = python_string_from_dict(kwargs, "client_verify"); if (ca != NULL) { python_long_from_dict(kwargs, "verify_depth", &depth); if (depth < 0) { PyErr_Format(PyExc_RuntimeError, "invalid depth '%d'", depth); return (NULL); } crl = python_string_from_dict(kwargs, "crl"); } } else if (key != NULL || cert != NULL || ca != NULL) { kore_log(LOG_INFO, "ignoring tls settings for '%s'", name); } if (kore_domain_lookup(srv, name) != NULL) { PyErr_SetString(PyExc_RuntimeError, "domain exists"); return (NULL); } if ((domain = PyObject_New(struct pydomain, &pydomain_type)) == NULL) return (NULL); domain->next = NULL; domain->kwargs = NULL; if ((domain->config = kore_domain_new(name)) == NULL) fatal("failed to create new domain configuration"); if (!kore_domain_attach(domain->config, srv)) fatal("failed to attach domain configuration"); if (srv->tls) { domain->config->certkey = kore_strdup(key); domain->config->certfile = kore_strdup(cert); #if defined(KORE_USE_ACME) domain->config->acme = acme; if (domain->config->acme) { kore_free(akey); kore_free(acert); } #endif if (ca != NULL) { domain->config->cafile = kore_strdup(ca); domain->config->x509_verify_depth = depth; if (crl != NULL) domain->config->crlfile = kore_strdup(crl); } } return ((PyObject *)domain); } static PyObject * python_kore_route(PyObject *self, PyObject *args, PyObject *kwargs) { const char *path; PyObject *inner; struct pyroute *route; if ((route = PyObject_New(struct pyroute, &pyroute_type)) == NULL) return (NULL); if (!PyArg_ParseTuple(args, "s", &path)) return (NULL); route->domain = NULL; route->kwargs = kwargs; route->path = kore_strdup(path); Py_XINCREF(route->kwargs); inner = PyObject_GetAttrString((PyObject *)route, "inner"); if (inner == NULL) { Py_DECREF((PyObject *)route); PyErr_SetString(PyExc_RuntimeError, "failed to find inner"); return (NULL); } return (inner); } static PyObject * python_kore_gather(PyObject *self, PyObject *args, PyObject *kwargs) { struct pygather_op *op; PyObject *obj; struct pygather_coro *coro; Py_ssize_t sz, idx; int concurrency; if (coro_running == NULL) { PyErr_SetString(PyExc_RuntimeError, "kore.gather only available in coroutines"); return (NULL); } sz = PyTuple_Size(args); if (sz > INT_MAX) { PyErr_SetString(PyExc_TypeError, "too many arguments"); return (NULL); } if (kwargs != NULL && (obj = PyDict_GetItemString(kwargs, "concurrency")) != NULL) { if (!PyLong_Check(obj)) { PyErr_SetString(PyExc_TypeError, "concurrency level must be an integer"); return (NULL); } PyErr_Clear(); concurrency = (int)PyLong_AsLong(obj); if (concurrency == -1 && PyErr_Occurred()) return (NULL); if (concurrency == 0) concurrency = sz; } else { concurrency = sz; } op = PyObject_New(struct pygather_op, &pygather_op_type); if (op == NULL) return (NULL); op->running = 0; op->count = (int)sz; op->coro = coro_running; op->concurrency = concurrency; TAILQ_INIT(&op->results); TAILQ_INIT(&op->coroutines); for (idx = 0; idx < sz; idx++) { if ((obj = PyTuple_GetItem(args, idx)) == NULL) { Py_DECREF((PyObject *)op); return (NULL); } if (!PyCoro_CheckExact(obj)) { Py_DECREF((PyObject *)op); PyErr_SetString(PyExc_TypeError, "not a coroutine"); return (NULL); } Py_INCREF(obj); coro = kore_pool_get(&gather_coro_pool); coro->coro = python_coro_create(obj, NULL); coro->coro->gatherop = op; TAILQ_INSERT_TAIL(&op->coroutines, coro, list); if (idx > concurrency - 1) python_coro_suspend(coro->coro); else op->running++; } return ((PyObject *)op); } static PyObject * python_kore_lock(PyObject *self, PyObject *args) { struct pylock *lock; if ((lock = PyObject_New(struct pylock, &pylock_type)) == NULL) return (NULL); lock->owner = NULL; TAILQ_INIT(&lock->ops); return ((PyObject *)lock); } static PyObject * python_kore_fatal(PyObject *self, PyObject *args) { const char *reason; if (!PyArg_ParseTuple(args, "s", &reason)) reason = "python_kore_fatal: PyArg_ParseTuple failed"; fatal("%s", reason); /* not reached */ Py_RETURN_TRUE; } static PyObject * python_kore_fatalx(PyObject *self, PyObject *args) { const char *reason; if (!PyArg_ParseTuple(args, "s", &reason)) reason = "python_kore_fatalx: PyArg_ParseTuple failed"; fatalx("%s", reason); /* not reached */ Py_RETURN_TRUE; } static PyObject * python_kore_setname(PyObject *self, PyObject *args) { const char *name; extern char *kore_progname; if (!PyArg_ParseTuple(args, "s", &name)) return (NULL); kore_free(kore_progname); kore_progname = kore_strdup(name); Py_RETURN_NONE; } static PyObject * python_kore_sigtrap(PyObject *self, PyObject *args) { int sig; if (!PyArg_ParseTuple(args, "i", &sig)) return (NULL); kore_signal_trap(sig); Py_RETURN_NONE; } static PyObject * python_kore_sendobj(PyObject *self, PyObject *args, PyObject *kwargs) { long val; u_int16_t dst; char *ptr; Py_ssize_t length; PyObject *object, *bytes; if (!PyArg_ParseTuple(args, "O", &object)) return (NULL); bytes = PyObject_CallFunctionObjArgs(pickle_dumps, object, NULL); if (bytes == NULL) return (NULL); if (PyBytes_AsStringAndSize(bytes, &ptr, &length) == -1) { Py_DECREF(bytes); return (NULL); } dst = KORE_MSG_WORKER_ALL; if (kwargs != NULL) { if (python_long_from_dict(kwargs, "worker", &val)) { if (val <= 0 || val > worker_count || val >= KORE_WORKER_MAX) { PyErr_Format(PyExc_RuntimeError, "worker %ld invalid", val); Py_DECREF(bytes); return (NULL); } dst = val; } } kore_msg_send(dst, KORE_PYTHON_SEND_OBJ, ptr, length); Py_DECREF(bytes); Py_RETURN_NONE; } static void python_kore_recvobj(struct kore_msg *msg, const void *data) { struct kore_runtime *rt; PyObject *onmsg, *ret, *bytes, *obj; if ((onmsg = kore_module_getsym("koreapp.onmsg", &rt)) == NULL) return; if (rt->type != KORE_RUNTIME_PYTHON) return; if ((bytes = PyBytes_FromStringAndSize(data, msg->length)) == NULL) { Py_DECREF(onmsg); kore_python_log_error("koreapp.onmsg"); return; } obj = PyObject_CallFunctionObjArgs(pickle_loads, bytes, NULL); Py_DECREF(bytes); if (obj == NULL) { Py_DECREF(onmsg); kore_python_log_error("koreapp.onmsg"); return; } ret = PyObject_CallFunctionObjArgs(onmsg, obj, NULL); kore_python_log_error("koreapp.onmsg"); Py_DECREF(obj); Py_DECREF(onmsg); Py_XDECREF(ret); } static PyObject * python_kore_suspend(PyObject *self, PyObject *args) { struct pysuspend_op *op; int delay; if (!PyArg_ParseTuple(args, "i", &delay)) return (NULL); op = PyObject_New(struct pysuspend_op, &pysuspend_op_type); if (op == NULL) return (NULL); op->timer = NULL; op->delay = delay; op->coro = coro_running; op->state = PYSUSPEND_OP_INIT; return ((PyObject *)op); } static PyObject * python_kore_shutdown(PyObject *self, PyObject *args) { kore_shutdown(); Py_RETURN_TRUE; } static PyObject * python_kore_coroname(PyObject *self, PyObject *args) { const char *name; if (coro_running == NULL) { PyErr_SetString(PyExc_RuntimeError, "kore.coroname() only available in coroutines"); return (NULL); } if (!PyArg_ParseTuple(args, "s", &name)) return (NULL); kore_free(coro_running->name); coro_running->name = kore_strdup(name); Py_RETURN_NONE; } static PyObject * python_kore_corotrace(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "b", &coro_tracing)) return (NULL); Py_RETURN_NONE; } static PyObject * python_kore_timer(PyObject *self, PyObject *args, PyObject *kwargs) { u_int64_t ms; PyObject *obj; int flags; struct pytimer *timer; if (worker == NULL) { PyErr_SetString(PyExc_RuntimeError, "kore.timer not supported on parent process"); return (NULL); } if (!PyArg_ParseTuple(args, "OKi", &obj, &ms, &flags)) return (NULL); if (flags & ~(KORE_TIMER_FLAGS)) { PyErr_SetString(PyExc_RuntimeError, "invalid flags"); return (NULL); } if ((timer = PyObject_New(struct pytimer, &pytimer_type)) == NULL) return (NULL); timer->udata = NULL; timer->flags = flags; timer->callable = obj; timer->run = kore_timer_add(pytimer_run, ms, timer, flags); Py_INCREF((PyObject *)timer); Py_INCREF(timer->callable); if (kwargs != NULL) { if ((obj = PyDict_GetItemString(kwargs, "data")) != NULL) { Py_INCREF(obj); timer->udata = obj; } } return ((PyObject *)timer); } static PyObject * python_kore_proc(PyObject *self, PyObject *args) { const char *cmd; struct pyproc *proc; char *copy, *argv[32], *env[1]; int timeo, in_pipe[2], out_pipe[2]; timeo = -1; if (coro_running == NULL) { PyErr_SetString(PyExc_RuntimeError, "kore.proc only available in coroutines"); return (NULL); } if (!PyArg_ParseTuple(args, "s|i", &cmd, &timeo)) return (NULL); if (pipe(in_pipe) == -1) { PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } if (pipe(out_pipe) == -1) { close(in_pipe[0]); close(in_pipe[1]); PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } if ((proc = PyObject_New(struct pyproc, &pyproc_type)) == NULL) { close(in_pipe[0]); close(in_pipe[1]); close(out_pipe[0]); close(out_pipe[1]); return (NULL); } proc->pid = -1; proc->op = NULL; proc->apid = -1; proc->reaped = 0; proc->status = 0; proc->timer = NULL; proc->coro = coro_running; proc->in = pysocket_alloc(); proc->out = pysocket_alloc(); if (proc->in == NULL || proc->out == NULL) { Py_DECREF((PyObject *)proc); return (NULL); } TAILQ_INSERT_TAIL(&procs, proc, list); proc->pid = fork(); if (proc->pid == -1) { if (errno == ENOSYS) { Py_DECREF((PyObject *)proc); PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } fatal("python_kore_proc: fork(): %s", errno_s); } if (proc->pid == 0) { close(in_pipe[1]); close(out_pipe[0]); if (dup2(out_pipe[1], STDOUT_FILENO) == -1 || dup2(out_pipe[1], STDERR_FILENO) == -1 || dup2(in_pipe[0], STDIN_FILENO) == -1) fatal("dup2: %s", errno_s); env[0] = NULL; copy = kore_strdup(cmd); python_split_arguments(copy, argv, 32); (void)execve(argv[0], argv, env); kore_log(LOG_ERR, "kore.proc failed to execute %s (%s)", argv[0], errno_s); exit(1); } close(in_pipe[0]); close(out_pipe[1]); if (!kore_connection_nonblock(in_pipe[1], 0) || !kore_connection_nonblock(out_pipe[0], 0)) fatal("failed to mark kore.proc pipes are non-blocking"); proc->apid = proc->pid; proc->in->fd = in_pipe[1]; proc->out->fd = out_pipe[0]; if (timeo != -1) { proc->timer = kore_timer_add(pyproc_timeout, timeo, proc, KORE_TIMER_ONESHOT); } return ((PyObject *)proc); } static PyObject * python_import(const char *path) { struct stat st; PyObject *module; char *dir, *file, *copy, *p; if (stat(path, &st) == -1) fatal("python_import: stat(%s): %s", path, errno_s); if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) fatal("python_import: '%s' is not a file or directory", path); copy = kore_strdup(path); if ((p = dirname(copy)) == NULL) fatal("dirname: %s: %s", path, errno_s); dir = kore_strdup(p); kore_free(copy); copy = kore_strdup(path); if ((p = basename(copy)) == NULL) fatal("basename: %s: %s", path, errno_s); file = kore_strdup(p); kore_free(copy); if ((p = strrchr(file, '.')) != NULL) *p = '\0'; python_append_path(dir); if (S_ISDIR(st.st_mode)) python_append_path(path); module = PyImport_ImportModule(file); if (module == NULL) PyErr_Print(); kore_free(dir); kore_free(file); return (module); } static PyObject * python_callable(PyObject *module, const char *symbol) { char *base, *method; PyObject *res, *obj, *meth; res = NULL; obj = NULL; base = kore_strdup(symbol); if ((method = strchr(base, '.')) != NULL) *(method)++ = '\0'; if ((obj = PyObject_GetAttrString(module, base)) == NULL) goto out; if (method != NULL) { if ((meth = PyObject_GetAttrString(obj, method)) == NULL) goto out; Py_DECREF(obj); obj = meth; } if (!PyCallable_Check(obj)) goto out; res = obj; obj = NULL; out: if (obj != NULL) Py_DECREF(obj); PyErr_Clear(); kore_free(base); return (res); } static PyObject * pyconnection_alloc(struct connection *c) { struct pyconnection *pyc; pyc = PyObject_New(struct pyconnection, &pyconnection_type); if (pyc == NULL) return (NULL); pyc->c = c; return ((PyObject *)pyc); } static PyObject * pyconnection_disconnect(struct pyconnection *pyc, PyObject *args) { kore_connection_disconnect(pyc->c); Py_RETURN_TRUE; } static PyObject * pyconnection_get_fd(struct pyconnection *pyc, void *closure) { PyObject *fd; if ((fd = PyLong_FromLong(pyc->c->fd)) == NULL) return (PyErr_NoMemory()); return (fd); } static PyObject * pyconnection_get_addr(struct pyconnection *pyc, void *closure) { void *ptr; PyObject *result; char addr[INET6_ADDRSTRLEN]; switch (pyc->c->family) { case AF_INET: ptr = &pyc->c->addr.ipv4.sin_addr; break; case AF_INET6: ptr = &pyc->c->addr.ipv6.sin6_addr; break; default: PyErr_SetString(PyExc_RuntimeError, "invalid family"); return (NULL); } if (inet_ntop(pyc->c->family, ptr, addr, sizeof(addr)) == NULL) { PyErr_SetString(PyExc_RuntimeError, "inet_ntop failed"); return (NULL); } if ((result = PyUnicode_FromString(addr)) == NULL) return (PyErr_NoMemory()); return (result); } static PyObject * pyconnection_get_peer_x509(struct pyconnection *pyc, void *closure) { size_t len; u_int8_t *der; PyObject *bytes; if (pyc->c->tls_cert == NULL) { Py_RETURN_NONE; } if (!kore_tls_x509_data(pyc->c, &der, &len)) { PyErr_SetString(PyExc_RuntimeError, "failed to obtain certificate data"); return (NULL); } bytes = PyBytes_FromStringAndSize((char *)der, len); kore_free(der); return (bytes); } static PyObject * pyconnection_get_peer_x509dict(struct pyconnection *pyc, void *closure) { KORE_X509_NAMES *name; PyObject *dict, *issuer, *subject, *ret; ret = NULL; issuer = NULL; subject = NULL; if (pyc->c->tls_cert == NULL) { Py_RETURN_NONE; } if ((dict = PyDict_New()) == NULL) goto out; if ((issuer = PyDict_New()) == NULL) goto out; if (PyDict_SetItemString(dict, "issuer", issuer) == -1) goto out; if ((subject = PyDict_New()) == NULL) goto out; if (PyDict_SetItemString(dict, "subject", subject) == -1) goto out; PyErr_Clear(); if ((name = kore_tls_x509_subject_name(pyc->c)) == NULL) { PyErr_Format(PyExc_RuntimeError, "failed to obtain x509 subjectName"); goto out; } if (!kore_tls_x509name_foreach(name, 0, subject, pyconnection_x509_cb)) { if (PyErr_Occurred() == NULL) { PyErr_Format(PyExc_RuntimeError, "failed to add subject name to dictionary"); } goto out; } if ((name = kore_tls_x509_issuer_name(pyc->c)) == NULL) { PyErr_Format(PyExc_RuntimeError, "failed to obtain x509 issuerName"); goto out; } if (!kore_tls_x509name_foreach(name, 0, issuer, pyconnection_x509_cb)) { if (PyErr_Occurred() == NULL) { PyErr_Format(PyExc_RuntimeError, "failed to add issuer name to dictionary"); } goto out; } ret = dict; dict = NULL; out: Py_XDECREF(dict); Py_XDECREF(issuer); Py_XDECREF(subject); return (ret); } static int pyconnection_x509_cb(void *udata, int islast, int nid, const char *field, const void *data, size_t len, int flags) { PyObject *dict, *obj; dict = udata; if ((obj = PyUnicode_FromStringAndSize(data, len)) == NULL) return (KORE_RESULT_ERROR); if (PyDict_SetItemString(dict, field, obj) == -1) { Py_DECREF(obj); return (KORE_RESULT_ERROR); } Py_DECREF(obj); return (KORE_RESULT_OK); } static void pytimer_run(void *arg, u_int64_t now) { PyObject *ret; struct pytimer *timer = arg; PyErr_Clear(); ret = PyObject_CallFunctionObjArgs(timer->callable, timer->udata, NULL); Py_XDECREF(ret); Py_XDECREF(timer->udata); timer->udata = NULL; kore_python_log_error("pytimer_run"); if (timer->flags & KORE_TIMER_ONESHOT) { timer->run = NULL; Py_DECREF((PyObject *)timer); } } static void pytimer_dealloc(struct pytimer *timer) { if (timer->run != NULL) { kore_timer_remove(timer->run); timer->run = NULL; } if (timer->callable != NULL) { Py_DECREF(timer->callable); timer->callable = NULL; } PyObject_Del((PyObject *)timer); } static PyObject * pytimer_close(struct pytimer *timer, PyObject *args) { if (timer->run != NULL) { kore_timer_remove(timer->run); timer->run = NULL; } if (timer->callable != NULL) { Py_DECREF(timer->callable); timer->callable = NULL; } if (timer->udata != NULL) { Py_DECREF(timer->udata); timer->udata = NULL; } Py_INCREF((PyObject *)timer); Py_RETURN_TRUE; } static void pysuspend_op_dealloc(struct pysuspend_op *op) { if (op->timer != NULL) { kore_timer_remove(op->timer); op->timer = NULL; } PyObject_Del((PyObject *)op); } static PyObject * pysuspend_op_await(PyObject *sop) { Py_INCREF(sop); return (sop); } static PyObject * pysuspend_op_iternext(struct pysuspend_op *op) { switch (op->state) { case PYSUSPEND_OP_INIT: op->timer = kore_timer_add(pysuspend_wakeup, op->delay, op, KORE_TIMER_ONESHOT); op->state = PYSUSPEND_OP_WAIT; break; case PYSUSPEND_OP_WAIT: break; case PYSUSPEND_OP_CONTINUE: PyErr_SetNone(PyExc_StopIteration); return (NULL); default: fatal("unknown state %d for pysuspend_op", op->state); } Py_RETURN_NONE; } static void pysuspend_wakeup(void *arg, u_int64_t now) { struct pysuspend_op *op = arg; op->timer = NULL; op->state = PYSUSPEND_OP_CONTINUE; if (op->coro->request != NULL) http_request_wakeup(op->coro->request); else python_coro_wakeup(op->coro); } static struct pysocket * pysocket_alloc(void) { struct pysocket *sock; if ((sock = PyObject_New(struct pysocket, &pysocket_type)) == NULL) return (NULL); sock->fd = -1; sock->family = -1; sock->protocol = -1; sock->scheduled = 0; sock->socket = NULL; sock->recvop = NULL; sock->sendop = NULL; sock->event.s = sock; sock->event.evt.flags = 0; sock->event.evt.type = KORE_TYPE_PYSOCKET; sock->event.evt.handle = pysocket_evt_handle; return (sock); } static void pysocket_dealloc(struct pysocket *sock) { if (sock->scheduled && sock->fd != -1) { kore_platform_disable_read(sock->fd); #if !defined(__linux__) kore_platform_disable_write(sock->fd); #endif } if (sock->socket != NULL) { Py_DECREF(sock->socket); } else if (sock->fd != -1) { (void)close(sock->fd); } PyObject_Del((PyObject *)sock); } static PyObject * pysocket_send(struct pysocket *sock, PyObject *args) { Py_buffer buf; PyObject *ret; if (!PyArg_ParseTuple(args, "y*", &buf)) return (NULL); ret = pysocket_op_create(sock, PYSOCKET_TYPE_SEND, buf.buf, buf.len); PyBuffer_Release(&buf); return (ret); } static PyObject * pysocket_sendto(struct pysocket *sock, PyObject *args) { Py_buffer buf; struct pysocket_op *op; PyObject *ret; int port; const char *ip, *sockaddr; switch (sock->family) { case AF_INET: if (!PyArg_ParseTuple(args, "siy*", &ip, &port, &buf)) return (NULL); if (port <= 0 || port >= USHRT_MAX) { PyErr_SetString(PyExc_RuntimeError, "invalid port"); return (NULL); } break; case AF_UNIX: if (!PyArg_ParseTuple(args, "sy*", &sockaddr, &buf)) return (NULL); break; default: PyErr_SetString(PyExc_RuntimeError, "unsupported family"); return (NULL); } ret = pysocket_op_create(sock, PYSOCKET_TYPE_SENDTO, buf.buf, buf.len); PyBuffer_Release(&buf); op = (struct pysocket_op *)ret; switch (sock->family) { case AF_INET: op->sendaddr.ipv4.sin_family = AF_INET; op->sendaddr.ipv4.sin_port = htons(port); op->sendaddr.ipv4.sin_addr.s_addr = inet_addr(ip); break; case AF_UNIX: op->sendaddr.sun.sun_family = AF_UNIX; if (kore_strlcpy(op->sendaddr.sun.sun_path, sockaddr, sizeof(op->sendaddr.sun.sun_path)) >= sizeof(op->sendaddr.sun.sun_path)) { Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "unix socket path too long"); return (NULL); } break; default: Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "unsupported family"); return (NULL); } return (ret); } static PyObject * pysocket_recv(struct pysocket *sock, PyObject *args) { Py_ssize_t len; struct pysocket_op *op; PyObject *obj; int timeo; timeo = -1; if (!PyArg_ParseTuple(args, "n|i", &len, &timeo)) return (NULL); obj = pysocket_op_create(sock, PYSOCKET_TYPE_RECV, NULL, len); if (obj == NULL) return (NULL); op = (struct pysocket_op *)obj; if (timeo != -1) { op->timer = kore_timer_add(pysocket_op_timeout, timeo, op, KORE_TIMER_ONESHOT); } return (obj); } static PyObject * pysocket_recvmsg(struct pysocket *sock, PyObject *args) { Py_ssize_t len; if (!PyArg_ParseTuple(args, "n", &len)) return (NULL); return (pysocket_op_create(sock, PYSOCKET_TYPE_RECVMSG, NULL, len)); } static PyObject * pysocket_recvfrom(struct pysocket *sock, PyObject *args) { Py_ssize_t len; if (!PyArg_ParseTuple(args, "n", &len)) return (NULL); return (pysocket_op_create(sock, PYSOCKET_TYPE_RECVFROM, NULL, len)); } static PyObject * pysocket_accept(struct pysocket *sock, PyObject *args) { return (pysocket_op_create(sock, PYSOCKET_TYPE_ACCEPT, NULL, 0)); } static PyObject * pysocket_connect(struct pysocket *sock, PyObject *args) { const char *host; int port, len; port = 0; if (!PyArg_ParseTuple(args, "s|i", &host, &port)) return (NULL); if (port < 0 || port > USHRT_MAX) { PyErr_SetString(PyExc_RuntimeError, "invalid port number"); return (NULL); } switch (sock->family) { case AF_INET: sock->addr.ipv4.sin_family = AF_INET; sock->addr.ipv4.sin_port = htons(port); if (inet_pton(sock->family, host, &sock->addr.ipv4.sin_addr) == -1) { PyErr_SetString(PyExc_RuntimeError, "invalid host"); return (NULL); } sock->addr_len = sizeof(sock->addr.ipv4); break; case AF_UNIX: sock->addr.sun.sun_family = AF_UNIX; len = snprintf(sock->addr.sun.sun_path, sizeof(sock->addr.sun.sun_path), "%s", host); if (len == -1 || (size_t)len >= sizeof(sock->addr.sun.sun_path)) { PyErr_SetString(PyExc_RuntimeError, "path too long"); return (NULL); } #if defined(__linux__) /* Assume abstract socket if prefixed with '@'. */ if (sock->addr.sun.sun_path[0] == '@') sock->addr.sun.sun_path[0] = '\0'; #endif sock->addr_len = sizeof(sock->addr.sun.sun_family) + len; break; default: fatal("unsupported socket family %d", sock->family); } return (pysocket_op_create(sock, PYSOCKET_TYPE_CONNECT, NULL, 0)); } static PyObject * pysocket_close(struct pysocket *sock, PyObject *args) { if (sock->scheduled) { sock->scheduled = 0; kore_platform_disable_read(sock->fd); #if !defined(__linux__) kore_platform_disable_write(sock->fd); #endif } if (sock->socket != NULL) { Py_DECREF(sock->socket); sock->socket = NULL; } else if (sock->fd != -1) { (void)close(sock->fd); } sock->fd = -1; sock->event.evt.handle(&sock->event, 1); Py_RETURN_TRUE; } static void pysocket_op_dealloc(struct pysocket_op *op) { if (op->type == PYSOCKET_TYPE_RECV || op->type == PYSOCKET_TYPE_RECVMSG || op->type == PYSOCKET_TYPE_RECVFROM || op->type == PYSOCKET_TYPE_SEND || op->type == PYSOCKET_TYPE_SENDTO) kore_buf_cleanup(&op->buffer); switch (op->type) { case PYSOCKET_TYPE_RECV: case PYSOCKET_TYPE_ACCEPT: case PYSOCKET_TYPE_RECVMSG: case PYSOCKET_TYPE_RECVFROM: if (op->socket->recvop != op) fatal("recvop mismatch"); op->socket->recvop = NULL; break; case PYSOCKET_TYPE_SEND: case PYSOCKET_TYPE_SENDTO: case PYSOCKET_TYPE_CONNECT: if (op->socket->sendop != op) fatal("sendop mismatch"); op->socket->sendop = NULL; break; } if (op->timer != NULL) { kore_timer_remove(op->timer); op->timer = NULL; } op->coro->sockop = NULL; Py_DECREF(op->socket); PyObject_Del((PyObject *)op); } static PyObject * pysocket_op_create(struct pysocket *sock, int type, const void *ptr, size_t len) { struct pysocket_op *op; if (coro_running->sockop != NULL) fatal("pysocket_op_create: coro has active socketop"); switch (type) { case PYSOCKET_TYPE_RECV: case PYSOCKET_TYPE_ACCEPT: case PYSOCKET_TYPE_RECVMSG: case PYSOCKET_TYPE_RECVFROM: if (sock->recvop != NULL) { PyErr_SetString(PyExc_RuntimeError, "only one recv operation can be done per socket"); return (NULL); } break; case PYSOCKET_TYPE_SEND: case PYSOCKET_TYPE_SENDTO: case PYSOCKET_TYPE_CONNECT: if (sock->sendop != NULL) { PyErr_SetString(PyExc_RuntimeError, "only one send operation can be done per socket"); return (NULL); } break; default: fatal("unknown pysocket_op type %u", type); } op = PyObject_New(struct pysocket_op, &pysocket_op_type); if (op == NULL) return (NULL); op->eof = 0; op->self = op; op->type = type; op->timer = NULL; op->socket = sock; op->coro = coro_running; coro_running->sockop = op; Py_INCREF(op->socket); switch (type) { case PYSOCKET_TYPE_RECV: case PYSOCKET_TYPE_RECVMSG: case PYSOCKET_TYPE_RECVFROM: sock->recvop = op; kore_buf_init(&op->buffer, len); break; case PYSOCKET_TYPE_SEND: case PYSOCKET_TYPE_SENDTO: sock->sendop = op; kore_buf_init(&op->buffer, len); kore_buf_append(&op->buffer, ptr, len); kore_buf_reset(&op->buffer); break; case PYSOCKET_TYPE_ACCEPT: sock->recvop = op; break; case PYSOCKET_TYPE_CONNECT: sock->sendop = op; break; default: fatal("unknown pysocket_op type %u", type); } if (sock->scheduled == 0) { sock->scheduled = 1; kore_platform_event_all(sock->fd, &sock->event); } return ((PyObject *)op); } static PyObject * pysocket_op_await(PyObject *obj) { Py_INCREF(obj); return (obj); } static PyObject * pysocket_op_iternext(struct pysocket_op *op) { PyObject *ret; if (op->socket->fd == -1) { PyErr_SetNone(PyExc_StopIteration); return (NULL); } if (op->eof) { if (op->coro->exception != NULL) { PyErr_SetString(op->coro->exception, op->coro->exception_msg); op->coro->exception = NULL; return (NULL); } if (op->type != PYSOCKET_TYPE_RECV) { PyErr_SetString(PyExc_RuntimeError, "socket EOF"); return (NULL); } /* Drain the recv socket. */ op->socket->event.evt.flags |= KORE_EVENT_READ; return (pysocket_async_recv(op)); } switch (op->type) { case PYSOCKET_TYPE_CONNECT: ret = pysocket_async_connect(op); break; case PYSOCKET_TYPE_ACCEPT: ret = pysocket_async_accept(op); break; case PYSOCKET_TYPE_RECV: case PYSOCKET_TYPE_RECVMSG: case PYSOCKET_TYPE_RECVFROM: ret = pysocket_async_recv(op); break; case PYSOCKET_TYPE_SEND: case PYSOCKET_TYPE_SENDTO: ret = pysocket_async_send(op); break; default: PyErr_SetString(PyExc_RuntimeError, "invalid op type"); return (NULL); } return (ret); } static void pysocket_op_timeout(void *arg, u_int64_t now) { struct pysocket_op *op = arg; op->eof = 1; op->timer = NULL; op->coro->exception = PyExc_TimeoutError; op->coro->exception_msg = "timeout before operation completed"; if (op->coro->request != NULL) http_request_wakeup(op->coro->request); else python_coro_wakeup(op->coro); } static PyObject * pysocket_async_connect(struct pysocket_op *op) { if (connect(op->socket->fd, (struct sockaddr *)&op->socket->addr, op->socket->addr_len) == -1) { if (errno != EALREADY && errno != EINPROGRESS && errno != EISCONN && errno != EAGAIN) { PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } if (errno != EISCONN) { Py_RETURN_NONE; } } PyErr_SetNone(PyExc_StopIteration); return (NULL); } static PyObject * pysocket_async_accept(struct pysocket_op *op) { int fd; struct pysocket *sock; if (!(op->socket->event.evt.flags & KORE_EVENT_READ)) { Py_RETURN_NONE; } if ((sock = pysocket_alloc()) == NULL) return (NULL); sock->addr_len = sizeof(sock->addr); if ((fd = accept(op->socket->fd, (struct sockaddr *)&sock->addr, &sock->addr_len)) == -1) { Py_DECREF((PyObject *)sock); if (errno == EAGAIN || errno == EWOULDBLOCK) { op->socket->event.evt.flags &= ~KORE_EVENT_READ; Py_RETURN_NONE; } PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } if (!kore_connection_nonblock(fd, 0)) { Py_DECREF((PyObject *)sock); PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } sock->fd = fd; sock->socket = NULL; sock->family = op->socket->family; sock->protocol = op->socket->protocol; PyErr_SetObject(PyExc_StopIteration, (PyObject *)sock); Py_DECREF((PyObject *)sock); return (NULL); } static PyObject * pysocket_async_recv(struct pysocket_op *op) { ssize_t ret; size_t len; u_int16_t port; struct iovec iov; struct msghdr msg; socklen_t socklen; struct sockaddr *sendaddr; const char *ptr, *ip; u_int8_t ancdata[1024]; PyObject *bytes, *result, *tuple, *list; if (!(op->socket->event.evt.flags & KORE_EVENT_READ)) { Py_RETURN_NONE; } socklen = 0; for (;;) { switch (op->type) { case PYSOCKET_TYPE_RECV: ret = read(op->socket->fd, op->buffer.data, op->buffer.length); break; case PYSOCKET_TYPE_RECVMSG: memset(&msg, 0, sizeof(msg)); iov.iov_base = op->buffer.data; iov.iov_len = op->buffer.length; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = &op->sendaddr; msg.msg_namelen = sizeof(op->sendaddr); msg.msg_control = ancdata; msg.msg_controllen = sizeof(ancdata); memset(&op->sendaddr, 0, sizeof(op->sendaddr)); ret = recvmsg(op->socket->fd, &msg, 0); break; case PYSOCKET_TYPE_RECVFROM: sendaddr = (struct sockaddr *)&op->sendaddr; switch (op->socket->family) { case AF_INET: socklen = sizeof(op->sendaddr.ipv4); break; case AF_UNIX: socklen = sizeof(op->sendaddr.sun); break; default: fatal("%s: non AF_INET/AF_UNIX", __func__); } memset(sendaddr, 0, socklen); ret = recvfrom(op->socket->fd, op->buffer.data, op->buffer.length, 0, sendaddr, &socklen); break; default: fatal("%s: unknown type %d", __func__, op->type); } if (ret == -1) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { op->socket->event.evt.flags &= ~KORE_EVENT_READ; Py_RETURN_NONE; } PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } break; } op->coro->exception = NULL; op->coro->exception_msg = NULL; if (op->timer != NULL) { kore_timer_remove(op->timer); op->timer = NULL; } if (op->type == PYSOCKET_TYPE_RECV && ret == 0) { PyErr_SetNone(PyExc_StopIteration); return (NULL); } ptr = (const char *)op->buffer.data; if ((bytes = PyBytes_FromStringAndSize(ptr, ret)) == NULL) return (NULL); list = NULL; switch (op->type) { case PYSOCKET_TYPE_RECV: PyErr_SetObject(PyExc_StopIteration, bytes); Py_DECREF(bytes); return (NULL); case PYSOCKET_TYPE_RECVMSG: socklen = msg.msg_namelen; if ((list = python_cmsg_to_list(&msg)) == NULL) { Py_DECREF(bytes); return (NULL); } break; case PYSOCKET_TYPE_RECVFROM: break; default: fatal("%s: unknown type %d", __func__, op->type); } switch(op->socket->family) { case AF_INET: port = ntohs(op->sendaddr.ipv4.sin_port); ip = inet_ntoa(op->sendaddr.ipv4.sin_addr); if (op->type == PYSOCKET_TYPE_RECVFROM) tuple = Py_BuildValue("(sHN)", ip, port, bytes); else tuple = Py_BuildValue("(sHNN)", ip, port, bytes, list); break; case AF_UNIX: len = strlen(op->sendaddr.sun.sun_path); #if defined(__linux__) if (len == 0 && socklen > 0) { len = socklen - sizeof(sa_family_t); op->sendaddr.sun.sun_path[0] = '@'; op->sendaddr.sun.sun_path[len] = '\0'; } #endif if (len == 0) { if (op->type == PYSOCKET_TYPE_RECVFROM) { tuple = Py_BuildValue("(ON)", Py_None, bytes); } else { tuple = Py_BuildValue("(ONN)", Py_None, bytes, list); } } else { if (op->type == PYSOCKET_TYPE_RECVFROM) { tuple = Py_BuildValue("(sN)", op->sendaddr.sun.sun_path, bytes); } else { tuple = Py_BuildValue("(sNN)", op->sendaddr.sun.sun_path, bytes, list); } } break; default: fatal("%s: non AF_INET/AF_UNIX", __func__); } if (tuple == NULL) { Py_XDECREF(list); Py_DECREF(bytes); return (NULL); } result = PyObject_CallFunctionObjArgs(PyExc_StopIteration, tuple, NULL); if (result == NULL) { Py_DECREF(tuple); return (NULL); } Py_DECREF(tuple); PyErr_SetObject(PyExc_StopIteration, result); Py_DECREF(result); return (NULL); } static PyObject * pysocket_async_send(struct pysocket_op *op) { ssize_t ret; socklen_t socklen; const struct sockaddr *sendaddr; if (!(op->socket->event.evt.flags & KORE_EVENT_WRITE)) { Py_RETURN_NONE; } for (;;) { if (op->type == PYSOCKET_TYPE_SEND) { ret = write(op->socket->fd, op->buffer.data + op->buffer.offset, op->buffer.length - op->buffer.offset); } else { sendaddr = (const struct sockaddr *)&op->sendaddr; switch (op->socket->family) { case AF_INET: socklen = sizeof(op->sendaddr.ipv4); break; case AF_UNIX: socklen = sizeof(op->sendaddr.sun); #if defined(__linux__) if (op->sendaddr.sun.sun_path[0] == '@') { socklen = sizeof(sa_family_t) + strlen(op->sendaddr.sun.sun_path); op->sendaddr.sun.sun_path[0] = '\0'; } #endif break; default: fatal("non AF_INET/AF_UNIX in %s", __func__); } ret = sendto(op->socket->fd, op->buffer.data + op->buffer.offset, op->buffer.length - op->buffer.offset, 0, sendaddr, socklen); } if (ret == -1) { if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) { op->socket->event.evt.flags &= ~KORE_EVENT_WRITE; Py_RETURN_NONE; } PyErr_SetString(PyExc_RuntimeError, errno_s); return (NULL); } break; } op->buffer.offset += (size_t)ret; if (op->buffer.offset == op->buffer.length) { PyErr_SetNone(PyExc_StopIteration); return (NULL); } Py_RETURN_NONE; } static void pysocket_evt_handle(void *arg, int eof) { struct pysocket_event *event = arg; struct pysocket *socket = event->s; if ((eof || (event->evt.flags & KORE_EVENT_READ)) && socket->recvop != NULL) { if (socket->recvop->coro->request != NULL) http_request_wakeup(socket->recvop->coro->request); else python_coro_wakeup(socket->recvop->coro); socket->recvop->eof = eof; } if ((eof || (event->evt.flags & KORE_EVENT_WRITE)) && socket->sendop != NULL) { if (socket->sendop->coro->request != NULL) http_request_wakeup(socket->sendop->coro->request); else python_coro_wakeup(socket->sendop->coro); socket->sendop->eof = eof; } } static void pyqueue_dealloc(struct pyqueue *queue) { struct pyqueue_object *object; struct pyqueue_waiting *waiting; while ((object = TAILQ_FIRST(&queue->objects)) != NULL) { TAILQ_REMOVE(&queue->objects, object, list); Py_DECREF(object->obj); kore_pool_put(&queue_object_pool, object); } while ((waiting = TAILQ_FIRST(&queue->waiting)) != NULL) { TAILQ_REMOVE(&queue->waiting, waiting, list); if (waiting->op != NULL) waiting->op->waiting = NULL; kore_pool_put(&queue_wait_pool, waiting); } PyObject_Del((PyObject *)queue); } static PyObject * pyqueue_pop(struct pyqueue *queue, PyObject *args) { struct pyqueue_op *op; if ((op = PyObject_New(struct pyqueue_op, &pyqueue_op_type)) == NULL) return (NULL); op->queue = queue; op->waiting = kore_pool_get(&queue_wait_pool); op->waiting->op = op; op->waiting->coro = coro_running; TAILQ_INSERT_TAIL(&queue->waiting, op->waiting, list); Py_INCREF((PyObject *)queue); return ((PyObject *)op); } static PyObject * pyqueue_popnow(struct pyqueue *queue, PyObject *args) { PyObject *obj; struct pyqueue_object *object; if ((object = TAILQ_FIRST(&queue->objects)) == NULL) { Py_RETURN_NONE; } TAILQ_REMOVE(&queue->objects, object, list); obj = object->obj; kore_pool_put(&queue_object_pool, object); return (obj); } static PyObject * pyqueue_push(struct pyqueue *queue, PyObject *args) { PyObject *obj; struct pyqueue_object *object; struct pyqueue_waiting *waiting; if (!PyArg_ParseTuple(args, "O", &obj)) return (NULL); Py_INCREF(obj); object = kore_pool_get(&queue_object_pool); object->obj = obj; TAILQ_INSERT_TAIL(&queue->objects, object, list); /* Wakeup first in line if any. */ if ((waiting = TAILQ_FIRST(&queue->waiting)) != NULL) { TAILQ_REMOVE(&queue->waiting, waiting, list); /* wakeup HTTP request if one is tied. */ if (waiting->coro->request != NULL) http_request_wakeup(waiting->coro->request); else python_coro_wakeup(waiting->coro); waiting->op->waiting = NULL; kore_pool_put(&queue_wait_pool, waiting); } Py_RETURN_TRUE; } static void pyqueue_op_dealloc(struct pyqueue_op *op) { if (op->waiting != NULL) { TAILQ_REMOVE(&op->queue->waiting, op->waiting, list); kore_pool_put(&queue_wait_pool, op->waiting); op->waiting = NULL; } Py_DECREF((PyObject *)op->queue); PyObject_Del((PyObject *)op); } static PyObject * pyqueue_op_await(PyObject *obj) { Py_INCREF(obj); return (obj); } static PyObject * pyqueue_op_iternext(struct pyqueue_op *op) { PyObject *obj; struct pyqueue_object *object; struct pyqueue_waiting *waiting; if ((object = TAILQ_FIRST(&op->queue->objects)) == NULL) { Py_RETURN_NONE; } TAILQ_REMOVE(&op->queue->objects, object, list); obj = object->obj; kore_pool_put(&queue_object_pool, object); TAILQ_FOREACH(waiting, &op->queue->waiting, list) { if (waiting->coro->id == coro_running->id) { TAILQ_REMOVE(&op->queue->waiting, waiting, list); waiting->op->waiting = NULL; kore_pool_put(&queue_wait_pool, waiting); break; } } PyErr_SetObject(PyExc_StopIteration, obj); Py_DECREF(obj); return (NULL); } static void pylock_dealloc(struct pylock *lock) { struct pylock_op *op; while ((op = TAILQ_FIRST(&lock->ops)) != NULL) { TAILQ_REMOVE(&lock->ops, op, list); op->active = 0; op->coro->lockop = NULL; Py_DECREF((PyObject *)op); } PyObject_Del((PyObject *)lock); } static PyObject * pylock_trylock(struct pylock *lock, PyObject *args) { if (lock->owner != NULL) Py_RETURN_FALSE; lock->owner = coro_running; Py_RETURN_TRUE; } static PyObject * pylock_release(struct pylock *lock, PyObject *args) { if (lock->owner == NULL) { PyErr_SetString(PyExc_RuntimeError, "no lock owner set"); return (NULL); } if (lock->owner->id != coro_running->id) { PyErr_SetString(PyExc_RuntimeError, "lock not owned by caller"); return (NULL); } pylock_do_release(lock); Py_RETURN_NONE; } static PyObject * pylock_aenter(struct pylock *lock, PyObject *args) { struct pylock_op *op; if (coro_running->lockop != NULL) { fatal("%s: lockop not NULL for %" PRIu64, __func__, coro_running->id); } if (lock->owner != NULL && lock->owner->id == coro_running->id) { PyErr_SetString(PyExc_RuntimeError, "recursive lock detected"); return (NULL); } if ((op = PyObject_New(struct pylock_op, &pylock_op_type)) == NULL) return (NULL); op->active = 1; op->lock = lock; op->locking = 1; op->coro = coro_running; coro_running->lockop = op; Py_INCREF((PyObject *)op); Py_INCREF((PyObject *)lock); TAILQ_INSERT_TAIL(&lock->ops, op, list); return ((PyObject *)op); } static PyObject * pylock_aexit(struct pylock *lock, PyObject *args) { struct pylock_op *op; if (coro_running->lockop != NULL) { fatal("%s: lockop not NULL for %" PRIu64, __func__, coro_running->id); } if (lock->owner == NULL || lock->owner->id != coro_running->id) { PyErr_SetString(PyExc_RuntimeError, "invalid lock owner"); return (NULL); } if ((op = PyObject_New(struct pylock_op, &pylock_op_type)) == NULL) return (NULL); op->active = 1; op->lock = lock; op->locking = 0; op->coro = coro_running; coro_running->lockop = op; Py_INCREF((PyObject *)op); Py_INCREF((PyObject *)lock); TAILQ_INSERT_TAIL(&lock->ops, op, list); return ((PyObject *)op); } static void pylock_do_release(struct pylock *lock) { struct pylock_op *op; lock->owner = NULL; TAILQ_FOREACH(op, &lock->ops, list) { if (op->locking == 0) continue; op->active = 0; op->coro->lockop = NULL; TAILQ_REMOVE(&lock->ops, op, list); if (op->coro->request != NULL) http_request_wakeup(op->coro->request); else python_coro_wakeup(op->coro); Py_DECREF((PyObject *)op); break; } } static void pylock_op_dealloc(struct pylock_op *op) { if (op->active) { TAILQ_REMOVE(&op->lock->ops, op, list); op->active = 0; } op->coro->lockop = NULL; Py_DECREF((PyObject *)op->lock); PyObject_Del((PyObject *)op); } static PyObject * pylock_op_await(PyObject *obj) { Py_INCREF(obj); return (obj); } static PyObject * pylock_op_iternext(struct pylock_op *op) { if (op->locking == 0) { if (op->lock->owner == NULL) { PyErr_SetString(PyExc_RuntimeError, "no lock owner set"); return (NULL); } if (op->lock->owner->id != coro_running->id) { PyErr_SetString(PyExc_RuntimeError, "lock not owned by caller"); return (NULL); } pylock_do_release(op->lock); } else { if (op->lock->owner != NULL) { /* * We could be beat by another coroutine that grabbed * the lock even if we were the one woken up for it. */ if (op->active == 0) { op->active = 1; op->coro->lockop = op; TAILQ_INSERT_HEAD(&op->lock->ops, op, list); Py_INCREF((PyObject *)op); } Py_RETURN_NONE; } op->lock->owner = coro_running; } if (op->active) { op->active = 0; op->coro->lockop = NULL; TAILQ_REMOVE(&op->lock->ops, op, list); Py_DECREF((PyObject *)op); } PyErr_SetNone(PyExc_StopIteration); return (NULL); } static void pyproc_timeout(void *arg, u_int64_t now) { struct pyproc *proc = arg; proc->timer = NULL; if (proc->coro->sockop != NULL) proc->coro->sockop->eof = 1; proc->coro->exception = PyExc_TimeoutError; proc->coro->exception_msg = "timeout before process exited"; if (proc->coro->request != NULL) http_request_wakeup(proc->coro->request); else python_coro_wakeup(proc->coro); } static void pyproc_dealloc(struct pyproc *proc) { int status; TAILQ_REMOVE(&procs, proc, list); if (proc->timer != NULL) { kore_timer_remove(proc->timer); proc->timer = NULL; } if (proc->pid != -1) { if (kill(proc->pid, SIGKILL) == -1) { kore_log(LOG_NOTICE, "kore.proc failed to send SIGKILL %d (%s)", proc->pid, errno_s); } for (;;) { if (waitpid(proc->pid, &status, 0) == -1) { if (errno == EINTR) continue; kore_log(LOG_NOTICE, "kore.proc failed to wait for %d (%s)", proc->pid, errno_s); } break; } } if (proc->in != NULL) { Py_DECREF((PyObject *)proc->in); proc->in = NULL; } if (proc->out != NULL) { Py_DECREF((PyObject *)proc->out); proc->out = NULL; } PyObject_Del((PyObject *)proc); } static PyObject * pyproc_kill(struct pyproc *proc, PyObject *args) { if (proc->pid != -1 && kill(proc->pid, SIGKILL) == -1) kore_log(LOG_NOTICE, "kill(%d): %s", proc->pid, errno_s); Py_RETURN_TRUE; } static PyObject * pyproc_reap(struct pyproc *proc, PyObject *args) { struct pyproc_op *op; if (proc->op != NULL) { PyErr_Format(PyExc_RuntimeError, "process %d already being reaped", proc->apid); return (NULL); } if (proc->timer != NULL) { kore_timer_remove(proc->timer); proc->timer = NULL; } if ((op = PyObject_New(struct pyproc_op, &pyproc_op_type)) == NULL) return (NULL); op->proc = proc; op->coro = coro_running; proc->op = op; Py_INCREF((PyObject *)proc); return ((PyObject *)op); } static PyObject * pyproc_recv(struct pyproc *proc, PyObject *args) { Py_ssize_t len; struct pysocket_op *op; PyObject *obj; int timeo; timeo = -1; if (proc->out == NULL) { PyErr_SetString(PyExc_RuntimeError, "stdout closed"); return (NULL); } if (!PyArg_ParseTuple(args, "n|i", &len, &timeo)) return (NULL); obj = pysocket_op_create(proc->out, PYSOCKET_TYPE_RECV, NULL, len); if (obj == NULL) return (NULL); op = (struct pysocket_op *)obj; if (timeo != -1) { op->timer = kore_timer_add(pysocket_op_timeout, timeo, op, KORE_TIMER_ONESHOT); } return (obj); } static PyObject * pyproc_send(struct pyproc *proc, PyObject *args) { Py_buffer buf; PyObject *ret; if (proc->in == NULL) { PyErr_SetString(PyExc_RuntimeError, "stdin closed"); return (NULL); } if (!PyArg_ParseTuple(args, "y*", &buf)) return (NULL); ret = pysocket_op_create(proc->in, PYSOCKET_TYPE_SEND, buf.buf, buf.len); PyBuffer_Release(&buf); return (ret); } static PyObject * pyproc_close_stdin(struct pyproc *proc, PyObject *args) { if (proc->in != NULL) { Py_DECREF((PyObject *)proc->in); proc->in = NULL; } Py_RETURN_TRUE; } static PyObject * pyproc_get_pid(struct pyproc *proc, void *closure) { return (PyLong_FromLong(proc->apid)); } static void pyproc_op_dealloc(struct pyproc_op *op) { Py_DECREF((PyObject *)op->proc); PyObject_Del((PyObject *)op); } static PyObject * pyproc_op_await(PyObject *sop) { Py_INCREF(sop); return (sop); } static PyObject * pyproc_op_iternext(struct pyproc_op *op) { int ret; PyObject *res; if (op->proc->coro->exception != NULL) { PyErr_SetString(op->proc->coro->exception, op->proc->coro->exception_msg); op->proc->coro->exception = NULL; return (NULL); } if (op->proc->reaped == 0) Py_RETURN_NONE; if (WIFSTOPPED(op->proc->status)) { op->proc->reaped = 0; Py_RETURN_NONE; } if (WIFEXITED(op->proc->status)) { ret = WEXITSTATUS(op->proc->status); } else { ret = op->proc->status; } if ((res = PyLong_FromLong(ret)) == NULL) return (NULL); PyErr_SetObject(PyExc_StopIteration, res); Py_DECREF(res); return (NULL); } static void pygather_reap_coro(struct pygather_op *op, struct python_coro *reap) { struct pygather_coro *coro; struct pygather_result *result; #if PY_VERSION_HEX >= 0x030A0000 PyObject *type, *traceback; #endif TAILQ_FOREACH(coro, &op->coroutines, list) { if (coro->coro->id == reap->id) break; } if (coro == NULL) fatal("coroutine %" PRIu64 " not found in gather", reap->id); op->running--; if (op->running < 0) fatal("gatherop: running miscount (%d)", op->running); result = kore_pool_get(&gather_result_pool); result->obj = NULL; #if PY_VERSION_HEX < 0x030A0000 if (_PyGen_FetchStopIterationValue(&result->obj) == -1) { result->obj = Py_None; Py_INCREF(Py_None); } #else if (PyErr_Occurred()) { Py_XDECREF(coro->coro->result); PyErr_Fetch(&type, &coro->coro->result, &traceback); Py_DECREF(type); Py_XDECREF(traceback); } else { if (coro->coro->result == NULL) { coro->coro->result = Py_None; Py_INCREF(Py_None); } } result->obj = coro->coro->result; Py_INCREF(result->obj); #endif TAILQ_INSERT_TAIL(&op->results, result, list); TAILQ_REMOVE(&op->coroutines, coro, list); kore_pool_put(&gather_coro_pool, coro); kore_python_coro_delete(reap); } static void pygather_op_dealloc(struct pygather_op *op) { struct python_coro *old; struct pygather_coro *coro, *next; struct pygather_result *res, *rnext; /* * Since we are calling kore_python_coro_delete() on all the * remaining coroutines in this gather op we must remember the * original coroutine that is running as the removal will end * up setting coro_running to NULL. */ old = coro_running; for (coro = TAILQ_FIRST(&op->coroutines); coro != NULL; coro = next) { next = TAILQ_NEXT(coro, list); TAILQ_REMOVE(&op->coroutines, coro, list); /* Make sure we don't end up in pygather_reap_coro(). */ coro->coro->gatherop = NULL; kore_python_coro_delete(coro->coro); kore_pool_put(&gather_coro_pool, coro); } coro_running = old; for (res = TAILQ_FIRST(&op->results); res != NULL; res = rnext) { rnext = TAILQ_NEXT(res, list); TAILQ_REMOVE(&op->results, res, list); Py_DECREF(res->obj); kore_pool_put(&gather_result_pool, res); } PyObject_Del((PyObject *)op); } static PyObject * pygather_op_await(PyObject *obj) { Py_INCREF(obj); return (obj); } static PyObject * pygather_op_iternext(struct pygather_op *op) { int idx; struct pygather_coro *coro; struct pygather_result *res, *next; PyObject *list, *obj; if (!TAILQ_EMPTY(&op->coroutines)) { if (op->running > 0) Py_RETURN_NONE; TAILQ_FOREACH(coro, &op->coroutines, list) { if (op->running >= op->concurrency) break; python_coro_wakeup(coro->coro); op->running++; } Py_RETURN_NONE; } if ((list = PyList_New(op->count)) == NULL) return (NULL); idx = 0; for (res = TAILQ_FIRST(&op->results); res != NULL; res = next) { next = TAILQ_NEXT(res, list); TAILQ_REMOVE(&op->results, res, list); obj = res->obj; res->obj = NULL; kore_pool_put(&gather_result_pool, res); if (PyList_SetItem(list, idx++, obj) != 0) { Py_DECREF(list); return (NULL); } } PyErr_SetObject(PyExc_StopIteration, list); Py_DECREF(list); return (NULL); } static PyObject * pyhttp_request_alloc(const struct http_request *req) { union { const void *cp; void *p; } ptr; struct pyhttp_request *pyreq; pyreq = PyObject_New(struct pyhttp_request, &pyhttp_request_type); if (pyreq == NULL) return (NULL); /* * Hack around all http apis taking a non-const pointer and us having * a const pointer for the req data structure. This is because we * could potentially be called from a validator where the argument * is a http_request pointer. */ ptr.cp = req; pyreq->req = ptr.p; pyreq->data = NULL; pyreq->dict = NULL; return ((PyObject *)pyreq); } static PyObject * pyhttp_file_alloc(struct http_file *file) { struct pyhttp_file *pyfile; pyfile = PyObject_New(struct pyhttp_file, &pyhttp_file_type); if (pyfile == NULL) return (NULL); pyfile->file = file; return ((PyObject *)pyfile); } static int pyhttp_preprocess(struct http_request *req) { struct reqcall *rq; PyObject *ret; rq = req->py_rqnext; while (rq) { req->py_rqnext = TAILQ_NEXT(rq, list); PyErr_Clear(); ret = PyObject_CallFunctionObjArgs(rq->f, req->py_req, NULL); if (ret == NULL) { kore_python_log_error("preprocess"); http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return (KORE_RESULT_ERROR); } if (ret == Py_False) { Py_DECREF(ret); return (KORE_RESULT_ERROR); } if (PyCoro_CheckExact(ret)) { req->py_coro = python_coro_create(ret, req); if (python_coro_run(req->py_coro) == KORE_RESULT_OK) { http_request_wakeup(req); kore_python_coro_delete(req->py_coro); req->py_coro = NULL; rq = req->py_rqnext; continue; } return (KORE_RESULT_RETRY); } Py_DECREF(ret); rq = req->py_rqnext; } return (KORE_RESULT_OK); } static PyObject * pyhttp_response(struct pyhttp_request *pyreq, PyObject *args) { struct connection *c; char *ptr; Py_ssize_t length; int status; struct pyhttp_iterobj *iterobj; PyObject *obj, *iterator; length = -1; if (!PyArg_ParseTuple(args, "iO", &status, &obj)) return (NULL); if (PyBytes_CheckExact(obj)) { if (PyBytes_AsStringAndSize(obj, &ptr, &length) == -1) return (NULL); if (length < 0) { PyErr_SetString(PyExc_TypeError, "invalid length"); return (NULL); } Py_INCREF(obj); http_response_stream(pyreq->req, status, ptr, length, pyhttp_response_sent, obj); } else if (obj == Py_None) { http_response(pyreq->req, status, NULL, 0); } else { c = pyreq->req->owner; if (c->state == CONN_STATE_DISCONNECTING) { Py_RETURN_FALSE; } if ((iterator = PyObject_GetIter(obj)) == NULL) return (NULL); iterobj = kore_pool_get(&iterobj_pool); iterobj->iterator = iterator; iterobj->connection = c; iterobj->remove = 0; kore_buf_init(&iterobj->buf, 4096); c->hdlr_extra = iterobj; c->flags |= CONN_IS_BUSY; c->disconnect = pyhttp_iterobj_disconnect; pyreq->req->flags |= HTTP_REQUEST_NO_CONTENT_LENGTH; http_response_header(pyreq->req, "transfer-encoding", "chunked"); http_response(pyreq->req, status, NULL, 0); pyhttp_iterobj_next(iterobj); } Py_RETURN_TRUE; } static int pyhttp_response_sent(struct netbuf *nb) { PyObject *data; data = nb->extra; Py_DECREF(data); return (KORE_RESULT_OK); } static int pyhttp_iterobj_next(struct pyhttp_iterobj *iterobj) { struct netbuf *nb; PyObject *obj; const char *ptr; Py_ssize_t length; PyErr_Clear(); if ((obj = PyIter_Next(iterobj->iterator)) == NULL) { if (PyErr_Occurred()) { kore_python_log_error("pyhttp_iterobj_next"); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } if ((ptr = PyUnicode_AsUTF8AndSize(obj, &length)) == NULL) { kore_python_log_error("pyhttp_iterobj_next"); return (KORE_RESULT_ERROR); } kore_buf_reset(&iterobj->buf); kore_buf_appendf(&iterobj->buf, "%lx\r\n", length); kore_buf_append(&iterobj->buf, ptr, length); kore_buf_appendf(&iterobj->buf, "\r\n"); Py_DECREF(obj); net_send_stream(iterobj->connection, iterobj->buf.data, iterobj->buf.offset, pyhttp_iterobj_chunk_sent, &nb); nb->extra = iterobj; return (KORE_RESULT_RETRY); } static int pyhttp_iterobj_chunk_sent(struct netbuf *nb) { int ret; struct pyhttp_iterobj *iterobj; iterobj = nb->extra; if (iterobj->remove) { ret = KORE_RESULT_ERROR; } else { ret = pyhttp_iterobj_next(iterobj); } if (ret != KORE_RESULT_RETRY) { iterobj->connection->hdlr_extra = NULL; iterobj->connection->disconnect = NULL; iterobj->connection->flags &= ~CONN_IS_BUSY; if (iterobj->remove == 0) http_start_recv(iterobj->connection); kore_buf_reset(&iterobj->buf); kore_buf_appendf(&iterobj->buf, "0\r\n\r\n"); net_send_queue(iterobj->connection, iterobj->buf.data, iterobj->buf.offset); Py_DECREF(iterobj->iterator); kore_buf_cleanup(&iterobj->buf); kore_pool_put(&iterobj_pool, iterobj); } else { ret = KORE_RESULT_OK; } return (ret); } static void pyhttp_iterobj_disconnect(struct connection *c) { struct pyhttp_iterobj *iterobj; iterobj = c->hdlr_extra; iterobj->remove = 1; c->hdlr_extra = NULL; } static PyObject * pyhttp_response_header(struct pyhttp_request *pyreq, PyObject *args) { const char *header, *value; if (!PyArg_ParseTuple(args, "ss", &header, &value)) return (NULL); http_response_header(pyreq->req, header, value); Py_RETURN_TRUE; } static PyObject * pyhttp_request_header(struct pyhttp_request *pyreq, PyObject *args) { const char *value; const char *header; PyObject *result; if (!PyArg_ParseTuple(args, "s", &header)) return (NULL); if (!http_request_header(pyreq->req, header, &value)) { Py_RETURN_NONE; } if ((result = PyUnicode_FromString(value)) == NULL) return (PyErr_NoMemory()); return (result); } static PyObject * pyhttp_body_read(struct pyhttp_request *pyreq, PyObject *args) { ssize_t ret; size_t len; Py_ssize_t pylen; PyObject *result; u_int8_t buf[1024]; if (!PyArg_ParseTuple(args, "n", &pylen) || pylen < 0) return (NULL); len = (size_t)pylen; if (len > sizeof(buf)) { PyErr_SetString(PyExc_RuntimeError, "len > sizeof(buf)"); return (NULL); } ret = http_body_read(pyreq->req, buf, len); if (ret == -1) { PyErr_SetString(PyExc_RuntimeError, "http_body_read() failed"); return (NULL); } result = Py_BuildValue("ny#", ret, buf, ret); if (result == NULL) return (PyErr_NoMemory()); return (result); } static PyObject * pyhttp_populate_get(struct pyhttp_request *pyreq, PyObject *args) { http_populate_get(pyreq->req); Py_RETURN_TRUE; } static PyObject * pyhttp_populate_post(struct pyhttp_request *pyreq, PyObject *args) { http_populate_post(pyreq->req); Py_RETURN_TRUE; } static PyObject * pyhttp_populate_multi(struct pyhttp_request *pyreq, PyObject *args) { http_populate_multipart_form(pyreq->req); Py_RETURN_TRUE; } static PyObject * pyhttp_populate_cookies(struct pyhttp_request *pyreq, PyObject *args) { http_populate_cookies(pyreq->req); Py_RETURN_TRUE; } static PyObject * pyhttp_argument(struct pyhttp_request *pyreq, PyObject *args) { const char *name; PyObject *value; char *string; if (!PyArg_ParseTuple(args, "s", &name)) return (NULL); if (!http_argument_get_string(pyreq->req, name, &string)) { Py_RETURN_NONE; } if ((value = PyUnicode_FromString(string)) == NULL) return (PyErr_NoMemory()); return (value); } static PyObject * pyhttp_cookie(struct pyhttp_request *pyreq, PyObject *args) { const char *name; PyObject *value; char *string; if (!PyArg_ParseTuple(args, "s", &name)) return (NULL); if (!http_request_cookie(pyreq->req, name, &string)) { Py_RETURN_NONE; } if ((value = PyUnicode_FromString(string)) == NULL) return (NULL); return (value); } static PyObject * pyhttp_file_lookup(struct pyhttp_request *pyreq, PyObject *args) { const char *name; struct http_file *file; PyObject *pyfile; if (!PyArg_ParseTuple(args, "s", &name)) return (NULL); if ((file = http_file_lookup(pyreq->req, name)) == NULL) { Py_RETURN_NONE; } if ((pyfile = pyhttp_file_alloc(file)) == NULL) return (PyErr_NoMemory()); return (pyfile); } static PyObject * pyhttp_file_read(struct pyhttp_file *pyfile, PyObject *args) { ssize_t ret; size_t len; Py_ssize_t pylen; PyObject *result; u_int8_t buf[1024]; if (!PyArg_ParseTuple(args, "n", &pylen) || pylen < 0) return (NULL); len = (size_t)pylen; if (len > sizeof(buf)) { PyErr_SetString(PyExc_RuntimeError, "len > sizeof(buf)"); return (NULL); } ret = http_file_read(pyfile->file, buf, len); if (ret == -1) { PyErr_SetString(PyExc_RuntimeError, "http_file_read() failed"); return (NULL); } result = Py_BuildValue("ny#", ret, buf, ret); if (result == NULL) return (PyErr_NoMemory()); return (result); } static PyObject * pyhttp_websocket_handshake(struct pyhttp_request *pyreq, PyObject *args) { struct connection *c; PyObject *onconnect, *onmsg, *ondisconnect; if (!PyArg_ParseTuple(args, "OOO", &onconnect, &onmsg, &ondisconnect)) return (NULL); kore_websocket_handshake(pyreq->req, NULL, NULL, NULL); c = pyreq->req->owner; Py_INCREF(onconnect); Py_INCREF(onmsg); Py_INCREF(ondisconnect); c->ws_connect = kore_calloc(1, sizeof(struct kore_runtime_call)); c->ws_connect->addr = onconnect; c->ws_connect->runtime = &kore_python_runtime; c->ws_message = kore_calloc(1, sizeof(struct kore_runtime_call)); c->ws_message->addr = onmsg; c->ws_message->runtime = &kore_python_runtime; c->ws_disconnect = kore_calloc(1, sizeof(struct kore_runtime_call)); c->ws_disconnect->addr = ondisconnect; c->ws_disconnect->runtime = &kore_python_runtime; python_runtime_connect(onconnect, c); Py_RETURN_TRUE; } static PyObject * pyconnection_websocket_send(struct pyconnection *pyc, PyObject *args) { int op; ssize_t len; const char *data; if (pyc->c->proto != CONN_PROTO_WEBSOCKET) { PyErr_SetString(PyExc_TypeError, "not a websocket connection"); return (NULL); } len = -1; if (!PyArg_ParseTuple(args, "iy#", &op, &data, &len)) return (NULL); if (len < 0) { PyErr_SetString(PyExc_TypeError, "invalid length"); return (NULL); } switch (op) { case WEBSOCKET_OP_TEXT: case WEBSOCKET_OP_BINARY: break; default: PyErr_SetString(PyExc_TypeError, "invalid op parameter"); return (NULL); } kore_websocket_send(pyc->c, op, data, len); Py_RETURN_TRUE; } static PyObject * python_websocket_broadcast(PyObject *self, PyObject *args) { struct connection *c; ssize_t len; struct pyconnection *pyc; const char *data; PyObject *pysrc; int op, broadcast; len = -1; if (!PyArg_ParseTuple(args, "Oiy#i", &pysrc, &op, &data, &len, &broadcast)) return (NULL); if (len < 0) { PyErr_SetString(PyExc_TypeError, "invalid length"); return (NULL); } switch (op) { case WEBSOCKET_OP_TEXT: case WEBSOCKET_OP_BINARY: break; default: PyErr_SetString(PyExc_TypeError, "invalid op parameter"); return (NULL); } if (pysrc == Py_None) { c = NULL; } else { if (!PyObject_TypeCheck(pysrc, &pyconnection_type)) return (NULL); pyc = (struct pyconnection *)pysrc; c = pyc->c; } kore_websocket_broadcast(c, op, data, len, broadcast); Py_RETURN_TRUE; } static PyObject * pyhttp_get_host(struct pyhttp_request *pyreq, void *closure) { PyObject *host; if ((host = PyUnicode_FromString(pyreq->req->host)) == NULL) return (PyErr_NoMemory()); return (host); } static PyObject * pyhttp_get_path(struct pyhttp_request *pyreq, void *closure) { PyObject *path; if ((path = PyUnicode_FromString(pyreq->req->path)) == NULL) return (PyErr_NoMemory()); return (path); } static PyObject * pyhttp_get_body(struct pyhttp_request *pyreq, void *closure) { ssize_t ret; struct kore_buf buf; PyObject *body; u_int8_t data[BUFSIZ]; kore_buf_init(&buf, 1024); if (!http_body_rewind(pyreq->req)) { PyErr_SetString(PyExc_RuntimeError, "http_body_rewind() failed"); return (NULL); } for (;;) { ret = http_body_read(pyreq->req, data, sizeof(data)); if (ret == -1) { kore_buf_cleanup(&buf); PyErr_SetString(PyExc_RuntimeError, "http_body_read() failed"); return (NULL); } if (ret == 0) break; kore_buf_append(&buf, data, (size_t)ret); } body = PyBytes_FromStringAndSize((char *)buf.data, buf.offset); kore_buf_free(&buf); if (body == NULL) return (PyErr_NoMemory()); return (body); } static PyObject * pyhttp_get_agent(struct pyhttp_request *pyreq, void *closure) { return (PyUnicode_FromString(pyreq->req->path)); } static PyObject * pyhttp_get_method(struct pyhttp_request *pyreq, void *closure) { return (PyLong_FromUnsignedLong(pyreq->req->method)); } static PyObject * pyhttp_get_protocol(struct pyhttp_request *pyreq, void *closure) { struct connection *c; const char *proto; c = pyreq->req->owner; if (c->owner->server->tls) proto = "https"; else proto = "http"; return (PyUnicode_FromString(proto)); } static PyObject * pyhttp_get_body_path(struct pyhttp_request *pyreq, void *closure) { if (pyreq->req->http_body_path == NULL) { Py_RETURN_NONE; } return (PyUnicode_FromString(pyreq->req->http_body_path)); } static PyObject * pyhttp_get_body_digest(struct pyhttp_request *pyreq, void *closure) { PyObject *digest; digest = PyBytes_FromStringAndSize((char *)pyreq->req->http_body_digest, sizeof(pyreq->req->http_body_digest)); return (digest); } static PyObject * pyhttp_get_connection(struct pyhttp_request *pyreq, void *closure) { PyObject *pyc; if (pyreq->req->owner == NULL) { Py_RETURN_NONE; } if ((pyc = pyconnection_alloc(pyreq->req->owner)) == NULL) return (PyErr_NoMemory()); return (pyc); } static PyObject * pyhttp_file_get_name(struct pyhttp_file *pyfile, void *closure) { PyObject *name; if ((name = PyUnicode_FromString(pyfile->file->name)) == NULL) return (PyErr_NoMemory()); return (name); } static PyObject * pyhttp_file_get_filename(struct pyhttp_file *pyfile, void *closure) { PyObject *name; if ((name = PyUnicode_FromString(pyfile->file->filename)) == NULL) return (PyErr_NoMemory()); return (name); } void pyroute_dealloc(struct pyroute *route) { kore_free(route->path); Py_XDECREF(route->func); Py_XDECREF(route->kwargs); PyObject_Del((PyObject *)route); } static PyObject * pyroute_inner(struct pyroute *route, PyObject *args) { PyObject *obj; if (!PyArg_ParseTuple(args, "O", &obj)) return (NULL); if (!PyCallable_Check(obj)) return (NULL); route->func = obj; Py_INCREF(route->func); TAILQ_INSERT_TAIL(&routes, route, list); return (route->func); } void pydomain_dealloc(struct pydomain *domain) { PyObject_Del((PyObject *)domain); } static int pydomain_set_accesslog(struct pydomain *domain, PyObject *arg, void *closure) { const char *path; if (!PyUnicode_CheckExact(arg)) return (-1); if (domain->config->accesslog != -1) { PyErr_Format(PyExc_RuntimeError, "domain %s accesslog already set", domain->config->domain); return (-1); } path = PyUnicode_AsUTF8(arg); domain->config->accesslog = open(path, O_CREAT | O_APPEND | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (domain->config->accesslog == -1) { PyErr_Format(PyExc_RuntimeError, "failed to open accesslog for %s (%s:%s)", domain->config->domain, path, errno_s); return (-1); } return (0); } static PyObject * pydomain_filemaps(struct pydomain *domain, PyObject *args) { Py_ssize_t idx; struct kore_route *rt; const char *url, *path; PyObject *dict, *key, *value, *auth; if (!PyArg_ParseTuple(args, "O", &dict)) return (NULL); if (!PyDict_CheckExact(dict)) { PyErr_SetString(PyExc_RuntimeError, "filemaps not a dict"); return (NULL); } idx = 0; while (PyDict_Next(dict, &idx, &key, &value)) { if (!PyUnicode_CheckExact(key)) { PyErr_SetString(PyExc_RuntimeError, "filemap key not a string"); return (NULL); } url = PyUnicode_AsUTF8(key); if (!PyUnicode_CheckExact(value) && !PyTuple_CheckExact(value)) { PyErr_SetString(PyExc_RuntimeError, "filemap value can be either be a string or tuple"); return (NULL); } if (PyTuple_CheckExact(value)) { auth = PyTuple_GetItem(value, 1); if (!PyDict_CheckExact(auth)) { PyErr_SetString(PyExc_RuntimeError, "filemap value tuple auth is not a dict"); return (NULL); } value = PyTuple_GetItem(value, 0); if (!PyUnicode_CheckExact(value)) { PyErr_SetString(PyExc_RuntimeError, "filemap value tuple path is invalid"); return (NULL); } } else { auth = NULL; } path = PyUnicode_AsUTF8(value); rt = kore_filemap_create(domain->config, path, url, NULL); if (rt == NULL) { PyErr_Format(PyExc_RuntimeError, "failed to create filemap %s->%s for %s", url, path, domain->config->domain); return (NULL); } if (auth != NULL) { if (!python_route_auth(auth, rt)) { kore_python_log_error("python_route_auth"); kore_route_free(rt); return (KORE_RESULT_ERROR); } } } Py_RETURN_NONE; } static PyObject * pydomain_route(struct pydomain *domain, PyObject *args, PyObject *kwargs) { PyObject *obj; const char *path; struct pyroute *route; if (!PyArg_ParseTuple(args, "sO", &path, &obj)) return (NULL); if (!PyCallable_Check(obj)) return (NULL); if ((route = PyObject_New(struct pyroute, &pyroute_type)) == NULL) return (NULL); route->kwargs = kwargs; route->domain = domain->config; route->path = kore_strdup(path); Py_XINCREF(route->kwargs); route->func = obj; Py_INCREF(route->func); TAILQ_INSERT_TAIL(&routes, route, list); Py_RETURN_NONE; } static int python_route_install(struct pyroute *route) { const char *val; struct kore_domain *domain; struct kore_route *rt, *entry; PyObject *kwargs, *repr, *obj; if ((repr = PyObject_Repr(route->func)) == NULL) { kore_python_log_error("python_route_install"); return (KORE_RESULT_ERROR); } domain = python_route_domain_resolve(route); rt = kore_calloc(1, sizeof(*rt)); rt->dom = domain; rt->methods = HTTP_METHOD_ALL; rt->path = kore_strdup(route->path); TAILQ_INIT(&rt->params); val = PyUnicode_AsUTF8(repr); rt->func = kore_strdup(val); kwargs = route->kwargs; rt->rcall = kore_calloc(1, sizeof(struct kore_runtime_call)); rt->rcall->addr = route->func; rt->rcall->runtime = &kore_python_runtime; Py_INCREF(rt->rcall->addr); if (kwargs != NULL) { if ((obj = PyDict_GetItemString(kwargs, "methods")) != NULL) { if (!python_route_methods(obj, kwargs, rt)) { kore_python_log_error("python_route_install"); kore_route_free(rt); return (KORE_RESULT_ERROR); } } if ((obj = PyDict_GetItemString(kwargs, "auth")) != NULL) { if (!python_route_auth(obj, rt)) { kore_python_log_error("python_route_install"); kore_route_free(rt); return (KORE_RESULT_ERROR); } } if ((obj = PyDict_GetItemString(kwargs, "hooks")) != NULL) { if (!python_route_hooks(obj, rt)) { kore_python_log_error("python_route_install"); kore_route_free(rt); return (KORE_RESULT_ERROR); } } } if (rt->path[0] == '/') { rt->type = HANDLER_TYPE_STATIC; } else { rt->type = HANDLER_TYPE_DYNAMIC; if (regcomp(&rt->rctx, rt->path, REG_EXTENDED)) fatal("failed to compile regex for '%s'", rt->path); } TAILQ_FOREACH(entry, &domain->routes, list) { if (!strcmp(entry->path, rt->path) && (entry->methods & rt->methods)) fatal("duplicate route for '%s'", route->path); } TAILQ_INSERT_TAIL(&domain->routes, rt, list); return (KORE_RESULT_OK); } static struct kore_domain * python_route_domain_resolve(struct pyroute *route) { struct kore_server *srv; const char *name; struct kore_domain *domain; if (route->domain != NULL) return (route->domain); if (route->kwargs != NULL) name = python_string_from_dict(route->kwargs, "domain"); else name = NULL; if (name != NULL) { domain = NULL; LIST_FOREACH(srv, &kore_servers, list) { TAILQ_FOREACH(domain, &srv->domains, list) { if (!strcmp(domain->domain, name)) break; } } if (domain == NULL) fatal("domain '%s' does not exist", name); } else { if ((domain = kore_domain_byid(1)) != NULL) fatal("ambiguous domain on route, please specify one"); if ((domain = kore_domain_byid(0)) == NULL) fatal("no domains configured, please configure one"); } return (domain); } static int python_route_methods(PyObject *obj, PyObject *kwargs, struct kore_route *rt) { const char *val; PyObject *item; int method; Py_ssize_t list_len, idx; if (!PyList_CheckExact(obj)) { PyErr_SetString(PyExc_RuntimeError, "methods not a list"); return (KORE_RESULT_ERROR); } rt->methods = 0; list_len = PyList_Size(obj); for (idx = 0; idx < list_len; idx++) { if ((item = PyList_GetItem(obj, idx)) == NULL) return (KORE_RESULT_ERROR); if ((val = PyUnicode_AsUTF8(item)) == NULL) return (KORE_RESULT_ERROR); if ((method = http_method_value(val)) == 0) { PyErr_Format(PyExc_RuntimeError, "unknown HTTP method: %s", val); return (KORE_RESULT_ERROR); } rt->methods |= method; if (method == HTTP_METHOD_GET) rt->methods |= HTTP_METHOD_HEAD; if (!python_route_params(kwargs, rt, val, method, 0)) return (KORE_RESULT_ERROR); if (!python_route_params(kwargs, rt, "qs", method, 1)) return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } static int python_route_params(PyObject *kwargs, struct kore_route *rt, const char *method, int type, int qs) { Py_ssize_t idx; const char *val; int vtype; struct kore_validator *vldr; struct kore_route_params *param; PyObject *obj, *key, *item; if ((obj = PyDict_GetItemString(kwargs, method)) == NULL) return (KORE_RESULT_OK); if (!PyDict_CheckExact(obj)) return (KORE_RESULT_ERROR); idx = 0; while (PyDict_Next(obj, &idx, &key, &item)) { if (!PyUnicode_CheckExact(key)) return (KORE_RESULT_ERROR); val = PyUnicode_AsUTF8(key); if (PyUnicode_CheckExact(item)) { vtype = KORE_VALIDATOR_TYPE_REGEX; } else if (PyCallable_Check(item)) { vtype = KORE_VALIDATOR_TYPE_FUNCTION; } else { PyErr_Format(PyExc_RuntimeError, "validator '%s' must be regex or function", val); return (KORE_RESULT_ERROR); } vldr = kore_calloc(1, sizeof(*vldr)); vldr->type = vtype; if (vtype == KORE_VALIDATOR_TYPE_REGEX) { val = PyUnicode_AsUTF8(item); if (regcomp(&(vldr->rctx), val, REG_EXTENDED | REG_NOSUB)) { PyErr_Format(PyExc_RuntimeError, "Invalid regex (%s)", val); kore_free(vldr); return (KORE_RESULT_ERROR); } } else { vldr->rcall = kore_calloc(1, sizeof(*vldr->rcall)); vldr->rcall->addr = item; vldr->rcall->runtime = &kore_python_runtime; Py_INCREF(item); } val = PyUnicode_AsUTF8(key); vldr->name = kore_strdup(val); param = kore_calloc(1, sizeof(*param)); param->flags = 0; param->method = type; param->validator = vldr; param->name = kore_strdup(val); if (type == HTTP_METHOD_GET || qs == 1) param->flags = KORE_PARAMS_QUERY_STRING; TAILQ_INSERT_TAIL(&rt->params, param, list); } return (KORE_RESULT_OK); } static int python_route_auth(PyObject *dict, struct kore_route *rt) { int type; struct kore_auth *auth; struct kore_validator *vldr; PyObject *obj, *repr; const char *value, *redir; if (!PyDict_CheckExact(dict)) return (KORE_RESULT_ERROR); if ((value = python_string_from_dict(dict, "type")) == NULL) { PyErr_SetString(PyExc_RuntimeError, "missing or invalid 'type' keyword"); return (KORE_RESULT_ERROR); } if (!strcmp(value, "cookie")) { type = KORE_AUTH_TYPE_COOKIE; } else if (!strcmp(value, "header")) { type = KORE_AUTH_TYPE_HEADER; } else { PyErr_Format(PyExc_RuntimeError, "invalid 'type' (%s) in auth dictionary for '%s'", value, rt->path); return (KORE_RESULT_ERROR); } if ((value = python_string_from_dict(dict, "value")) == NULL) { PyErr_SetString(PyExc_RuntimeError, "missing or invalid 'value' keyword"); return (KORE_RESULT_ERROR); } redir = python_string_from_dict(dict, "redirect"); if ((obj = PyDict_GetItemString(dict, "verify")) == NULL || !PyCallable_Check(obj)) { PyErr_Format(PyExc_RuntimeError, "missing 'verify' in auth dictionary for '%s'", rt->path); return (KORE_RESULT_ERROR); } auth = kore_calloc(1, sizeof(*auth)); auth->type = type; auth->value = kore_strdup(value); if (redir != NULL) auth->redirect = kore_strdup(redir); vldr = kore_calloc(1, sizeof(*vldr)); vldr->type = KORE_VALIDATOR_TYPE_FUNCTION; vldr->rcall = kore_calloc(1, sizeof(*vldr->rcall)); vldr->rcall->addr = obj; vldr->rcall->runtime = &kore_python_runtime; Py_INCREF(obj); if ((repr = PyObject_Repr(obj)) == NULL) { kore_free(vldr->rcall); kore_free(vldr); kore_free(auth); return (KORE_RESULT_ERROR); } value = PyUnicode_AsUTF8(repr); vldr->name = kore_strdup(value); Py_DECREF(repr); auth->validator = vldr; rt->auth = auth; return (KORE_RESULT_OK); } static int python_route_hooks(PyObject *dict, struct kore_route *rt) { if (!PyDict_CheckExact(dict)) return (KORE_RESULT_ERROR); if (!python_route_hook_set(dict, "on_free", &rt->on_free)) return (KORE_RESULT_ERROR); if (!python_route_hook_set(dict, "on_headers", &rt->on_headers)) return (KORE_RESULT_ERROR); if (!python_route_hook_set(dict, "on_body_chunk", &rt->on_body_chunk)) return (KORE_RESULT_ERROR); return (KORE_RESULT_OK); } static int python_route_hook_set(PyObject *dict, const char *name, struct kore_runtime_call **out) { PyObject *obj; struct kore_runtime_call *rcall; if ((obj = PyDict_GetItemString(dict, name)) == NULL) return (KORE_RESULT_OK); if (!PyCallable_Check(obj)) { PyErr_Format(PyExc_RuntimeError, "%s for a route not callable", name); Py_DECREF(obj); return (KORE_RESULT_ERROR); } rcall = kore_calloc(1, sizeof(struct kore_runtime_call)); rcall->addr = obj; rcall->runtime = &kore_python_runtime; Py_INCREF(rcall->addr); *out = rcall; return (KORE_RESULT_OK); } #if defined(KORE_USE_PGSQL) static PyObject * python_kore_pgsql_query(PyObject *self, PyObject *args, PyObject *kwargs) { struct pykore_pgsql *op; PyObject *obj; const char *db, *query; if (!PyArg_ParseTuple(args, "ss", &db, &query)) return (NULL); op = PyObject_New(struct pykore_pgsql, &pykore_pgsql_type); if (op == NULL) return (NULL); op->binary = 0; op->param.count = 0; op->param.objs = NULL; op->param.values = NULL; op->param.lengths = NULL; op->param.formats = NULL; op->result = NULL; op->coro = coro_running; op->db = kore_strdup(db); op->query = kore_strdup(query); op->state = PYKORE_PGSQL_PREINIT; memset(&op->sql, 0, sizeof(op->sql)); if (kwargs != NULL) { if ((obj = PyDict_GetItemString(kwargs, "params")) != NULL) { if (!pykore_pgsql_params(op, obj)) { Py_DECREF((PyObject *)op); return (NULL); } } if ((obj = PyDict_GetItemString(kwargs, "binary")) != NULL) { if (obj == Py_True) { op->binary = 1; } else if (obj == Py_False) { op->binary = 0; } else { Py_DECREF((PyObject *)op); PyErr_SetString(PyExc_RuntimeError, "pgsql: binary not True or False"); return (NULL); } } } return ((PyObject *)op); } static int pykore_pgsql_params(struct pykore_pgsql *op, PyObject *list) { union { const char *cp; char *p; } ptr; PyObject *item; int format; Py_ssize_t i, len, vlen; if (!PyList_CheckExact(list)) { if (list == Py_None) return (KORE_RESULT_OK); PyErr_SetString(PyExc_RuntimeError, "pgsql: params keyword must be a list"); return (KORE_RESULT_ERROR); } len = PyList_Size(list); if (len == 0) return (KORE_RESULT_OK); if (len > INT_MAX) { PyErr_SetString(PyExc_RuntimeError, "pgsql: list length too large"); return (KORE_RESULT_ERROR); } op->param.count = len; op->param.lengths = kore_calloc(len, sizeof(int)); op->param.formats = kore_calloc(len, sizeof(int)); op->param.values = kore_calloc(len, sizeof(char *)); op->param.objs = kore_calloc(len, sizeof(PyObject *)); for (i = 0; i < len; i++) { if ((item = PyList_GetItem(list, i)) == NULL) return (KORE_RESULT_ERROR); if (PyUnicode_CheckExact(item)) { format = 0; ptr.cp = PyUnicode_AsUTF8AndSize(item, &vlen); } else if (PyBytes_CheckExact(item)) { format = 1; if (PyBytes_AsStringAndSize(item, &ptr.p, &vlen) == -1) ptr.p = NULL; } else { PyErr_Format(PyExc_RuntimeError, "pgsql: item %zu is not a string or bytes", i); return (KORE_RESULT_ERROR); } if (ptr.cp == NULL) return (KORE_RESULT_ERROR); op->param.lengths[i] = vlen; op->param.values[i] = ptr.cp; op->param.formats[i] = format; /* Hold on to it since we are directly referencing its data. */ op->param.objs[i] = item; Py_INCREF(item); } return (KORE_RESULT_OK); } static void pykore_pgsql_dealloc(struct pykore_pgsql *pysql) { Py_ssize_t i; kore_free(pysql->db); kore_free(pysql->query); kore_pgsql_cleanup(&pysql->sql); if (pysql->result != NULL) Py_DECREF(pysql->result); for (i = 0; i < pysql->param.count; i++) Py_XDECREF(pysql->param.objs[i]); kore_free(pysql->param.objs); kore_free(pysql->param.values); kore_free(pysql->param.lengths); kore_free(pysql->param.formats); PyObject_Del((PyObject *)pysql); } static PyObject * pykore_pgsql_iternext(struct pykore_pgsql *pysql) { switch (pysql->state) { case PYKORE_PGSQL_PREINIT: kore_pgsql_init(&pysql->sql); kore_pgsql_bind_callback(&pysql->sql, pykore_pgsql_callback, pysql); pysql->state = PYKORE_PGSQL_INITIALIZE; /* fallthrough */ case PYKORE_PGSQL_INITIALIZE: if (!kore_pgsql_setup(&pysql->sql, pysql->db, KORE_PGSQL_ASYNC)) { if (pysql->sql.state == KORE_PGSQL_STATE_INIT) break; PyErr_Format(PyExc_RuntimeError, "pgsql error: %s", pysql->sql.error); return (NULL); } /* fallthrough */ case PYKORE_PGSQL_QUERY: if (pysql->param.count > 0) { if (!kore_pgsql_query_param_fields(&pysql->sql, pysql->query, pysql->binary, pysql->param.count, pysql->param.values, pysql->param.lengths, pysql->param.formats)) { PyErr_Format(PyExc_RuntimeError, "pgsql error: %s", pysql->sql.error); return (NULL); } } else { if (!kore_pgsql_query(&pysql->sql, pysql->query)) { PyErr_Format(PyExc_RuntimeError, "pgsql error: %s", pysql->sql.error); return (NULL); } } pysql->state = PYKORE_PGSQL_WAIT; break; wait_again: case PYKORE_PGSQL_WAIT: switch (pysql->sql.state) { case KORE_PGSQL_STATE_WAIT: break; case KORE_PGSQL_STATE_COMPLETE: PyErr_SetNone(PyExc_StopIteration); if (pysql->result != NULL) { PyErr_SetObject(PyExc_StopIteration, pysql->result); Py_DECREF(pysql->result); pysql->result = NULL; } else { PyErr_SetObject(PyExc_StopIteration, Py_None); } return (NULL); case KORE_PGSQL_STATE_ERROR: PyErr_Format(PyExc_RuntimeError, "failed to perform query: %s", pysql->sql.error); return (NULL); case KORE_PGSQL_STATE_RESULT: if (!pykore_pgsql_result(pysql)) return (NULL); goto wait_again; default: kore_pgsql_continue(&pysql->sql); goto wait_again; } break; default: PyErr_SetString(PyExc_RuntimeError, "bad pykore_pgsql state"); return (NULL); } /* tell caller to wait. */ Py_RETURN_NONE; } static void pykore_pgsql_callback(struct kore_pgsql *pgsql, void *arg) { struct pykore_pgsql *op = arg; if (op->coro->request != NULL) http_request_wakeup(op->coro->request); else python_coro_wakeup(op->coro); } static PyObject * pykore_pgsql_await(PyObject *obj) { Py_INCREF(obj); return (obj); } static int pykore_pgsql_result(struct pykore_pgsql *pysql) { const char *val; char key[64]; PyObject *list, *pyrow, *pyval; int rows, row, field, fields, len; if ((list = PyList_New(0)) == NULL) { PyErr_SetNone(PyExc_MemoryError); return (KORE_RESULT_ERROR); } rows = kore_pgsql_ntuples(&pysql->sql); fields = kore_pgsql_nfields(&pysql->sql); for (row = 0; row < rows; row++) { if ((pyrow = PyDict_New()) == NULL) { Py_DECREF(list); PyErr_SetNone(PyExc_MemoryError); return (KORE_RESULT_ERROR); } for (field = 0; field < fields; field++) { val = kore_pgsql_getvalue(&pysql->sql, row, field); len = kore_pgsql_getlength(&pysql->sql, row, field); if (kore_pgsql_column_binary(&pysql->sql, field)) { pyval = PyBytes_FromStringAndSize(val, len); } else { pyval = PyUnicode_FromString(val); } if (pyval == NULL) { Py_DECREF(pyrow); Py_DECREF(list); PyErr_SetNone(PyExc_MemoryError); return (KORE_RESULT_ERROR); } (void)snprintf(key, sizeof(key), "%s", kore_pgsql_fieldname(&pysql->sql, field)); if (PyDict_SetItemString(pyrow, key, pyval) == -1) { Py_DECREF(pyval); Py_DECREF(pyrow); Py_DECREF(list); PyErr_SetString(PyExc_RuntimeError, "failed to add new value to row"); return (KORE_RESULT_ERROR); } Py_DECREF(pyval); } if (PyList_Insert(list, row, pyrow) == -1) { Py_DECREF(pyrow); Py_DECREF(list); PyErr_SetString(PyExc_RuntimeError, "failed to add new row to list"); return (KORE_RESULT_ERROR); } Py_DECREF(pyrow); } pysql->result = list; kore_pgsql_continue(&pysql->sql); return (KORE_RESULT_OK); } #endif #if defined(KORE_USE_CURL) static PyObject * python_curlopt_set(struct pycurl_data *data, long opt, PyObject *value) { int i; for (i = 0; py_curlopt[i].name != NULL; i++) { if (py_curlopt[i].value == opt) break; } if (py_curlopt[i].name == NULL) { PyErr_Format(PyExc_RuntimeError, "invalid option '%ld'", opt); return (NULL); } if (py_curlopt[i].cb == NULL) { PyErr_Format(PyExc_RuntimeError, "option '%s' not implemented", py_curlopt[i].name); return (NULL); } return (py_curlopt[i].cb(data, i, value)); } static int python_curlopt_from_dict(struct pycurl_data *data, PyObject *dict) { long opt; Py_ssize_t idx; PyObject *key, *value, *obj; idx = 0; if (!PyDict_CheckExact(dict)) { PyErr_SetString(PyExc_RuntimeError, "curlopt must be a dictionary"); return (KORE_RESULT_ERROR); } while (PyDict_Next(dict, &idx, &key, &value)) { if (!PyLong_CheckExact(key)) { PyErr_Format(PyExc_RuntimeError, "invalid key in curlopt keyword"); return (KORE_RESULT_ERROR); } opt = PyLong_AsLong(key); if ((obj = python_curlopt_set(data, opt, value)) == NULL) return (KORE_RESULT_ERROR); Py_DECREF(obj); } return (KORE_RESULT_OK); } static PyObject * python_kore_curl_handle(PyObject *self, PyObject *args) { const char *url; struct pycurl_handle *handle; if (!PyArg_ParseTuple(args, "s", &url)) return (NULL); handle = PyObject_New(struct pycurl_handle, &pycurl_handle_type); if (handle == NULL) return (NULL); handle->url = kore_strdup(url); memset(&handle->data.curl, 0, sizeof(handle->data.curl)); handle->body = NULL; LIST_INIT(&handle->data.slists); if (!kore_curl_init(&handle->data.curl, handle->url, KORE_CURL_ASYNC)) { Py_DECREF((PyObject *)handle); PyErr_SetString(PyExc_RuntimeError, "failed to setup call"); return (NULL); } return ((PyObject *)handle); } static void pycurl_handle_dealloc(struct pycurl_handle *handle) { struct pycurl_slist *psl; while ((psl = LIST_FIRST(&handle->data.slists))) { LIST_REMOVE(psl, list); curl_slist_free_all(psl->slist); kore_free(psl); } if (handle->body != NULL) kore_buf_free(handle->body); kore_free(handle->url); kore_curl_cleanup(&handle->data.curl); PyObject_Del((PyObject *)handle); } static PyObject * pycurl_handle_setbody(struct pycurl_handle *handle, PyObject *args) { PyObject *obj; char *ptr; Py_ssize_t length; if (!PyArg_ParseTuple(args, "O", &obj)) return (NULL); if (handle->body != NULL) { PyErr_SetString(PyExc_RuntimeError, "curl handle already has body attached"); return (NULL); } if (!PyBytes_CheckExact(obj)) { PyErr_SetString(PyExc_RuntimeError, "curl.setbody expects bytes"); return (NULL); } if (PyBytes_AsStringAndSize(obj, &ptr, &length) == -1) return (NULL); if (length < 0) { PyErr_SetString(PyExc_TypeError, "invalid length"); return (NULL); } handle->body = kore_buf_alloc(length); kore_buf_append(handle->body, ptr, length); kore_buf_reset(handle->body); curl_easy_setopt(handle->data.curl.handle, CURLOPT_READFUNCTION, kore_curl_frombuf); curl_easy_setopt(handle->data.curl.handle, CURLOPT_READDATA, handle->body); curl_easy_setopt(handle->data.curl.handle, CURLOPT_UPLOAD, 1); Py_RETURN_TRUE; } static PyObject * pycurl_handle_setopt(struct pycurl_handle *handle, PyObject *args) { int opt; PyObject *value; if (!PyArg_ParseTuple(args, "iO", &opt, &value)) return (NULL); return (python_curlopt_set(&handle->data, opt, value)); } static PyObject * pycurl_handle_setopt_string(struct pycurl_data *data, int idx, PyObject *obj) { const char *str; if (!PyUnicode_Check(obj)) { PyErr_Format(PyExc_RuntimeError, "option '%s' requires a string as argument", py_curlopt[idx].name); return (NULL); } if ((str = PyUnicode_AsUTF8(obj)) == NULL) return (NULL); curl_easy_setopt(data->curl.handle, CURLOPTTYPE_OBJECTPOINT + py_curlopt[idx].value, str); Py_RETURN_TRUE; } static PyObject * pycurl_handle_setopt_long(struct pycurl_data *data, int idx, PyObject *obj) { long val; if (!PyLong_CheckExact(obj)) { PyErr_Format(PyExc_RuntimeError, "option '%s' requires a long as argument", py_curlopt[idx].name); return (NULL); } PyErr_Clear(); val = PyLong_AsLong(obj); if (val == -1 && PyErr_Occurred()) return (NULL); curl_easy_setopt(data->curl.handle, CURLOPTTYPE_LONG + py_curlopt[idx].value, val); Py_RETURN_TRUE; } static PyObject * pycurl_handle_setopt_slist(struct pycurl_data *data, int idx, PyObject *obj) { struct pycurl_slist *psl; PyObject *item; const char *sval; struct curl_slist *slist; Py_ssize_t list_len, i; if (!PyList_CheckExact(obj)) { PyErr_Format(PyExc_RuntimeError, "option '%s' requires a list as argument", py_curlopt[idx].name); return (NULL); } slist = NULL; list_len = PyList_Size(obj); for (i = 0; i < list_len; i++) { if ((item = PyList_GetItem(obj, i)) == NULL) return (NULL); if (!PyUnicode_Check(item)) return (NULL); if ((sval = PyUnicode_AsUTF8AndSize(item, NULL)) == NULL) return (NULL); if ((slist = curl_slist_append(slist, sval)) == NULL) fatal("%s: curl_slist_append failed", __func__); } psl = kore_calloc(1, sizeof(*psl)); psl->slist = slist; LIST_INSERT_HEAD(&data->slists, psl, list); curl_easy_setopt(data->curl.handle, CURLOPTTYPE_OBJECTPOINT + py_curlopt[idx].value, slist); Py_RETURN_TRUE; } static PyObject * pycurl_handle_run(struct pycurl_handle *handle, PyObject *args) { struct pycurl_handle_op *op; op = PyObject_New(struct pycurl_handle_op, &pycurl_handle_op_type); if (op == NULL) return (NULL); Py_INCREF(handle); op->handle = handle; op->coro = coro_running; op->state = CURL_CLIENT_OP_RUN; kore_curl_bind_callback(&handle->data.curl, python_curl_handle_callback, op); return ((PyObject *)op); } static void pycurl_handle_op_dealloc(struct pycurl_handle_op *op) { Py_DECREF(op->handle); PyObject_Del((PyObject *)op); } static PyObject * pycurl_handle_op_await(PyObject *op) { Py_INCREF(op); return (op); } static PyObject * pycurl_handle_op_iternext(struct pycurl_handle_op *op) { size_t len; PyObject *result; const u_int8_t *response; if (op->state == CURL_CLIENT_OP_RUN) { kore_curl_run(&op->handle->data.curl); op->state = CURL_CLIENT_OP_RESULT; Py_RETURN_NONE; } if (op->handle->body != NULL) { kore_buf_free(op->handle->body); op->handle->body = NULL; } if (!kore_curl_success(&op->handle->data.curl)) { /* Do not log the url here, may contain some sensitive data. */ PyErr_Format(PyExc_RuntimeError, "request failed: %s", kore_curl_strerror(&op->handle->data.curl)); return (NULL); } kore_curl_response_as_bytes(&op->handle->data.curl, &response, &len); if ((result = PyBytes_FromStringAndSize((const char *)response, len)) == NULL) return (NULL); PyErr_SetObject(PyExc_StopIteration, result); Py_DECREF(result); return (NULL); } static PyObject * python_kore_httpclient(PyObject *self, PyObject *args, PyObject *kwargs) { struct pyhttp_client *client; const char *url, *v; if (!PyArg_ParseTuple(args, "s", &url)) return (NULL); client = PyObject_New(struct pyhttp_client, &pyhttp_client_type); if (client == NULL) return (NULL); client->unix = NULL; client->tlskey = NULL; client->curlopt = NULL; client->tlscert = NULL; client->cabundle = NULL; client->tlsverify = 1; client->url = kore_strdup(url); if (kwargs != NULL) { if ((v = python_string_from_dict(kwargs, "tlscert")) != NULL) client->tlscert = kore_strdup(v); if ((v = python_string_from_dict(kwargs, "tlskey")) != NULL) client->tlskey = kore_strdup(v); if ((v = python_string_from_dict(kwargs, "cabundle")) != NULL) client->cabundle = kore_strdup(v); if ((v = python_string_from_dict(kwargs, "unix")) != NULL) client->unix = kore_strdup(v); client->curlopt = PyDict_GetItemString(kwargs, "curlopt"); Py_XINCREF(client->curlopt); python_bool_from_dict(kwargs, "tlsverify", &client->tlsverify); } if ((client->tlscert != NULL && client->tlskey == NULL) || (client->tlskey != NULL && client->tlscert == NULL)) { Py_DECREF((PyObject *)client); PyErr_SetString(PyExc_RuntimeError, "invalid TLS client configuration"); return (NULL); } return ((PyObject *)client); } static void pyhttp_client_dealloc(struct pyhttp_client *client) { kore_free(client->url); kore_free(client->unix); kore_free(client->tlskey); kore_free(client->tlscert); kore_free(client->cabundle); Py_XDECREF(client->curlopt); PyObject_Del((PyObject *)client); } static PyObject * pyhttp_client_get(struct pyhttp_client *client, PyObject *args, PyObject *kwargs) { return (pyhttp_client_request(client, HTTP_METHOD_GET, kwargs)); } static PyObject * pyhttp_client_put(struct pyhttp_client *client, PyObject *args, PyObject *kwargs) { return (pyhttp_client_request(client, HTTP_METHOD_PUT, kwargs)); } static PyObject * pyhttp_client_post(struct pyhttp_client *client, PyObject *args, PyObject *kwargs) { return (pyhttp_client_request(client, HTTP_METHOD_POST, kwargs)); } static PyObject * pyhttp_client_head(struct pyhttp_client *client, PyObject *args, PyObject *kwargs) { return (pyhttp_client_request(client, HTTP_METHOD_HEAD, kwargs)); } static PyObject * pyhttp_client_patch(struct pyhttp_client *client, PyObject *args, PyObject *kwargs) { return (pyhttp_client_request(client, HTTP_METHOD_PATCH, kwargs)); } static PyObject * pyhttp_client_delete(struct pyhttp_client *client, PyObject *args, PyObject *kwargs) { return (pyhttp_client_request(client, HTTP_METHOD_DELETE, kwargs)); } static PyObject * pyhttp_client_options(struct pyhttp_client *client, PyObject *args, PyObject *kwargs) { return (pyhttp_client_request(client, HTTP_METHOD_OPTIONS, kwargs)); } static PyObject * pyhttp_client_request(struct pyhttp_client *client, int m, PyObject *kwargs) { struct pyhttp_client_op *op; char *ptr; const char *k, *v; Py_ssize_t length, idx; PyObject *data, *headers, *key, *obj; ptr = NULL; length = 0; headers = NULL; if (kwargs != NULL && ((headers = PyDict_GetItemString(kwargs, "headers")) != NULL)) { if (!PyDict_CheckExact(headers)) { PyErr_SetString(PyExc_RuntimeError, "headers keyword must be a dict"); return (NULL); } } switch (m) { case HTTP_METHOD_GET: case HTTP_METHOD_HEAD: case HTTP_METHOD_OPTIONS: break; case HTTP_METHOD_PUT: case HTTP_METHOD_POST: case HTTP_METHOD_PATCH: case HTTP_METHOD_DELETE: length = -1; if (kwargs == NULL) { if (m == HTTP_METHOD_DELETE) { length = 0; break; } PyErr_Format(PyExc_RuntimeError, "no keyword arguments given, but body expected ", http_method_text(m)); return (NULL); } if ((data = PyDict_GetItemString(kwargs, "body")) == NULL) return (NULL); if (PyBytes_AsStringAndSize(data, &ptr, &length) == -1) return (NULL); if (length < 0) { PyErr_SetString(PyExc_TypeError, "invalid length"); return (NULL); } break; default: fatal("%s: unknown method %d", __func__, m); } op = PyObject_New(struct pyhttp_client_op, &pyhttp_client_op_type); if (op == NULL) return (NULL); if (!kore_curl_init(&op->data.curl, client->url, KORE_CURL_ASYNC)) { Py_DECREF((PyObject *)op); PyErr_SetString(PyExc_RuntimeError, "failed to setup call"); return (NULL); } op->headers = 0; op->coro = coro_running; op->state = CURL_CLIENT_OP_RUN; LIST_INIT(&op->data.slists); Py_INCREF(client); op->client = client; kore_curl_http_setup(&op->data.curl, m, ptr, length); kore_curl_bind_callback(&op->data.curl, python_curl_http_callback, op); /* Go in with our own bare hands. */ if (client->unix != NULL) { #if defined(__linux__) if (client->unix[0] == '@') { curl_easy_setopt(op->data.curl.handle, CURLOPT_ABSTRACT_UNIX_SOCKET, client->unix + 1); } else { curl_easy_setopt(op->data.curl.handle, CURLOPT_UNIX_SOCKET_PATH, client->unix); } #else curl_easy_setopt(op->data.curl.handle, CURLOPT_UNIX_SOCKET_PATH, client->unix); #endif } if (client->tlskey != NULL && client->tlscert != NULL) { curl_easy_setopt(op->data.curl.handle, CURLOPT_SSLCERT, client->tlscert); curl_easy_setopt(op->data.curl.handle, CURLOPT_SSLKEY, client->tlskey); } if (client->tlsverify == 0) { curl_easy_setopt(op->data.curl.handle, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(op->data.curl.handle, CURLOPT_SSL_VERIFYPEER, 0); } if (client->curlopt != NULL) { if (!python_curlopt_from_dict(&op->data, client->curlopt)) { Py_DECREF((PyObject *)op); return (NULL); } } if (client->cabundle != NULL) { curl_easy_setopt(op->data.curl.handle, CURLOPT_CAINFO, client->cabundle); } if (headers != NULL) { idx = 0; while (PyDict_Next(headers, &idx, &key, &obj)) { if ((k = PyUnicode_AsUTF8(key)) == NULL) { Py_DECREF((PyObject *)op); return (NULL); } if ((v = PyUnicode_AsUTF8(obj)) == NULL) { Py_DECREF((PyObject *)op); return (NULL); } kore_curl_http_set_header(&op->data.curl, k, v); } } if (kwargs != NULL) { if ((obj = PyDict_GetItemString(kwargs, "curlopt")) != NULL) { if (!python_curlopt_from_dict(&op->data, obj)) { Py_DECREF((PyObject *)op); return (NULL); } } python_bool_from_dict(kwargs, "return_headers", &op->headers); } return ((PyObject *)op); } static void pyhttp_client_op_dealloc(struct pyhttp_client_op *op) { struct pycurl_slist *psl; while ((psl = LIST_FIRST(&op->data.slists))) { LIST_REMOVE(psl, list); curl_slist_free_all(psl->slist); kore_free(psl); } Py_DECREF(op->client); kore_curl_cleanup(&op->data.curl); PyObject_Del((PyObject *)op); } static PyObject * pyhttp_client_op_await(PyObject *op) { Py_INCREF(op); return (op); } static PyObject * pyhttp_client_op_iternext(struct pyhttp_client_op *op) { size_t len; struct http_header *hdr; const u_int8_t *response; PyObject *result, *tuple, *dict, *value; if (op->state == CURL_CLIENT_OP_RUN) { kore_curl_run(&op->data.curl); op->state = CURL_CLIENT_OP_RESULT; Py_RETURN_NONE; } if (!kore_curl_success(&op->data.curl)) { PyErr_Format(PyExc_RuntimeError, "request to '%s' failed: %s", op->data.curl.url, kore_curl_strerror(&op->data.curl)); return (NULL); } kore_curl_response_as_bytes(&op->data.curl, &response, &len); if (op->headers) { kore_curl_http_parse_headers(&op->data.curl); if ((dict = PyDict_New()) == NULL) return (NULL); TAILQ_FOREACH(hdr, &op->data.curl.http.resp_hdrs, list) { value = PyUnicode_FromString(hdr->value); if (value == NULL) { Py_DECREF(dict); return (NULL); } if (PyDict_SetItemString(dict, hdr->header, value) == -1) { Py_DECREF(dict); Py_DECREF(value); return (NULL); } Py_DECREF(value); } if ((tuple = Py_BuildValue("(iOy#)", op->data.curl.http.status, dict, (const char *)response, len)) == NULL) return (NULL); Py_DECREF(dict); } else { if ((tuple = Py_BuildValue("(iy#)", op->data.curl.http.status, (const char *)response, len)) == NULL) return (NULL); } result = PyObject_CallFunctionObjArgs(PyExc_StopIteration, tuple, NULL); if (result == NULL) { Py_DECREF(tuple); return (NULL); } Py_DECREF(tuple); PyErr_SetObject(PyExc_StopIteration, result); Py_DECREF(result); return (NULL); } static void python_curl_http_callback(struct kore_curl *curl, void *arg) { struct pyhttp_client_op *op = arg; if (op->coro->request != NULL) http_request_wakeup(op->coro->request); else python_coro_wakeup(op->coro); } static void python_curl_handle_callback(struct kore_curl *curl, void *arg) { struct pycurl_handle_op *op = arg; if (op->coro->request != NULL) http_request_wakeup(op->coro->request); else python_coro_wakeup(op->coro); } #endif kore-4.2.3/src/route.c000066400000000000000000000060731430066302400145550ustar00rootroot00000000000000/* * Copyright (c) 2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "kore.h" #include "http.h" struct kore_route * kore_route_create(struct kore_domain *dom, const char *path, int type) { struct kore_route *rt; rt = kore_calloc(1, sizeof(*rt)); rt->dom = dom; rt->type = type; rt->path = kore_strdup(path); rt->methods = HTTP_METHOD_ALL; TAILQ_INIT(&rt->params); if (rt->type == HANDLER_TYPE_DYNAMIC) { if (regcomp(&rt->rctx, rt->path, REG_EXTENDED | REG_NOSUB)) { kore_route_free(rt); return (NULL); } } TAILQ_INSERT_TAIL(&dom->routes, rt, list); return (rt); } void kore_route_free(struct kore_route *rt) { struct kore_route_params *param; if (rt == NULL) return; kore_free(rt->func); kore_free(rt->path); if (rt->type == HANDLER_TYPE_DYNAMIC) regfree(&rt->rctx); /* Drop all validators associated with this handler */ while ((param = TAILQ_FIRST(&rt->params)) != NULL) { TAILQ_REMOVE(&rt->params, param, list); kore_free(param->name); kore_free(param); } kore_free(rt); } void kore_route_callback(struct kore_route *rt, const char *func) { if ((rt->rcall = kore_runtime_getcall(func)) == NULL) fatal("callback '%s' for '%s' not found", func, rt->path); kore_free(rt->func); rt->func = kore_strdup(func); } int kore_route_lookup(struct http_request *req, struct kore_domain *dom, int method, struct kore_route **out) { struct kore_route *rt; int exists; exists = 0; *out = NULL; TAILQ_FOREACH(rt, &dom->routes, list) { if (rt->type == HANDLER_TYPE_STATIC) { if (!strcmp(rt->path, req->path)) { if (rt->methods & method) { *out = rt; return (1); } exists++; } } else { if (!regexec(&rt->rctx, req->path, HTTP_CAPTURE_GROUPS, req->cgroups, 0)) { if (rt->methods & method) { *out = rt; return (1); } exists++; } } } return (exists); } void kore_route_reload(void) { struct kore_route *rt; struct kore_server *srv; struct kore_domain *dom; LIST_FOREACH(srv, &kore_servers, list) { TAILQ_FOREACH(dom, &srv->domains, list) { TAILQ_FOREACH(rt, &dom->routes, list) { kore_free(rt->rcall); rt->rcall = kore_runtime_getcall(rt->func); if (rt->rcall == NULL) { fatal("no function '%s' for route '%s'", rt->func, rt->path); } rt->errors = 0; } } } } kore-4.2.3/src/runtime.c000066400000000000000000000137301430066302400151000ustar00rootroot00000000000000/* * Copyright (c) 2017-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "kore.h" #if !defined(KORE_NO_HTTP) #include "http.h" #endif #if defined(KORE_USE_PYTHON) #include "python_api.h" #endif static void native_runtime_execute(void *); static int native_runtime_onload(void *, int); static void native_runtime_signal(void *, int); static void native_runtime_connect(void *, struct connection *); static void native_runtime_configure(void *, int, char **); #if !defined(KORE_NO_HTTP) static int native_runtime_http_request(void *, struct http_request *); static void native_runtime_http_request_free(void *, struct http_request *); static void native_runtime_http_body_chunk(void *, struct http_request *, const void *, size_t); static int native_runtime_validator(void *, struct http_request *, const void *); static void native_runtime_wsmessage(void *, struct connection *, u_int8_t, const void *, size_t); #endif struct kore_runtime kore_native_runtime = { KORE_RUNTIME_NATIVE, #if !defined(KORE_NO_HTTP) .http_request = native_runtime_http_request, .http_request_free = native_runtime_http_request_free, .http_body_chunk = native_runtime_http_body_chunk, .validator = native_runtime_validator, .wsconnect = native_runtime_connect, .wsmessage = native_runtime_wsmessage, .wsdisconnect = native_runtime_connect, #endif .onload = native_runtime_onload, .signal = native_runtime_signal, .connect = native_runtime_connect, .execute = native_runtime_execute, .configure = native_runtime_configure }; struct kore_runtime_call * kore_runtime_getcall(const char *symbol) { void *ptr; struct kore_runtime_call *rcall; struct kore_runtime *runtime; ptr = kore_module_getsym(symbol, &runtime); if (ptr == NULL) return (NULL); rcall = kore_malloc(sizeof(*rcall)); rcall->addr = ptr; rcall->runtime = runtime; return (rcall); } void kore_runtime_execute(struct kore_runtime_call *rcall) { rcall->runtime->execute(rcall->addr); } void kore_runtime_configure(struct kore_runtime_call *rcall, int argc, char **argv) { rcall->runtime->configure(rcall->addr, argc, argv); } int kore_runtime_onload(struct kore_runtime_call *rcall, int action) { return (rcall->runtime->onload(rcall->addr, action)); } void kore_runtime_connect(struct kore_runtime_call *rcall, struct connection *c) { rcall->runtime->connect(rcall->addr, c); } void kore_runtime_signal(struct kore_runtime_call *rcall, int sig) { rcall->runtime->signal(rcall->addr, sig); } #if !defined(KORE_NO_HTTP) int kore_runtime_http_request(struct kore_runtime_call *rcall, struct http_request *req) { return (rcall->runtime->http_request(rcall->addr, req)); } void kore_runtime_http_request_free(struct kore_runtime_call *rcall, struct http_request *req) { rcall->runtime->http_request_free(rcall->addr, req); } void kore_runtime_http_body_chunk(struct kore_runtime_call *rcall, struct http_request *req, const void *data, size_t len) { rcall->runtime->http_body_chunk(rcall->addr, req, data, len); } int kore_runtime_validator(struct kore_runtime_call *rcall, struct http_request *req, const void *data) { return (rcall->runtime->validator(rcall->addr, req, data)); } void kore_runtime_wsconnect(struct kore_runtime_call *rcall, struct connection *c) { rcall->runtime->wsconnect(rcall->addr, c); } void kore_runtime_wsmessage(struct kore_runtime_call *rcall, struct connection *c, u_int8_t op, const void *data, size_t len) { rcall->runtime->wsmessage(rcall->addr, c, op, data, len); } void kore_runtime_wsdisconnect(struct kore_runtime_call *rcall, struct connection *c) { rcall->runtime->wsdisconnect(rcall->addr, c); } #endif static void native_runtime_execute(void *addr) { void (*cb)(void); *(void **)&(cb) = addr; cb(); } static void native_runtime_configure(void *addr, int argc, char **argv) { void (*cb)(int, char **); *(void **)&(cb) = addr; cb(argc, argv); } static void native_runtime_connect(void *addr, struct connection *c) { void (*cb)(struct connection *); *(void **)&(cb) = addr; cb(c); } static int native_runtime_onload(void *addr, int action) { int (*cb)(int); *(void **)&(cb) = addr; return (cb(action)); } static void native_runtime_signal(void *addr, int sig) { void (*cb)(int); *(void **)&(cb) = addr; cb(sig); } #if !defined(KORE_NO_HTTP) static int native_runtime_http_request(void *addr, struct http_request *req) { int (*cb)(struct http_request *); *(void **)&(cb) = addr; return (cb(req)); } static void native_runtime_http_request_free(void *addr, struct http_request *req) { int (*cb)(struct http_request *); *(void **)&(cb) = addr; cb(req); } static void native_runtime_http_body_chunk(void *addr, struct http_request *req, const void *data, size_t len) { void (*cb)(struct http_request *, const void *, size_t); *(void **)&(cb) = addr; cb(req, data, len); } static int native_runtime_validator(void *addr, struct http_request *req, const void *data) { int (*cb)(struct http_request *, const void *); *(void **)&(cb) = addr; return (cb(req, data)); } static void native_runtime_wsmessage(void *addr, struct connection *c, u_int8_t op, const void *data, size_t len) { void (*cb)(struct connection *, u_int8_t, const void *, size_t); *(void **)&(cb) = addr; cb(c, op, data, len); } #endif kore-4.2.3/src/seccomp.c000066400000000000000000000302431430066302400150440ustar00rootroot00000000000000/* * Copyright (c) 2019-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kore.h" #include "seccomp.h" #include "platform.h" #if defined(KORE_USE_PYTHON) #include "python_api.h" #endif #if !defined(SECCOMP_KILL_POLICY) #define SECCOMP_KILL_POLICY SECCOMP_RET_KILL #endif /* * The bare minimum to be able to run kore. These are added last and can * be overwritten by a filter program that is added before hand. */ static struct sock_filter filter_kore[] = { /* Deny these, but with EACCESS instead of dying. */ KORE_SYSCALL_DENY(ioctl, EACCES), /* File related. */ #if defined(SYS_open) KORE_SYSCALL_ALLOW(open), #endif KORE_SYSCALL_ALLOW(read), #if defined(SYS_stat) KORE_SYSCALL_ALLOW(stat), #endif #if defined(SYS_stat64) KORE_SYSCALL_ALLOW(stat64), #endif #if defined(SYS_lstat) KORE_SYSCALL_ALLOW(lstat), #endif KORE_SYSCALL_ALLOW(fstat), #if defined(SYS_fstat64) KORE_SYSCALL_ALLOW(fstat64), #endif KORE_SYSCALL_ALLOW(write), KORE_SYSCALL_ALLOW(fcntl), #if defined(SYS_fcntl64) KORE_SYSCALL_ALLOW(fcntl64), #endif KORE_SYSCALL_ALLOW(lseek), #if defined(SYS__llseek) KORE_SYSCALL_ALLOW(_llseek), #endif KORE_SYSCALL_ALLOW(close), KORE_SYSCALL_ALLOW(openat), #if defined(SYS_access) KORE_SYSCALL_ALLOW(access), #endif KORE_SYSCALL_ALLOW(writev), KORE_SYSCALL_ALLOW(getcwd), #if defined(SYS_unlink) KORE_SYSCALL_ALLOW(unlink), #endif #if defined(SYS_readlink) KORE_SYSCALL_ALLOW(readlink), #endif #if defined(SYS_readlinkat) KORE_SYSCALL_ALLOW(readlinkat), #endif /* Process related. */ KORE_SYSCALL_ALLOW(exit), KORE_SYSCALL_ALLOW(kill), KORE_SYSCALL_ALLOW(getpid), KORE_SYSCALL_ALLOW(getuid), KORE_SYSCALL_ALLOW(geteuid), KORE_SYSCALL_ALLOW(exit_group), KORE_SYSCALL_ALLOW(nanosleep), #if defined(SYS_clock_nanosleep) KORE_SYSCALL_ALLOW(clock_nanosleep), #endif #if defined(SYS_sigreturn) KORE_SYSCALL_ALLOW(sigreturn), #endif /* Memory related. */ KORE_SYSCALL_ALLOW(brk), KORE_SYSCALL_ALLOW(munmap), /* Deny mmap/mprotect calls with PROT_EXEC/PROT_WRITE protection. */ #if defined(SYS_mmap) KORE_SYSCALL_DENY_WITH_FLAG(mmap, 2, PROT_EXEC | PROT_WRITE, EINVAL), #endif #if defined(SYS_mmap2) KORE_SYSCALL_DENY_WITH_FLAG(mmap2, 2, PROT_EXEC | PROT_WRITE, EINVAL), #endif KORE_SYSCALL_DENY_WITH_FLAG(mprotect, 2, PROT_EXEC, EINVAL), #if defined(SYS_mmap) KORE_SYSCALL_ALLOW(mmap), #endif #if defined(SYS_mmap2) KORE_SYSCALL_ALLOW(mmap2), #endif KORE_SYSCALL_ALLOW(madvise), KORE_SYSCALL_ALLOW(mprotect), /* Net related. */ #if defined(SYS_poll) KORE_SYSCALL_ALLOW(poll), #endif KORE_SYSCALL_ALLOW(ppoll), #if defined(SYS_send) KORE_SYSCALL_ALLOW(send), #endif KORE_SYSCALL_ALLOW(sendto), KORE_SYSCALL_ALLOW(accept), KORE_SYSCALL_ALLOW(sendfile), #if defined(SYS_recv) KORE_SYSCALL_ALLOW(recv), #endif KORE_SYSCALL_ALLOW(recvfrom), KORE_SYSCALL_ALLOW(epoll_ctl), KORE_SYSCALL_ALLOW(setsockopt), #if defined(SYS_epoll_wait) KORE_SYSCALL_ALLOW(epoll_wait), #endif KORE_SYSCALL_ALLOW(epoll_pwait), /* Signal related. */ KORE_SYSCALL_ALLOW(sigaltstack), KORE_SYSCALL_ALLOW(rt_sigreturn), KORE_SYSCALL_ALLOW(rt_sigaction), KORE_SYSCALL_ALLOW(rt_sigprocmask), /* "Other" without clear category. */ KORE_SYSCALL_ALLOW(futex), #if defined(SYS_clock_gettime) KORE_SYSCALL_ALLOW(clock_gettime), #endif #if defined(__NR_getrandom) KORE_SYSCALL_ALLOW(getrandom), #endif }; /* bpf program prologue. */ static struct sock_filter filter_prologue[] = { /* Load arch member into accumulator (A) (arch is __u32). */ KORE_BPF_LOAD(arch, 0), /* Compare accumulator against constant, if false jump over kill. */ KORE_BPF_CMP(SECCOMP_AUDIT_ARCH, 1, 0), KORE_BPF_RET(SECCOMP_RET_KILL), /* Load the system call number into the accumulator. */ KORE_BPF_LOAD(nr, 0), }; /* bpf program epilogue. */ static struct sock_filter filter_epilogue[] = { /* Return hit if no system calls matched our list. */ BPF_STMT(BPF_RET+BPF_K, SECCOMP_KILL_POLICY) }; static struct sock_filter *seccomp_filter_update(struct sock_filter *, const char *, size_t); #define filter_prologue_len KORE_FILTER_LEN(filter_prologue) #define filter_epilogue_len KORE_FILTER_LEN(filter_epilogue) static void seccomp_register_violation(pid_t); struct filter { char *name; struct sock_filter *prog; size_t instructions; TAILQ_ENTRY(filter) list; }; static TAILQ_HEAD(, filter) filters; static struct filter *ufilter = NULL; /* * If enabled will instruct the parent process to ptrace its children and * log any seccomp SECCOMP_RET_TRACE rule. */ int kore_seccomp_tracing = 0; void kore_seccomp_init(void) { TAILQ_INIT(&filters); } void kore_seccomp_drop(void) { struct filter *filter; while ((filter = TAILQ_FIRST(&filters)) != NULL) { if (!kore_quiet) { kore_log(LOG_INFO, "seccomp filter '%s' dropped", filter->name); } TAILQ_REMOVE(&filters, filter, list); kore_free(filter->name); kore_free(filter); } TAILQ_INIT(&filters); } void kore_seccomp_enable(void) { struct sock_filter *sf; struct sock_fprog prog; struct kore_runtime_call *rcall; struct filter *filter; size_t prog_len, off, i; /* * If kore_seccomp_tracing is turned on, set the default policy to * SECCOMP_RET_TRACE so we can log the system calls. */ if (kore_seccomp_tracing) { filter_epilogue[0].k = SECCOMP_RET_TRACE; kore_log(LOG_NOTICE, "seccomp tracing enabled"); } #if defined(KORE_USE_PYTHON) ufilter = TAILQ_FIRST(&filters); kore_python_seccomp_hook("koreapp.seccomp"); ufilter = NULL; #endif /* Allow application to add its own filters. */ if ((rcall = kore_runtime_getcall("kore_seccomp_hook")) != NULL) { ufilter = TAILQ_FIRST(&filters); kore_runtime_execute(rcall); kore_free(rcall); ufilter = NULL; } if (worker->id != KORE_WORKER_KEYMGR) { /* Add worker required syscalls. */ kore_seccomp_filter("worker", filter_kore, KORE_FILTER_LEN(filter_kore)); } /* Start with the prologue. */ prog_len = filter_prologue_len; /* Now account for all enabled filters. */ TAILQ_FOREACH(filter, &filters, list) prog_len += filter->instructions; /* Finally add the epilogue. */ prog_len += filter_epilogue_len; /* Build the entire bpf program now. */ if ((sf = calloc(prog_len, sizeof(*sf))) == NULL) fatalx("calloc"); off = 0; for (i = 0; i < filter_prologue_len; i++) sf[off++] = filter_prologue[i]; TAILQ_FOREACH(filter, &filters, list) { for (i = 0; i < filter->instructions; i++) sf[off++] = filter->prog[i]; } for (i = 0; i < filter_epilogue_len; i++) sf[off++] = filter_epilogue[i]; /* Lock and load it. */ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) fatalx("prctl: %s", errno_s); prog.filter = sf; prog.len = prog_len; if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) fatalx("prctl: %s", errno_s); #if defined(KORE_USE_PYTHON) kore_python_seccomp_cleanup(); #endif } int kore_seccomp_filter(const char *name, void *prog, size_t len) { struct filter *filter; TAILQ_FOREACH(filter, &filters, list) { if (!strcmp(filter->name, name)) return (KORE_RESULT_ERROR); } filter = kore_calloc(1, sizeof(*filter)); filter->prog = prog; filter->instructions = len; filter->name = kore_strdup(name); if (ufilter) { TAILQ_INSERT_BEFORE(ufilter, filter, list); } else { TAILQ_INSERT_TAIL(&filters, filter, list); } return (KORE_RESULT_OK); } void kore_seccomp_traceme(void) { if (kore_seccomp_tracing == 0) return; if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) fatalx("ptrace: %s", errno_s); if (kill(worker->pid, SIGSTOP) == -1) fatalx("kill: %s", errno_s); } int kore_seccomp_trace(pid_t pid, int status) { int evt; if (kore_seccomp_tracing == 0) return (KORE_RESULT_ERROR); if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) { if (ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESECCOMP | PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK) == -1) fatal("ptrace: %s", errno_s); if (ptrace(PTRACE_CONT, pid, NULL, NULL) == -1) fatal("ptrace: %s", errno_s); return (KORE_RESULT_OK); } if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { evt = status >> 8; if (evt == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) seccomp_register_violation(pid); if (ptrace(PTRACE_CONT, pid, NULL, NULL) == -1) fatal("ptrace: %s", errno_s); return (KORE_RESULT_OK); } if (WIFSTOPPED(status)) { if (ptrace(PTRACE_CONT, pid, NULL, WSTOPSIG(status)) == -1) fatal("ptrace: %s", errno_s); return (KORE_RESULT_OK); } return (KORE_RESULT_ERROR); } int kore_seccomp_syscall_resolve(const char *name) { int i; for (i = 0; kore_syscall_map[i].name != NULL; i++) { if (!strcmp(name, kore_syscall_map[i].name)) return (kore_syscall_map[i].nr); } return (-1); } const char * kore_seccomp_syscall_name(long sysnr) { int i; for (i = 0; kore_syscall_map[i].name != NULL; i++) { if (kore_syscall_map[i].nr == sysnr) return (kore_syscall_map[i].name); } return ("unknown"); } struct sock_filter * kore_seccomp_syscall_filter(const char *name, int action) { struct sock_filter filter[] = { KORE_SYSCALL_FILTER(exit, action), KORE_BPF_GUARD }; return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter))); } struct sock_filter * kore_seccomp_syscall_arg(const char *name, int action, int arg, int value) { struct sock_filter filter[] = { KORE_SYSCALL_ARG(exit, arg, value, action), KORE_BPF_GUARD }; return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter))); } struct sock_filter * kore_seccomp_syscall_mask(const char *name, int action, int arg, int value) { struct sock_filter filter[] = { KORE_SYSCALL_MASK(exit, arg, value, action), KORE_BPF_GUARD }; return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter))); } struct sock_filter * kore_seccomp_syscall_flag(const char *name, int action, int arg, int value) { struct sock_filter filter[] = { KORE_SYSCALL_WITH_FLAG(exit, arg, value, action), KORE_BPF_GUARD }; return (seccomp_filter_update(filter, name, KORE_FILTER_LEN(filter))); } static void seccomp_register_violation(pid_t pid) { int idx; struct kore_worker *kw; struct iovec iov; #if defined(__arm__) struct pt_regs regs; #else struct user_regs_struct regs; #endif long sysnr; const char *name; iov.iov_base = ®s; iov.iov_len = sizeof(regs); if (ptrace(PTRACE_GETREGSET, pid, 1, &iov) == -1) fatal("ptrace: %s", errno_s); #if SECCOMP_AUDIT_ARCH == AUDIT_ARCH_X86_64 sysnr = regs.orig_rax; #elif SECCOMP_AUDIT_ARCH == AUDIT_ARCH_AARCH64 sysnr = regs.regs[8]; #elif SECCOMP_AUDIT_ARCH == AUDIT_ARCH_ARM sysnr = regs.uregs[7]; #else #error "platform not supported" #endif name = NULL; for (idx = 0; idx < worker_count; idx++) { kw = kore_worker_data(idx); if (kw->pid == pid) { name = kore_worker_name(kw->id); break; } } if (name == NULL) name = ""; kore_log(LOG_INFO, "seccomp violation, %s pid=%d, syscall=%ld:%s", name, pid, sysnr, kore_seccomp_syscall_name(sysnr)); } static struct sock_filter * seccomp_filter_update(struct sock_filter *filter, const char *name, size_t elm) { int nr; struct sock_filter *result; if ((nr = kore_seccomp_syscall_resolve(name)) == -1) return (NULL); result = kore_calloc(elm, sizeof(struct sock_filter)); memcpy(result, filter, elm * sizeof(struct sock_filter)); /* Update the syscall number to the one specified. */ result[0].k = nr; return (result); } kore-4.2.3/src/sha1.c000066400000000000000000000122241430066302400142460ustar00rootroot00000000000000/* $OpenBSD: sha1.c,v 1.27 2019/06/07 22:56:36 dtucker Exp $ */ /* * SHA-1 in C * By Steve Reid * 100% Public Domain * * Test Vectors (from FIPS PUB 180-1) * "abc" * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 * A million repetitions of "a" * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F */ #include #include #include "kore.h" #include "sha1.h" #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) /* * blk0() and blk() perform the initial expand. * I got the idea of expanding during the round function from SSLeay */ #if BYTE_ORDER == LITTLE_ENDIAN # define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ |(rol(block->l[i],8)&0x00FF00FF)) #else # define blk0(i) block->l[i] #endif #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ ^block->l[(i+2)&15]^block->l[i&15],1)) /* * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 */ #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); typedef union { u_int8_t c[64]; u_int32_t l[16]; } CHAR64LONG16; /* * Hash a single 512-bit block. This is the core of the algorithm. */ void SHA1Transform(u_int32_t state[5], const u_int8_t buffer[SHA1_BLOCK_LENGTH]) { u_int32_t a, b, c, d, e; u_int8_t workspace[SHA1_BLOCK_LENGTH]; CHAR64LONG16 *block = (CHAR64LONG16 *)workspace; (void)memcpy(block, buffer, SHA1_BLOCK_LENGTH); /* Copy context->state[] to working vars */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; /* 4 rounds of 20 operations each. Loop unrolled. */ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); /* Add the working vars back into context.state[] */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; /* Wipe variables */ a = b = c = d = e = 0; } /* * SHA1Init - Initialize new context */ void SHA1Init(SHA1_CTX *context) { /* SHA1 initialization constants */ context->count = 0; context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; context->state[4] = 0xC3D2E1F0; } /* * Run your data through this. */ void SHA1Update(SHA1_CTX *context, const u_int8_t *data, size_t len) { size_t i, j; j = (size_t)((context->count >> 3) & 63); context->count += ((u_int64_t)len << 3); if ((j + len) > 63) { (void)memcpy(&context->buffer[j], data, (i = 64-j)); SHA1Transform(context->state, context->buffer); for ( ; i + 63 < len; i += 64) SHA1Transform(context->state, &data[i]); j = 0; } else { i = 0; } (void)memcpy(&context->buffer[j], &data[i], len - i); } /* * Add padding and return the message digest. */ void SHA1Pad(SHA1_CTX *context) { u_int8_t finalcount[8]; u_int i; for (i = 0; i < 8; i++) { finalcount[i] = (u_int8_t)((context->count >> ((7 - (i & 7)) * 8)) & 255); /* Endian independent */ } SHA1Update(context, (u_int8_t *)"\200", 1); while ((context->count & 504) != 448) SHA1Update(context, (u_int8_t *)"\0", 1); SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ } void SHA1Final(u_int8_t digest[SHA1_DIGEST_LENGTH], SHA1_CTX *context) { u_int i; SHA1Pad(context); for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { digest[i] = (u_int8_t) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); } kore_mem_zero(context, sizeof(*context)); } kore-4.2.3/src/sha2.c000066400000000000000000000607741430066302400142640ustar00rootroot00000000000000/* $OpenBSD: sha2.c,v 1.28 2019/07/23 12:35:22 dtucker Exp $ */ /* * FILE: sha2.c * AUTHOR: Aaron D. Gifford * * Copyright (c) 2000-2001, Aaron D. Gifford * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $ */ /* OPENBSD ORIGINAL: lib/libc/hash/sha2.c */ #include #include #if defined(__APPLE__) #include #else #include #endif #include "kore.h" #include "sha2.h" /* * UNROLLED TRANSFORM LOOP NOTE: * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform * loop version for the hash transform rounds (defined using macros * later in this file). Either define on the command line, for example: * * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c * * or define below: * * #define SHA2_UNROLL_TRANSFORM * */ #if defined(__amd64__) || defined(__i386__) #define SHA2_UNROLL_TRANSFORM #endif /*** SHA-224/256/384/512 Machine Architecture Definitions *****************/ /* * BYTE_ORDER NOTE: * * Please make sure that your system defines BYTE_ORDER. If your * architecture is little-endian, make sure it also defines * LITTLE_ENDIAN and that the two (BYTE_ORDER and LITTLE_ENDIAN) are * equivalent. * * If your system does not define the above, then you can do so by * hand like this: * * #define LITTLE_ENDIAN 1234 * #define BIG_ENDIAN 4321 * * And for little-endian machines, add: * * #define BYTE_ORDER LITTLE_ENDIAN * * Or for big-endian machines: * * #define BYTE_ORDER BIG_ENDIAN * * The FreeBSD machine this was written on defines BYTE_ORDER * appropriately by including (which in turn includes * where the appropriate definitions are actually * made). */ #if !defined(BYTE_ORDER) || (BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN) #error Define BYTE_ORDER to be equal to either LITTLE_ENDIAN or BIG_ENDIAN #endif /*** SHA-224/256/384/512 Various Length Definitions ***********************/ /* NOTE: Most of these are in sha2.h */ #define SHA224_SHORT_BLOCK_LENGTH (SHA224_BLOCK_LENGTH - 8) #define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) #define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16) #define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16) /*** ENDIAN SPECIFIC COPY MACROS **************************************/ #define BE_8_TO_32(dst, cp) do { \ (dst) = (u_int32_t)(cp)[3] | ((u_int32_t)(cp)[2] << 8) | \ ((u_int32_t)(cp)[1] << 16) | ((u_int32_t)(cp)[0] << 24); \ } while(0) #define BE_8_TO_64(dst, cp) do { \ (dst) = (u_int64_t)(cp)[7] | ((u_int64_t)(cp)[6] << 8) | \ ((u_int64_t)(cp)[5] << 16) | ((u_int64_t)(cp)[4] << 24) | \ ((u_int64_t)(cp)[3] << 32) | ((u_int64_t)(cp)[2] << 40) | \ ((u_int64_t)(cp)[1] << 48) | ((u_int64_t)(cp)[0] << 56); \ } while (0) #define BE_64_TO_8(cp, src) do { \ (cp)[0] = (src) >> 56; \ (cp)[1] = (src) >> 48; \ (cp)[2] = (src) >> 40; \ (cp)[3] = (src) >> 32; \ (cp)[4] = (src) >> 24; \ (cp)[5] = (src) >> 16; \ (cp)[6] = (src) >> 8; \ (cp)[7] = (src); \ } while (0) #define BE_32_TO_8(cp, src) do { \ (cp)[0] = (src) >> 24; \ (cp)[1] = (src) >> 16; \ (cp)[2] = (src) >> 8; \ (cp)[3] = (src); \ } while (0) /* * Macro for incrementally adding the unsigned 64-bit integer n to the * unsigned 128-bit integer (represented using a two-element array of * 64-bit words): */ #define ADDINC128(w,n) do { \ (w)[0] += (u_int64_t)(n); \ if ((w)[0] < (n)) { \ (w)[1]++; \ } \ } while (0) /*** THE SIX LOGICAL FUNCTIONS ****************************************/ /* * Bit shifting and rotation (used by the six SHA-XYZ logical functions: * * NOTE: The naming of R and S appears backwards here (R is a SHIFT and * S is a ROTATION) because the SHA-224/256/384/512 description document * (see http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf) uses this * same "backwards" definition. */ /* Shift-right (used in SHA-224, SHA-256, SHA-384, and SHA-512): */ #define R(b,x) ((x) >> (b)) /* 32-bit Rotate-right (used in SHA-224 and SHA-256): */ #define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b)))) /* 64-bit Rotate-right (used in SHA-384 and SHA-512): */ #define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b)))) /* Two of six logical functions used in SHA-224, SHA-256, SHA-384, and SHA-512: */ #define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) #define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) /* Four of six logical functions used in SHA-224 and SHA-256: */ #define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x))) #define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x))) #define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x))) #define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x))) /* Four of six logical functions used in SHA-384 and SHA-512: */ #define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x))) #define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x))) #define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x))) #define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x))) /*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ /* Hash constant words K for SHA-224 and SHA-256: */ static const u_int32_t K256[64] = { 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL }; /* Initial hash value H for SHA-256: */ static const u_int32_t sha256_initial_hash_value[8] = { 0x6a09e667UL, 0xbb67ae85UL, 0x3c6ef372UL, 0xa54ff53aUL, 0x510e527fUL, 0x9b05688cUL, 0x1f83d9abUL, 0x5be0cd19UL }; /* Hash constant words K for SHA-384 and SHA-512: */ static const u_int64_t K512[80] = { 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL }; /* Initial hash value H for SHA-512 */ static const u_int64_t sha512_initial_hash_value[8] = { 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL }; /* Initial hash value H for SHA-384 */ static const u_int64_t sha384_initial_hash_value[8] = { 0xcbbb9d5dc1059ed8ULL, 0x629a292a367cd507ULL, 0x9159015a3070dd17ULL, 0x152fecd8f70e5939ULL, 0x67332667ffc00b31ULL, 0x8eb44a8768581511ULL, 0xdb0c2e0d64f98fa7ULL, 0x47b5481dbefa4fa4ULL }; /*** SHA-256: *********************************************************/ void SHA256Init(SHA2_CTX *context) { memcpy(context->state.st32, sha256_initial_hash_value, sizeof(sha256_initial_hash_value)); memset(context->buffer, 0, sizeof(context->buffer)); context->bitcount[0] = 0; } #ifdef SHA2_UNROLL_TRANSFORM /* Unrolled SHA-256 round macros: */ #define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) do { \ BE_8_TO_32(W256[j], data); \ data += 4; \ T1 = (h) + Sigma1_256((e)) + Ch((e), (f), (g)) + K256[j] + W256[j]; \ (d) += T1; \ (h) = T1 + Sigma0_256((a)) + Maj((a), (b), (c)); \ j++; \ } while(0) #define ROUND256(a,b,c,d,e,f,g,h) do { \ s0 = W256[(j+1)&0x0f]; \ s0 = sigma0_256(s0); \ s1 = W256[(j+14)&0x0f]; \ s1 = sigma1_256(s1); \ T1 = (h) + Sigma1_256((e)) + Ch((e), (f), (g)) + K256[j] + \ (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); \ (d) += T1; \ (h) = T1 + Sigma0_256((a)) + Maj((a), (b), (c)); \ j++; \ } while(0) void SHA256Transform(u_int32_t state[8], const u_int8_t data[SHA256_BLOCK_LENGTH]) { u_int32_t a, b, c, d, e, f, g, h, s0, s1; u_int32_t T1, W256[16]; int j; /* Initialize registers with the prev. intermediate value */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; f = state[5]; g = state[6]; h = state[7]; j = 0; do { /* Rounds 0 to 15 (unrolled): */ ROUND256_0_TO_15(a,b,c,d,e,f,g,h); ROUND256_0_TO_15(h,a,b,c,d,e,f,g); ROUND256_0_TO_15(g,h,a,b,c,d,e,f); ROUND256_0_TO_15(f,g,h,a,b,c,d,e); ROUND256_0_TO_15(e,f,g,h,a,b,c,d); ROUND256_0_TO_15(d,e,f,g,h,a,b,c); ROUND256_0_TO_15(c,d,e,f,g,h,a,b); ROUND256_0_TO_15(b,c,d,e,f,g,h,a); } while (j < 16); /* Now for the remaining rounds up to 63: */ do { ROUND256(a,b,c,d,e,f,g,h); ROUND256(h,a,b,c,d,e,f,g); ROUND256(g,h,a,b,c,d,e,f); ROUND256(f,g,h,a,b,c,d,e); ROUND256(e,f,g,h,a,b,c,d); ROUND256(d,e,f,g,h,a,b,c); ROUND256(c,d,e,f,g,h,a,b); ROUND256(b,c,d,e,f,g,h,a); } while (j < 64); /* Compute the current intermediate hash value */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* Clean up */ a = b = c = d = e = f = g = h = T1 = 0; } #else /* SHA2_UNROLL_TRANSFORM */ void SHA256Transform(u_int32_t state[8], const u_int8_t data[SHA256_BLOCK_LENGTH]) { u_int32_t a, b, c, d, e, f, g, h, s0, s1; u_int32_t T1, T2, W256[16]; int j; /* Initialize registers with the prev. intermediate value */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; f = state[5]; g = state[6]; h = state[7]; j = 0; do { BE_8_TO_32(W256[j], data); data += 4; /* Apply the SHA-256 compression function to update a..h */ T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + W256[j]; T2 = Sigma0_256(a) + Maj(a, b, c); h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; j++; } while (j < 16); do { /* Part of the message block expansion: */ s0 = W256[(j+1)&0x0f]; s0 = sigma0_256(s0); s1 = W256[(j+14)&0x0f]; s1 = sigma1_256(s1); /* Apply the SHA-256 compression function to update a..h */ T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); T2 = Sigma0_256(a) + Maj(a, b, c); h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; j++; } while (j < 64); /* Compute the current intermediate hash value */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* Clean up */ a = b = c = d = e = f = g = h = T1 = T2 = 0; } #endif /* SHA2_UNROLL_TRANSFORM */ void SHA256Update(SHA2_CTX *context, const u_int8_t *data, size_t len) { u_int64_t freespace, usedspace; /* Calling with no data is valid (we do nothing) */ if (len == 0) return; usedspace = (context->bitcount[0] >> 3) % SHA256_BLOCK_LENGTH; if (usedspace > 0) { /* Calculate how much free space is available in the buffer */ freespace = SHA256_BLOCK_LENGTH - usedspace; if (len >= freespace) { /* Fill the buffer completely and process it */ memcpy(&context->buffer[usedspace], data, freespace); context->bitcount[0] += freespace << 3; len -= freespace; data += freespace; SHA256Transform(context->state.st32, context->buffer); } else { /* The buffer is not yet full */ memcpy(&context->buffer[usedspace], data, len); context->bitcount[0] += (u_int64_t)len << 3; /* Clean up: */ usedspace = freespace = 0; return; } } while (len >= SHA256_BLOCK_LENGTH) { /* Process as many complete blocks as we can */ SHA256Transform(context->state.st32, data); context->bitcount[0] += SHA256_BLOCK_LENGTH << 3; len -= SHA256_BLOCK_LENGTH; data += SHA256_BLOCK_LENGTH; } if (len > 0) { /* There's left-overs, so save 'em */ memcpy(context->buffer, data, len); context->bitcount[0] += len << 3; } /* Clean up: */ usedspace = freespace = 0; } void SHA256Pad(SHA2_CTX *context) { unsigned int usedspace; usedspace = (context->bitcount[0] >> 3) % SHA256_BLOCK_LENGTH; if (usedspace > 0) { /* Begin padding with a 1 bit: */ context->buffer[usedspace++] = 0x80; if (usedspace <= SHA256_SHORT_BLOCK_LENGTH) { /* Set-up for the last transform: */ memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace); } else { if (usedspace < SHA256_BLOCK_LENGTH) { memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace); } /* Do second-to-last transform: */ SHA256Transform(context->state.st32, context->buffer); /* Prepare for last transform: */ memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH); } } else { /* Set-up for the last transform: */ memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH); /* Begin padding with a 1 bit: */ *context->buffer = 0x80; } /* Store the length of input data (in bits) in big endian format: */ BE_64_TO_8(&context->buffer[SHA256_SHORT_BLOCK_LENGTH], context->bitcount[0]); /* Final transform: */ SHA256Transform(context->state.st32, context->buffer); /* Clean up: */ usedspace = 0; } void SHA256Final(u_int8_t digest[SHA256_DIGEST_LENGTH], SHA2_CTX *context) { SHA256Pad(context); #if BYTE_ORDER == LITTLE_ENDIAN int i; /* Convert TO host byte order */ for (i = 0; i < 8; i++) BE_32_TO_8(digest + i * 4, context->state.st32[i]); #else memcpy(digest, context->state.st32, SHA256_DIGEST_LENGTH); #endif kore_mem_zero(context, sizeof(*context)); } /*** SHA-512: *********************************************************/ void SHA512Init(SHA2_CTX *context) { memcpy(context->state.st64, sha512_initial_hash_value, sizeof(sha512_initial_hash_value)); memset(context->buffer, 0, sizeof(context->buffer)); context->bitcount[0] = context->bitcount[1] = 0; } #ifdef SHA2_UNROLL_TRANSFORM /* Unrolled SHA-512 round macros: */ #define ROUND512_0_TO_15(a,b,c,d,e,f,g,h) do { \ BE_8_TO_64(W512[j], data); \ data += 8; \ T1 = (h) + Sigma1_512((e)) + Ch((e), (f), (g)) + K512[j] + W512[j]; \ (d) += T1; \ (h) = T1 + Sigma0_512((a)) + Maj((a), (b), (c)); \ j++; \ } while(0) #define ROUND512(a,b,c,d,e,f,g,h) do { \ s0 = W512[(j+1)&0x0f]; \ s0 = sigma0_512(s0); \ s1 = W512[(j+14)&0x0f]; \ s1 = sigma1_512(s1); \ T1 = (h) + Sigma1_512((e)) + Ch((e), (f), (g)) + K512[j] + \ (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); \ (d) += T1; \ (h) = T1 + Sigma0_512((a)) + Maj((a), (b), (c)); \ j++; \ } while(0) void SHA512Transform(u_int64_t state[8], const u_int8_t data[SHA512_BLOCK_LENGTH]) { u_int64_t a, b, c, d, e, f, g, h, s0, s1; u_int64_t T1, W512[16]; int j; /* Initialize registers with the prev. intermediate value */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; f = state[5]; g = state[6]; h = state[7]; j = 0; do { /* Rounds 0 to 15 (unrolled): */ ROUND512_0_TO_15(a,b,c,d,e,f,g,h); ROUND512_0_TO_15(h,a,b,c,d,e,f,g); ROUND512_0_TO_15(g,h,a,b,c,d,e,f); ROUND512_0_TO_15(f,g,h,a,b,c,d,e); ROUND512_0_TO_15(e,f,g,h,a,b,c,d); ROUND512_0_TO_15(d,e,f,g,h,a,b,c); ROUND512_0_TO_15(c,d,e,f,g,h,a,b); ROUND512_0_TO_15(b,c,d,e,f,g,h,a); } while (j < 16); /* Now for the remaining rounds up to 79: */ do { ROUND512(a,b,c,d,e,f,g,h); ROUND512(h,a,b,c,d,e,f,g); ROUND512(g,h,a,b,c,d,e,f); ROUND512(f,g,h,a,b,c,d,e); ROUND512(e,f,g,h,a,b,c,d); ROUND512(d,e,f,g,h,a,b,c); ROUND512(c,d,e,f,g,h,a,b); ROUND512(b,c,d,e,f,g,h,a); } while (j < 80); /* Compute the current intermediate hash value */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* Clean up */ a = b = c = d = e = f = g = h = T1 = 0; } #else /* SHA2_UNROLL_TRANSFORM */ void SHA512Transform(u_int64_t state[8], const u_int8_t data[SHA512_BLOCK_LENGTH]) { u_int64_t a, b, c, d, e, f, g, h, s0, s1; u_int64_t T1, T2, W512[16]; int j; /* Initialize registers with the prev. intermediate value */ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; f = state[5]; g = state[6]; h = state[7]; j = 0; do { BE_8_TO_64(W512[j], data); data += 8; /* Apply the SHA-512 compression function to update a..h */ T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + W512[j]; T2 = Sigma0_512(a) + Maj(a, b, c); h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; j++; } while (j < 16); do { /* Part of the message block expansion: */ s0 = W512[(j+1)&0x0f]; s0 = sigma0_512(s0); s1 = W512[(j+14)&0x0f]; s1 = sigma1_512(s1); /* Apply the SHA-512 compression function to update a..h */ T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); T2 = Sigma0_512(a) + Maj(a, b, c); h = g; g = f; f = e; e = d + T1; d = c; c = b; b = a; a = T1 + T2; j++; } while (j < 80); /* Compute the current intermediate hash value */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; state[5] += f; state[6] += g; state[7] += h; /* Clean up */ a = b = c = d = e = f = g = h = T1 = T2 = 0; } #endif /* SHA2_UNROLL_TRANSFORM */ void SHA512Update(SHA2_CTX *context, const u_int8_t *data, size_t len) { size_t freespace, usedspace; /* Calling with no data is valid (we do nothing) */ if (len == 0) return; usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; if (usedspace > 0) { /* Calculate how much free space is available in the buffer */ freespace = SHA512_BLOCK_LENGTH - usedspace; if (len >= freespace) { /* Fill the buffer completely and process it */ memcpy(&context->buffer[usedspace], data, freespace); ADDINC128(context->bitcount, freespace << 3); len -= freespace; data += freespace; SHA512Transform(context->state.st64, context->buffer); } else { /* The buffer is not yet full */ memcpy(&context->buffer[usedspace], data, len); ADDINC128(context->bitcount, len << 3); /* Clean up: */ usedspace = freespace = 0; return; } } while (len >= SHA512_BLOCK_LENGTH) { /* Process as many complete blocks as we can */ SHA512Transform(context->state.st64, data); ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3); len -= SHA512_BLOCK_LENGTH; data += SHA512_BLOCK_LENGTH; } if (len > 0) { /* There's left-overs, so save 'em */ memcpy(context->buffer, data, len); ADDINC128(context->bitcount, len << 3); } /* Clean up: */ usedspace = freespace = 0; } void SHA512Pad(SHA2_CTX *context) { unsigned int usedspace; usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; if (usedspace > 0) { /* Begin padding with a 1 bit: */ context->buffer[usedspace++] = 0x80; if (usedspace <= SHA512_SHORT_BLOCK_LENGTH) { /* Set-up for the last transform: */ memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace); } else { if (usedspace < SHA512_BLOCK_LENGTH) { memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace); } /* Do second-to-last transform: */ SHA512Transform(context->state.st64, context->buffer); /* And set-up for the last transform: */ memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2); } } else { /* Prepare for final transform: */ memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH); /* Begin padding with a 1 bit: */ *context->buffer = 0x80; } /* Store the length of input data (in bits) in big endian format: */ BE_64_TO_8(&context->buffer[SHA512_SHORT_BLOCK_LENGTH], context->bitcount[1]); BE_64_TO_8(&context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8], context->bitcount[0]); /* Final transform: */ SHA512Transform(context->state.st64, context->buffer); /* Clean up: */ usedspace = 0; } void SHA512Final(u_int8_t digest[SHA512_DIGEST_LENGTH], SHA2_CTX *context) { SHA512Pad(context); #if BYTE_ORDER == LITTLE_ENDIAN int i; /* Convert TO host byte order */ for (i = 0; i < 8; i++) BE_64_TO_8(digest + i * 8, context->state.st64[i]); #else memcpy(digest, context->state.st64, SHA512_DIGEST_LENGTH); #endif kore_mem_zero(context, sizeof(*context)); } /*** SHA-384: *********************************************************/ void SHA384Init(SHA2_CTX *context) { memcpy(context->state.st64, sha384_initial_hash_value, sizeof(sha384_initial_hash_value)); memset(context->buffer, 0, sizeof(context->buffer)); context->bitcount[0] = context->bitcount[1] = 0; } /* Equivalent of MAKE_CLONE (which is a no-op) for SHA384 funcs */ void SHA384Transform(u_int64_t state[8], const u_int8_t data[SHA512_BLOCK_LENGTH]) { SHA512Transform(state, data); } void SHA384Update(SHA2_CTX *context, const u_int8_t *data, size_t len) { SHA512Update(context, data, len); } void SHA384Pad(SHA2_CTX *context) { SHA512Pad(context); } void SHA384Final(u_int8_t digest[SHA384_DIGEST_LENGTH], SHA2_CTX *context) { SHA384Pad(context); #if BYTE_ORDER == LITTLE_ENDIAN int i; /* Convert TO host byte order */ for (i = 0; i < 6; i++) BE_64_TO_8(digest + i * 8, context->state.st64[i]); #else memcpy(digest, context->state.st64, SHA384_DIGEST_LENGTH); #endif /* Zero out state data */ kore_mem_zero(context, sizeof(*context)); } kore-4.2.3/src/tasks.c000066400000000000000000000164531430066302400145470ustar00rootroot00000000000000/* * Copyright (c) 2014 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "kore.h" #include "http.h" #include "tasks.h" #if defined(__linux__) #include "seccomp.h" static struct sock_filter filter_task[] = { KORE_SYSCALL_ALLOW(clone), KORE_SYSCALL_ALLOW(socketpair), KORE_SYSCALL_ALLOW(set_robust_list), }; #endif static u_int8_t threads; static TAILQ_HEAD(, kore_task_thread) task_threads; u_int16_t kore_task_threads = KORE_TASK_THREADS; static void *task_thread(void *); static void task_channel_read(int, void *, u_int32_t); static void task_channel_write(int, void *, u_int32_t); static void task_thread_spawn(struct kore_task_thread **); #define THREAD_FD_ASSIGN(t, f, i, o) \ do { \ if (pthread_self() == t) { \ f = i; \ } else { \ f = o; \ } \ } while (0); void kore_task_init(void) { threads = 0; TAILQ_INIT(&task_threads); #if defined(__linux__) kore_seccomp_filter("task", filter_task, KORE_FILTER_LEN(filter_task)); #endif } void kore_task_create(struct kore_task *t, int (*entry)(struct kore_task *)) { t->cb = NULL; #if !defined(KORE_NO_HTTP) t->req = NULL; #endif t->evt.type = KORE_TYPE_TASK; t->evt.handle = kore_task_handle; t->entry = entry; t->state = KORE_TASK_STATE_CREATED; pthread_rwlock_init(&(t->lock), NULL); if (socketpair(AF_UNIX, SOCK_STREAM, 0, t->fds) == -1) fatal("kore_task_create: socketpair() %s", errno_s); } void kore_task_run(struct kore_task *t) { struct kore_task_thread *tt; kore_platform_schedule_read(t->fds[0], t); if (threads < kore_task_threads) { /* task_thread_spawn() will lock tt->lock for us. */ task_thread_spawn(&tt); } else { /* Cycle task around. */ if ((tt = TAILQ_FIRST(&task_threads)) == NULL) fatal("no available tasks threads?"); pthread_mutex_lock(&(tt->lock)); TAILQ_REMOVE(&task_threads, tt, list); TAILQ_INSERT_TAIL(&task_threads, tt, list); } t->thread = tt; TAILQ_INSERT_TAIL(&(tt->tasks), t, list); pthread_mutex_unlock(&(tt->lock)); pthread_cond_signal(&(tt->cond)); } #if !defined(KORE_NO_HTTP) void kore_task_bind_request(struct kore_task *t, struct http_request *req) { if (t->cb != NULL) fatal("cannot bind cbs and requests at the same time"); t->req = req; LIST_INSERT_HEAD(&(req->tasks), t, rlist); http_request_sleep(req); } #endif void kore_task_bind_callback(struct kore_task *t, void (*cb)(struct kore_task *)) { #if !defined(KORE_NO_HTTP) if (t->req != NULL) fatal("cannot bind requests and cbs at the same time"); #endif t->cb = cb; } void kore_task_destroy(struct kore_task *t) { #if !defined(KORE_NO_HTTP) if (t->req != NULL) { t->req = NULL; LIST_REMOVE(t, rlist); } #endif pthread_rwlock_wrlock(&(t->lock)); if (t->fds[0] != -1) { (void)close(t->fds[0]); t->fds[0] = -1; } if (t->fds[1] != -1) { (void)close(t->fds[1]); t->fds[1] = -1; } pthread_rwlock_unlock(&(t->lock)); pthread_rwlock_destroy(&(t->lock)); } int kore_task_finished(struct kore_task *t) { return ((kore_task_state(t) == KORE_TASK_STATE_FINISHED)); } void kore_task_finish(struct kore_task *t) { pthread_rwlock_wrlock(&(t->lock)); if (t->fds[1] != -1) { (void)close(t->fds[1]); t->fds[1] = -1; } pthread_rwlock_unlock(&(t->lock)); } void kore_task_channel_write(struct kore_task *t, void *data, u_int32_t len) { int fd; THREAD_FD_ASSIGN(t->thread->tid, fd, t->fds[1], t->fds[0]); task_channel_write(fd, &len, sizeof(len)); task_channel_write(fd, data, len); } u_int32_t kore_task_channel_read(struct kore_task *t, void *out, u_int32_t len) { int fd; u_int32_t dlen, bytes; THREAD_FD_ASSIGN(t->thread->tid, fd, t->fds[1], t->fds[0]); task_channel_read(fd, &dlen, sizeof(dlen)); if (dlen > len) bytes = len; else bytes = dlen; task_channel_read(fd, out, bytes); return (dlen); } void kore_task_handle(void *arg, int finished) { struct kore_task *t = arg; #if !defined(KORE_NO_HTTP) if (t->req != NULL) http_request_wakeup(t->req); #endif if (finished) { kore_platform_disable_read(t->fds[0]); kore_task_set_state(t, KORE_TASK_STATE_FINISHED); #if !defined(KORE_NO_HTTP) if (t->req != NULL) { if (t->req->flags & HTTP_REQUEST_DELETE) kore_task_destroy(t); } #endif } if (t->cb != NULL) t->cb(t); } int kore_task_state(struct kore_task *t) { int s; pthread_rwlock_rdlock(&(t->lock)); s = t->state; pthread_rwlock_unlock(&(t->lock)); return (s); } void kore_task_set_state(struct kore_task *t, int state) { pthread_rwlock_wrlock(&(t->lock)); t->state = state; pthread_rwlock_unlock(&(t->lock)); } int kore_task_result(struct kore_task *t) { int r; pthread_rwlock_rdlock(&(t->lock)); r = t->result; pthread_rwlock_unlock(&(t->lock)); return (r); } void kore_task_set_result(struct kore_task *t, int result) { pthread_rwlock_wrlock(&(t->lock)); t->result = result; pthread_rwlock_unlock(&(t->lock)); } static void task_channel_write(int fd, void *data, u_int32_t len) { ssize_t r; u_int8_t *d; u_int32_t offset; d = data; offset = 0; while (offset != len) { r = send(fd, d + offset, len - offset, 0); if (r == -1 && errno == EINTR) continue; if (r == -1) fatal("task_channel_write: %s", errno_s); offset += r; } } static void task_channel_read(int fd, void *out, u_int32_t len) { ssize_t r; u_int8_t *d; u_int32_t offset; d = out; offset = 0; while (offset != len) { r = read(fd, d + offset, len - offset); if (r == -1 && errno == EINTR) continue; if (r == -1) fatal("task_channel_read: %s", errno_s); if (r == 0) fatal("task_channel_read: unexpected eof"); offset += r; } } static void task_thread_spawn(struct kore_task_thread **out) { struct kore_task_thread *tt; tt = kore_malloc(sizeof(*tt)); tt->idx = threads++; TAILQ_INIT(&(tt->tasks)); pthread_cond_init(&(tt->cond), NULL); pthread_mutex_init(&(tt->lock), NULL); pthread_mutex_lock(&(tt->lock)); TAILQ_INSERT_TAIL(&task_threads, tt, list); if (pthread_create(&(tt->tid), NULL, task_thread, tt) != 0) fatal("pthread_create: %s", errno_s); *out = tt; } static void * task_thread(void *arg) { struct kore_task *t; struct kore_task_thread *tt = arg; pthread_mutex_lock(&(tt->lock)); for (;;) { if (TAILQ_EMPTY(&(tt->tasks))) pthread_cond_wait(&(tt->cond), &(tt->lock)); t = TAILQ_FIRST(&(tt->tasks)); TAILQ_REMOVE(&(tt->tasks), t, list); pthread_mutex_unlock(&(tt->lock)); kore_task_set_state(t, KORE_TASK_STATE_RUNNING); kore_task_set_result(t, t->entry(t)); kore_task_finish(t); pthread_mutex_lock(&(tt->lock)); } pthread_exit(NULL); /* NOTREACHED */ return (NULL); } kore-4.2.3/src/timer.c000066400000000000000000000047431430066302400145410ustar00rootroot00000000000000/* * Copyright (c) 2016-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "kore.h" TAILQ_HEAD(timerlist, kore_timer) kore_timers; void kore_timer_init(void) { TAILQ_INIT(&kore_timers); } struct kore_timer * kore_timer_add(void (*cb)(void *, u_int64_t), u_int64_t interval, void *arg, int flags) { struct kore_timer *timer, *t; timer = kore_malloc(sizeof(*timer)); timer->cb = cb; timer->arg = arg; timer->flags = flags; timer->interval = interval; timer->nextrun = kore_time_ms() + timer->interval; TAILQ_FOREACH(t, &kore_timers, list) { if (t->nextrun > timer->nextrun) { TAILQ_INSERT_BEFORE(t, timer, list); return (timer); } } TAILQ_INSERT_TAIL(&kore_timers, timer, list); return (timer); } void kore_timer_remove(struct kore_timer *timer) { TAILQ_REMOVE(&kore_timers, timer, list); kore_free(timer); } u_int64_t kore_timer_next_run(u_int64_t now) { struct kore_timer *timer; if ((timer = TAILQ_FIRST(&kore_timers)) != NULL) { if (timer->nextrun > now) return (timer->nextrun - now); return (0); } return (KORE_WAIT_INFINITE); } void kore_timer_run(u_int64_t now) { struct kore_timer *timer, *t, *prev; prev = NULL; while ((timer = TAILQ_FIRST(&kore_timers)) != NULL) { if (timer == prev) break; if (timer->nextrun > now) break; TAILQ_REMOVE(&kore_timers, timer, list); timer->cb(timer->arg, now); if (timer->flags & KORE_TIMER_ONESHOT) { kore_free(timer); } else { prev = timer; timer->nextrun = now + timer->interval; TAILQ_FOREACH(t, &kore_timers, list) { if (t->nextrun > timer->nextrun) { TAILQ_INSERT_BEFORE(t, timer, list); break; } } if (t == NULL) TAILQ_INSERT_TAIL(&kore_timers, timer, list); } } } kore-4.2.3/src/tls_none.c000066400000000000000000000061121430066302400152320ustar00rootroot00000000000000/* * Copyright (c) 2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * An empty TLS backend that does nothing, useful if you do * not require any TLS stuff in Kore. */ #include #include "kore.h" struct kore_privsep keymgr_privsep; char *kore_rand_file = NULL; int kore_keymgr_active = 0; int kore_tls_supported(void) { return (KORE_RESULT_ERROR); } void kore_keymgr_cleanup(int final) { } void kore_tls_init(void) { kore_log(LOG_ERR, "No compiled in TLS backend"); } void kore_tls_cleanup(void) { } void kore_tls_dh_check(void) { } void kore_tls_keymgr_init(void) { } void kore_tls_connection_cleanup(struct connection *c) { } void kore_tls_domain_cleanup(struct kore_domain *dom) { } void kore_tls_seed(const void *data, size_t len) { } void kore_keymgr_run(void) { fatal("%s: not supported", __func__); } void kore_tls_version_set(int version) { fatal("%s: not supported", __func__); } int kore_tls_dh_load(const char *path) { fatal("%s: not supported", __func__); } int kore_tls_ciphersuite_set(const char *list) { fatal("%s: not supported", __func__); } void kore_tls_domain_setup(struct kore_domain *dom, int type, const void *data, size_t datalen) { fatal("%s: not supported", __func__); } void kore_tls_domain_crl(struct kore_domain *dom, const void *pem, size_t pemlen) { fatal("%s: not supported", __func__); } int kore_tls_connection_accept(struct connection *c) { fatal("%s: not supported", __func__); } int kore_tls_read(struct connection *c, size_t *bytes) { fatal("%s: not supported", __func__); } int kore_tls_write(struct connection *c, size_t len, size_t *written) { fatal("%s: not supported", __func__); } KORE_PRIVATE_KEY * kore_tls_rsakey_load(const char *path) { fatal("%s: not supported", __func__); } KORE_PRIVATE_KEY * kore_tls_rsakey_generate(const char *path) { fatal("%s: not supported", __func__); } KORE_X509_NAMES * kore_tls_x509_subject_name(struct connection *c) { fatal("%s: not supported", __func__); } KORE_X509_NAMES * kore_tls_x509_issuer_name(struct connection *c) { fatal("%s: not supported", __func__); } int kore_tls_x509name_foreach(KORE_X509_NAMES *name, int flags, void *udata, int (*cb)(void *, int, int, const char *, const void *, size_t, int)) { fatal("%s: not supported", __func__); } int kore_tls_x509_data(struct connection *c, u_int8_t **ptr, size_t *olen) { fatal("%s: not supported", __func__); } kore-4.2.3/src/tls_openssl.c000066400000000000000000000662361430066302400157730ustar00rootroot00000000000000/* * Copyright (c) 2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This TLS backend is the original TLS code used in Kore. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "kore.h" #include "http.h" #define TLS_SESSION_ID "kore_tls_sessionid" static int tls_domain_x509_verify(int, X509_STORE_CTX *); static X509 *tls_domain_load_certificate_chain(SSL_CTX *, const void *, size_t); static int tls_sni_cb(SSL *, int *, void *); static void tls_info_callback(const SSL *, int, int); #if defined(KORE_USE_ACME) static void tls_acme_challenge_set_cert(SSL *, struct kore_domain *); static int tls_acme_alpn(SSL *, const unsigned char **, unsigned char *, const unsigned char *, unsigned int, void *); #endif static void tls_keymgr_await_data(void); static void tls_keymgr_msg_response(struct kore_msg *, const void *); static int tls_keymgr_rsa_init(RSA *); static int tls_keymgr_rsa_finish(RSA *); static int tls_keymgr_rsa_privenc(int, const unsigned char *, unsigned char *, RSA *, int); static ECDSA_SIG *tls_keymgr_ecdsa_sign(const unsigned char *, int, const BIGNUM *, const BIGNUM *, EC_KEY *); static RSA_METHOD *keymgr_rsa_meth = NULL; static EC_KEY_METHOD *keymgr_ec_meth = NULL; static DH *dh_params = NULL; static int tls_version = KORE_TLS_VERSION_BOTH; static char *tls_cipher_list = KORE_DEFAULT_CIPHER_LIST; static u_int8_t keymgr_buf[2048]; static size_t keymgr_buflen = 0; static int keymgr_response = 0; #if defined(KORE_USE_ACME) static u_int8_t acme_alpn_name[] = { 0xa, 'a', 'c', 'm', 'e', '-', 't', 'l', 's', '/', '1' }; #endif struct kore_privsep keymgr_privsep; int kore_keymgr_active = 0; int kore_tls_supported(void) { return (KORE_RESULT_OK); } void kore_tls_init(void) { SSL_library_init(); SSL_load_error_strings(); ERR_load_crypto_strings(); if ((keymgr_rsa_meth = RSA_meth_new("kore RSA keymgr method", RSA_METHOD_FLAG_NO_CHECK)) == NULL) fatal("failed to setup RSA method"); RSA_meth_set_init(keymgr_rsa_meth, tls_keymgr_rsa_init); RSA_meth_set_finish(keymgr_rsa_meth, tls_keymgr_rsa_finish); RSA_meth_set_priv_enc(keymgr_rsa_meth, tls_keymgr_rsa_privenc); if ((keymgr_ec_meth = EC_KEY_METHOD_new(NULL)) == NULL) fatal("failed to allocate EC KEY method"); EC_KEY_METHOD_set_sign(keymgr_ec_meth, NULL, NULL, tls_keymgr_ecdsa_sign); kore_log(LOG_NOTICE, "TLS backend %s", OPENSSL_VERSION_TEXT); #if !defined(TLS1_3_VERSION) if (!kore_quiet) { kore_log(LOG_NOTICE, "%s has no TLS 1.3 - will only use TLS 1.2", OPENSSL_VERSION_TEXT); } #endif } void kore_tls_cleanup(void) { RSA_meth_free(keymgr_rsa_meth); EC_KEY_METHOD_free(keymgr_ec_meth); } void kore_tls_version_set(int version) { tls_version = version; } void kore_tls_dh_check(void) { if (dh_params != NULL) return; if (!kore_tls_dh_load(KORE_DHPARAM_PATH)) fatal("failed to load default DH parameters"); } int kore_tls_dh_load(const char *path) { BIO *bio; if (dh_params != NULL) { kore_log(LOG_ERR, "tls_dhparam already specified"); return (KORE_RESULT_ERROR); } if ((bio = BIO_new_file(path, "r")) == NULL) { kore_log(LOG_ERR, "tls_dhparam file '%s' not accessible", path); return (KORE_RESULT_ERROR); } dh_params = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); if (dh_params == NULL) { kore_log(LOG_ERR, "PEM_read_bio_DHparams(): %s", ssl_errno_s); return (KORE_RESULT_ERROR); } return (KORE_RESULT_OK); } int kore_tls_ciphersuite_set(const char *list) { if (strcmp(tls_cipher_list, KORE_DEFAULT_CIPHER_LIST)) { kore_log(LOG_ERR, "tls_cipher specified twice"); return (KORE_RESULT_ERROR); } tls_cipher_list = kore_strdup(list); return (KORE_RESULT_OK); } void kore_tls_keymgr_init(void) { const RSA_METHOD *meth; if ((meth = RSA_get_default_method()) == NULL) fatal("failed to obtain RSA method"); RSA_meth_set_pub_enc(keymgr_rsa_meth, RSA_meth_get_pub_enc(meth)); RSA_meth_set_pub_dec(keymgr_rsa_meth, RSA_meth_get_pub_dec(meth)); RSA_meth_set_bn_mod_exp(keymgr_rsa_meth, RSA_meth_get_bn_mod_exp(meth)); kore_msg_register(KORE_MSG_KEYMGR_RESP, tls_keymgr_msg_response); } void kore_tls_domain_setup(struct kore_domain *dom, int type, const void *data, size_t datalen) { const u_int8_t *ptr; RSA *rsa; X509 *x509; EVP_PKEY *pkey; STACK_OF(X509_NAME) *certs; EC_KEY *eckey; const SSL_METHOD *method; if (dom->tls_ctx != NULL) SSL_CTX_free(dom->tls_ctx); if ((method = TLS_method()) == NULL) fatalx("TLS_method(): %s", ssl_errno_s); if ((dom->tls_ctx = SSL_CTX_new(method)) == NULL) fatalx("SSL_ctx_new(): %s", ssl_errno_s); if (!SSL_CTX_set_min_proto_version(dom->tls_ctx, TLS1_2_VERSION)) fatalx("SSL_CTX_set_min_proto_version: %s", ssl_errno_s); #if defined(TLS1_3_VERSION) if (!SSL_CTX_set_max_proto_version(dom->tls_ctx, TLS1_3_VERSION)) fatalx("SSL_CTX_set_max_proto_version: %s", ssl_errno_s); #else if (!SSL_CTX_set_max_proto_version(dom->tls_ctx, TLS1_2_VERSION)) fatalx("SSL_CTX_set_min_proto_version: %s", ssl_errno_s); #endif switch (tls_version) { case KORE_TLS_VERSION_1_3: #if defined(TLS1_3_VERSION) if (!SSL_CTX_set_min_proto_version(dom->tls_ctx, TLS1_3_VERSION)) { fatalx("SSL_CTX_set_min_proto_version: %s", ssl_errno_s); } break; #endif case KORE_TLS_VERSION_1_2: if (!SSL_CTX_set_max_proto_version(dom->tls_ctx, TLS1_2_VERSION)) { fatalx("SSL_CTX_set_min_proto_version: %s", ssl_errno_s); } break; case KORE_TLS_VERSION_BOTH: break; default: fatalx("unknown tls_version: %d", tls_version); return; } switch (type) { case KORE_PEM_CERT_CHAIN: x509 = tls_domain_load_certificate_chain(dom->tls_ctx, data, datalen); break; case KORE_DER_CERT_DATA: ptr = data; if ((x509 = d2i_X509(NULL, &ptr, datalen)) == NULL) fatalx("d2i_X509: %s", ssl_errno_s); if (SSL_CTX_use_certificate(dom->tls_ctx, x509) == 0) fatalx("SSL_CTX_use_certificate: %s", ssl_errno_s); break; default: fatalx("%s: unknown type %d", __func__, type); } if (x509 == NULL) { kore_log(LOG_NOTICE, "failed to load certificate for '%s': %s", dom->domain, ssl_errno_s); SSL_CTX_free(dom->tls_ctx); dom->tls_ctx = NULL; return; } if ((pkey = X509_get_pubkey(x509)) == NULL) fatalx("certificate has no public key"); switch (EVP_PKEY_id(pkey)) { case EVP_PKEY_RSA: if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) fatalx("no RSA public key present"); RSA_set_app_data(rsa, dom); RSA_set_method(rsa, keymgr_rsa_meth); break; case EVP_PKEY_EC: if ((eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) fatalx("no EC public key present"); EC_KEY_set_ex_data(eckey, 0, dom); EC_KEY_set_method(eckey, keymgr_ec_meth); break; default: fatalx("unknown public key in certificate"); } if (!SSL_CTX_use_PrivateKey(dom->tls_ctx, pkey)) fatalx("SSL_CTX_use_PrivateKey(): %s", ssl_errno_s); if (!SSL_CTX_check_private_key(dom->tls_ctx)) { fatalx("Public/Private key for %s do not match (%s)", dom->domain, ssl_errno_s); } if (dh_params == NULL) fatal("no DH parameters specified"); SSL_CTX_set_tmp_dh(dom->tls_ctx, dh_params); SSL_CTX_set_options(dom->tls_ctx, SSL_OP_SINGLE_DH_USE); if (!SSL_CTX_set_ecdh_auto(dom->tls_ctx, 1)) fatalx("SSL_CTX_set_ecdh_auto: %s", ssl_errno_s); SSL_CTX_set_options(dom->tls_ctx, SSL_OP_SINGLE_ECDH_USE); SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_COMPRESSION); if (dom->cafile != NULL) { if ((certs = SSL_load_client_CA_file(dom->cafile)) == NULL) { fatalx("SSL_load_client_CA_file(%s): %s", dom->cafile, ssl_errno_s); } SSL_CTX_load_verify_locations(dom->tls_ctx, dom->cafile, NULL); SSL_CTX_set_verify_depth(dom->tls_ctx, dom->x509_verify_depth); SSL_CTX_set_client_CA_list(dom->tls_ctx, certs); SSL_CTX_set_verify(dom->tls_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, tls_domain_x509_verify); } SSL_CTX_set_session_id_context(dom->tls_ctx, (unsigned char *)TLS_SESSION_ID, strlen(TLS_SESSION_ID)); SSL_CTX_set_mode(dom->tls_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); if (tls_version == KORE_TLS_VERSION_BOTH) { SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_SSLv2); SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_SSLv3); SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_TLSv1); SSL_CTX_set_options(dom->tls_ctx, SSL_OP_NO_TLSv1_1); } SSL_CTX_set_options(dom->tls_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); SSL_CTX_set_cipher_list(dom->tls_ctx, tls_cipher_list); SSL_CTX_set_info_callback(dom->tls_ctx, tls_info_callback); SSL_CTX_set_tlsext_servername_callback(dom->tls_ctx, tls_sni_cb); #if defined(KORE_USE_ACME) SSL_CTX_set_alpn_select_cb(dom->tls_ctx, tls_acme_alpn, dom); #endif X509_free(x509); } void kore_tls_domain_crl(struct kore_domain *dom, const void *pem, size_t pemlen) { int err; BIO *in; X509_CRL *crl; X509_STORE *store; ERR_clear_error(); in = BIO_new_mem_buf(pem, pemlen); if ((store = SSL_CTX_get_cert_store(dom->tls_ctx)) == NULL) { BIO_free(in); kore_log(LOG_ERR, "SSL_CTX_get_cert_store(): %s", ssl_errno_s); return; } for (;;) { crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL); if (crl == NULL) { err = ERR_GET_REASON(ERR_peek_last_error()); if (err == PEM_R_NO_START_LINE) { ERR_clear_error(); break; } kore_log(LOG_WARNING, "failed to read CRL %s: %s", dom->crlfile, ssl_errno_s); continue; } if (!X509_STORE_add_crl(store, crl)) { err = ERR_GET_REASON(ERR_peek_last_error()); if (err == X509_R_CERT_ALREADY_IN_HASH_TABLE) { X509_CRL_free(crl); continue; } kore_log(LOG_WARNING, "failed to add CRL %s: %s", dom->crlfile, ssl_errno_s); X509_CRL_free(crl); continue; } } BIO_free(in); X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); } void kore_tls_domain_cleanup(struct kore_domain *dom) { if (dom->tls_ctx != NULL) SSL_CTX_free(dom->tls_ctx); } int kore_tls_connection_accept(struct connection *c) { int r; if (primary_dom == NULL) { kore_log(LOG_NOTICE, "TLS handshake but no TLS configured on server"); return (KORE_RESULT_ERROR); } if (primary_dom->tls_ctx == NULL) { kore_log(LOG_NOTICE, "TLS configuration for %s not yet complete", primary_dom->domain); return (KORE_RESULT_ERROR); } if (c->tls == NULL) { c->tls = SSL_new(primary_dom->tls_ctx); if (c->tls == NULL) return (KORE_RESULT_ERROR); SSL_set_fd(c->tls, c->fd); SSL_set_accept_state(c->tls); if (!SSL_set_ex_data(c->tls, 0, c)) return (KORE_RESULT_ERROR); if (primary_dom->cafile != NULL) c->flags |= CONN_LOG_TLS_FAILURE; } ERR_clear_error(); r = SSL_accept(c->tls); if (r <= 0) { r = SSL_get_error(c->tls, r); switch (r) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: kore_connection_start_idletimer(c); return (KORE_RESULT_RETRY); default: if (c->flags & CONN_LOG_TLS_FAILURE) { kore_log(LOG_NOTICE, "SSL_accept: %s", ssl_errno_s); } return (KORE_RESULT_ERROR); } } #if defined(KORE_USE_ACME) if (c->proto == CONN_PROTO_ACME_ALPN) { kore_log(LOG_INFO, "disconnecting acme client"); kore_connection_disconnect(c); return (KORE_RESULT_ERROR); } #endif if (SSL_get_verify_mode(c->tls) & SSL_VERIFY_PEER) { c->tls_cert = SSL_get_peer_certificate(c->tls); if (c->tls_cert == NULL) { kore_log(LOG_NOTICE, "no peer certificate"); return (KORE_RESULT_ERROR); } } else { c->tls_cert = NULL; } return (KORE_RESULT_OK); } int kore_tls_read(struct connection *c, size_t *bytes) { int r; ERR_clear_error(); r = SSL_read(c->tls, (c->rnb->buf + c->rnb->s_off), (c->rnb->b_len - c->rnb->s_off)); if (c->tls_reneg > 1) return (KORE_RESULT_ERROR); if (r <= 0) { r = SSL_get_error(c->tls, r); switch (r) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: c->evt.flags &= ~KORE_EVENT_READ; return (KORE_RESULT_OK); case SSL_ERROR_ZERO_RETURN: return (KORE_RESULT_ERROR); case SSL_ERROR_SYSCALL: switch (errno) { case EINTR: *bytes = 0; return (KORE_RESULT_OK); case EAGAIN: c->evt.flags &= ~KORE_EVENT_READ; c->snb->flags |= NETBUF_MUST_RESEND; return (KORE_RESULT_OK); default: break; } /* FALLTHROUGH */ default: if (c->flags & CONN_LOG_TLS_FAILURE) { kore_log(LOG_NOTICE, "SSL_read(): %s", ssl_errno_s); } return (KORE_RESULT_ERROR); } } *bytes = (size_t)r; return (KORE_RESULT_OK); } int kore_tls_write(struct connection *c, size_t len, size_t *written) { int r; if (len > INT_MAX) return (KORE_RESULT_ERROR); ERR_clear_error(); r = SSL_write(c->tls, (c->snb->buf + c->snb->s_off), len); if (c->tls_reneg > 1) return (KORE_RESULT_ERROR); if (r <= 0) { r = SSL_get_error(c->tls, r); switch (r) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: c->evt.flags &= ~KORE_EVENT_WRITE; c->snb->flags |= NETBUF_MUST_RESEND; return (KORE_RESULT_OK); case SSL_ERROR_SYSCALL: switch (errno) { case EINTR: *written = 0; return (KORE_RESULT_OK); case EAGAIN: c->evt.flags &= ~KORE_EVENT_WRITE; c->snb->flags |= NETBUF_MUST_RESEND; return (KORE_RESULT_OK); default: break; } /* FALLTHROUGH */ default: if (c->flags & CONN_LOG_TLS_FAILURE) { kore_log(LOG_NOTICE, "SSL_write(): %s", ssl_errno_s); } return (KORE_RESULT_ERROR); } } *written = (size_t)r; return (KORE_RESULT_OK); } void kore_tls_connection_cleanup(struct connection *c) { if (c->tls != NULL) { SSL_shutdown(c->tls); SSL_free(c->tls); } if (c->tls_cert != NULL) X509_free(c->tls_cert); if (c->tls_sni != NULL) kore_free(c->tls_sni); } KORE_PRIVATE_KEY * kore_tls_rsakey_load(const char *path) { FILE *fp; KORE_PRIVATE_KEY *pkey; if (access(path, R_OK) == -1) return (NULL); if ((fp = fopen(path, "r")) == NULL) fatalx("%s(%s): %s", __func__, path, errno_s); if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) fatalx("PEM_read_PrivateKey: %s", ssl_errno_s); fclose(fp); return (pkey); } KORE_PRIVATE_KEY * kore_tls_rsakey_generate(const char *path) { FILE *fp; EVP_PKEY_CTX *ctx; KORE_PRIVATE_KEY *pkey; if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL)) == NULL) fatalx("EVP_PKEY_CTX_new_id: %s", ssl_errno_s); if (EVP_PKEY_keygen_init(ctx) <= 0) fatalx("EVP_PKEY_keygen_init: %s", ssl_errno_s); if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KORE_RSAKEY_BITS) <= 0) fatalx("EVP_PKEY_CTX_set_rsa_keygen_bits: %s", ssl_errno_s); pkey = NULL; if (EVP_PKEY_keygen(ctx, &pkey) <= 0) fatalx("EVP_PKEY_keygen: %s", ssl_errno_s); if (path != NULL) { if ((fp = fopen(path, "w")) == NULL) fatalx("fopen(%s): %s", path, errno_s); if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) fatalx("PEM_write_PrivateKey: %s", ssl_errno_s); fclose(fp); } return (pkey); } KORE_X509_NAMES * kore_tls_x509_subject_name(struct connection *c) { X509_NAME *name; if ((name = X509_get_subject_name(c->tls_cert)) == NULL) kore_log(LOG_NOTICE, "X509_get_subject_name: %s", ssl_errno_s); return (name); } KORE_X509_NAMES * kore_tls_x509_issuer_name(struct connection *c) { X509_NAME *name; if ((name = X509_get_issuer_name(c->tls_cert)) == NULL) kore_log(LOG_NOTICE, "X509_get_issuer_name: %s", ssl_errno_s); return (name); } int kore_tls_x509name_foreach(KORE_X509_NAMES *name, int flags, void *udata, int (*cb)(void *, int, int, const char *, const void *, size_t, int)) { u_int8_t *data; ASN1_STRING *astr; X509_NAME_ENTRY *entry; const char *field; int islast, ret, idx, namelen, nid, len; data = NULL; ret = KORE_RESULT_ERROR; if ((namelen = X509_NAME_entry_count(name)) == 0) goto cleanup; for (idx = 0; idx < namelen; idx++) { if ((entry = X509_NAME_get_entry(name, idx)) == NULL) goto cleanup; nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry)); if ((field = OBJ_nid2sn(nid)) == NULL) goto cleanup; switch (nid) { case NID_commonName: nid = KORE_X509_NAME_COMMON_NAME; break; default: nid = -1; break; } if ((astr = X509_NAME_ENTRY_get_data(entry)) == NULL) goto cleanup; data = NULL; if ((len = ASN1_STRING_to_UTF8(&data, astr)) < 0) goto cleanup; if (idx != (namelen - 1)) islast = 0; else islast = 1; if (!cb(udata, islast, nid, field, data, len, flags)) goto cleanup; OPENSSL_free(data); data = NULL; } ret = KORE_RESULT_OK; cleanup: if (data != NULL) OPENSSL_free(data); return (ret); } int kore_tls_x509_data(struct connection *c, u_int8_t **ptr, size_t *olen) { int len; u_int8_t *der, *pp; if ((len = i2d_X509(c->tls_cert, NULL)) <= 0) { kore_log(LOG_NOTICE, "i2d_X509: %s", ssl_errno_s); return (KORE_RESULT_ERROR); } der = kore_calloc(1, len); pp = der; if (i2d_X509(c->tls_cert, &pp) <= 0) { kore_free(der); kore_log(LOG_NOTICE, "i2d_X509: %s", ssl_errno_s); return (KORE_RESULT_ERROR); } *ptr = der; *olen = len; return (KORE_RESULT_OK); } void kore_tls_seed(const void *data, size_t len) { RAND_poll(); RAND_seed(data, len); } static void tls_info_callback(const SSL *ssl, int flags, int ret) { struct connection *c; if (flags & SSL_CB_HANDSHAKE_START) { if ((c = SSL_get_app_data(ssl)) == NULL) fatal("no SSL_get_app_data"); #if defined(TLS1_3_VERSION) if (SSL_version(ssl) != TLS1_3_VERSION) #endif c->tls_reneg++; } } static int tls_sni_cb(SSL *ssl, int *ad, void *arg) { struct connection *c; struct kore_domain *dom; const char *sname; if ((c = SSL_get_ex_data(ssl, 0)) == NULL) fatal("no connection data in %s", __func__); sname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (sname != NULL) c->tls_sni = kore_strdup(sname); if (sname != NULL && (dom = kore_domain_lookup(c->owner->server, sname)) != NULL) { if (dom->tls_ctx == NULL) { kore_log(LOG_NOTICE, "TLS configuration for %s not complete", dom->domain); return (SSL_TLSEXT_ERR_NOACK); } SSL_set_SSL_CTX(ssl, dom->tls_ctx); if (dom->cafile != NULL) { SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); c->flags |= CONN_LOG_TLS_FAILURE; } else { SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); } #if defined(KORE_USE_ACME) /* * If ALPN callback was called before SNI was parsed we * must make sure we swap to the correct certificate now. */ if (c->flags & CONN_TLS_ALPN_ACME_SEEN) tls_acme_challenge_set_cert(ssl, dom); c->flags |= CONN_TLS_SNI_SEEN; #endif return (SSL_TLSEXT_ERR_OK); } return (SSL_TLSEXT_ERR_NOACK); } static int tls_keymgr_rsa_init(RSA *rsa) { if (rsa != NULL) { RSA_set_flags(rsa, RSA_flags(rsa) | RSA_FLAG_EXT_PKEY | RSA_METHOD_FLAG_NO_CHECK); return (1); } return (0); } static int tls_keymgr_rsa_privenc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding) { int ret; size_t len; struct kore_keyreq *req; struct kore_domain *dom; len = sizeof(*req) + flen; if (len > sizeof(keymgr_buf)) fatal("keymgr_buf too small"); if ((dom = RSA_get_app_data(rsa)) == NULL) fatal("RSA key has no domain attached"); memset(keymgr_buf, 0, sizeof(keymgr_buf)); req = (struct kore_keyreq *)keymgr_buf; if (kore_strlcpy(req->domain, dom->domain, sizeof(req->domain)) >= sizeof(req->domain)) fatal("%s: domain truncated", __func__); req->data_len = flen; req->padding = padding; memcpy(&req->data[0], from, req->data_len); kore_msg_send(KORE_WORKER_KEYMGR, KORE_MSG_KEYMGR_REQ, keymgr_buf, len); tls_keymgr_await_data(); ret = -1; if (keymgr_response) { if (keymgr_buflen < INT_MAX && (int)keymgr_buflen == RSA_size(rsa)) { ret = RSA_size(rsa); memcpy(to, keymgr_buf, RSA_size(rsa)); } } keymgr_buflen = 0; keymgr_response = 0; kore_platform_event_all(worker->msg[1]->fd, worker->msg[1]); return (ret); } static int tls_keymgr_rsa_finish(RSA *rsa) { return (1); } static ECDSA_SIG * tls_keymgr_ecdsa_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *in_kinv, const BIGNUM *in_r, EC_KEY *eckey) { size_t len; ECDSA_SIG *sig; const u_int8_t *ptr; struct kore_domain *dom; struct kore_keyreq *req; if (in_kinv != NULL || in_r != NULL) return (NULL); len = sizeof(*req) + dgst_len; if (len > sizeof(keymgr_buf)) fatal("keymgr_buf too small"); if ((dom = EC_KEY_get_ex_data(eckey, 0)) == NULL) fatal("EC_KEY has no domain"); memset(keymgr_buf, 0, sizeof(keymgr_buf)); req = (struct kore_keyreq *)keymgr_buf; if (kore_strlcpy(req->domain, dom->domain, sizeof(req->domain)) >= sizeof(req->domain)) fatal("%s: domain truncated", __func__); req->data_len = dgst_len; memcpy(&req->data[0], dgst, req->data_len); kore_msg_send(KORE_WORKER_KEYMGR, KORE_MSG_KEYMGR_REQ, keymgr_buf, len); tls_keymgr_await_data(); if (keymgr_response) { ptr = keymgr_buf; sig = d2i_ECDSA_SIG(NULL, &ptr, keymgr_buflen); } else { sig = NULL; } keymgr_buflen = 0; keymgr_response = 0; kore_platform_event_all(worker->msg[1]->fd, worker->msg[1]); return (sig); } static void tls_keymgr_await_data(void) { int ret; struct pollfd pfd[1]; u_int64_t start, cur; #if !defined(KORE_NO_HTTP) int process_requests; #endif /* * We need to wait until the keymgr responds to us, so keep doing * net_recv_flush() until our callback for KORE_MSG_KEYMGR_RESP * tells us that we have obtained the response. * * This means other internal messages can still be delivered by * this worker process to the appropriate callbacks but we do not * drop out until we've either received an answer from the keymgr * or until the timeout has been reached (1 second currently). * * If we end up waiting for the keymgr process we will call * http_process (if not built with NOHTTP=1) to further existing * requests so those do not block too much. * * This means that all incoming data will stop being processed * while existing requests will get processed until we return * from this call. */ start = kore_time_ms(); kore_platform_disable_read(worker->msg[1]->fd); keymgr_response = 0; memset(keymgr_buf, 0, sizeof(keymgr_buf)); #if !defined(KORE_NO_HTTP) process_requests = 0; #endif for (;;) { #if !defined(KORE_NO_HTTP) if (process_requests) { http_process(); process_requests = 0; } #endif pfd[0].fd = worker->msg[1]->fd; pfd[0].events = POLLIN; pfd[0].revents = 0; ret = poll(pfd, 1, 100); if (ret == -1) { if (errno == EINTR) continue; fatal("poll: %s", errno_s); } cur = kore_time_ms(); if ((cur - start) > 1000) break; if (ret == 0) { #if !defined(KORE_NO_HTTP) /* No activity on channel, process HTTP requests. */ process_requests = 1; #endif continue; } if (pfd[0].revents & (POLLERR | POLLHUP)) break; if (!(pfd[0].revents & POLLIN)) break; worker->msg[1]->evt.flags |= KORE_EVENT_READ; if (!net_recv_flush(worker->msg[1])) break; if (keymgr_response) break; #if !defined(KORE_NO_HTTP) /* If we've spent 100ms already, process HTTP requests. */ if ((cur - start) > 100) { process_requests = 1; } #endif } } static void tls_keymgr_msg_response(struct kore_msg *msg, const void *data) { keymgr_response = 1; keymgr_buflen = msg->length; if (keymgr_buflen > sizeof(keymgr_buf)) return; memcpy(keymgr_buf, data, keymgr_buflen); } static int tls_domain_x509_verify(int ok, X509_STORE_CTX *ctx) { X509 *cert; const char *text; int error, depth; error = X509_STORE_CTX_get_error(ctx); cert = X509_STORE_CTX_get_current_cert(ctx); if (ok == 0 && cert != NULL) { text = X509_verify_cert_error_string(error); depth = X509_STORE_CTX_get_error_depth(ctx); kore_log(LOG_WARNING, "X509 verification error depth:%d - %s", depth, text); /* Continue on CRL validity errors. */ switch (error) { case X509_V_ERR_CRL_HAS_EXPIRED: case X509_V_ERR_CRL_NOT_YET_VALID: case X509_V_ERR_UNABLE_TO_GET_CRL: ok = 1; break; } } return (ok); } /* * What follows is basically a reimplementation of * SSL_CTX_use_certificate_chain_file() from OpenSSL but with our * BIO set to the pem data that we received. */ static X509 * tls_domain_load_certificate_chain(SSL_CTX *ctx, const void *data, size_t len) { unsigned long err; BIO *in; X509 *x, *ca; ERR_clear_error(); in = BIO_new_mem_buf(data, len); if ((x = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL)) == NULL) return (NULL); /* refcount for x509 will go up one. */ if (SSL_CTX_use_certificate(ctx, x) == 0) return (NULL); SSL_CTX_clear_chain_certs(ctx); ERR_clear_error(); while ((ca = PEM_read_bio_X509(in, NULL, NULL, NULL)) != NULL) { /* ca its reference count won't be increased. */ if (SSL_CTX_add0_chain_cert(ctx, ca) == 0) return (NULL); } err = ERR_peek_last_error(); if (ERR_GET_LIB(err) != ERR_LIB_PEM || ERR_GET_REASON(err) != PEM_R_NO_START_LINE) return (NULL); BIO_free(in); return (x); } #if defined(KORE_USE_ACME) static int tls_acme_alpn(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *udata) { struct connection *c; if ((c = SSL_get_ex_data(ssl, 0)) == NULL) fatal("%s: no connection data present", __func__); if (inlen != sizeof(acme_alpn_name)) return (SSL_TLSEXT_ERR_NOACK); if (memcmp(acme_alpn_name, in, sizeof(acme_alpn_name))) return (SSL_TLSEXT_ERR_NOACK); *out = in + 1; *outlen = inlen - 1; c->flags |= CONN_TLS_ALPN_ACME_SEEN; /* * If SNI was already done, we can continue, otherwise we mark * that we saw the right ALPN negotiation on this connection * and wait for the SNI extension to be parsed. */ if (c->flags & CONN_TLS_SNI_SEEN) { /* SNI was seen, we are on the right domain. */ tls_acme_challenge_set_cert(ssl, udata); } return (SSL_TLSEXT_ERR_OK); } static void tls_acme_challenge_set_cert(SSL *ssl, struct kore_domain *dom) { struct connection *c; const unsigned char *ptr; X509 *x509; if (dom->acme == 0) { kore_log(LOG_NOTICE, "[%s] ACME not active", dom->domain); return; } if (dom->acme_challenge == 0) { kore_log(LOG_NOTICE, "[%s] ACME auth challenge not active", dom->domain); return; } kore_log(LOG_INFO, "[%s] acme-tls/1 challenge requested", dom->domain); if ((c = SSL_get_ex_data(ssl, 0)) == NULL) fatal("%s: no connection data present", __func__); ptr = dom->acme_cert; if ((x509 = d2i_X509(NULL, &ptr, dom->acme_cert_len)) == NULL) fatal("d2i_X509: %s", ssl_errno_s); if (SSL_use_certificate(ssl, x509) == 0) fatal("SSL_use_certificate: %s", ssl_errno_s); SSL_clear_chain_certs(ssl); SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); c->proto = CONN_PROTO_ACME_ALPN; } #endif /* KORE_USE_ACME */ kore-4.2.3/src/utils.c000066400000000000000000000333201430066302400145520ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "kore.h" static struct { char *name; int value; } month_names[] = { { "Jan", 0 }, { "Feb", 1 }, { "Mar", 2 }, { "Apr", 3 }, { "May", 4 }, { "Jun", 5 }, { "Jul", 6 }, { "Aug", 7 }, { "Sep", 8 }, { "Oct", 9 }, { "Nov", 10 }, { "Dec", 11 }, { NULL, 0 }, }; static void fatal_log(const char *, va_list); static int utils_base64_encode(const void *, size_t, char **, const char *, int); static int utils_base64_decode(const char *, u_int8_t **, size_t *, const char *, int); static int utils_x509name_tobuf(void *, int, int, const char *, const void *, size_t, int); static char b64_table[] = \ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static char b64url_table[] = \ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; /* b64_table and b64url_table are the same size. */ #define B64_TABLE_LEN (sizeof(b64_table)) size_t kore_strlcpy(char *dst, const char *src, const size_t len) { char *d = dst; const char *s = src; const char *end = dst + len - 1; if (len == 0) fatal("kore_strlcpy: len == 0"); while ((*d = *s) != '\0') { if (d == end) { *d = '\0'; break; } d++; s++; } while (*s != '\0') s++; return (s - src); } int kore_snprintf(char *str, size_t size, int *len, const char *fmt, ...) { int l; va_list args; va_start(args, fmt); l = vsnprintf(str, size, fmt, args); va_end(args); if (l == -1 || (size_t)l >= size) return (KORE_RESULT_ERROR); if (len != NULL) *len = l; return (KORE_RESULT_OK); } long long kore_strtonum(const char *str, int base, long long min, long long max, int *err) { long long l; char *ep; if (min > max) { *err = KORE_RESULT_ERROR; return (0); } errno = 0; l = strtoll(str, &ep, base); if (errno != 0 || str == ep || *ep != '\0') { *err = KORE_RESULT_ERROR; return (0); } if (l < min) { *err = KORE_RESULT_ERROR; return (0); } if (l > max) { *err = KORE_RESULT_ERROR; return (0); } *err = KORE_RESULT_OK; return (l); } u_int64_t kore_strtonum64(const char *str, int sign, int *err) { u_int64_t l; long long ll; char *ep; int check; l = 0; check = 1; ll = strtoll(str, &ep, 10); if ((errno == EINVAL || errno == ERANGE) && (ll == LLONG_MIN || ll == LLONG_MAX)) { if (sign) { *err = KORE_RESULT_ERROR; return (0); } check = 0; } if (!sign) { l = strtoull(str, &ep, 10); if ((errno == EINVAL || errno == ERANGE) && l == ULONG_MAX) { *err = KORE_RESULT_ERROR; return (0); } if (check && ll < 0) { *err = KORE_RESULT_ERROR; return (0); } } if (str == ep || *ep != '\0') { *err = KORE_RESULT_ERROR; return (0); } *err = KORE_RESULT_OK; return ((sign) ? (u_int64_t)ll : l); } double kore_strtodouble(const char *str, long double min, long double max, int *err) { double d; char *ep; if (min > max) { *err = KORE_RESULT_ERROR; return (0); } errno = 0; d = strtod(str, &ep); if (errno == ERANGE || str == ep || *ep != '\0') { *err = KORE_RESULT_ERROR; return (0); } if (d < min) { *err = KORE_RESULT_ERROR; return (0); } if (d > max) { *err = KORE_RESULT_ERROR; return (0); } *err = KORE_RESULT_OK; return (d); } int kore_split_string(char *input, const char *delim, char **out, size_t ele) { int count; char **ap; if (ele == 0) return (0); count = 0; for (ap = out; ap < &out[ele - 1] && (*ap = strsep(&input, delim)) != NULL;) { if (**ap != '\0') { ap++; count++; } } *ap = NULL; return (count); } void kore_strip_chars(char *in, const char strip, char **out) { u_int32_t len; char *s, *p; len = strlen(in); *out = kore_malloc(len + 1); p = *out; for (s = in; s < (in + len); s++) { if (*s == strip) continue; *p++ = *s; } *p = '\0'; } time_t kore_date_to_time(const char *http_date) { time_t t; int err, i; struct tm tm, *ltm; char *args[7], *tbuf[5], *sdup; time(&t); ltm = localtime(&t); sdup = kore_strdup(http_date); t = KORE_RESULT_ERROR; if (kore_split_string(sdup, " ", args, 7) != 6) { kore_log(LOG_WARNING, "misformed http-date: '%s'", http_date); goto out; } memset(&tm, 0, sizeof(tm)); tm.tm_year = kore_strtonum(args[3], 10, 1900, 2068, &err) - 1900; if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed year in http-date: '%s'", http_date); goto out; } for (i = 0; month_names[i].name != NULL; i++) { if (!strcmp(month_names[i].name, args[2])) { tm.tm_mon = month_names[i].value; break; } } if (month_names[i].name == NULL) { kore_log(LOG_WARNING, "misformed month in http-date: '%s'", http_date); goto out; } tm.tm_mday = kore_strtonum(args[1], 10, 1, 31, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed mday in http-date: '%s'", http_date); goto out; } if (kore_split_string(args[4], ":", tbuf, 5) != 3) { kore_log(LOG_WARNING, "misformed HH:MM:SS in http-date: '%s'", http_date); goto out; } tm.tm_hour = kore_strtonum(tbuf[0], 10, 0, 23, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed hour in http-date: '%s'", http_date); goto out; } tm.tm_min = kore_strtonum(tbuf[1], 10, 0, 59, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed minutes in http-date: '%s'", http_date); goto out; } tm.tm_sec = kore_strtonum(tbuf[2], 10, 0, 60, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed seconds in http-date: '%s'", http_date); goto out; } tm.tm_isdst = ltm->tm_isdst; t = mktime(&tm) + ltm->tm_gmtoff; if (t == -1) { t = 0; kore_log(LOG_WARNING, "mktime() on '%s' failed", http_date); } out: kore_free(sdup); return (t); } char * kore_time_to_date(time_t now) { struct tm *tm; static time_t last = 0; static char tbuf[32]; if (now != last) { last = now; tm = gmtime(&now); if (!strftime(tbuf, sizeof(tbuf), "%a, %d %b %Y %T GMT", tm)) return (NULL); } return (tbuf); } u_int64_t kore_time_ms(void) { struct timespec ts; (void)clock_gettime(CLOCK_MONOTONIC, &ts); return ((u_int64_t)(ts.tv_sec * 1000 + (ts.tv_nsec / 1000000))); } int kore_base64url_encode(const void *data, size_t len, char **out, int flags) { return (utils_base64_encode(data, len, out, b64url_table, flags)); } int kore_base64_encode(const void *data, size_t len, char **out) { return (utils_base64_encode(data, len, out, b64_table, 0)); } int kore_base64url_decode(const char *in, u_int8_t **out, size_t *olen, int flags) { return (utils_base64_decode(in, out, olen, b64url_table, flags)); } int kore_base64_decode(const char *in, u_int8_t **out, size_t *olen) { return (utils_base64_decode(in, out, olen, b64_table, 0)); } void * kore_mem_find(void *src, size_t slen, const void *needle, size_t len) { size_t pos; for (pos = 0; pos < slen; pos++) { if ( *((u_int8_t *)src + pos) != *(const u_int8_t *)needle) continue; if ((slen - pos) < len) return (NULL); if (!memcmp((u_int8_t *)src + pos, needle, len)) return ((u_int8_t *)src + pos); } return (NULL); } char * kore_text_trim(char *string, size_t len) { char *end; if (len == 0) return (string); end = (string + len) - 1; while (isspace(*(unsigned char *)string) && string < end) string++; while (isspace(*(unsigned char *)end) && end > string) *(end)-- = '\0'; return (string); } char * kore_read_line(FILE *fp, char *in, size_t len) { char *p, *t; if (fgets(in, len, fp) == NULL) return (NULL); p = in; in[strcspn(in, "\n")] = '\0'; while (isspace(*(unsigned char *)p)) p++; if (p[0] == '#' || p[0] == '\0') { p[0] = '\0'; return (p); } for (t = p; *t != '\0'; t++) { if (*t == '\t') *t = ' '; } return (p); } const char * kore_worker_name(int id) { static char buf[64]; switch (id) { case KORE_WORKER_KEYMGR: (void)snprintf(buf, sizeof(buf), "[keymgr]"); break; case KORE_WORKER_ACME: (void)snprintf(buf, sizeof(buf), "[acme]"); break; default: (void)snprintf(buf, sizeof(buf), "[wrk %d]", id); break; } return (buf); } int kore_x509_issuer_name(struct connection *c, char **out, int flags) { struct kore_buf buf; KORE_X509_NAMES *name; if ((name = kore_tls_x509_issuer_name(c)) == NULL) return (KORE_RESULT_ERROR); kore_buf_init(&buf, 1024); if (!kore_tls_x509name_foreach(name, flags, &buf, utils_x509name_tobuf)) { kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } *out = kore_buf_stringify(&buf, NULL); buf.offset = 0; buf.data = NULL; return (KORE_RESULT_OK); } int kore_x509_subject_name(struct connection *c, char **out, int flags) { struct kore_buf buf; KORE_X509_NAMES *name; if ((name = kore_tls_x509_subject_name(c)) == NULL) return (KORE_RESULT_ERROR); kore_buf_init(&buf, 1024); if (!kore_tls_x509name_foreach(name, flags, &buf, utils_x509name_tobuf)) { kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } *out = kore_buf_stringify(&buf, NULL); buf.offset = 0; buf.data = NULL; return (KORE_RESULT_OK); } void fatal(const char *fmt, ...) { va_list args; va_start(args, fmt); fatal_log(fmt, args); va_end(args); exit(1); } void fatalx(const char *fmt, ...) { va_list args; /* In case people call fatalx() from the parent context. */ if (worker != NULL) kore_msg_send(KORE_MSG_PARENT, KORE_MSG_FATALX, NULL, 0); va_start(args, fmt); fatal_log(fmt, args); va_end(args); exit(1); } static void fatal_log(const char *fmt, va_list args) { char buf[2048]; (void)vsnprintf(buf, sizeof(buf), fmt, args); kore_log(LOG_ERR, "fatal: %s", buf); if (worker != NULL && worker->id == KORE_WORKER_KEYMGR) kore_keymgr_cleanup(1); } static int utils_x509name_tobuf(void *udata, int islast, int nid, const char *field, const void *data, size_t len, int flags) { struct kore_buf *buf = udata; if (flags & KORE_X509_COMMON_NAME_ONLY) { if (nid == KORE_X509_NAME_COMMON_NAME) kore_buf_append(buf, data, len); } else { kore_buf_appendf(buf, "%s=", field); kore_buf_append(buf, data, len); if (!islast) kore_buf_appendf(buf, " "); } return (KORE_RESULT_OK); } static int utils_base64_encode(const void *data, size_t len, char **out, const char *table, int flags) { u_int8_t n; size_t nb; const u_int8_t *ptr; u_int32_t bytes; struct kore_buf result; nb = 0; ptr = data; kore_buf_init(&result, (len / 3) * 4); while (len > 0) { if (len > 2) { nb = 3; bytes = *ptr++ << 16; bytes |= *ptr++ << 8; bytes |= *ptr++; } else if (len > 1) { nb = 2; bytes = *ptr++ << 16; bytes |= *ptr++ << 8; } else if (len == 1) { nb = 1; bytes = *ptr++ << 16; } else { kore_buf_cleanup(&result); return (KORE_RESULT_ERROR); } n = (bytes >> 18) & 0x3f; kore_buf_append(&result, &(table[n]), 1); n = (bytes >> 12) & 0x3f; kore_buf_append(&result, &(table[n]), 1); if (nb > 1) { n = (bytes >> 6) & 0x3f; kore_buf_append(&result, &(table[n]), 1); if (nb > 2) { n = bytes & 0x3f; kore_buf_append(&result, &(table[n]), 1); } } len -= nb; } if (!(flags & KORE_BASE64_RAW)) { switch (nb) { case 1: kore_buf_appendf(&result, "=="); break; case 2: kore_buf_appendf(&result, "="); break; case 3: break; default: kore_buf_cleanup(&result); return (KORE_RESULT_ERROR); } } /* result.data gets taken over so no need to cleanup result. */ *out = kore_buf_stringify(&result, NULL); return (KORE_RESULT_OK); } static int utils_base64_decode(const char *in, u_int8_t **out, size_t *olen, const char *table, int flags) { int i, c; u_int8_t d, n, o; struct kore_buf *res, buf; const char *ptr, *pad; u_int32_t b, len, plen, idx; i = 4; b = 0; d = 0; c = 0; len = strlen(in); memset(&buf, 0, sizeof(buf)); if (flags & KORE_BASE64_RAW) { switch (len % 4) { case 2: plen = 2; pad = "=="; break; case 3: plen = 1; pad = "="; break; default: return (KORE_RESULT_ERROR); } kore_buf_init(&buf, len + plen); kore_buf_append(&buf, in, len); kore_buf_append(&buf, pad, plen); len = len + plen; ptr = (const char *)buf.data; } else { ptr = in; } res = kore_buf_alloc(len); for (idx = 0; idx < len; idx++) { c = ptr[idx]; if (c == '=') break; for (o = 0; o < B64_TABLE_LEN; o++) { if (table[o] == c) { d = o; break; } } if (o == B64_TABLE_LEN) { *out = NULL; kore_buf_free(res); kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } b |= (d & 0x3f) << ((i - 1) * 6); i--; if (i == 0) { for (i = 2; i >= 0; i--) { n = (b >> (8 * i)); kore_buf_append(res, &n, 1); } b = 0; i = 4; } } if (c == '=') { if (i > 2) { *out = NULL; kore_buf_free(res); kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } o = i; for (i = 2; i >= o; i--) { n = (b >> (8 * i)); kore_buf_append(res, &n, 1); } } kore_buf_cleanup(&buf); *out = kore_buf_release(res, olen); return (KORE_RESULT_OK); } kore-4.2.3/src/validator.c000066400000000000000000000062161430066302400154030ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "kore.h" TAILQ_HEAD(, kore_validator) validators; void kore_validator_init(void) { TAILQ_INIT(&validators); } int kore_validator_add(const char *name, u_int8_t type, const char *arg) { int ret; struct kore_validator *val; val = kore_malloc(sizeof(*val)); val->type = type; switch (val->type) { case KORE_VALIDATOR_TYPE_REGEX: ret = regcomp(&(val->rctx), arg, REG_EXTENDED | REG_NOSUB); if (ret) { kore_free(val); kore_log(LOG_NOTICE, "validator %s has bad regex %s (%d)", name, arg, ret); return (KORE_RESULT_ERROR); } break; case KORE_VALIDATOR_TYPE_FUNCTION: val->rcall = kore_runtime_getcall(arg); if (val->rcall == NULL) { kore_free(val); kore_log(LOG_NOTICE, "validator %s has undefined callback %s", name, arg); return (KORE_RESULT_ERROR); } break; default: kore_free(val); return (KORE_RESULT_ERROR); } val->arg = kore_strdup(arg); val->name = kore_strdup(name); TAILQ_INSERT_TAIL(&validators, val, list); return (KORE_RESULT_OK); } int kore_validator_run(struct http_request *req, const char *name, char *data) { struct kore_validator *val; TAILQ_FOREACH(val, &validators, list) { if (strcmp(val->name, name)) continue; return (kore_validator_check(req, val, data)); } return (KORE_RESULT_ERROR); } int kore_validator_check(struct http_request *req, struct kore_validator *val, const void *data) { int r; switch (val->type) { case KORE_VALIDATOR_TYPE_REGEX: if (!regexec(&(val->rctx), data, 0, NULL, 0)) r = KORE_RESULT_OK; else r = KORE_RESULT_ERROR; break; case KORE_VALIDATOR_TYPE_FUNCTION: r = kore_runtime_validator(val->rcall, req, data); break; default: r = KORE_RESULT_ERROR; kore_log(LOG_NOTICE, "invalid type %d for validator %s", val->type, val->name); break; } return (r); } void kore_validator_reload(void) { struct kore_validator *val; TAILQ_FOREACH(val, &validators, list) { if (val->type != KORE_VALIDATOR_TYPE_FUNCTION) continue; kore_free(val->rcall); val->rcall = kore_runtime_getcall(val->arg); if (val->rcall == NULL) fatal("no function for validator %s found", val->arg); } } struct kore_validator * kore_validator_lookup(const char *name) { struct kore_validator *val; TAILQ_FOREACH(val, &validators, list) { if (!strcmp(val->name, name)) return (val); } return (NULL); } kore-4.2.3/src/websocket.c000066400000000000000000000222211430066302400153760ustar00rootroot00000000000000/* * Copyright (c) 2014-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "kore.h" #include "http.h" #include "sha1.h" #define WEBSOCKET_FRAME_HDR 2 #define WEBSOCKET_MASK_LEN 4 #define WEBSOCKET_PAYLOAD_SINGLE 125 #define WEBSOCKET_PAYLOAD_EXTEND_1 126 #define WEBSOCKET_PAYLOAD_EXTEND_2 127 #define WEBSOCKET_OPCODE_MASK 0x0f #define WEBSOCKET_FRAME_LENGTH(x) ((x) & ~(1 << 7)) #define WEBSOCKET_HAS_MASK(x) ((x) & (1 << 7)) #define WEBSOCKET_HAS_FINFLAG(x) ((x) & (1 << 7)) #define WEBSOCKET_RSV(x, i) ((x) & (1 << (7 - i))) #define WEBSOCKET_SERVER_RESPONSE "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" u_int64_t kore_websocket_timeout = 120000; u_int64_t kore_websocket_maxframe = 16384; static int websocket_recv_frame(struct netbuf *); static int websocket_recv_opcode(struct netbuf *); static void websocket_disconnect(struct connection *); static void websocket_frame_build(struct kore_buf *, u_int8_t, const void *, size_t); void kore_websocket_handshake(struct http_request *req, const char *onconnect, const char *onmessage, const char *ondisconnect) { SHA1_CTX sctx; struct kore_buf *buf; char *base64; const char *key, *version; u_int8_t digest[SHA1_DIGEST_LENGTH]; if (!http_request_header(req, "sec-websocket-key", &key)) { http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0); return; } if (!http_request_header(req, "sec-websocket-version", &version)) { http_response_header(req, "sec-websocket-version", "13"); http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0); return; } if (strcmp(version, "13")) { http_response_header(req, "sec-websocket-version", "13"); http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0); return; } buf = kore_buf_alloc(128); kore_buf_appendf(buf, "%s%s", key, WEBSOCKET_SERVER_RESPONSE); SHA1Init(&sctx); SHA1Update(&sctx, buf->data, buf->offset); SHA1Final(digest, &sctx); kore_buf_free(buf); if (!kore_base64_encode(digest, sizeof(digest), &base64)) { http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0); return; } http_response_header(req, "upgrade", "websocket"); http_response_header(req, "connection", "upgrade"); http_response_header(req, "sec-websocket-accept", base64); kore_free(base64); req->owner->proto = CONN_PROTO_WEBSOCKET; http_response(req, HTTP_STATUS_SWITCHING_PROTOCOLS, NULL, 0); net_recv_reset(req->owner, WEBSOCKET_FRAME_HDR, websocket_recv_opcode); req->owner->disconnect = websocket_disconnect; req->owner->rnb->flags &= ~NETBUF_CALL_CB_ALWAYS; req->owner->http_timeout = 0; req->owner->idle_timer.start = kore_time_ms(); req->owner->idle_timer.length = kore_websocket_timeout; if (onconnect != NULL) { req->owner->ws_connect = kore_runtime_getcall(onconnect); if (req->owner->ws_connect == NULL) fatal("no symbol '%s' for ws_connect", onconnect); } else { req->owner->ws_connect = NULL; } if (onmessage != NULL) { req->owner->ws_message = kore_runtime_getcall(onmessage); if (req->owner->ws_message == NULL) fatal("no symbol '%s' for ws_message", onmessage); } else { req->owner->ws_message = NULL; } if (ondisconnect != NULL) { req->owner->ws_disconnect = kore_runtime_getcall(ondisconnect); if (req->owner->ws_disconnect == NULL) fatal("no symbol '%s' for ws_disconnect", ondisconnect); } else { req->owner->ws_disconnect = NULL; } if (req->owner->ws_connect != NULL) kore_runtime_wsconnect(req->owner->ws_connect, req->owner); } int kore_websocket_send_clean(struct netbuf *nb) { kore_free(nb->buf); return (0); } void kore_websocket_send(struct connection *c, u_int8_t op, const void *data, size_t len) { struct kore_buf frame; kore_buf_init(&frame, len); websocket_frame_build(&frame, op, data, len); net_send_stream(c, frame.data, frame.offset, kore_websocket_send_clean, NULL); /* net_send_stream() takes over the buffer data pointer. */ frame.data = NULL; kore_buf_cleanup(&frame); net_send_flush(c); } void kore_websocket_broadcast(struct connection *src, u_int8_t op, const void *data, size_t len, int scope) { struct connection *c; struct kore_buf *frame; frame = kore_buf_alloc(len); websocket_frame_build(frame, op, data, len); TAILQ_FOREACH(c, &connections, list) { if (c != src && c->proto == CONN_PROTO_WEBSOCKET) { net_send_queue(c, frame->data, frame->offset); net_send_flush(c); } } if (scope == WEBSOCKET_BROADCAST_GLOBAL) { kore_msg_send(KORE_MSG_WORKER_ALL, KORE_MSG_WEBSOCKET, frame->data, frame->offset); } kore_buf_free(frame); } static void websocket_frame_build(struct kore_buf *frame, u_int8_t op, const void *data, size_t len) { u_int8_t len_1; u_int16_t len16; u_int64_t len64; if (len > WEBSOCKET_PAYLOAD_SINGLE) { if (len <= USHRT_MAX) len_1 = WEBSOCKET_PAYLOAD_EXTEND_1; else len_1 = WEBSOCKET_PAYLOAD_EXTEND_2; } else { len_1 = len; } op |= (1 << 7); kore_buf_append(frame, &op, sizeof(op)); len_1 &= ~(1 << 7); kore_buf_append(frame, &len_1, sizeof(len_1)); if (len_1 > WEBSOCKET_PAYLOAD_SINGLE) { switch (len_1) { case WEBSOCKET_PAYLOAD_EXTEND_1: net_write16((u_int8_t *)&len16, len); kore_buf_append(frame, &len16, sizeof(len16)); break; case WEBSOCKET_PAYLOAD_EXTEND_2: net_write64((u_int8_t *)&len64, len); kore_buf_append(frame, &len64, sizeof(len64)); break; } } if (data != NULL && len > 0) kore_buf_append(frame, data, len); } static int websocket_recv_opcode(struct netbuf *nb) { u_int8_t op, len; struct connection *c = nb->owner; if (!WEBSOCKET_HAS_MASK(nb->buf[1])) return (KORE_RESULT_ERROR); if (WEBSOCKET_RSV(nb->buf[0], 1) || WEBSOCKET_RSV(nb->buf[0], 2) || WEBSOCKET_RSV(nb->buf[0], 3)) return (KORE_RESULT_ERROR); len = WEBSOCKET_FRAME_LENGTH(nb->buf[1]); op = nb->buf[0] & WEBSOCKET_OPCODE_MASK; switch (op) { case WEBSOCKET_OP_CONT: case WEBSOCKET_OP_TEXT: case WEBSOCKET_OP_BINARY: break; case WEBSOCKET_OP_CLOSE: case WEBSOCKET_OP_PING: case WEBSOCKET_OP_PONG: if (len > WEBSOCKET_PAYLOAD_SINGLE || !WEBSOCKET_HAS_FINFLAG(nb->buf[0])) return (KORE_RESULT_ERROR); break; default: return (KORE_RESULT_ERROR); } switch (len) { case WEBSOCKET_PAYLOAD_EXTEND_1: len += sizeof(u_int16_t); break; case WEBSOCKET_PAYLOAD_EXTEND_2: len += sizeof(u_int64_t); break; } len += WEBSOCKET_MASK_LEN; net_recv_expand(c, len, websocket_recv_frame); return (KORE_RESULT_OK); } static int websocket_recv_frame(struct netbuf *nb) { struct connection *c; int ret; u_int64_t len, i, total; u_int8_t op, moff, extra; c = nb->owner; op = nb->buf[0] & WEBSOCKET_OPCODE_MASK; len = WEBSOCKET_FRAME_LENGTH(nb->buf[1]); switch (len) { case WEBSOCKET_PAYLOAD_EXTEND_1: moff = 4; extra = sizeof(u_int16_t); len = net_read16(&nb->buf[2]); break; case WEBSOCKET_PAYLOAD_EXTEND_2: moff = 10; extra = sizeof(u_int64_t); len = net_read64(&nb->buf[2]); break; default: extra = 0; moff = 2; break; } if (len > kore_websocket_maxframe) return (KORE_RESULT_ERROR); extra += WEBSOCKET_FRAME_HDR; total = len + extra + WEBSOCKET_MASK_LEN; if (total > nb->b_len) { total -= nb->b_len; net_recv_expand(c, total, websocket_recv_frame); return (KORE_RESULT_OK); } if (total != nb->b_len) return (KORE_RESULT_ERROR); for (i = 0; i < len; i++) nb->buf[moff + 4 + i] ^= nb->buf[moff + (i % 4)]; ret = KORE_RESULT_OK; switch (op) { case WEBSOCKET_OP_PONG: break; case WEBSOCKET_OP_CONT: ret = KORE_RESULT_ERROR; kore_log(LOG_ERR, "%p: we do not support op 0x%02x yet", (void *)c, op); break; case WEBSOCKET_OP_TEXT: case WEBSOCKET_OP_BINARY: if (c->ws_message != NULL) { kore_runtime_wsmessage(c->ws_message, c, op, &nb->buf[moff + 4], len); } break; case WEBSOCKET_OP_CLOSE: c->evt.flags &= ~KORE_EVENT_READ; if (!(c->flags & CONN_WS_CLOSE_SENT)) { c->flags |= CONN_WS_CLOSE_SENT; kore_websocket_send(c, WEBSOCKET_OP_CLOSE, NULL, 0); } kore_connection_disconnect(c); break; case WEBSOCKET_OP_PING: kore_websocket_send(c, WEBSOCKET_OP_PONG, &nb->buf[moff + 4], len); break; default: return (KORE_RESULT_ERROR); } net_recv_reset(c, WEBSOCKET_FRAME_HDR, websocket_recv_opcode); return (ret); } static void websocket_disconnect(struct connection *c) { if (c->ws_disconnect != NULL) kore_runtime_wsdisconnect(c->ws_disconnect, c); if (!(c->flags & CONN_WS_CLOSE_SENT)) { c->flags |= CONN_WS_CLOSE_SENT; c->evt.flags &= ~KORE_EVENT_READ; kore_websocket_send(c, WEBSOCKET_OP_CLOSE, NULL, 0); } } kore-4.2.3/src/worker.c000066400000000000000000000536141430066302400147330ustar00rootroot00000000000000/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kore.h" #if defined(KORE_USE_ACME) #include "acme.h" #endif #if !defined(KORE_NO_HTTP) #include "http.h" #endif #if defined(KORE_USE_PGSQL) #include "pgsql.h" #endif #if defined(KORE_USE_TASKS) #include "tasks.h" #endif #if defined(KORE_USE_PYTHON) #include "python_api.h" #endif #if defined(KORE_USE_CURL) #include "curl.h" #endif #if defined(__linux__) #include "seccomp.h" #endif #define WORKER_SOLO_COUNT 3 #define WORKER(id) \ (struct kore_worker *)((u_int8_t *)kore_workers + \ (sizeof(struct kore_worker) * id)) struct wlock { volatile int lock; pid_t current; }; static int worker_trylock(void); static void worker_unlock(void); static void worker_reaper(pid_t, int); static void worker_runtime_teardown(void); static void worker_runtime_configure(void); static void worker_domain_check(struct kore_domain *); static struct kore_runtime_call *worker_runtime_signal(void); static inline int worker_acceptlock_obtain(void); static inline void worker_acceptlock_release(void); static void worker_accept_avail(struct kore_msg *, const void *); static void worker_entropy_recv(struct kore_msg *, const void *); static void worker_keymgr_response(struct kore_msg *, const void *); static pid_t worker_pgrp; static int accept_avail; static struct kore_worker *kore_workers; static int worker_no_lock; static int shm_accept_key; static struct wlock *accept_lock; struct kore_worker *worker = NULL; u_int8_t worker_set_affinity = 1; u_int32_t worker_accept_threshold = 16; u_int32_t worker_rlimit_nofiles = 768; u_int32_t worker_max_connections = 512; u_int32_t worker_active_connections = 0; int worker_policy = KORE_WORKER_POLICY_RESTART; int kore_worker_init(void) { size_t len; struct kore_worker *kw; u_int16_t idx, id, cpu; worker_no_lock = 0; if (worker_count == 0) worker_count = cpu_count; /* Account for the keymgr/acme even if we don't end up starting it. */ worker_count += 2; len = sizeof(*accept_lock) + (sizeof(struct kore_worker) * worker_count); shm_accept_key = shmget(IPC_PRIVATE, len, IPC_CREAT | IPC_EXCL | 0700); if (shm_accept_key == -1) fatal("kore_worker_init(): shmget() %s", errno_s); if ((accept_lock = shmat(shm_accept_key, NULL, 0)) == (void *)-1) fatal("kore_worker_init(): shmat() %s", errno_s); accept_lock->lock = 0; accept_lock->current = 0; kore_workers = (struct kore_worker *)((u_int8_t *)accept_lock + sizeof(*accept_lock)); memset(kore_workers, 0, sizeof(struct kore_worker) * worker_count); if (worker_count > cpu_count) kore_log(LOG_NOTICE, "more worker processes than cpu cores"); /* Setup log buffers. */ for (idx = KORE_WORKER_BASE; idx < worker_count; idx++) { kw = WORKER(idx); kw->lb.offset = 0; } if (!kore_quiet) kore_log(LOG_INFO, "starting worker processes"); if ((worker_pgrp = getpgrp()) == -1) fatal("%s: getpgrp(): %s", __func__, errno_s); /* Now start all the workers. */ id = 1; cpu = 1; for (idx = KORE_WORKER_BASE; idx < worker_count; idx++) { if (cpu >= cpu_count) cpu = 0; if (!kore_worker_spawn(idx, id++, cpu++)) return (KORE_RESULT_ERROR); } if (kore_keymgr_active) { #if defined(KORE_USE_ACME) /* The ACME process is only started if we need it. */ if (acme_domains) { if (!kore_worker_spawn(KORE_WORKER_ACME_IDX, KORE_WORKER_ACME, 0)) return (KORE_RESULT_ERROR); } #endif /* Now we can start the keymgr. */ if (!kore_worker_spawn(KORE_WORKER_KEYMGR_IDX, KORE_WORKER_KEYMGR, 0)) return (KORE_RESULT_ERROR); } if (!kore_quiet) kore_log(LOG_INFO, "all worker processes started"); return (KORE_RESULT_OK); } int kore_worker_spawn(u_int16_t idx, u_int16_t id, u_int16_t cpu) { int cnt; struct kore_worker *kw; #if defined(__linux__) int status; #endif kw = WORKER(idx); kw->id = id; kw->cpu = cpu; kw->running = 1; kw->ready = 0; kw->has_lock = 0; kw->active_route = NULL; if (socketpair(AF_UNIX, SOCK_STREAM, 0, kw->pipe) == -1) fatal("socketpair(): %s", errno_s); if (!kore_connection_nonblock(kw->pipe[0], 0) || !kore_connection_nonblock(kw->pipe[1], 0)) fatal("could not set pipe fds to nonblocking: %s", errno_s); switch (id) { case KORE_WORKER_KEYMGR: kw->ps = &keymgr_privsep; break; #if defined(KORE_USE_ACME) case KORE_WORKER_ACME: kw->ps = &acme_privsep; break; #endif default: kw->ps = &worker_privsep; break; } kw->pid = fork(); if (kw->pid == -1) fatal("could not spawn worker child: %s", errno_s); if (kw->pid == 0) { kw->pid = getpid(); kore_worker_entry(kw); exit(1); } else { for (cnt = 0; cnt < 50; cnt++) { if (kw->ready == 1) break; usleep(100000); #if defined(__linux__) /* * If seccomp_tracing is enabled, make sure we * handle the SIGSTOP from the child processes. */ if (kore_seccomp_tracing) { if (waitpid(kw->pid, &status, WNOHANG) > 0) kore_seccomp_trace(kw->pid, status); } #endif } if (kw->ready == 0) { kore_log(LOG_NOTICE, "worker %d failed to start, shutting down", kw->id); return (KORE_RESULT_ERROR); } } return (KORE_RESULT_OK); } struct kore_worker * kore_worker_data(u_int8_t idx) { if (idx >= worker_count) fatal("idx %u too large for worker count", idx); return (WORKER(idx)); } struct kore_worker * kore_worker_data_byid(u_int16_t id) { struct kore_worker *kw; u_int16_t idx; for (idx = 0; idx < worker_count; idx++) { kw = WORKER(idx); if (kw->id == id) return (kw); } return (NULL); } void kore_worker_shutdown(void) { struct kore_worker *kw; pid_t pid; int status; u_int16_t idx, done; if (!kore_quiet) { kore_log(LOG_NOTICE, "waiting for workers to drain and shutdown"); } for (;;) { for (idx = 0; idx < worker_count; idx++) { kw = WORKER(idx); if (kw->running == 0) continue; if (kw->pid != 0) { pid = waitpid(kw->pid, &status, 0); if (pid == -1 && errno != ECHILD) continue; #if defined(__linux__) kore_seccomp_trace(kw->pid, status); #endif kw->pid = 0; kw->running = 0; kw->msg[0]->evt.flags |= KORE_EVENT_READ; net_recv_flush(kw->msg[0]); if (!kore_quiet) { kore_log(LOG_NOTICE, "worker %s exited (%d)", kore_worker_name(kw->id), status); } } } done = 0; for (idx = 0; idx < worker_count; idx++) { kw = WORKER(idx); if (kw->running == 0) { done++; continue; } } if (done == worker_count) break; } if (shmctl(shm_accept_key, IPC_RMID, NULL) == -1) { kore_log(LOG_NOTICE, "failed to deleted shm segment: %s", errno_s); } } void kore_worker_dispatch_signal(int sig) { u_int16_t idx; struct kore_worker *kw; for (idx = 0; idx < worker_count; idx++) { kw = WORKER(idx); if (kw->pid == -1 || kw->pid == 0) continue; if (kill(kw->pid, sig) == -1) { kore_log(LOG_WARNING, "kill(%d, %d): %s", kw->pid, sig, errno_s); } } } void kore_worker_privsep(void) { rlim_t fd; struct rlimit rl; struct passwd *pw; if (worker == NULL) fatalx("%s called with no worker", __func__); pw = NULL; /* Must happen before chroot. */ if (worker->ps->skip_runas == 0) { if (worker->ps->runas == NULL) { fatalx("no runas user given for %s", kore_worker_name(worker->id)); } if ((pw = getpwnam(worker->ps->runas)) == NULL) { fatalx("cannot getpwnam(\"%s\") for user: %s", worker->ps->runas, errno_s); } } if (worker->ps->skip_chroot == 0) { if (chroot(worker->ps->root) == -1) { fatalx("cannot chroot(\"%s\"): %s", worker->ps->root, errno_s); } if (chdir("/") == -1) fatalx("cannot chdir(\"/\"): %s", errno_s); } else { if (chdir(worker->ps->root) == -1) { fatalx("cannot chdir(\"%s\"): %s", worker->ps->root, errno_s); } } if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { kore_log(LOG_WARNING, "getrlimit(RLIMIT_NOFILE): %s", errno_s); } else { for (fd = 0; fd < rl.rlim_cur; fd++) { if (fcntl(fd, F_GETFD, NULL) != -1) { worker_rlimit_nofiles++; } } } rl.rlim_cur = worker_rlimit_nofiles; rl.rlim_max = worker_rlimit_nofiles; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { kore_log(LOG_ERR, "setrlimit(RLIMIT_NOFILE, %u): %s", worker_rlimit_nofiles, errno_s); } if (worker->ps->skip_runas == 0) { if (setgroups(1, &pw->pw_gid) || #if defined(__MACH__) || defined(NetBSD) setgid(pw->pw_gid) || setegid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) #else setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) #endif fatalx("cannot drop privileges (%s)", errno_s); } kore_platform_sandbox(); } void kore_worker_entry(struct kore_worker *kw) { struct kore_runtime_call *sigcall; u_int64_t last_seed; int quit, had_lock, sig; u_int64_t netwait, now, next_timeo; worker = kw; if (!kore_foreground) closelog(); #if defined(__linux__) kore_seccomp_traceme(); #endif kore_platform_proctitle(kore_worker_name(kw->id)); if (worker_set_affinity == 1) kore_platform_worker_setcpu(kw); kore_pid = kw->pid; kore_signal_setup(); if (kw->id == KORE_WORKER_KEYMGR) { kore_keymgr_run(); exit(0); } #if defined(KORE_USE_ACME) if (kw->id == KORE_WORKER_ACME) { kore_acme_run(); exit(0); } #endif net_init(); kore_connection_init(); kore_platform_event_init(); kore_msg_worker_init(); #if defined(KORE_USE_TASKS) kore_task_init(); #endif kore_worker_privsep(); #if !defined(KORE_NO_HTTP) http_init(); kore_filemap_resolve_paths(); kore_accesslog_worker_init(); #endif kore_timer_init(); kore_fileref_init(); kore_tls_keymgr_init(); quit = 0; had_lock = 0; next_timeo = 0; accept_avail = 1; worker_active_connections = 0; last_seed = 0; if (kore_keymgr_active) { kore_msg_register(KORE_MSG_CRL, worker_keymgr_response); kore_msg_register(KORE_MSG_ENTROPY_RESP, worker_entropy_recv); kore_msg_register(KORE_MSG_CERTIFICATE, worker_keymgr_response); if (worker->restarted) { kore_msg_send(KORE_WORKER_KEYMGR, KORE_MSG_CERTIFICATE_REQ, NULL, 0); } #if defined(KORE_USE_ACME) kore_msg_register(KORE_ACME_CHALLENGE_SET_CERT, worker_keymgr_response); kore_msg_register(KORE_ACME_CHALLENGE_CLEAR_CERT, worker_keymgr_response); #endif } kore_msg_register(KORE_MSG_ACCEPT_AVAILABLE, worker_accept_avail); if (nlisteners == 0) worker_no_lock = 1; worker_runtime_configure(); kore_module_onload(); kore_domain_callback(worker_domain_check); kore_worker_started(); worker->restarted = 0; sigcall = worker_runtime_signal(); for (;;) { now = kore_time_ms(); if (kore_keymgr_active && (now - last_seed) > KORE_RESEED_TIME) { kore_msg_send(KORE_WORKER_KEYMGR, KORE_MSG_ENTROPY_REQ, NULL, 0); last_seed = now; } if (!worker->has_lock && accept_avail) { if (worker_acceptlock_obtain()) { accept_avail = 0; if (had_lock == 0) { kore_platform_enable_accept(); had_lock = 1; } } } netwait = kore_timer_next_run(now); if (netwait == KORE_WAIT_INFINITE) { if (sig_recv != 0) netwait = 10; #if !defined(KORE_NO_HTTP) if (http_request_count > 0) netwait = 100; #endif } #if defined(KORE_USE_PYTHON) if (kore_python_coro_pending()) netwait = 0; #endif kore_platform_event_wait(netwait); now = kore_time_ms(); if (worker->has_lock) worker_acceptlock_release(); if (!worker->has_lock) { if (had_lock == 1) { had_lock = 0; kore_platform_disable_accept(); } } sig = sig_recv; if (sig != 0) { switch (sig) { case SIGHUP: kore_module_reload(1); break; case SIGQUIT: case SIGINT: case SIGTERM: quit = 1; break; case SIGCHLD: #if defined(KORE_USE_PYTHON) kore_python_proc_reap(); #endif break; default: break; } if (sigcall != NULL) kore_runtime_signal(sigcall, sig); if (sig == sig_recv) sig_recv = 0; } if (quit) break; kore_timer_run(now); #if defined(KORE_USE_CURL) kore_curl_run_scheduled(); kore_curl_do_timeout(); #endif #if !defined(KORE_NO_HTTP) http_process(); #endif #if defined(KORE_USE_PYTHON) kore_python_coro_run(); #endif if (next_timeo <= now) { kore_connection_check_timeout(now); next_timeo = now + 500; } kore_connection_prune(KORE_CONNECTION_PRUNE_DISCONNECT); } worker_runtime_teardown(); kore_server_cleanup(); kore_platform_event_cleanup(); kore_connection_cleanup(); kore_domain_cleanup(); kore_tls_cleanup(); kore_module_cleanup(); #if !defined(KORE_NO_HTTP) http_cleanup(); #endif net_cleanup(); #if defined(KORE_USE_PYTHON) kore_python_cleanup(); #endif #if defined(KORE_USE_PGSQL) kore_pgsql_sys_cleanup(); #endif kore_mem_cleanup(); exit(0); } void kore_worker_reap(void) { pid_t pid; int status; for (;;) { pid = waitpid(-worker_pgrp, &status, WNOHANG); if (pid == -1) { if (errno != ECHILD && errno != EINTR) { kore_log(LOG_ERR, "%s: waitpid(): %s", __func__, errno_s); } break; } if (pid == 0) break; worker_reaper(pid, status); } } void kore_worker_make_busy(void) { if (worker_count == WORKER_SOLO_COUNT || worker_no_lock == 1) return; if (worker->has_lock) { worker_unlock(); worker->has_lock = 0; kore_msg_send(KORE_MSG_WORKER_ALL, KORE_MSG_ACCEPT_AVAILABLE, NULL, 0); } } int kore_worker_keymgr_response_verify(struct kore_msg *msg, const void *data, struct kore_domain **out) { struct kore_server *srv; struct kore_domain *dom; const struct kore_x509_msg *req; if (msg->length < sizeof(*req)) { kore_log(LOG_WARNING, "short keymgr message (%zu)", msg->length); return (KORE_RESULT_ERROR); } req = (const struct kore_x509_msg *)data; if (msg->length != (sizeof(*req) + req->data_len)) { kore_log(LOG_WARNING, "invalid keymgr payload (%zu)", msg->length); return (KORE_RESULT_ERROR); } if (req->domain[KORE_DOMAINNAME_LEN] != '\0') { kore_log(LOG_WARNING, "domain not NUL-terminated"); return (KORE_RESULT_ERROR); } if (out == NULL) return (KORE_RESULT_OK); dom = NULL; LIST_FOREACH(srv, &kore_servers, list) { dom = NULL; if (srv->tls == 0) continue; TAILQ_FOREACH(dom, &srv->domains, list) { if (!strcmp(dom->domain, req->domain)) break; } if (dom != NULL) break; } if (dom == NULL) { kore_log(LOG_WARNING, "got keymgr response for domain that does not exist"); return (KORE_RESULT_ERROR); } *out = dom; return (KORE_RESULT_OK); } void kore_worker_started(void) { const char *chroot; if (worker->ps->skip_chroot) chroot = "root"; else chroot = "chroot"; if (!kore_quiet) { kore_log(LOG_NOTICE, "started (#%d %s=%s%s%s)", getpid(), chroot, worker->ps->root, worker->ps->skip_runas ? "" : " user=", worker->ps->skip_runas ? "" : worker->ps->runas); } worker->ready = 1; } static void worker_runtime_configure(void) { struct kore_runtime_call *rcall; rcall = NULL; #if defined(KORE_USE_PYTHON) rcall = kore_runtime_getcall(KORE_PYTHON_WORKER_START_HOOK); #endif if (rcall == NULL) rcall = kore_runtime_getcall("kore_worker_configure"); if (rcall != NULL) { kore_runtime_execute(rcall); kore_free(rcall); } } static struct kore_runtime_call * worker_runtime_signal(void) { struct kore_runtime_call *rcall; rcall = NULL; #if defined(KORE_USE_PYTHON) rcall = kore_runtime_getcall(KORE_PYTHON_SIGNAL_HOOK); #endif if (rcall == NULL) rcall = kore_runtime_getcall("kore_worker_signal"); return (rcall); } static void worker_runtime_teardown(void) { struct kore_runtime_call *rcall; rcall = NULL; #if defined(KORE_USE_PYTHON) rcall = kore_runtime_getcall(KORE_PYTHON_WORKER_STOP_HOOK); #endif if (rcall == NULL) rcall = kore_runtime_getcall("kore_worker_teardown"); if (rcall != NULL) { kore_runtime_execute(rcall); kore_free(rcall); } } static void worker_domain_check(struct kore_domain *dom) { struct stat st; if (dom->cafile != NULL) { if (stat(dom->cafile, &st) == -1) fatalx("'%s': %s", dom->cafile, errno_s); if (access(dom->cafile, R_OK) == -1) fatalx("'%s': not readable", dom->cafile); } } static void worker_reaper(pid_t pid, int status) { u_int16_t idx; struct kore_worker *kw; const char *func; #if defined(__linux__) if (kore_seccomp_trace(pid, status)) return; #endif for (idx = 0; idx < worker_count; idx++) { kw = WORKER(idx); if (kw->pid != pid) continue; kw->msg[0]->evt.flags |= KORE_EVENT_READ; net_recv_flush(kw->msg[0]); if (!kore_quiet) { kore_log(LOG_NOTICE, "worker %s (%d) exited with status %d", kore_worker_name(kw->id), pid, status); } kw->running = 0; if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { kw->pid = 0; break; } func = "none"; #if !defined(KORE_NO_HTTP) if (kw->active_route != NULL) func = kw->active_route->func; #endif kore_log(LOG_NOTICE, "worker %d (pid: %d) (hdlr: %s) gone", kw->id, kw->pid, func); #if defined(__linux__) if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSYS) { kore_log(LOG_NOTICE, "worker %d died from sandbox violation", kw->id); } #endif if (kw->id == KORE_WORKER_KEYMGR || kw->id == KORE_WORKER_ACME) { kore_log(LOG_CRIT, "keymgr or acme process gone, stopping"); kw->pid = 0; kore_quit = KORE_QUIT_FATAL; break; } if (kw->pid == accept_lock->current && worker_no_lock == 0) worker_unlock(); #if !defined(KORE_NO_HTTP) if (kw->active_route != NULL) { kw->active_route->errors++; kore_log(LOG_NOTICE, "hdlr %s has caused %d error(s)", kw->active_route->func, kw->active_route->errors); } #endif if (worker_policy == KORE_WORKER_POLICY_TERMINATE) { kw->pid = 0; kore_log(LOG_NOTICE, "worker policy is 'terminate', stopping"); kore_quit = KORE_QUIT_FATAL; break; } if (kore_quit == KORE_QUIT_NONE) { kore_log(LOG_NOTICE, "restarting worker %d", kw->id); kw->restarted = 1; kore_msg_parent_remove(kw); if (!kore_worker_spawn(idx, kw->id, kw->cpu)) { kore_quit = KORE_QUIT_FATAL; kore_log(LOG_ERR, "failed to restart worker"); } else { kore_msg_parent_add(kw); } break; } } } static inline void worker_acceptlock_release(void) { if (worker_count == WORKER_SOLO_COUNT || worker_no_lock == 1) return; if (worker->has_lock != 1) return; if (worker_active_connections < worker_max_connections) { #if !defined(KORE_NO_HTTP) if (http_request_count < http_request_limit) return; #else return; #endif } #if defined(WORKER_DEBUG) kore_log(LOG_DEBUG, "worker busy, releasing lock"); #endif worker_unlock(); worker->has_lock = 0; kore_msg_send(KORE_MSG_WORKER_ALL, KORE_MSG_ACCEPT_AVAILABLE, NULL, 0); } static inline int worker_acceptlock_obtain(void) { int r; if (worker->has_lock == 1) return (1); if (worker_count == WORKER_SOLO_COUNT || worker_no_lock == 1) { worker->has_lock = 1; return (1); } if (worker_active_connections >= worker_max_connections) return (0); #if !defined(KORE_NO_HTTP) if (http_request_count >= http_request_limit) return (0); #endif r = 0; if (worker_trylock()) { r = 1; worker->has_lock = 1; #if defined(WORKER_DEBUG) kore_log(LOG_DEBUG, "got lock"); #endif } return (r); } static int worker_trylock(void) { if (!__sync_bool_compare_and_swap(&(accept_lock->lock), 0, 1)) return (0); accept_lock->current = worker->pid; return (1); } static void worker_unlock(void) { accept_lock->current = 0; if (!__sync_bool_compare_and_swap(&(accept_lock->lock), 1, 0)) kore_log(LOG_NOTICE, "worker_unlock(): wasn't locked"); } static void worker_accept_avail(struct kore_msg *msg, const void *data) { accept_avail = 1; } static void worker_entropy_recv(struct kore_msg *msg, const void *data) { if (msg->length != 1024) { kore_log(LOG_WARNING, "invalid entropy response (got:%zu - wanted:1024)", msg->length); } kore_tls_seed(data, msg->length); } static void worker_keymgr_response(struct kore_msg *msg, const void *data) { struct kore_domain *dom; const struct kore_x509_msg *req; if (!kore_worker_keymgr_response_verify(msg, data, &dom)) return; req = (const struct kore_x509_msg *)data; switch (msg->id) { case KORE_MSG_CERTIFICATE: kore_tls_domain_setup(dom, KORE_PEM_CERT_CHAIN, req->data, req->data_len); break; case KORE_MSG_CRL: kore_tls_domain_crl(dom, req->data, req->data_len); break; #if defined(KORE_USE_ACME) case KORE_ACME_CHALLENGE_SET_CERT: if (dom->tls_ctx == NULL) { kore_tls_domain_setup(dom, KORE_DER_CERT_DATA, req->data, req->data_len); } kore_free(dom->acme_cert); dom->acme_cert_len = req->data_len; dom->acme_cert = kore_calloc(1, req->data_len); memcpy(dom->acme_cert, req->data, req->data_len); kore_log(LOG_NOTICE, "[%s] tls-alpn-01 challenge active", dom->domain); dom->acme_challenge = 1; break; case KORE_ACME_CHALLENGE_CLEAR_CERT: dom->acme_cert_len = 0; dom->acme_challenge = 0; kore_free(dom->acme_cert); dom->acme_cert = NULL; kore_log(LOG_NOTICE, "[%s] tls-alpn-01 challenge disabled", dom->domain); break; #endif default: kore_log(LOG_WARNING, "unknown keymgr request %u", msg->id); break; } } kore-4.2.3/tools/000077500000000000000000000000001430066302400136165ustar00rootroot00000000000000kore-4.2.3/tools/kore-serve/000077500000000000000000000000001430066302400157005ustar00rootroot00000000000000kore-4.2.3/tools/kore-serve/.gitignore000066400000000000000000000000561430066302400176710ustar00rootroot00000000000000*.o .flavor .objs kore-serve.so assets.h cert kore-4.2.3/tools/kore-serve/conf/000077500000000000000000000000001430066302400166255ustar00rootroot00000000000000kore-4.2.3/tools/kore-serve/conf/build.conf000066400000000000000000000004751430066302400206010ustar00rootroot00000000000000single_binary=yes kore_source=../../ kore_flavor=NOTLS=1 cflags=-std=c99 -Werror cflags=-Wall -Wmissing-declarations -Wshadow cflags=-Wstrict-prototypes -Wmissing-prototypes cflags=-Wpointer-arith -Wcast-qual -Wsign-compare dev { } darwin { } openbsd { } netbsd { } freebsd { } linux { cflags=-D_GNU_SOURCE } kore-4.2.3/tools/kore-serve/conf/kore-serve.conf000066400000000000000000000000431430066302400215530ustar00rootroot00000000000000# kore-serve configuration # empty kore-4.2.3/tools/kore-serve/src/000077500000000000000000000000001430066302400164675ustar00rootroot00000000000000kore-4.2.3/tools/kore-serve/src/kore-serve.c000066400000000000000000000042271430066302400207220ustar00rootroot00000000000000/* * Copyright (c) 2020 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Simple static file serving over non TLS. Heavily used by myself * when working on kore-site. */ #include #include #include #include static void usage(void) { fprintf(stderr, "Usage: kore-serve [-i ip] [-p port] [-r root]\n"); exit(1); } void kore_parent_configure(int argc, char *argv[]) { int ch; struct kore_domain *dom; struct kore_server *srv; char *rpath; const char *ip, *port, *root; root = "."; port = "8888"; ip = "127.0.0.1"; kore_quiet = 1; kore_foreground = 1; skip_runas = 1; skip_chroot = 1; kore_filemap_ext = kore_strdup(".html"); while ((ch = getopt(argc, argv, "hi:p:r:")) != -1) { switch (ch) { case 'i': ip = optarg; break; case 'h': usage(); break; case 'p': port = optarg; break; case 'r': root = optarg; break; default: usage(); } } if ((rpath = realpath(root, NULL)) == NULL) fatal("realpath(%s): %s", root, errno_s); kore_log(LOG_INFO, "%s -> http://%s:%s", rpath, ip, port); srv = kore_server_create("kore-serve"); srv->tls = 0; if (!kore_server_bind(srv, ip, port, NULL)) fatal("Failed to bind to %s:%s (%s)", ip, port, errno_s); kore_server_finalize(srv); dom = kore_domain_new("*"); kore_domain_attach(dom, srv); if (kore_filemap_create(dom, rpath, "/", NULL) == NULL) fatal("failed to create filemap for %s", rpath); }