├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── Version.rc ├── appveyor.yml ├── coc ├── coc.c ├── coc.ps1 ├── connect-or-cut.c ├── connect-or-cut.sln ├── connect-or-cut.spec ├── connect-or-cut.vcxproj ├── connect-or-cut.vcxproj.filters ├── connect-or-cut.vcxproj.user ├── inject.c ├── inject.h ├── packages.config ├── resource.h ├── resource1.h ├── sys_queue.h ├── tcpcontest.c ├── testsuite ├── testsuite.ps1 └── vs2015 ├── coc ├── Version.aps ├── Version.rc ├── coc.vcxproj ├── coc.vcxproj.filters ├── coc.vcxproj.user ├── error.c └── resource.h └── tcpcontest ├── Version.aps ├── Version.rc ├── resource.h ├── tcpcontest.vcxproj └── tcpcontest.vcxproj.filters /.gitignore: -------------------------------------------------------------------------------- 1 | /ipch 2 | *.db 3 | *.opendb 4 | /.vs/connect-or-cut/v14 5 | /packages/minhook.1.3.3 6 | /Release 7 | /vs2015/coc/Debug 8 | /vs2015/coc/Release 9 | /vs2015/tcpcontest/Debug 10 | /vs2015/tcpcontest/Release 11 | /Debug 12 | /vs2015/coc/x64/Debug 13 | /vs2015/coc/x64/Release 14 | /vs2015/tcpcontest/x64/Debug 15 | /vs2015/tcpcontest/x64/Release 16 | /x64/Debug 17 | /x64/Release 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: 3 | - clang 4 | - gcc 5 | os: 6 | - linux 7 | - osx 8 | before_install: 9 | - test $TRAVIS_OS_NAME = linux && sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1 || true 10 | script: make test os=$(uname -s) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Ⓒ 2015-2019 Thomas Girard 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of connect-or-cut nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC := connect-or-cut.c 2 | OBJ := $(SRC:.c=.o) 3 | ABI := 1 4 | VER := $(ABI).0.4 5 | LIB := libconnect-or-cut.so 6 | TGT := $(LIB).$(VER) 7 | TST := tcpcontest 8 | LNK := $(LIB).$(ABI) 9 | 10 | DESTDIR ?= /usr/local 11 | DESTBIN ?= $(DESTDIR)/bin 12 | DESTLIB ?= $(DESTDIR)/lib 13 | 14 | OPTION_STEALTH_1 := -DCOC_STEALTH 15 | 16 | 32_CFLAGS := -m32 17 | 32__LDFLAGS := -m32 18 | SunOS__LDFLAGS := -lsocket -lnsl 19 | SunOS_CPPFLAGS := -DMISSING_STRNDUP 20 | SunOS_CFLAGS := -mt -w 21 | SunOS_LIBFLAGS := -G -h $(LNK) 22 | GCC_CFLAGS := -Wall -pthread 23 | GCC_LIBFLAGS := -pthread -shared -Wl,-soname,$(LNK) 24 | Linux_LIBFLAGS := -ldl $(GCC_LIBFLAGS) 25 | FreeBSD_LIBFLAGS := $(GCC_LIBFLAGS) 26 | NetBSD_LIBFLAGS := $(GCC_LIBFLAGS) 27 | OpenBSD_LIBFLAGS := $(GCC_LIBFLAGS) 28 | DragonFly_LIBFLAGS:= $(GCC_LIBFLAGS) 29 | Linux_CFLAGS := $(GCC_CFLAGS) 30 | NetBSD_CFLAGS := $(GCC_CFLAGS) 31 | FreeBSD_CFLAGS := $(GCC_CFLAGS) 32 | OpenBSD_CFLAGS := $(GCC_CFLAGS) 33 | DragonFly_CFLAGS := $(GCC_CFLAGS) 34 | Darwin_LIBFLAGS := -dynamiclib -flat_namespace -ldl -Wl,-dylib_install_name,$(LNK) 35 | Darwin_CFLAGS := -fno-common 36 | 37 | CFLAGS += -fPIC ${${os}_CFLAGS} ${${bits}_CFLAGS} 38 | CPPFLAGS+= ${OPTION_STEALTH_${stealth}} ${${os}_CPPFLAGS} 39 | LDFLAGS += ${${os}__LDFLAGS} ${${bits}__LDFLAGS} 40 | 41 | .PHONY: all 42 | all: $(TGT) $(TST) 43 | 44 | .PHONY: clean 45 | clean: 46 | rm -f $(OBJ) $(TGT) $(LNK) $(TST) $(TST).o 47 | 48 | $(TGT): $(OBJ) 49 | $(CC) -o $(TGT) $(OBJ) $(LDFLAGS) ${${os}_LIBFLAGS} 50 | rm -f $(LNK) 51 | ln -s $(TGT) $(LNK) 52 | 53 | $(TST): $(TST).o 54 | $(CC) -o $(TST) $(TST).o $(LDFLAGS) 55 | 56 | .PHONY: install 57 | install: $(TGT) 58 | mkdir -p $(DESTBIN) 59 | install -m755 coc $(DESTBIN) 60 | mkdir -p $(DESTLIB) 61 | install -m755 $(TGT) $(DESTLIB) 62 | (cd $(DESTLIB) && rm -f $(LNK) && ln -s $(TGT) $(LNK)) 63 | 64 | .PHONY: test 65 | test: $(TGT) $(TST) 66 | ./testsuite 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # connect-or-cut [![Build Status](https://travis-ci.org/tgg/connect-or-cut.svg)](https://travis-ci.org/tgg/connect-or-cut) [![AppVeyor Status](https://ci.appveyor.com/api/projects/status/github/tgg/connect-or-cut?svg=true)](https://ci.appveyor.com/project/teejeejee/connect-or-cut) 2 | 3 | Simple network sandbox for Unix and Windows. 4 | 5 | ## Demo 6 | 7 | [![asciicast](https://asciinema.org/a/66ggwn843r9qudwi13nyn5ru0.png)](https://asciinema.org/a/66ggwn843r9qudwi13nyn5ru0?autoplay=1&speed=2) 8 | 9 | ## What it is 10 | 11 | connect-or-cut is a small library to inject into a program to prevent 12 | it from connecting where it should not. 13 | 14 | This is similar to a firewall, except that: 15 | 16 | * you do not need specific privileges to use it 17 | * only injected process and its sub-processes are affected, not 18 | the full system 19 | 20 | ## Installation 21 | 22 | ### Unix 23 | Provided you have a C compiler, GNU or BSD make on a Unix box: 24 | 25 | $ git clone https://github.com/tgg/connect-or-cut.git 26 | $ cd connect-or-cut 27 | $ make os=$(uname -s) 28 | 29 | If you want to compile it for 32 bits: 30 | 31 | $ make os=$(uname -s) bits=32 32 | 33 | If you want to compile it with debug information: 34 | 35 | $ CFLAGS=-g make os=$(uname -s) 36 | 37 | ### Windows 38 | Download binaries or compile using Visual Studio 2015. 39 | 40 | 41 | ## Using it 42 | ### Unix 43 | 44 | $ ./coc -d -a '*.google.com' -a '*.1e100.net' -b '*' firefox 45 | 46 | Or to do it manually: 47 | 48 | $ LD_PRELOAD=./libconnect-or-cut.so COC_ALLOW='212.27.40.240:53;212.27.40.241:53;*.google.com;*.1e100.net' COC_BLOCK='*' firefox 49 | 50 | This invokes firefox, allowing only outgoing connection to my ISP 51 | provided name servers, to all google.com and 1e100.net addresses. 52 | 53 | Everything else will be blocked, with connection blocking messages 54 | displayed on stderr. 55 | 56 | `coc` is a helper script that will set appropriate variables for you: 57 | 58 | $ ./coc -h 59 | Usage: coc [OPTION]... [--] COMMAND [ARGS] 60 | Prevent connections to blocked addresses in COMMAND. 61 | 62 | If no COMMAND is specified but some addresses are configured to be allowed or 63 | blocked, then shell snippets to set the chosen configuration are displayed. 64 | 65 | OPTIONS: 66 | -d, --allow-dns Allow connections to DNS nameservers. 67 | -a, --allow=ADDRESS[/BITS][:PORT] Allow connections to ADDRESS[/BITS][:PORT]. 68 | -b, --block=ADDRESS[/BITS][:PORT] Prevent connections to ADDRESS[/BITS][:PORT]. 69 | BITS is the number of bits in CIDR notation 70 | prefix. When BITS is specified, the rule 71 | matches the IP range. 72 | -h, --help Print this help message. 73 | -t, --log-target=LOG Where to log. LOG is a comma-separated list 74 | that can contain the following values: 75 | - stderr This is the default 76 | - syslog Write to syslog 77 | - file Write to COMMAND.coc file 78 | -p, --log-path=PATH Path for file log. 79 | -l, --log-level=LEVEL What to log. LEVEL can contain one of the 80 | following values: 81 | - silent Do not log anything 82 | - error Log errors 83 | - block Log errors and blocked 84 | connections 85 | - allow Log errors, blocked and 86 | allowed connections 87 | - debug Log everything 88 | -v, --version Print connect-or-cut version. 89 | 90 | ### Windows 91 | 92 | From a PowerShell console: 93 | 94 | PS> .\coc.ps1 -AllowDNS -Allow '*.google.com,*.1e100.net' -Block '*' '"C:\Program Files\Mozilla Firefox\firefox.exe"' 95 | 96 | Or to do it manually, from cmd: 97 | 98 | > set COC_ALLOW=212.27.40.240:53;212.27.40.241:53;*.google.com;*.1e100.net 99 | > set COC_BLOCK=* 100 | > coc.exe "C:\Program Files\Mozilla Firefox\firefox.exe" 101 | 102 | This invokes firefox, allowing only outgoing connection to my ISP 103 | provided name servers, to all google.com and 1e100.net addresses. 104 | 105 | Everything else will be blocked. 106 | 107 | `coc.ps1` is a helper script that will set appropriate variables for you: 108 | 109 | PS> .\coc.ps1 -Help 110 | NAME 111 | coc.ps1 112 | 113 | SUMMARY 114 | coc configures connect-or-cut for the specified program. 115 | 116 | 117 | SYNTAX 118 | coc.ps1 [-AllowDNS] [-Allow ] [-Block ] [-Help] [-LogTarget ] [-LogPath ] 119 | [-LogLevel ] [-Version] [-ProgramAndParams ] [] 120 | 121 | 122 | DESCRIPTION 123 | coc helper script sets environment variables then invokes coc.exe on the 124 | specified program. If no program is specified it outputs environment 125 | variables that need to be set to replicate the requested rules. 126 | 127 | Whenever a TCP connection is requested, the address where to connect 128 | is checked against the list of allowed hosts. If it's in it, the connection 129 | is allowed. If not, the list of blocked hosts is checked. If it's in it, 130 | the connection is rejected. If not, the connection is allowed. 131 | 132 | 133 | PARAMETERS 134 | -AllowDNS [] 135 | Allow connections to DNS nameservers. 136 | 137 | -Allow 138 | List of allowed hosts. Can be specified as host, host:port, host:port/bits or 139 | as a glob like "*.google.com". Specifying "*" allows every connection. 140 | 141 | -Block 142 | List of blocked hosts. Can be specified as host, host:port, host:port/bits or 143 | as a glob like "*.google.com". Specifying "*" can block every connection. 144 | 145 | -Help [] 146 | Shows this help message. 147 | 148 | -LogTarget 149 | The location where to write logs. Valid values are: "stderr", which is the 150 | default; "syslog"; and "file". Multiple values can be specified. 151 | 152 | -LogPath 153 | The path where to store logs when logging to files. 154 | 155 | -LogLevel 156 | What to log. Can be one of: "silent" to discard logging, "error" to log 157 | only errors, "block" to also log blocked connections,"allow" to also 158 | log allowed connections, and "debug" to add debugging information. Default 159 | value is "block". 160 | 161 | -Version [] 162 | Shows the version of connect-or-cut library. 163 | 164 | -ProgramAndParams 165 | The program (and its arguments) to launch with the specified rules. 166 | If not specified, the cmd instructions to replicate these rules are displayed. 167 | 168 | ## Use cases 169 | 170 | You can use connect-or-cut to: 171 | 172 | 1. Sandbox an untrusted application to prevent all ourgoing connections from it 173 | 2. Monitor where an application is connecting to understand how it works 174 | 3. Filter out advertising sites during your web navigation 175 | 176 | ## Environment variables 177 | 178 | Again, `coc` helper script should be used to do the heavy-lifting here. 179 | 180 | * `COC_ALLOW` is a comma separated list of addresses to allow 181 | * `COC_BLOCK` is a comma separated list of addresses to block 182 | * `COC_LOG_LEVEL` defines the level of log, from `0` (silent) to `4` (debug) 183 | * `COC_LOG_TARGET` is a bitwise between the following values: 184 | * `1` log to stderr 185 | * `2` log to syslog 186 | * `4` log to a file 187 | 188 | ## Limitations 189 | 190 | * connect-or-cut does not work for programs: 191 | * performing connect syscall directly; 192 | * statically linked (e.g. Go binaries) 193 | * Only outgoing connection using TCP over IPv4 or IPv6 work for now. 194 | * If you are using a network proxy then: 195 | * you need to allow outgoing connections to the proxy 196 | * blocking will not work for the traffic redirected to the proxy, since 197 | it will do the connection instead of the source machine 198 | * Tested on: 199 | * Debian GNU/Linux with gcc and clang 200 | * FreeBSD 11.0-STABLE with clang 201 | * NetBSD 7 with gcc 202 | * Solaris 10 203 | * macOS 10.4 204 | * Windows 10 with Visual Studio 2015 205 | 206 | Portability patches welcome! 207 | 208 | ## Known issues 209 | 210 | * Aliases from /etc/hosts are not honoured. For instance if a machine is 211 | aliased and the long name conforms to a pattern it will be missed. 212 | The workaround for this is to add the short name to the rule set. 213 | 214 | This will require parsing /etc/hosts by hand, given that: 215 | - getnameinfo does not provide access to aliases 216 | - gethostbyaddr/gethostent, which do, are not reentrant on all platforms 217 | 218 | * On Windows, injection into UWP processes does not work. This means 219 | connect-or-cut cannot (yet?) work with Edge browser. 220 | 221 | ## News 222 | * Version 1.0.4 (2019-01-13) 223 | * Add CIDR notation (contributed by saito tom) 224 | * Preliminary version for Windows 225 | 226 | * Version 1.0.3 (2017-03-21) 227 | * Fix testsuite on SunOS 228 | * Fix RPM compilation in 32 bits 229 | 230 | * Version 1.0.2 (2017-03-21) 231 | * Implement IPv6 filtering 232 | * Fixes for macOS and Solaris 233 | * Add a testsuite 234 | * Bugs fixed: 235 | * Crash before initialization when SELinux is enabled 236 | * Crash when log file could not be created 237 | * Wrong connect() return value when blocking calls 238 | 239 | * Version 1.0.1 (2016-11-01) 240 | * Add some consistency checks 241 | * Use a SONAME (this broke Mac OS X compilation) 242 | * Provide RPM package 243 | * `coc` script now display LD_PRELOAD when used in dry mode 244 | 245 | * Version 1.0.0 (2016-04-12) 246 | 247 | ## Roadmap 248 | 249 | * WARN if localhost is not allowed 250 | * Make filtering algorithm configurable. For now it's always: 251 | * check against ALLOW list and ALLOW connection if it's in it; 252 | * else check against BLOCK list and BLOCK connection if it's in it; 253 | * else ALLOW by default 254 | * Implement `accept` filtering 255 | -------------------------------------------------------------------------------- /Version.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgg/connect-or-cut/21b859ac707dee6a91e88ed834a16423dfd52538/Version.rc -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '1.0.4.{build}' 2 | image: Visual Studio 2015 3 | 4 | platform: 5 | - x86 6 | - x64 7 | 8 | configuration: 9 | - Debug 10 | - Release 11 | 12 | environment: 13 | SECURE_AV_SECRET: 14 | secure: mCZ3m3JZoBBJ8wDcZzY6KsPhQ9B5OrldfwQUgVR/wOo= 15 | 16 | before_build: 17 | - cmd: nuget restore 18 | 19 | build: 20 | project: connect-or-cut.sln 21 | 22 | before_test: 23 | - ps: if ($env:PLATFORM -eq "x64") { cp coc.ps1 $env:PLATFORM\$env:CONFIGURATION } 24 | - ps: if ($env:PLATFORM -eq "x64") { cp testsuite.ps1 $env:PLATFORM\$env:CONFIGURATION } 25 | 26 | test_script: 27 | - ps: if ($env:PLATFORM -eq "x64") { & $env:PLATFORM\$env:CONFIGURATION\testsuite.ps1 -Verbose } 28 | 29 | after_build: 30 | - ps: | 31 | if ($env:CONFIGURATION -eq "Release") { 32 | if ($env:PLATFORM -eq "x64") { 33 | $folder = "$env:APPVEYOR_BUILD_FOLDER\x64\Release" 34 | $bit = "64" 35 | } else { 36 | $folder = "$env:APPVEYOR_BUILD_FOLDER\Release" 37 | $bit = "32" 38 | } 39 | 7z a "connect-or-cut-$env:APPVEYOR_BUILD_VERSION-$bit.zip" README.md LICENSE coc.ps1 testsuite.ps1 "$folder\*.dll" "$folder\*.exe" 40 | Push-AppveyorArtifact "connect-or-cut-$env:APPVEYOR_BUILD_VERSION-$bit.zip" 41 | } 42 | - ps: | 43 | if (($env:CONFIGURATION -eq "Release") -and ($env:PLATFORM -eq "x64")) { 44 | $apiUrl = 'https://ci.appveyor.com/api' 45 | $token = $env:SECURE_AV_SECRET 46 | $headers = @{ 47 | "Authorization" = "Bearer $token" 48 | "Content-type" = "application/json" 49 | } 50 | $accountName = $env:APPVEYOR_ACCOUNT_NAME 51 | $projectSlug = $env:APPVEYOR_PROJECT_SLUG 52 | $downloadLocation = 'C:\projects' 53 | # get project with last build details 54 | $project = Invoke-RestMethod -Method Get -Uri "$apiUrl/projects/$accountName/$projectSlug" -Headers $headers 55 | # x86 Release is job 2 56 | $jobId = $project.build.jobs[2].jobId 57 | $artifactFileName = "connect-or-cut-$env:APPVEYOR_BUILD_VERSION-32.zip" 58 | # artifact will be downloaded as 59 | $localArtifactPath = "$downloadLocation\$artifactFileName" 60 | # download artifact 61 | Invoke-RestMethod -Method Get -Uri "$apiUrl/buildjobs/$jobId/artifacts/$artifactFileName" ` 62 | -OutFile $localArtifactPath -Headers @{ "Authorization" = "Bearer $token" } 63 | 7z e $localArtifactPath coc32.exe connect-or-cut32.dll 64 | cp "connect-or-cut-$env:APPVEYOR_BUILD_VERSION-64.zip" "connect-or-cut-$env:APPVEYOR_BUILD_VERSION.zip" 65 | 7z u "connect-or-cut-$env:APPVEYOR_BUILD_VERSION.zip" coc32.exe connect-or-cut32.dll 66 | Push-AppveyorArtifact "connect-or-cut-$env:APPVEYOR_BUILD_VERSION.zip" 67 | } 68 | 69 | deploy: 70 | description: 'connect-or-cut v$(appveyor_build_version)' 71 | provider: GitHub 72 | auth_token: 73 | secure: 2J7opUzk3HlidHQBGIm76vtRaCwvuobgN/KxvDv6Ak/ksx14KKH5FKInwDUcTPRQ 74 | artifact: connect-or-cut-$(appveyor_build_version).zip 75 | draft: true 76 | on: 77 | branch: master 78 | configuration: Release 79 | platform: x64 80 | APPVEYOR_REPO_TAG: true -------------------------------------------------------------------------------- /coc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # connect-or-cut -- block unwanted connect() calls. 3 | # 4 | # Copyright Ⓒ 2015, 2016 Thomas Girard 5 | # 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions 10 | # are met: 11 | # 12 | # * Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # 15 | # * Redistributions in binary form must reproduce the above copyright 16 | # notice, this list of conditions and the following disclaimer in the 17 | # documentation and/or other materials provided with the distribution. 18 | # 19 | # * Neither the name of the author nor the names of its contributors 20 | # may be used to endorse or promote products derived from this software 21 | # without specific prior written permission. 22 | # 23 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 27 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 | # SUCH DAMAGE. 34 | OGLOB=`set +o | grep glob` 35 | set -f 36 | 37 | _abs_dirname() { 38 | (cd `dirname $1` && pwd) 39 | } 40 | 41 | EXT=".so.1" 42 | P="`_abs_dirname $0`/" 43 | F=`echo ${P} | sed -e 's#/bin/$#/lib/#'` 44 | 45 | case "${P}" in 46 | /usr/bin/) 47 | P="" 48 | ;; 49 | */bin/) 50 | P="$F" 51 | ;; 52 | esac 53 | 54 | LIB="${P}libconnect-or-cut${EXT}" 55 | FLIB="${F}libconnect-or-cut${EXT}" 56 | 57 | _warn() { 58 | cat >&2 </dev/null | grep 'connect-or-cut v' | uniq` 70 | if test "a$V" = "a"; then 71 | unset V 72 | _die "couldn't find connect-or-cut version in $FLIB!" 73 | else 74 | echo $V 75 | unset V 76 | fi 77 | } 78 | 79 | _help() { 80 | cat <&2 340 | exit 1 341 | fi 342 | fi 343 | 344 | unset EXT 345 | unset P 346 | unset F 347 | 348 | export COC_LOG_TARGET 349 | export COC_LOG_LEVEL 350 | export COC_LOG_PATH 351 | 352 | _append_preload 353 | unset preload 354 | unset LIB 355 | unset FLIB 356 | 357 | eval $OGLOB 358 | unset OGLOB 359 | exec "$@" 360 | -------------------------------------------------------------------------------- /coc.c: -------------------------------------------------------------------------------- 1 | /* coc -- Helper process to inject connect-or-cut.dll. 2 | * 3 | * Copyright Ⓒ 2017-2019 Thomas Girard 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * * Neither the name of the author nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | */ 34 | 35 | #define _CRT_SECURE_NO_WARNINGS 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "inject.h" 42 | 43 | extern void ErrorExit(LPTSTR lpszFunction); 44 | 45 | static TCHAR* MoveBeforeFirstArgument(TCHAR *lpCommandLine) 46 | { 47 | TCHAR* p = lpCommandLine; 48 | BOOL bInQuotes = FALSE; 49 | size_t uiBackslashes = 0; 50 | 51 | while (*p != _T('\0')) 52 | { 53 | if (*p == _T('\\')) 54 | { 55 | uiBackslashes++; 56 | } 57 | else if (*p == _T('"')) 58 | { 59 | if (uiBackslashes % 2 == 0) 60 | { 61 | bInQuotes = !bInQuotes; 62 | } 63 | 64 | uiBackslashes = 0; 65 | } 66 | else if (*p == _T(' ')) 67 | { 68 | uiBackslashes = 0; 69 | 70 | if (!bInQuotes) 71 | break; 72 | } 73 | else 74 | { 75 | uiBackslashes = 0; 76 | } 77 | 78 | p++; 79 | } 80 | 81 | return p; 82 | } 83 | 84 | int _tmain(int argc, _TCHAR* argv[]) 85 | { 86 | if (argc == 1) 87 | { 88 | _tprintf(_T("Usage: %s COMMAND [ARGS]\n"), argv[0]); 89 | return 1; 90 | } 91 | 92 | TCHAR *lpThisCommandLine = GetCommandLine(); 93 | 94 | size_t lpThisLen = _tcslen(lpThisCommandLine) + 1; 95 | HANDLE hHeap = GetProcessHeap(); 96 | if (hHeap == NULL) 97 | { 98 | ErrorExit(_T("GetProcessHeap")); 99 | } 100 | 101 | TCHAR *lpThatCommandLine = HeapAlloc(hHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, lpThisLen * sizeof(TCHAR)); 102 | if (lpThatCommandLine == NULL) 103 | { 104 | ErrorExit(_T("HeapAlloc")); 105 | } 106 | 107 | _tcsncpy(lpThatCommandLine, lpThisCommandLine, lpThisLen); 108 | 109 | TCHAR *lpOtherCommandLine = MoveBeforeFirstArgument(lpThatCommandLine); 110 | 111 | *lpOtherCommandLine = _T('\0'); 112 | lpOtherCommandLine++; 113 | 114 | // Weird 115 | if (*lpOtherCommandLine == _T(' ')) 116 | lpOtherCommandLine++; 117 | 118 | STARTUPINFO si; 119 | PROCESS_INFORMATION pi; 120 | ZeroMemory(&si, sizeof(si)); 121 | ZeroMemory(&pi, sizeof(pi)); 122 | 123 | stCreateProcess cpArgs = { 124 | .fn = CreateProcess, 125 | .lpApplicationName = NULL, 126 | .lpCommandLine = lpOtherCommandLine, 127 | .lpProcessAttributes = NULL, 128 | .lpThreadAttributes = NULL, 129 | .bInheritHandles = TRUE, 130 | .dwCreationFlags = 0, 131 | .lpEnvironment = NULL, 132 | .lpCurrentDirectory = NULL, 133 | .lpStartupInfo = &si, 134 | .lpProcessInformation = &pi 135 | }; 136 | 137 | BOOL bWaitForCompletion; 138 | 139 | if (!CreateProcessThenInject(&cpArgs, &bWaitForCompletion)) 140 | { 141 | ErrorExit(_T("CreateProcess")); 142 | } 143 | 144 | HeapFree(hHeap, HEAP_NO_SERIALIZE, lpThatCommandLine); 145 | lpThatCommandLine = NULL; 146 | 147 | if (!bWaitForCompletion) 148 | { 149 | CloseHandle(pi.hProcess); 150 | CloseHandle(pi.hThread); 151 | 152 | return 0; 153 | } 154 | 155 | WaitForSingleObject(pi.hProcess, INFINITE); 156 | 157 | DWORD dwExitCode; 158 | GetExitCodeProcess(pi.hProcess, &dwExitCode); 159 | 160 | CloseHandle(pi.hProcess); 161 | CloseHandle(pi.hThread); 162 | 163 | return dwExitCode; 164 | } 165 | -------------------------------------------------------------------------------- /coc.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | coc configures connect-or-cut for the specified program. 4 | 5 | .DESCRIPTION 6 | coc helper script sets environment variables then invokes coc.exe on the 7 | specified program. If no program is specified it outputs environment 8 | variables that need to be set to replicate the requested rules. 9 | 10 | Whenever a TCP connection is requested, the address where to connect 11 | is checked against the list of allowed hosts. If it's in it, the connection 12 | is allowed. If not, the list of blocked hosts is checked. If it's in it, 13 | the connection is rejected. If not, the connection is allowed. 14 | 15 | .PARAMETER AllowDNS 16 | Allow connections to DNS nameservers. 17 | 18 | .PARAMETER Allow 19 | List of allowed hosts. Can be specified as host, host:port, host:port/bits or 20 | as a glob like "*.google.com". Specifying "*" allows every connection. 21 | 22 | .PARAMETER Block 23 | List of blocked hosts. Can be specified as host, host:port, host:port/bits or 24 | as a glob like "*.google.com". Specifying "*" can block every connection. 25 | 26 | .PARAMETER Help 27 | Shows this help message. 28 | 29 | .PARAMETER LogTarget 30 | The location where to write logs. Valid values are: "stderr", which is the 31 | default; "syslog"; and "file". Multiple values can be specified. 32 | 33 | .PARAMETER LogPath 34 | The path where to store logs when logging to files. 35 | 36 | .PARAMETER LogLevel 37 | What to log. Can be one of: "silent" to discard logging, "error" to log 38 | only errors, "block" to also log blocked connections,"allow" to also 39 | log allowed connections, and "debug" to add debugging information. Default 40 | value is "block". 41 | 42 | .PARAMETER Version 43 | Shows the version of connect-or-cut library. 44 | 45 | .PARAMETER ProgramAndParams 46 | The program (and its arguments) to launch with the specified rules. 47 | If not specified, the cmd instructions to replicate these rules are displayed. 48 | 49 | .NOTES 50 | Version: 1.0.4 51 | Author: Thomas Girard 52 | 53 | .LINK 54 | https://github.com/tgg/connect-or-cut 55 | 56 | #> 57 | [CmdletBinding(PositionalBinding=$false)] 58 | param ( 59 | [switch] 60 | $AllowDNS = $false, 61 | 62 | [string[]] 63 | $Allow = @(), 64 | 65 | [string[]] 66 | $Block = @(), 67 | 68 | [switch] 69 | $Help, 70 | 71 | [ValidateSet("stderr","syslog","file")] 72 | [string[]] 73 | $LogTarget = @("stderr"), 74 | 75 | [string] 76 | $LogPath, 77 | 78 | [ValidateSet("silent","error","block","allow","debug")] 79 | [string] 80 | $LogLevel = "block", 81 | 82 | [switch] 83 | $Version, 84 | 85 | [Parameter(ValueFromRemainingArguments=$true)] 86 | [string[]]$ProgramAndParams 87 | ) 88 | 89 | function Split-String([string]$s) { 90 | if ($s -eq $null) { @() } 91 | else { ,$s.Split(";") } # , prevents automatic conversion string[1] => string 92 | } 93 | 94 | function Join-String([string[]]$a) { 95 | if ($a.Count -eq 0) { $null } 96 | else { [string]::Join(";", $a) } 97 | } 98 | 99 | function Append-ProcessEnvironment([string]$name, [string[]]$values) { 100 | $current = Split-String([Environment]::GetEnvironmentVariable($name, "Process")) 101 | 102 | # De-duplication and removal of empty vars happens here 103 | $set = New-Object System.Collections.Generic.HashSet[string] 104 | foreach ($v in $current) { 105 | if ($v) { [void]$set.Add($v) } 106 | } 107 | foreach ($v in $values) { 108 | if ($v) { [void]$set.Add($v) } 109 | } 110 | 111 | $current = New-Object 'string[]' $set.Count 112 | $set.CopyTo($current) 113 | 114 | [Environment]::SetEnvironmentVariable($name, (Join-String($current)), "Process") 115 | } 116 | 117 | function Get-DnsServers() { 118 | try { 119 | # This is not the right thing to do because it limits to IPv4. 120 | # Patches welcome! 121 | (Get-DnsClientServerAddress -AddressFamily IPv4 | where { $_.ServerAddresses.count -gt 0 }).ServerAddresses 122 | 123 | } catch [System.Management.Automation.CommandNotFoundException] { 124 | # Get-DnsClientServerAddress is available only in Windows >= 8 125 | # Adapted from Get-DNSServers.ps1 written by Sitaram Pamarthi (http://techibee.com) 126 | Write-Debug "Using Get-WmiObject to find DNS servers" 127 | 128 | try { 129 | $Networks = Get-WmiObject -Class Win32_NetworkAdapterConfiguration ` 130 | -Filter IPEnabled=TRUE ` 131 | -ComputerName localhost ` 132 | -ErrorAction Stop 133 | } catch { 134 | Write-Error "Unable to find DNS server for localhost; aborting!" 135 | exit 1 136 | } 137 | 138 | $DNSServers = @() 139 | 140 | foreach ($Network in $Networks) { 141 | if ($Network.DNSServerSearchOrder) { 142 | $DNSServers += $Network.DNSServerSearchOrder 143 | } 144 | } 145 | 146 | $DNSServers 147 | } 148 | } 149 | 150 | $scriptPath = Split-Path -parent $MyInvocation.MyCommand.Definition 151 | $LogTargets = @{"stderr" = 1; "syslog" = 2; "file" = 4} 152 | $LogLevels = @{"silent" = 0; "error" = 1; "block" = 2; "allow" = 3; "debug" = 4} 153 | 154 | if ($Help -eq $true) { 155 | help $MyInvocation.MyCommand.Definition -Detailed 156 | exit 0 157 | 158 | } elseif ($Version -eq $true) { 159 | $lib = Join-Path -Path $scriptPath -ChildPath "connect-or-cut.dll" 160 | 161 | try { 162 | Write-Output "connect-or-cut $((Get-Item $lib -ErrorAction Stop).VersionInfo.FileVersion)" 163 | exit 0 164 | 165 | } catch [System.Management.Automation.ItemNotFoundException] { 166 | Write-Error "Cannot find connect-or-cut.dll in $scriptPath!" 167 | exit 1 168 | } 169 | } 170 | 171 | if ($AllowDNS) { 172 | Get-DnsServers | ForEach-Object { 173 | Append-ProcessEnvironment -name "COC_ALLOW" -values "${_}:53" 174 | } 175 | } 176 | 177 | Append-ProcessEnvironment -name "COC_ALLOW" -values $Allow 178 | Append-ProcessEnvironment -name "COC_BLOCK" -values $Block 179 | 180 | Write-Verbose "COC_ALLOW is: $env:COC_ALLOW" 181 | Write-Verbose "COC_BLOCK is: $env:COC_BLOCK" 182 | 183 | $env:COC_LOG_LEVEL=$LogLevels[$LogLevel] 184 | Write-Verbose "COC_LOG_LEVEL is: $env:COC_LOG_LEVEL" 185 | 186 | if ($LogPath) { 187 | $env:COC_LOG_PATH="$LogPath" 188 | Write-Verbose "COC_LOG_PATH is: $env:COC_LOG_PATH" 189 | } 190 | 191 | if ($LogTarget) { 192 | $acc = 0 193 | 194 | foreach ($target in $LogTarget) { 195 | $acc = $acc -bor $LogTargets[$target] 196 | } 197 | 198 | $env:COC_LOG_TARGET="$acc" 199 | Write-Verbose "COC_LOG_TARGET is: $env:COC_LOG_TARGET" 200 | } 201 | 202 | if ($ProgramAndParams) { 203 | $exe = Join-Path -Path $scriptPath -ChildPath "coc.exe" 204 | Write-Verbose "Starting: $exe $ProgramAndParams" 205 | Start-Process -FilePath $exe -Wait -NoNewWindow -ArgumentList $ProgramAndParams 206 | 207 | } else { 208 | # If no program was specified but we have COC_ variables set we print them. 209 | if ($env:COC_ALLOW -or $env:COC_BLOCK) { 210 | foreach ($e in @("COC_ALLOW","COC_BLOCK","COC_LOG_TARGET","COC_LOG_LEVEL","COC_LOG_PATH")) { 211 | $v = [Environment]::GetEnvironmentVariable($e, "Process") 212 | if ($v) { Write-Host "set $e=$v" } 213 | } 214 | exit 0 215 | 216 | } else { 217 | Write-Error "Missing command!" 218 | help $MyInvocation.MyCommand.Definition 219 | exit 1 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /connect-or-cut.c: -------------------------------------------------------------------------------- 1 | /* connect-or-cut -- block unwanted connect() calls. 2 | * 3 | * Copyright Ⓒ 2015-2019 Thomas Girard 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * * Neither the name of the author nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | */ 34 | 35 | #define _GNU_SOURCE 36 | 37 | #ifndef _WIN32 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #define SOCKET int 49 | #define HOOK(fn) fn 50 | #define WSAAPI /* nothing */ 51 | #else 52 | #define _CRT_SECURE_NO_WARNINGS 53 | #include 54 | #define WIN32_LEAN_AND_MEAN 55 | #ifdef COC_EXPORTS 56 | #define COC_API __declspec(dllexport) 57 | #else 58 | #define COC_API __declspec(dllimport) 59 | #endif 60 | #define HOOK(fn) COC_API __stdcall hook_##fn 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include "sys_queue.h" 67 | #include "inject.h" 68 | #include "MinHook.h" 69 | #pragma comment(lib, "Ws2_32.lib") 70 | #pragma comment(lib, "shlwapi.lib") 71 | #pragma comment(lib, "IPHLPAPI.lib") 72 | #ifdef _M_X64 73 | #ifdef _DEBUG 74 | #pragma comment(lib, "libMinHook-x64-v140-mtd.lib") 75 | #else 76 | #pragma comment(lib, "libMinHook-x64-v140-mt.lib") 77 | #endif 78 | #elif defined _M_IX86 79 | #ifdef _DEBUG 80 | #pragma comment(lib, "libMinHook-x86-v140-mtd.lib") 81 | #else 82 | #pragma comment(lib, "libMinHook-x86-v140-mt.lib") 83 | #endif 84 | #endif 85 | #endif 86 | 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | 100 | #ifdef _WIN32 101 | typedef uint16_t in_port_t; 102 | #define MISSING_STRNDUP 103 | #define MAXNS 30 104 | #define LOG_ERR 3 105 | #define LOG_WARNING 4 106 | #define LOG_INFO 6 107 | #define LOG_DEBUG 7 108 | #define vsyslog(l,f,a) /* Not supported */ 109 | #define pthread_testcancel() /* Not supported */ 110 | #define localtime_r(ti,tm) localtime_s(tm, ti) 111 | #define fnmatch(p,s,f) (!PathMatchSpecA(s,p)) 112 | #endif 113 | 114 | #if defined(__APPLE__) && defined(__MACH__) 115 | #include 116 | #if __MAC_OS_X_VERSION_MAX_ALLOWED < 1070 117 | #define MISSING_STRNDUP 118 | #endif 119 | #endif 120 | 121 | #ifdef __SunOS_5_11 122 | #undef MISSING_STRNDUP 123 | #endif 124 | 125 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 126 | #include 127 | #if defined(BSD) 128 | #define HAVE_GETPROGNAME 129 | #endif 130 | #endif 131 | 132 | #if (defined(sun) || defined(__sun)) && (defined(__SVR4) || defined(__svr4__)) 133 | #define HAVE_GETEXECNAME 134 | #include 135 | #endif 136 | 137 | typedef enum coc_address_type 138 | { 139 | COC_IPV4_ADDR = 1 << 0, /* 1 */ 140 | COC_IPV6_ADDR = 1 << 1, /* 2 */ 141 | COC_GLOB_ADDR = 1 << 2 /* 4 */ 142 | } coc_address_type_t; 143 | 144 | #define COC_HOST_ADDR (1 << 3) /* 8 */ 145 | 146 | typedef enum coc_log_level 147 | { 148 | COC_SILENT_LOG_LEVEL = -1, /* mapped from 0. */ 149 | COC_ERROR_LOG_LEVEL = LOG_ERR, /* mapped from 1 (to 3). */ 150 | COC_BLOCK_LOG_LEVEL = LOG_WARNING, /* mapped from 2 (to 4). */ 151 | COC_ALLOW_LOG_LEVEL = LOG_INFO, /* mapped from 3 (to 6). */ 152 | COC_DEBUG_LOG_LEVEL = LOG_DEBUG /* mapped from 4 (to 7). */ 153 | } coc_log_level_t; 154 | 155 | typedef enum coc_log_target 156 | { 157 | COC_STDERR_LOG = 1 << 0, /* 1 */ 158 | COC_SYSLOG_LOG = 1 << 1, /* 2 */ 159 | COC_FILE_LOG = 1 << 2 /* 4 */ 160 | } coc_log_target_t; 161 | 162 | typedef enum coc_rule_type 163 | { 164 | COC_ALLOW = 0, 165 | COC_BLOCK = 1 166 | } coc_rule_type_t; 167 | 168 | static const char *rule_type_name[] = { 169 | "ALLOW", 170 | "BLOCK" 171 | }; 172 | 173 | static const char *address_type_name[] = { 174 | [COC_IPV4_ADDR] = "IPv4", 175 | [COC_IPV6_ADDR] = "IPv6", 176 | [COC_GLOB_ADDR] = "glob", 177 | [COC_HOST_ADDR] = "host" 178 | }; 179 | 180 | typedef struct coc_entry { 181 | union { 182 | struct in_addr ipv4; 183 | struct in6_addr ipv6; 184 | char *glob; 185 | } addr; 186 | union { 187 | struct in_addr ipv4; 188 | struct in6_addr ipv6; 189 | } netmask; 190 | in_port_t port; 191 | coc_address_type_t addr_type; 192 | coc_rule_type_t rule_type; 193 | SLIST_ENTRY(coc_entry) entries; 194 | bool is_subnet; 195 | } coc_entry_t; 196 | 197 | static inline 198 | coc_entry_t * 199 | coc_entry_alloc (void) 200 | { 201 | return (coc_entry_t *) malloc (sizeof(coc_entry_t)); 202 | } 203 | 204 | SLIST_HEAD(coc_list, coc_entry) coc_list_head = { NULL }; 205 | 206 | #define COC_ALLOW_ENV_VAR_NAME "COC_ALLOW" 207 | #define COC_BLOCK_ENV_VAR_NAME "COC_BLOCK" 208 | #define COC_LOG_LEVEL_ENV_VAR_NAME "COC_LOG_LEVEL" 209 | #define COC_LOG_PATH_ENV_VAR_NAME "COC_LOG_PATH" 210 | #define COC_LOG_TARGET_ENV_VAR_NAME "COC_LOG_TARGET" 211 | #if defined(__APPLE__) && defined(__MACH__) 212 | #define COC_PRELOAD_ENV_VAR_NAME "DYLD_INSERT_LIBRARIES" 213 | #else 214 | #define COC_PRELOAD_ENV_VAR_NAME "LD_PRELOAD" 215 | #endif 216 | 217 | #if defined(HAVE_GETEXECNAME) 218 | static inline const char * 219 | getprogname () 220 | { 221 | const char *fullpath = getexecname (); 222 | return basename ((char *) fullpath); 223 | } 224 | 225 | #elif !defined(HAVE_GETPROGNAME) 226 | #ifdef _WIN32 227 | char __progname[_MAX_FNAME]; 228 | #else 229 | extern char *__progname; 230 | #endif 231 | 232 | static inline const char * 233 | getprogname () 234 | { 235 | #ifdef _WIN32 236 | char szThisModule[MAX_PATH] = { 0 }; 237 | GetModuleFileNameA(NULL, szThisModule, MAX_PATH); 238 | _splitpath(szThisModule, NULL, NULL, __progname, NULL); 239 | #endif 240 | return __progname; 241 | } 242 | #endif 243 | 244 | static const char version[] = "connect-or-cut v1.0.4"; 245 | static volatile bool initialized = false; 246 | static bool needs_dns_lookup = false; 247 | static coc_log_level_t log_level = COC_BLOCK_LOG_LEVEL; 248 | static coc_log_target_t log_target = COC_STDERR_LOG; 249 | static char *log_file_name = NULL; 250 | 251 | #ifdef __GNUC__ 252 | static void 253 | coc_log (coc_log_level_t level, const char *format, ...) 254 | __attribute__ ((__format__ (__printf__, 2, 3))); 255 | #endif 256 | 257 | static void 258 | coc_log (coc_log_level_t level, const char *format, ...) 259 | { 260 | if (log_level >= level) 261 | { 262 | struct tm now_tm; 263 | time_t now; 264 | char buffer[sizeof ("YYYY-MM-DDTHH:MM:SS ")]; 265 | time (&now); 266 | localtime_r (&now, &now_tm); 267 | strftime (buffer, sizeof (buffer), "%Y-%m-%dT%H:%M:%S ", &now_tm); 268 | 269 | if ((log_target & COC_FILE_LOG) == COC_FILE_LOG) 270 | { 271 | va_list ap; 272 | va_start (ap, format); 273 | FILE *log_stream = fopen (log_file_name, "a"); 274 | 275 | if (!log_stream) 276 | { 277 | fprintf (stderr, "Unable to create log file %s; discarding file logs\n", log_file_name); 278 | log_target &= ~COC_FILE_LOG; 279 | } 280 | else 281 | { 282 | fprintf (log_stream, "%s", buffer); 283 | vfprintf (log_stream, format, ap); 284 | fclose (log_stream); 285 | } 286 | 287 | va_end (ap); 288 | } 289 | 290 | if ((log_target & COC_STDERR_LOG) == COC_STDERR_LOG) 291 | { 292 | va_list ap; 293 | va_start (ap, format); 294 | fprintf (stderr, "%s", buffer); 295 | vfprintf (stderr, format, ap); 296 | fflush (stderr); 297 | va_end (ap); 298 | } 299 | 300 | if ((log_target & COC_SYSLOG_LOG) == COC_SYSLOG_LOG) 301 | { 302 | va_list ap; 303 | va_start (ap, format); 304 | /* 305 | * syslog will be automatically opened at first invocation. 306 | * 307 | * We do not change anything to this because: 308 | * - we know that syslog will not be used during initialization 309 | * of our library 310 | * - the program that will be invoked after our library is 311 | * preloaded can use syslog with non default settings. So 312 | * we reuse these in order not to interfere with the 313 | * program. 314 | */ 315 | vsyslog (level, format, ap); 316 | va_end (ap); 317 | } 318 | } 319 | } 320 | 321 | #define DIE(...) do { \ 322 | coc_log (COC_ERROR_LOG_LEVEL,"ERROR " __VA_ARGS__); \ 323 | exit (EXIT_FAILURE); \ 324 | } while (0) 325 | 326 | #ifdef MISSING_STRNDUP 327 | static inline char * 328 | strndup (const char *s, size_t n) 329 | { 330 | size_t len = strlen (s); 331 | 332 | if (len > n) 333 | { 334 | len = n; 335 | } 336 | 337 | char *ns = (char *) malloc (len + 1); 338 | 339 | if (ns != NULL) 340 | { 341 | ns[len] = '\0'; 342 | memcpy (ns, s, len); 343 | } 344 | 345 | return ns; 346 | } 347 | #endif 348 | 349 | 350 | static inline void 351 | mask_ipv4 (struct in_addr *addr, const struct in_addr *netmask) 352 | { 353 | addr->s_addr &= netmask->s_addr; 354 | } 355 | 356 | static inline void 357 | mask_ipv6 (struct in6_addr *addr, const struct in6_addr *netmask) 358 | { 359 | uint32_t *_addr = (uint32_t *) addr; 360 | const uint32_t *_netmask = (uint32_t *) netmask; 361 | 362 | int i; 363 | for (i = 0; i < 4; i++) 364 | { 365 | _addr[i] &= _netmask[i]; 366 | } 367 | } 368 | 369 | static void 370 | make_ipv4_netmask (struct in_addr *netmask, uint8_t cidr_prefix_length) 371 | { 372 | memset (netmask, 0, sizeof(struct in_addr)); 373 | 374 | if (cidr_prefix_length > 0) 375 | { 376 | netmask->s_addr = htonl (0xFFFFFFFFu << (32u - cidr_prefix_length)); 377 | } 378 | } 379 | 380 | static void 381 | make_ipv6_netmask (struct in6_addr *netmask, uint8_t cidr_prefix_length) 382 | { 383 | memset (netmask, 0, sizeof(struct in6_addr)); 384 | 385 | uint8_t *p_netmask = netmask->s6_addr; 386 | while (8 < cidr_prefix_length) 387 | { 388 | *p_netmask = 0xFFu; 389 | p_netmask++; 390 | cidr_prefix_length -= 8; 391 | } 392 | if (cidr_prefix_length > 0) 393 | { 394 | *p_netmask = (0xFFu << (8u - cidr_prefix_length)); 395 | } 396 | } 397 | 398 | static int 399 | coc_rule_add (const char * const str, const size_t len, const size_t rule_type) 400 | { 401 | int type = COC_IPV4_ADDR | COC_IPV6_ADDR | COC_GLOB_ADDR | COC_HOST_ADDR; 402 | const char *p = str; 403 | const char *service = NULL; 404 | const char *cidr_prefix_length_start = NULL; 405 | enum 406 | { 407 | IPV6_SB_NONE, 408 | IPV6_SB_OPEN, 409 | IPV6_SB_CLOSE 410 | } sb = IPV6_SB_NONE; 411 | size_t colon_count = 0; 412 | size_t ipv4_segment = 1; 413 | uint16_t segment = 0; /* IPv4 or IPv6 */ 414 | 415 | while (p < str + len) 416 | { 417 | unsigned char c = *p; 418 | 419 | /* `*' allowed only for glob. Let's go on to check for errors. */ 420 | if (c == '*') 421 | { 422 | /* `*' not allowed if only remaining choices are IPv4 or IPv6. */ 423 | if (!(type & ~(COC_IPV4_ADDR | COC_IPV6_ADDR))) 424 | { 425 | DIE ("`*' not allowed for IPv4 or IPv6, aborting\n"); 426 | } 427 | else 428 | { 429 | type = COC_GLOB_ADDR; 430 | } 431 | } 432 | 433 | /* `[' allowed only at beginning of address for IPv6. */ 434 | else if (c == '[') 435 | { 436 | if (p == str) 437 | { 438 | type = COC_IPV6_ADDR; 439 | sb = IPV6_SB_OPEN; 440 | } 441 | else 442 | { 443 | DIE ("`[' allowed only once for IPv6, aborting\n"); 444 | } 445 | } 446 | 447 | else if (c == ']') 448 | { 449 | if (type == COC_IPV6_ADDR && sb == IPV6_SB_OPEN) 450 | { 451 | sb = IPV6_SB_CLOSE; 452 | } 453 | else 454 | { 455 | DIE ("`]' unexpected, aborting\n"); 456 | } 457 | } 458 | 459 | else if (c == ':') 460 | { 461 | /* IPv6 uses `:' as a segment separator. So we count them. 462 | * Validation for IPv6 is done later with inet_pton. */ 463 | colon_count++; 464 | /* Assume `:' precedes port. We will check if not empty later. */ 465 | service = p + 1; 466 | 467 | if (colon_count > 1) 468 | { 469 | if ((type & COC_IPV6_ADDR) == COC_IPV6_ADDR) 470 | { 471 | type = COC_IPV6_ADDR; 472 | } 473 | 474 | if (type != COC_IPV6_ADDR || 475 | ((sb == IPV6_SB_OPEN && colon_count > 7) || 476 | colon_count > 8)) 477 | { 478 | DIE ("Extra `:' unexpected, aborting\n"); 479 | } 480 | 481 | if (colon_count == 8 || sb == IPV6_SB_CLOSE) 482 | { 483 | break; 484 | } 485 | else 486 | { 487 | service = NULL; 488 | } 489 | } 490 | 491 | else 492 | { 493 | /* If so far IPv4 was still a valid choice, then it is it now. */ 494 | if (p != str && ((type & COC_IPV4_ADDR) == COC_IPV4_ADDR)) 495 | { 496 | type = COC_IPV4_ADDR; 497 | 498 | if (ipv4_segment == 4) 499 | { 500 | break; 501 | } 502 | } 503 | } 504 | } 505 | 506 | /* We can see dots for IPv4, glob, or hostnames. We can rule 507 | * out IPv4 when we have a segment that does not fit what is 508 | * expected (e.g. higher than 255). 509 | */ 510 | else if (c == '.') 511 | { 512 | if (type == COC_IPV6_ADDR) 513 | { 514 | DIE ("`.' not allowed for IPv6, aborting\n"); 515 | } 516 | 517 | type &= ~COC_IPV6_ADDR; 518 | 519 | segment = 0; 520 | ipv4_segment++; 521 | 522 | if (ipv4_segment > 4) 523 | { 524 | if (type == COC_IPV4_ADDR) /* not possible for now. */ 525 | { 526 | DIE ("Extra `.' unexpected, aborting\n"); 527 | } 528 | else 529 | { 530 | type &= ~COC_IPV4_ADDR; 531 | } 532 | } 533 | } 534 | 535 | else if (isdigit (c)) 536 | { 537 | if ((type & COC_IPV4_ADDR) == COC_IPV4_ADDR && cidr_prefix_length_start == NULL) 538 | { 539 | /* INT30-C */ 540 | if ((segment > UINT16_MAX / 10) || 541 | (UINT16_MAX - (c - '0') < segment * 10)) 542 | { 543 | DIE ("Invalid IPv4 segment, aborting\n"); 544 | } 545 | else 546 | { 547 | segment = segment * 10 + (c - '0'); 548 | } 549 | 550 | if (segment > 255) 551 | { 552 | if (type == COC_IPV4_ADDR) /* not possible */ 553 | { 554 | DIE ("Invalid IPv4 address, aborting\n"); 555 | } 556 | else 557 | { 558 | coc_log (COC_DEBUG_LOG_LEVEL, 559 | "DEBUG %hd... is not an IPv4 segment\n", 560 | segment); 561 | type &= ~COC_IPV4_ADDR; 562 | } 563 | } 564 | } 565 | } 566 | 567 | else if (isxdigit (c)) /* and not a digit */ 568 | { 569 | if (type == COC_IPV4_ADDR) /* not possible */ 570 | { 571 | DIE ("Invalid IPv4 address, aborting\n"); 572 | } 573 | else 574 | { 575 | type &= ~COC_IPV4_ADDR; 576 | } 577 | } 578 | 579 | else if (isalnum (c)) /* not a digit nor an xdigit */ 580 | { 581 | if (!(type & ~(COC_IPV4_ADDR | COC_IPV6_ADDR))) 582 | { 583 | DIE ("`%c' unexpected, aborting\n", c); 584 | } 585 | else 586 | { 587 | type &= ~(COC_IPV4_ADDR | COC_IPV6_ADDR); 588 | } 589 | } 590 | 591 | else if (c == '-' || c == '_') 592 | { 593 | if (!(type & ~(COC_IPV4_ADDR | COC_IPV6_ADDR)) || p == str) 594 | { 595 | DIE ("`%c' unexpected, aborting\n", c); 596 | } 597 | else 598 | { 599 | type &= ~(COC_IPV4_ADDR | COC_IPV6_ADDR); 600 | } 601 | } 602 | 603 | else if (c == '/') 604 | { 605 | if (!(type & (COC_IPV4_ADDR | COC_IPV6_ADDR)) || cidr_prefix_length_start != NULL) 606 | { 607 | DIE ("`%c' unexpected, aborting\n", c); 608 | } 609 | else 610 | { 611 | cidr_prefix_length_start = p + 1; 612 | type &= (COC_IPV4_ADDR | COC_IPV6_ADDR); 613 | } 614 | } 615 | 616 | else 617 | { 618 | /* We may revisit this later for UTF-8 */ 619 | DIE ("`%c' unexpected here, aborting\n", c); 620 | } 621 | 622 | p++; 623 | } 624 | 625 | /* If so far IPv4 was still a valid choice, then it is it now. */ 626 | if ((type & COC_IPV4_ADDR) == COC_IPV4_ADDR) 627 | { 628 | type = COC_IPV4_ADDR; 629 | } 630 | 631 | /* If so far host was still a valid choice, then it is it now. */ 632 | else if ((type & COC_HOST_ADDR) == COC_HOST_ADDR) 633 | { 634 | type = COC_HOST_ADDR; 635 | } 636 | 637 | if (type == COC_IPV6_ADDR && sb == IPV6_SB_OPEN) 638 | { 639 | DIE ("`]' missing, aborting\n"); 640 | } 641 | 642 | assert (type == COC_IPV4_ADDR || 643 | type == COC_IPV6_ADDR || 644 | type == COC_HOST_ADDR || type == COC_GLOB_ADDR); 645 | 646 | in_port_t port = 0; 647 | /* If we have a port here, check if everything after that port is valid. */ 648 | if (service != NULL) 649 | { 650 | const char *t = service; 651 | bool getservbyname_needed = false; 652 | 653 | if (t == str + len) 654 | { 655 | DIE ("No port specified after `:', aborting\n"); 656 | } 657 | 658 | while (t < str + len) 659 | { 660 | unsigned char u = *t; 661 | 662 | if (isdigit (u)) 663 | { 664 | /* INT30-C */ 665 | if ((port > UINT16_MAX / 10) || 666 | (UINT16_MAX - (u - '0') < port * 10)) 667 | { 668 | DIE ("Invalid port number, aborting\n"); 669 | } 670 | else 671 | { 672 | port = port * 10 + (u - '0'); 673 | } 674 | } 675 | 676 | else if (isalnum (u) || u == '-') 677 | { 678 | getservbyname_needed = true; 679 | } 680 | 681 | else 682 | { 683 | DIE ("`%c' unexpected for port, aborting\n", u); 684 | } 685 | 686 | t++; 687 | } 688 | 689 | if (getservbyname_needed) 690 | { 691 | #ifdef _WIN32 692 | /* We need to initialize WinSock. It's safe to do so. */ 693 | WSADATA wsaData; 694 | int iWSErr = WSAStartup(MAKEWORD(2, 2), &wsaData); 695 | 696 | if (iWSErr != 0) 697 | { 698 | DIE("Cannot initialize WinSock 2 API, aborting\n"); 699 | } 700 | #endif 701 | 702 | char *svc = strndup (service, len - (service - str)); 703 | struct servent *svt = getservbyname (svc, "tcp"); 704 | 705 | if (svt != NULL) 706 | { 707 | port = ntohs (svt->s_port); 708 | free (svc); 709 | #ifdef _WIN32 710 | WSACleanup(); 711 | #endif 712 | } 713 | else 714 | { 715 | fprintf (stderr, "service `%s' not found, aborting\n", svc); 716 | free (svc); 717 | #ifdef _WIN32 718 | WSACleanup(); 719 | #endif 720 | exit (EXIT_FAILURE); 721 | } 722 | } 723 | 724 | if (port == 0) 725 | { 726 | DIE ("`0' not allowed for port, aborting\n"); 727 | } 728 | } 729 | 730 | uint8_t cidr_prefix_length = 0; 731 | if (cidr_prefix_length_start != NULL) 732 | { 733 | const char *p_end = str + len; 734 | 735 | if (service != NULL) 736 | { 737 | p_end = service - 1; 738 | } 739 | if (sb == IPV6_SB_CLOSE) 740 | { 741 | p_end--; 742 | } 743 | 744 | if (p_end - cidr_prefix_length_start <= 0) 745 | { 746 | DIE ("No CIDR prefix length specified after `/', aborting\n"); 747 | } 748 | else if (p_end - cidr_prefix_length_start > 3) 749 | { 750 | DIE ("Too long CIDR prefix length, aborting\n"); 751 | } 752 | 753 | const char *t; 754 | for (t = cidr_prefix_length_start; t < p_end; t++) 755 | { 756 | unsigned char u = *t; 757 | 758 | if (!isdigit (u)) 759 | { 760 | DIE ("`%c' unexpected for CIDR prefix length, aborting\n", u); 761 | } 762 | 763 | cidr_prefix_length = cidr_prefix_length * 10 + (u - '0'); 764 | } 765 | 766 | if ((type == COC_IPV4_ADDR && cidr_prefix_length > 32) || 767 | (type == COC_IPV6_ADDR && cidr_prefix_length > 128)) 768 | { 769 | DIE ("`%u' not allowed for CIDR prefix length, aborting\n", cidr_prefix_length); 770 | } 771 | } 772 | 773 | const char *host_start = str; 774 | const char *host_end = host_start + len; 775 | 776 | if (service != NULL) 777 | { 778 | host_end = service - 1; 779 | } 780 | if (sb == IPV6_SB_CLOSE) 781 | { 782 | host_start++; 783 | host_end--; 784 | } 785 | if (cidr_prefix_length_start != NULL) 786 | { 787 | host_end = cidr_prefix_length_start - 1; 788 | } 789 | 790 | size_t host_len = host_end - host_start; 791 | char *host = strndup (host_start, host_len); 792 | 793 | coc_log (COC_DEBUG_LOG_LEVEL, 794 | "DEBUG Adding %s rule for %s connection to %s:%hu\n", 795 | rule_type_name[rule_type], address_type_name[type], host, port); 796 | 797 | switch (type) 798 | { 799 | case COC_IPV6_ADDR: 800 | { 801 | coc_entry_t *e = coc_entry_alloc (); 802 | e->rule_type = rule_type; 803 | e->addr_type = COC_IPV6_ADDR; 804 | e->port = htons (port); 805 | e->is_subnet = cidr_prefix_length_start != NULL; 806 | 807 | if (inet_pton (AF_INET6, host, &e->addr.ipv6) != 1) 808 | { 809 | DIE ("Invalid IPv6 address: `%s', aborting\n", host); 810 | } 811 | 812 | if (e->is_subnet) 813 | { 814 | make_ipv6_netmask (&e->netmask.ipv6, cidr_prefix_length); 815 | mask_ipv6 (&e->addr.ipv6, &e->netmask.ipv6); 816 | } 817 | 818 | SLIST_INSERT_HEAD (&coc_list_head, e, entries); 819 | free (host); 820 | break; 821 | } 822 | 823 | case COC_IPV4_ADDR: 824 | { 825 | coc_entry_t *e = coc_entry_alloc (); 826 | e->rule_type = rule_type; 827 | e->addr_type = COC_IPV4_ADDR; 828 | e->port = htons (port); 829 | e->is_subnet = cidr_prefix_length_start != NULL; 830 | 831 | if (inet_pton (AF_INET, host, &e->addr.ipv4) != 1) 832 | { 833 | DIE ("Invalid IPv4 address: `%s', aborting\n", host); 834 | } 835 | 836 | if (e->is_subnet) 837 | { 838 | make_ipv4_netmask (&e->netmask.ipv4, cidr_prefix_length); 839 | mask_ipv4 (&e->addr.ipv4, &e->netmask.ipv4); 840 | } 841 | 842 | SLIST_INSERT_HEAD (&coc_list_head, e, entries); 843 | free (host); 844 | break; 845 | } 846 | 847 | case COC_GLOB_ADDR: 848 | { 849 | coc_entry_t *e = coc_entry_alloc (); 850 | e->rule_type = rule_type; 851 | e->addr_type = COC_GLOB_ADDR; 852 | e->port = htons (port); 853 | e->is_subnet = false; 854 | /* Here we transfer ownership of `host' to the entry. */ 855 | e->addr.glob = host; 856 | SLIST_INSERT_HEAD (&coc_list_head, e, entries); 857 | 858 | /* Do not perform DNS lookups for '*' rules. We don't need to. */ 859 | if (host[0] != '*' || host[1] != '\0') 860 | { 861 | needs_dns_lookup = true; 862 | } 863 | break; 864 | } 865 | 866 | case COC_HOST_ADDR: 867 | { 868 | struct addrinfo *ailist, *aip; 869 | struct addrinfo hints; 870 | int err; 871 | memset (&hints, 0, sizeof(struct addrinfo)); 872 | #ifdef AI_ADDRCONFIG 873 | hints.ai_flags |= AI_ADDRCONFIG; 874 | #endif 875 | hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 or others */ 876 | hints.ai_socktype = SOCK_STREAM; 877 | hints.ai_protocol = IPPROTO_TCP; 878 | 879 | #ifdef _WIN32 880 | /* We need to initialize WinSock. It's safe to do so. */ 881 | WSADATA wsaData; 882 | int iWSErr = WSAStartup(MAKEWORD(2, 2), &wsaData); 883 | 884 | if (iWSErr != 0) 885 | { 886 | DIE("Cannot initialize WinSock 2 API, aborting\n"); 887 | } 888 | #endif 889 | 890 | if ((err = getaddrinfo (host, NULL, &hints, &ailist)) != 0) 891 | { 892 | DIE ("%s, aborting\n", gai_strerror (err)); 893 | } 894 | 895 | for (aip = ailist; aip != NULL; aip = aip->ai_next) 896 | { 897 | if (aip->ai_family == AF_INET) 898 | { 899 | struct sockaddr_in *sa = (struct sockaddr_in *) aip->ai_addr; 900 | coc_entry_t *e = coc_entry_alloc (); 901 | e->rule_type = rule_type; 902 | e->addr_type = COC_IPV4_ADDR; 903 | e->port = htons (port); 904 | e->is_subnet = false; 905 | e->addr.ipv4 = sa->sin_addr; 906 | SLIST_INSERT_HEAD (&coc_list_head, e, entries); 907 | } 908 | 909 | else if (aip->ai_family == AF_INET6) 910 | { 911 | struct sockaddr_in6 *sa = 912 | (struct sockaddr_in6 *) aip->ai_addr; 913 | coc_entry_t *e = coc_entry_alloc (); 914 | e->rule_type = rule_type; 915 | e->addr_type = COC_IPV6_ADDR; 916 | e->port = htons (port); 917 | e->is_subnet = false; 918 | e->addr.ipv6 = sa->sin6_addr; 919 | SLIST_INSERT_HEAD (&coc_list_head, e, entries); 920 | } 921 | } 922 | 923 | freeaddrinfo (ailist); 924 | free (host); 925 | 926 | #ifdef _WIN32 927 | WSACleanup(); 928 | #endif 929 | 930 | break; 931 | } 932 | } 933 | 934 | return 0; 935 | } 936 | 937 | static size_t 938 | coc_rules_add (const char *rules, size_t rule_type) 939 | { 940 | size_t count = 0; 941 | 942 | if (rules) 943 | { 944 | const char *p = rules + strlen (rules); 945 | size_t len = 0; 946 | 947 | while (p >= rules) 948 | { 949 | if (*p == ';') 950 | { 951 | if (len > 1) 952 | { 953 | coc_rule_add (p + 1, len - 1, rule_type); 954 | } 955 | 956 | len = 1; 957 | count++; 958 | } 959 | else 960 | { 961 | len++; 962 | } 963 | 964 | p--; 965 | } 966 | 967 | if (len - 1 > 0) 968 | { 969 | coc_rule_add (p + 1, len - 1, rule_type); 970 | count++; 971 | } 972 | } 973 | 974 | return count; 975 | } 976 | 977 | static long 978 | coc_long_value (const char *name, const char *value, long lower_bound, 979 | long upper_bound) 980 | { 981 | char *endptr; 982 | errno = 0; 983 | long v = strtol (value, &endptr, 10); 984 | if ((errno == ERANGE && (v == LONG_MAX || v == LONG_MIN)) || 985 | (errno != 0 && value == 0L) || 986 | *endptr != '\0' || (v < lower_bound || v > upper_bound)) 987 | { 988 | DIE ("`%s' not valid for %s (should be between `%ld' and `%ld')\n", 989 | value, name, lower_bound, upper_bound); 990 | } 991 | 992 | return v; 993 | } 994 | 995 | 996 | /* The real connect function. WARNING: cancellation point. */ 997 | static int (WSAAPI *real_connect) (SOCKET fd, const struct sockaddr * addr, 998 | socklen_t addrlen); 999 | 1000 | typedef struct coc_resolver { 1001 | union { 1002 | struct in_addr ipv4; 1003 | struct in6_addr ipv6; 1004 | } addr; 1005 | bool isv6; 1006 | } coc_resolver_t; 1007 | 1008 | static void 1009 | coc_read_resolv (coc_resolver_t * out, size_t * index) 1010 | { 1011 | *index = 0; 1012 | 1013 | #ifndef _WIN32 1014 | #define NS "nameserver " 1015 | 1016 | char buffer[1024]; 1017 | FILE *resolv = fopen ("/etc/resolv.conf", "r"); 1018 | 1019 | if (!resolv) 1020 | { 1021 | DIE ("Could not read /etc/resolv.conf, aborting\n"); 1022 | } 1023 | 1024 | while (fgets(buffer, sizeof(buffer), resolv) && *index < MAXNS) 1025 | { 1026 | if (!strncmp(buffer, NS, sizeof(NS) - 1)) 1027 | { 1028 | char *ns = buffer + sizeof(NS) - 1; 1029 | size_t len = strlen(ns); 1030 | 1031 | if (ns[len - 1] == '\n') 1032 | { 1033 | ns[len - 1] = '\0'; 1034 | } 1035 | 1036 | coc_log(COC_DEBUG_LOG_LEVEL, "DEBUG Found nameserver: %s\n", ns); 1037 | 1038 | if (inet_pton(AF_INET, ns, &out[*index].addr.ipv4) == 1) 1039 | { 1040 | out[(*index)++].isv6 = false; 1041 | } 1042 | else if (inet_pton(AF_INET6, ns, &out[*index].addr.ipv6) == 1) 1043 | { 1044 | out[(*index)++].isv6 = true; 1045 | } 1046 | else 1047 | { 1048 | DIE("Cannot process nameserver: `%s'\n", ns); 1049 | } 1050 | } 1051 | } 1052 | 1053 | fclose(resolv); 1054 | 1055 | #else 1056 | /* We rely on GetAdaptersAddresses: 1057 | * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx 1058 | * to retrieve IPv4 and IPv6 DNS server addresses. 1059 | */ 1060 | DWORD dwRetVal = 0; 1061 | 1062 | // Set the flags to pass to GetAdaptersAddresses 1063 | ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_UNICAST; 1064 | 1065 | LPVOID lpMsgBuf = NULL; 1066 | 1067 | PIP_ADAPTER_ADDRESSES pAddresses = NULL; 1068 | ULONG outBufLen = 0; 1069 | ULONG iterations = 0; 1070 | 1071 | PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; 1072 | IP_ADAPTER_DNS_SERVER_ADDRESS *pDnServer = NULL; 1073 | 1074 | #define WORKING_BUFFER_SIZE 15000 1075 | #define MAX_TRIES 3 1076 | 1077 | // Allocate a 15 KB buffer to start with. 1078 | outBufLen = WORKING_BUFFER_SIZE; 1079 | 1080 | do { 1081 | pAddresses = (IP_ADAPTER_ADDRESSES *) malloc (outBufLen); 1082 | if (pAddresses == NULL) 1083 | { 1084 | DIE ("Cannot allocate memory for IP_ADAPTER_ADDRESSES, aborting\n"); 1085 | } 1086 | 1087 | dwRetVal = GetAdaptersAddresses (AF_UNSPEC, flags, NULL, pAddresses, &outBufLen); 1088 | 1089 | if (dwRetVal == ERROR_BUFFER_OVERFLOW) 1090 | { 1091 | free (pAddresses); 1092 | pAddresses = NULL; 1093 | } 1094 | else 1095 | { 1096 | break; 1097 | } 1098 | 1099 | iterations++; 1100 | 1101 | } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iterations < MAX_TRIES)); 1102 | 1103 | if (dwRetVal == NO_ERROR) { 1104 | // If successful, output some information from the data we received 1105 | pCurrAddresses = pAddresses; 1106 | 1107 | while (pCurrAddresses) { 1108 | pDnServer = pCurrAddresses->FirstDnsServerAddress; 1109 | 1110 | while (pDnServer && *index < MAXNS) { 1111 | ADDRESS_FAMILY family = pDnServer->Address.lpSockaddr->sa_family; 1112 | 1113 | if (family == AF_INET6) 1114 | { 1115 | out[*index].addr.ipv6 = ((struct sockaddr_in6 *) pDnServer->Address.lpSockaddr)->sin6_addr; 1116 | out[*index].isv6 = true; 1117 | } 1118 | else if (family == AF_INET) 1119 | { 1120 | out[*index].addr.ipv4 = ((struct sockaddr_in *) pDnServer->Address.lpSockaddr)->sin_addr; 1121 | out[*index].isv6 = false; 1122 | } 1123 | 1124 | (*index)++; 1125 | pDnServer = pDnServer->Next; 1126 | } 1127 | 1128 | pCurrAddresses = pCurrAddresses->Next; 1129 | } 1130 | } 1131 | else { 1132 | if (dwRetVal == ERROR_NO_DATA) 1133 | DIE("Cannot find any DNS server, aborting\n"); 1134 | else { 1135 | if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 1136 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 1137 | NULL, dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 1138 | (LPTSTR)&lpMsgBuf, 0, NULL)) { 1139 | coc_log(COC_ERROR_LOG_LEVEL, "ERROR %s\n", lpMsgBuf); 1140 | LocalFree(lpMsgBuf); 1141 | free (pAddresses); 1142 | exit(1); 1143 | } 1144 | } 1145 | } 1146 | #endif 1147 | } 1148 | 1149 | const char * 1150 | coc_version (void) 1151 | { 1152 | return version; 1153 | } 1154 | 1155 | #ifdef _WIN32 1156 | 1157 | int HOOK(connect(SOCKET fd, const struct sockaddr *addr, socklen_t addrlen)); 1158 | void coc_init(void); 1159 | pfnCreateProcess real_CreateProcess; 1160 | BOOL HOOK(CreateProcess( 1161 | LPCTSTR lpApplicationName, 1162 | LPTSTR lpCommandLine, 1163 | LPSECURITY_ATTRIBUTES lpProcessAttributes, 1164 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 1165 | BOOL bInheritHandles, 1166 | DWORD dwCreationFlags, 1167 | LPVOID lpEnvironment, 1168 | LPCTSTR lpCurrentDirectory, 1169 | LPSTARTUPINFO lpStartupInfo, 1170 | LPPROCESS_INFORMATION lpProcessInformation)) 1171 | { 1172 | stCreateProcess cpArgs = { 1173 | .fn = real_CreateProcess, 1174 | .lpApplicationName = lpApplicationName, 1175 | .lpCommandLine = lpCommandLine, 1176 | .lpProcessAttributes = lpProcessAttributes, 1177 | .lpThreadAttributes = lpThreadAttributes, 1178 | .bInheritHandles = bInheritHandles, 1179 | .dwCreationFlags = dwCreationFlags, 1180 | .lpEnvironment = lpEnvironment, 1181 | .lpCurrentDirectory = lpCurrentDirectory, 1182 | .lpStartupInfo = lpStartupInfo, 1183 | .lpProcessInformation = lpProcessInformation 1184 | }; 1185 | 1186 | BOOL isConsole; 1187 | 1188 | return CreateProcessThenInject(&cpArgs, &isConsole); 1189 | } 1190 | 1191 | #endif 1192 | 1193 | static inline void 1194 | coc_sym_connect (void) 1195 | { 1196 | if (real_connect == NULL) 1197 | { 1198 | #ifdef _WIN32 1199 | if (MH_Initialize() != MH_OK) 1200 | { 1201 | DIE("Cannot initialize MinHook, aborting\n"); 1202 | } 1203 | 1204 | if (MH_CreateHookApiEx(L"ws2_32", "connect", &hook_connect, (LPVOID *) &real_connect, NULL) != MH_OK) 1205 | { 1206 | DIE("Cannot hook connect(), aborting\n"); 1207 | } 1208 | 1209 | /* TODO: add WSAConnect, ConnectEx */ 1210 | if (MH_CreateHook(&CreateProcess, &hook_CreateProcess, (LPVOID *) &real_CreateProcess) != MH_OK) 1211 | { 1212 | DIE("Cannot hook CreateProcess, aborting\n"); 1213 | } 1214 | 1215 | if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) 1216 | { 1217 | DIE("Cannot enable hooks, aborting\n"); 1218 | } 1219 | #else 1220 | real_connect = 1221 | (int (*)(int, const struct sockaddr *, socklen_t)) dlsym (RTLD_NEXT, 1222 | "connect"); 1223 | 1224 | if (real_connect == NULL) 1225 | { 1226 | char *error = dlerror (); 1227 | 1228 | if (error == NULL) 1229 | { 1230 | error = "connect is NULL"; 1231 | } 1232 | 1233 | DIE ("%s\n", error); 1234 | } 1235 | #endif 1236 | } 1237 | } 1238 | 1239 | #ifdef _WIN32 1240 | BOOL APIENTRY DllMain(HMODULE hModule, 1241 | DWORD ul_reason_for_call, 1242 | LPVOID lpReserved 1243 | ) 1244 | { 1245 | switch (ul_reason_for_call) 1246 | { 1247 | case DLL_PROCESS_ATTACH: 1248 | coc_init (); 1249 | break; 1250 | case DLL_THREAD_ATTACH: 1251 | case DLL_THREAD_DETACH: 1252 | case DLL_PROCESS_DETACH: 1253 | break; 1254 | } 1255 | return TRUE; 1256 | } 1257 | #endif 1258 | 1259 | /* Called by dynamic linker when library is loaded. */ 1260 | #ifdef __SUNPRO_C 1261 | #pragma init (coc_init) 1262 | #elif defined(__GNUC__) 1263 | void coc_init (void) __attribute__ ((constructor)); 1264 | #endif 1265 | 1266 | void 1267 | coc_init (void) 1268 | { 1269 | coc_sym_connect (); 1270 | 1271 | char *level = getenv (COC_LOG_LEVEL_ENV_VAR_NAME); 1272 | if (level) 1273 | { 1274 | static const coc_log_level_t map[] = { 1275 | COC_SILENT_LOG_LEVEL, 1276 | COC_ERROR_LOG_LEVEL, 1277 | COC_BLOCK_LOG_LEVEL, 1278 | COC_ALLOW_LOG_LEVEL, 1279 | COC_DEBUG_LOG_LEVEL 1280 | }; 1281 | 1282 | long lvl = coc_long_value (COC_LOG_LEVEL_ENV_VAR_NAME, level, 1283 | 0, 1284 | sizeof (map) / sizeof (map[0]) - 1); 1285 | 1286 | log_level = map[(size_t) lvl]; 1287 | } 1288 | 1289 | char *target = getenv (COC_LOG_TARGET_ENV_VAR_NAME); 1290 | if (target) 1291 | { 1292 | log_target = coc_long_value (COC_LOG_TARGET_ENV_VAR_NAME, 1293 | target, COC_STDERR_LOG, 1294 | COC_STDERR_LOG | COC_SYSLOG_LOG | 1295 | COC_FILE_LOG); 1296 | 1297 | if ((log_target & COC_FILE_LOG) == COC_FILE_LOG) 1298 | { 1299 | const char *progname = getprogname (); 1300 | const char *log_path = getenv (COC_LOG_PATH_ENV_VAR_NAME); 1301 | if (!log_path) 1302 | { 1303 | log_path = "."; 1304 | } 1305 | #ifdef _WIN32 1306 | #define COC_PATH_SEP "\\" 1307 | #else 1308 | #define COC_PATH_SEP "/" 1309 | #endif 1310 | log_file_name = malloc (strlen (log_path) + strlen (progname) + 6); 1311 | sprintf (log_file_name, "%s" COC_PATH_SEP "%s.coc", log_path, progname); 1312 | } 1313 | } 1314 | 1315 | /* Initialize our singly-linked list. */ 1316 | SLIST_INIT (&coc_list_head); 1317 | 1318 | char *block = getenv (COC_BLOCK_ENV_VAR_NAME); 1319 | coc_rules_add (block, COC_BLOCK); 1320 | 1321 | char *allow = getenv (COC_ALLOW_ENV_VAR_NAME); 1322 | coc_rules_add (allow, COC_ALLOW); 1323 | 1324 | if (allow != NULL && block == NULL && needs_dns_lookup) 1325 | { 1326 | DIE ("Glob specified for ALLOW rule but no rule for BLOCK; aborting\n"); 1327 | } 1328 | 1329 | /* Fail if there is a glob and DNS is not allowed. */ 1330 | if (needs_dns_lookup) 1331 | { 1332 | #ifdef _WIN32 1333 | /* We need to initialize WinSock. It's safe to do so. */ 1334 | WSADATA wsaData; 1335 | int iWSErr = WSAStartup (MAKEWORD(2, 2), &wsaData); 1336 | 1337 | if (iWSErr != 0) 1338 | { 1339 | DIE("Cannot initialize WinSock 2 API, aborting\n"); 1340 | } 1341 | #endif 1342 | bool dns_server_found = false; 1343 | 1344 | /* Read nameserver entries in /etc/resolv.conf */ 1345 | coc_resolver_t dns[MAXNS] = { 0 }; 1346 | size_t dns_count; 1347 | coc_read_resolv (dns, &dns_count); 1348 | 1349 | /* Cycle in all allowed IP entries to check if we have one of these */ 1350 | coc_entry_t *e; 1351 | SLIST_FOREACH (e, &coc_list_head, entries) 1352 | { 1353 | if (dns_server_found) 1354 | { 1355 | break; 1356 | } 1357 | 1358 | if (e->rule_type == COC_ALLOW && 1359 | e->addr_type != COC_GLOB_ADDR && 1360 | (!e->port || e->port == htons (53))) 1361 | { 1362 | size_t i; 1363 | for (i = 0; i < dns_count; i++) 1364 | { 1365 | if ((e->addr_type == COC_IPV4_ADDR && 1366 | !dns[i].isv6 && 1367 | e->addr.ipv4.s_addr == dns[i].addr.ipv4.s_addr) || 1368 | (e->addr_type == COC_IPV6_ADDR && 1369 | dns[i].isv6 && 1370 | IN6_ARE_ADDR_EQUAL (&e->addr.ipv6, &dns[i].addr.ipv6))) 1371 | { 1372 | dns_server_found = true; 1373 | break; 1374 | } 1375 | } 1376 | } 1377 | } 1378 | 1379 | if (!dns_server_found) 1380 | { 1381 | DIE ("No DNS allowed while some glob rule need one, aborting\n"); 1382 | } 1383 | 1384 | #ifdef _WIN32 1385 | WSACleanup (); 1386 | #endif 1387 | 1388 | } 1389 | 1390 | initialized = true; 1391 | } 1392 | 1393 | #define INET4_FMLY(a) (a->sa_family == AF_INET) 1394 | #define INET4_CAST(a) ((const struct sockaddr_in *) a) 1395 | #define INET6_CAST(a) ((const struct sockaddr_in6 *) a) 1396 | #define INET4_PORT(a) (INET4_CAST (a)->sin_port) 1397 | #define INET6_PORT(a) (INET6_CAST (a)->sin6_port) 1398 | #define INETX_PORT(a) (INET4_FMLY (a) ? INET4_PORT (a) : INET6_PORT (a)) 1399 | #define INET4_ADDR(a) (&INET4_CAST (a)->sin_addr) 1400 | #define INET6_ADDR(a) (&INET6_CAST (a)->sin6_addr) 1401 | #define INETX_ADDR(a) (INET4_FMLY (a) ? (void *) INET4_ADDR (a) : (void *) INET6_ADDR (a)) 1402 | #define INET4_IN_6(i) ((struct in_addr *) ((i)->s6_addr + 12)) 1403 | 1404 | static inline 1405 | bool 1406 | coc_rule_match_ipv4 (const struct in_addr *rule_addr, in_port_t rule_port, const struct in_addr *addr, in_port_t port, bool rule_is_subnet, const struct in_addr *netmask) 1407 | { 1408 | struct in_addr masked_addr; 1409 | if (rule_is_subnet) 1410 | { 1411 | masked_addr = *addr; 1412 | mask_ipv4 (&masked_addr, netmask); 1413 | addr = &masked_addr; 1414 | } 1415 | return (rule_addr->s_addr == addr->s_addr && 1416 | (!rule_port || rule_port == port)); 1417 | } 1418 | 1419 | static inline 1420 | bool 1421 | coc_rule_match_ipv6 (const struct in6_addr *rule_addr, in_port_t rule_port, const struct in6_addr *addr, in_port_t port, bool rule_is_subnet, const struct in6_addr *netmask) 1422 | { 1423 | struct in6_addr masked_addr; 1424 | if (rule_is_subnet) 1425 | { 1426 | masked_addr = *addr; 1427 | mask_ipv6 (&masked_addr, netmask); 1428 | addr = &masked_addr; 1429 | } 1430 | return (IN6_ARE_ADDR_EQUAL (rule_addr, addr) && 1431 | (!rule_port || rule_port == port)); 1432 | } 1433 | 1434 | static inline 1435 | bool 1436 | coc_rule_match (coc_entry_t *e, const struct sockaddr *addr, const char *buf) 1437 | { 1438 | switch (e->addr_type) 1439 | { 1440 | case COC_IPV6_ADDR: 1441 | if (addr->sa_family == AF_INET6) 1442 | { 1443 | return coc_rule_match_ipv6 ( 1444 | &e->addr.ipv6, 1445 | e->port, 1446 | INET6_ADDR (addr), 1447 | INET6_PORT (addr), 1448 | e->is_subnet, 1449 | &e->netmask.ipv6); 1450 | } 1451 | else if (addr->sa_family == AF_INET) 1452 | { 1453 | if (IN6_IS_ADDR_V4MAPPED (&e->addr.ipv6)) 1454 | { 1455 | return coc_rule_match_ipv4 ( 1456 | INET4_IN_6 (&e->addr.ipv6), 1457 | e->port, 1458 | INET4_ADDR (addr), 1459 | INET4_PORT (addr), 1460 | e->is_subnet, 1461 | INET4_IN_6 (&e->netmask.ipv6)); 1462 | } 1463 | } 1464 | break; 1465 | 1466 | case COC_IPV4_ADDR: 1467 | if (addr->sa_family == AF_INET) 1468 | { 1469 | return coc_rule_match_ipv4 ( 1470 | &e->addr.ipv4, 1471 | e->port, 1472 | INET4_ADDR (addr), 1473 | INET4_PORT (addr), 1474 | e->is_subnet, 1475 | &e->netmask.ipv4); 1476 | } 1477 | else if (addr->sa_family == AF_INET6) 1478 | { 1479 | if (IN6_IS_ADDR_V4MAPPED (INET6_ADDR (addr))) 1480 | { 1481 | return coc_rule_match_ipv4 ( 1482 | &e->addr.ipv4, 1483 | e->port, 1484 | INET4_IN_6 (INET6_ADDR (addr)), 1485 | INET6_PORT (addr), 1486 | e->is_subnet, 1487 | &e->netmask.ipv4); 1488 | } 1489 | } 1490 | break; 1491 | 1492 | case COC_GLOB_ADDR: 1493 | return (((e->addr.glob[0] == '*' && e->addr.glob[1] == '\0') 1494 | || !fnmatch (e->addr.glob, buf, 0)) 1495 | && (!e->port || e->port == INETX_PORT (addr))); 1496 | } 1497 | 1498 | return false; 1499 | } 1500 | 1501 | /* 1502 | * Real work happens here. 1503 | * 1504 | * We don't do anything except for AF_INET and AF_INET6. 1505 | */ 1506 | int 1507 | HOOK(connect(SOCKET fd, const struct sockaddr *addr, socklen_t addrlen)) 1508 | { 1509 | if (!initialized) 1510 | { 1511 | /* With SELinux enabled `connect' gets called for audit purpose 1512 | _before_ `coc_init' so we ensure we have the real `connect' 1513 | symbol address here to avoid segfaulting. */ 1514 | coc_sym_connect (); 1515 | 1516 | /* Do not check connections if initialization is not complete. */ 1517 | return real_connect (fd, addr, addrlen); 1518 | } 1519 | 1520 | if (addr != NULL && 1521 | (addr->sa_family == AF_INET || addr->sa_family == AF_INET6)) 1522 | { 1523 | char str[INET6_ADDRSTRLEN]; 1524 | in_port_t port = INETX_PORT (addr); 1525 | inet_ntop (addr->sa_family, INETX_ADDR (addr), str, INET6_ADDRSTRLEN); 1526 | 1527 | char hbuf[NI_MAXHOST] = "*"; 1528 | 1529 | bool dns_lookup_done = !needs_dns_lookup; 1530 | 1531 | /* We have access to IP address and port where the 1532 | * connection is requested. 1533 | * 1534 | * Logic: 1535 | * 1. check if this address appears in our ALLOW rules: 1536 | * - if so, proceed to the real connect call 1537 | * 2. If not, check if it's in the BLOCK rules: 1538 | * - if so, call pthread_testcancel, then return EACCES 1539 | * 3. Otherwise proceed with regular connect call. */ 1540 | coc_entry_t *e; 1541 | SLIST_FOREACH (e, &coc_list_head, entries) 1542 | { 1543 | if (e->addr_type == COC_GLOB_ADDR) 1544 | { 1545 | if (!dns_lookup_done) 1546 | { 1547 | int rc = getnameinfo (addr, addrlen, hbuf, sizeof (hbuf), 1548 | NULL, 0, NI_NUMERICSERV); 1549 | 1550 | if (rc) 1551 | { 1552 | coc_log (COC_BLOCK_LOG_LEVEL, "ERROR resolving name: %s\n", 1553 | gai_strerror (rc)); 1554 | continue; 1555 | } 1556 | else 1557 | { 1558 | dns_lookup_done = true; 1559 | } 1560 | } 1561 | } 1562 | 1563 | coc_log (COC_DEBUG_LOG_LEVEL, 1564 | "DEBUG Checking %s rule for %s connection to %s:%hu\n", 1565 | rule_type_name[e->rule_type], 1566 | address_type_name[e->addr_type], 1567 | str, ntohs (e->port)); 1568 | 1569 | if (coc_rule_match(e, addr, hbuf)) 1570 | { 1571 | if (e->rule_type == COC_ALLOW) 1572 | { 1573 | coc_log (COC_ALLOW_LOG_LEVEL, "ALLOW connection to %s:%hu\n", str, 1574 | ntohs (port)); 1575 | return real_connect (fd, addr, addrlen); 1576 | } 1577 | else 1578 | { 1579 | pthread_testcancel (); 1580 | coc_log (COC_BLOCK_LOG_LEVEL, "BLOCK connection to %s:%hu\n", str, 1581 | ntohs (port)); 1582 | #ifdef _WIN32 1583 | WSASetLastError(WSAEACCES); 1584 | #else 1585 | errno = EACCES; 1586 | #endif 1587 | return -1; 1588 | } 1589 | } 1590 | } 1591 | 1592 | coc_log (COC_ALLOW_LOG_LEVEL, "ALLOW connection to %s:%hu\n", str, 1593 | ntohs (port)); 1594 | } 1595 | 1596 | return real_connect (fd, addr, addrlen); 1597 | } 1598 | -------------------------------------------------------------------------------- /connect-or-cut.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "connect-or-cut", "connect-or-cut.vcxproj", "{A34F0111-3E7B-4DF0-A11A-FE16297560D7}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "coc", "vs2015\coc\coc.vcxproj", "{09519461-063E-4EDA-A2DC-DBF87697C11A}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tcpcontest", "vs2015\tcpcontest\tcpcontest.vcxproj", "{1FF3631D-851D-4356-BB77-68F9B0E75C37}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A2DD0BCD-B256-4014-A8A3-68221707A5DB}" 13 | ProjectSection(SolutionItems) = preProject 14 | appveyor.yml = appveyor.yml 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|x64 = Debug|x64 21 | Debug|x86 = Debug|x86 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Debug|x64.ActiveCfg = Debug|x64 27 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Debug|x64.Build.0 = Debug|x64 28 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Debug|x86.ActiveCfg = Debug|Win32 29 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Debug|x86.Build.0 = Debug|Win32 30 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Release|x64.ActiveCfg = Release|x64 31 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Release|x64.Build.0 = Release|x64 32 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Release|x86.ActiveCfg = Release|Win32 33 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7}.Release|x86.Build.0 = Release|Win32 34 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Debug|x64.ActiveCfg = Debug|x64 35 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Debug|x64.Build.0 = Debug|x64 36 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Debug|x86.ActiveCfg = Debug|Win32 37 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Debug|x86.Build.0 = Debug|Win32 38 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Release|x64.ActiveCfg = Release|x64 39 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Release|x64.Build.0 = Release|x64 40 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Release|x86.ActiveCfg = Release|Win32 41 | {09519461-063E-4EDA-A2DC-DBF87697C11A}.Release|x86.Build.0 = Release|Win32 42 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Debug|x64.ActiveCfg = Debug|x64 43 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Debug|x64.Build.0 = Debug|x64 44 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Debug|x86.ActiveCfg = Debug|Win32 45 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Debug|x86.Build.0 = Debug|Win32 46 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Release|x64.ActiveCfg = Release|x64 47 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Release|x64.Build.0 = Release|x64 48 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Release|x86.ActiveCfg = Release|Win32 49 | {1FF3631D-851D-4356-BB77-68F9B0E75C37}.Release|x86.Build.0 = Release|Win32 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /connect-or-cut.spec: -------------------------------------------------------------------------------- 1 | %define sname connect-or-cut 2 | %define libname lib%{sname} 3 | %define libver 1 4 | %define debug_package %{nil} 5 | %ifarch i386 6 | %define bits 32 7 | %else 8 | %define bits 64 9 | %endif 10 | 11 | Name: %{libname}%{libver} 12 | Version: 1.0.3 13 | Release: 1%{?dist} 14 | Summary: Stateless LD_PRELOAD based poor man's firewall 15 | 16 | Group: System Environment/Libraries 17 | License: BSD 18 | URL: https://github.com/tgg/%{sname} 19 | Source0: https://github.com/tgg/archive/%{sname}/%{sname}-%{version}.tar.gz 20 | BuildRoot: %{_tmppath}/%{sname}-%{version}-%{release}-root-%(%{__id_u} -n) 21 | 22 | %description 23 | connect-or-cut is a small library to interpose with LD_PRELOAD to a program 24 | to prevent it from connecting where it should not. 25 | 26 | This is similar to a firewall, except that: 27 | - you do not need to be root to use it 28 | - only processes launched after LD_PRELOAD is set are affected, not the full 29 | system 30 | 31 | %ifarch noarch 32 | %package -n %{sname} 33 | Summary: Stateless LD_PRELOAD based poor man's firewall 34 | BuildArch: noarch 35 | Group: System Environment/Libraries 36 | Requires: %{libname}%{libver} = %{version}-%{release} 37 | 38 | %description -n connect-or-cut 39 | This package contains the 'coc' helper script to use the library. 40 | %endif 41 | 42 | %prep 43 | %setup -q -n %{sname}-%{version} 44 | 45 | 46 | %build 47 | %ifnarch noarch 48 | CFLAGS="-O2 -g -Wall" make %{?_smp_mflags} os=$(uname -s) bits=%{bits} 49 | %endif 50 | 51 | 52 | %install 53 | rm -rf %{buildroot} 54 | %ifarch noarch 55 | mkdir -p %{buildroot}%{_bindir} 56 | install -m755 coc %{buildroot}%{_bindir} 57 | %else 58 | make install DESTBIN=%{buildroot}%{_bindir} DESTLIB=%{buildroot}%{_libdir} 59 | rm %{buildroot}%{_bindir}/coc 60 | %endif 61 | 62 | 63 | %clean 64 | rm -rf %{buildroot} 65 | 66 | 67 | %ifarch noarch 68 | %files -n %{sname} 69 | %defattr(-,root,root,-) 70 | %doc README.md LICENSE 71 | %attr(755,-,-) %{_bindir}/coc 72 | %else 73 | %files 74 | %defattr(-,root,root,-) 75 | %{_libdir}/* 76 | %endif 77 | 78 | 79 | %post -p /sbin/ldconfig 80 | %postun -p /sbin/ldconfig 81 | 82 | 83 | %changelog 84 | * Tue Mar 21 2017 Thomas Girard - 1.0.3-1 85 | - update to version 1.0.3 86 | 87 | * Mon Nov 01 2016 Thomas Girard - 1.0.1-1 88 | - Initial version of the package 89 | -------------------------------------------------------------------------------- /connect-or-cut.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {A34F0111-3E7B-4DF0-A11A-FE16297560D7} 23 | Win32Proj 24 | connectorcut 25 | 8.1 26 | 27 | 28 | 29 | DynamicLibrary 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | DynamicLibrary 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | DynamicLibrary 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | DynamicLibrary 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 32.dll 75 | 76 | 77 | true 78 | .dll 79 | 80 | 81 | false 82 | $(SolutionDir)packages\minhook.1.3.3\lib\native\lib;$(LibraryPath) 83 | 32.dll 84 | 85 | 86 | false 87 | 88 | 89 | 90 | NotUsing 91 | Level3 92 | Disabled 93 | WIN32;_DEBUG;_WINDOWS;_USRDLL;COC_EXPORTS;%(PreprocessorDefinitions) 94 | true 95 | 96 | 97 | Windows 98 | true 99 | $(OutDir)$(TargetName)$(TargetExt) 100 | 101 | 102 | 103 | 104 | NotUsing 105 | Level3 106 | Disabled 107 | _DEBUG;_WINDOWS;_USRDLL;COC_EXPORTS;%(PreprocessorDefinitions) 108 | true 109 | 110 | 111 | Windows 112 | true 113 | 114 | 115 | 116 | 117 | Level3 118 | NotUsing 119 | MaxSpeed 120 | true 121 | true 122 | WIN32;NDEBUG;_WINDOWS;_USRDLL;COC_EXPORTS;%(PreprocessorDefinitions) 123 | true 124 | 125 | 126 | Windows 127 | true 128 | true 129 | true 130 | 131 | 132 | 133 | 134 | Level3 135 | NotUsing 136 | MaxSpeed 137 | true 138 | true 139 | NDEBUG;_WINDOWS;_USRDLL;COC_EXPORTS;%(PreprocessorDefinitions) 140 | true 141 | 142 | 143 | Windows 144 | true 145 | true 146 | true 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /connect-or-cut.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | Header Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | Header Files 34 | 35 | 36 | Header Files 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Resource Files 51 | 52 | 53 | -------------------------------------------------------------------------------- /connect-or-cut.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(TargetDir)coc.exe 5 | C:\Windows\System32\cmd.exe 6 | WindowsLocalDebugger 7 | 8 | 9 | C:\Users\Thoma\Source\Repos\connect-or-cut\Debug\coc.exe 10 | C:\Windows\System32\cmd.exe C:\Users\Thoma\Source\Repos\connect-or-cut\Debug\connect-or-cut.dll 11 | WindowsLocalDebugger 12 | 13 | 14 | $(TargetDir)coc.exe 15 | C:\Windows\System32\cmd.exe 16 | WindowsLocalDebugger 17 | 18 | -------------------------------------------------------------------------------- /inject.c: -------------------------------------------------------------------------------- 1 | /* inject.c -- Injects connect-or-cut DLL into a Windows process. 2 | * 3 | * Copyright Ⓒ 2017-2019 Thomas Girard 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * * Neither the name of the author nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | */ 34 | #define _CRT_SECURE_NO_WARNINGS 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include "inject.h" 40 | #pragma warning(push) 41 | #pragma warning(disable : 4091) 42 | #include 43 | #pragma warning(pop) 44 | #pragma comment(lib, "Imagehlp.lib") 45 | 46 | #ifdef UNICODE 47 | #define LoadLib "LoadLibraryW" 48 | #else 49 | #define LoadLib "LoadLibraryA" 50 | #endif 51 | 52 | // Yup, it's duplicated from error.c to avoid moving this into 53 | // another shared library. 54 | static void MessageBoxError(DWORD dwLastError, LPTSTR lpszFunction) 55 | { 56 | LPVOID lpError; 57 | LPVOID lpDisplayBuf; 58 | 59 | FormatMessage( 60 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 61 | FORMAT_MESSAGE_FROM_SYSTEM | 62 | FORMAT_MESSAGE_IGNORE_INSERTS, 63 | NULL, dwLastError, 64 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 65 | (LPTSTR)&lpError, 0, NULL); 66 | 67 | lpDisplayBuf = (LPVOID)LocalAlloc( 68 | LMEM_ZEROINIT, 69 | (_tcslen((LPTSTR)lpError) + _tcslen(lpszFunction) + 14) * sizeof(TCHAR)); 70 | StringCchPrintf( 71 | (LPTSTR)lpDisplayBuf, 72 | LocalSize(lpDisplayBuf) / sizeof(TCHAR), 73 | _T("%s failed: %s"), 74 | lpszFunction, lpError); 75 | 76 | MessageBox(NULL, (LPCTSTR)lpDisplayBuf, _T("connect-or-cut"), MB_OK | MB_ICONERROR); 77 | 78 | LocalFree(lpError); 79 | LocalFree(lpDisplayBuf); 80 | } 81 | 82 | static BOOL CheckNtImage(PCSTR lpszLibraryPath, BOOL *is64Bit, BOOL *isConsole) 83 | { 84 | if (!lpszLibraryPath || !is64Bit || !isConsole) 85 | { 86 | SetLastError(ERROR_INVALID_PARAMETER); 87 | return FALSE; 88 | } 89 | 90 | BOOL ret = FALSE; 91 | PLOADED_IMAGE pImage = ImageLoad(lpszLibraryPath, NULL); 92 | 93 | if (pImage != NULL) 94 | { 95 | IMAGE_NT_HEADERS *pNtHdr = pImage->FileHeader; 96 | 97 | if (pNtHdr->Signature == IMAGE_NT_SIGNATURE) 98 | { 99 | ret = TRUE; 100 | 101 | if (pNtHdr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) 102 | *is64Bit = FALSE; 103 | else if (pNtHdr->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) 104 | *is64Bit = TRUE; 105 | else // unsupported 106 | ret = FALSE; 107 | 108 | if (pNtHdr->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI) 109 | *isConsole = TRUE; 110 | else if (pNtHdr->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) 111 | *isConsole = FALSE; 112 | else // unknown? 113 | ret = FALSE; 114 | } 115 | 116 | ImageUnload(pImage); 117 | } 118 | 119 | return ret; 120 | } 121 | 122 | BOOL LoadProcessLibrary(HANDLE hProcess, LPTSTR lpszLibraryPath) 123 | { 124 | if (hProcess == NULL || lpszLibraryPath == NULL) 125 | { 126 | SetLastError(ERROR_INVALID_PARAMETER); 127 | return FALSE; 128 | } 129 | 130 | LPTHREAD_START_ROUTINE lpLoadLibrary = (LPTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(_T("kernel32.dll")), LoadLib); 131 | 132 | if (lpLoadLibrary == NULL) 133 | { 134 | // GetLastError() will contain relevant error. 135 | return FALSE; 136 | } 137 | 138 | size_t iLibraryPathLen = _tcslen(lpszLibraryPath) + 1; 139 | SIZE_T dwSize = iLibraryPathLen * sizeof(TCHAR); 140 | 141 | LPVOID lpPath = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE); 142 | if (lpPath == NULL) 143 | { 144 | // Could not allocate memory in the process. GetLastError() will contain 145 | // relevant error. 146 | return FALSE; 147 | } 148 | 149 | if (!WriteProcessMemory(hProcess, lpPath, lpszLibraryPath, dwSize, NULL)) 150 | { 151 | // Fail gracefully: release allocated memory (ignoring error), then 152 | // restore initial error so that GetLastError() in the callee works. 153 | DWORD dwError = GetLastError(); 154 | VirtualFreeEx(hProcess, lpPath, 0, MEM_RELEASE); 155 | SetLastError(dwError); 156 | return FALSE; 157 | } 158 | 159 | HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpLoadLibrary, lpPath, 0, NULL); 160 | if (hThread == NULL) 161 | { 162 | DWORD dwError = GetLastError(); 163 | VirtualFreeEx(hProcess, lpPath, 0, MEM_RELEASE); 164 | SetLastError(dwError); 165 | return FALSE; 166 | } 167 | 168 | DWORD dwWaitForThread = WaitForSingleObject(hThread, INFINITE); 169 | if (dwWaitForThread != WAIT_OBJECT_0) 170 | { 171 | // The only valid status would be WAIT_FAILED according to doc 172 | DWORD dwError = GetLastError(); 173 | // Here we don't know exactly whether the thread has completed or not, 174 | // so we forcibly kill it. 175 | TerminateThread(hThread, 0xdeadbeef); 176 | CloseHandle(hThread); 177 | VirtualFreeEx(hProcess, lpPath, 0, MEM_RELEASE); 178 | SetLastError(dwError); 179 | return FALSE; 180 | } 181 | 182 | DWORD dwExitCode; 183 | if (!GetExitCodeThread(hThread, &dwExitCode)) 184 | { 185 | DWORD dwError = GetLastError(); 186 | CloseHandle(hThread); 187 | VirtualFreeEx(hProcess, lpPath, 0, MEM_RELEASE); 188 | SetLastError(dwError); 189 | return FALSE; 190 | } 191 | 192 | if (dwExitCode == 0) 193 | { 194 | // The LoadLibrary call failed, but the thread is gone now, so 195 | // GetLastError() is no longer relevant. 196 | CloseHandle(hThread); 197 | VirtualFreeEx(hProcess, lpPath, 0, MEM_RELEASE); 198 | // We build significant error message, but with no explanation :-( 199 | SetLastError(TYPE_E_CANTLOADLIBRARY); 200 | return FALSE; 201 | } 202 | 203 | if (!CloseHandle(hThread)) 204 | { 205 | DWORD dwError = GetLastError(); 206 | VirtualFreeEx(hProcess, lpPath, 0, MEM_RELEASE); 207 | SetLastError(dwError); 208 | return FALSE; 209 | } 210 | 211 | if (!VirtualFreeEx(hProcess, lpPath, 0, MEM_RELEASE)) 212 | { 213 | return FALSE; 214 | } 215 | 216 | return TRUE; 217 | } 218 | 219 | BOOL CoCPath(LPTSTR szCoCPath) 220 | { 221 | TCHAR szThisPath[MAX_PATH] = { 0 }; 222 | 223 | HMODULE self = GetModuleHandle(_T("connect-or-cut.dll")); 224 | 225 | if (self == NULL) 226 | { 227 | self = GetModuleHandle(_T("connect-or-cut32.dll")); 228 | // If self is NULL again, then we assume we are being 229 | // injected from coc.exe program. 230 | } 231 | 232 | if (GetModuleFileName(self, szThisPath, MAX_PATH) == 0) 233 | { 234 | return FALSE; 235 | } 236 | 237 | LPTSTR lpszLastBackslash = _tcsrchr(szThisPath, _T('\\')); 238 | if (lpszLastBackslash == NULL) 239 | { 240 | SetLastError(ERROR_BAD_PATHNAME); 241 | return FALSE; 242 | } 243 | 244 | _tcsncpy(szCoCPath, szThisPath, lpszLastBackslash + 1 - szThisPath); 245 | return TRUE; 246 | } 247 | 248 | BOOL CoCExePath(LPTSTR lpPath, BOOL is64Bit) 249 | { 250 | if (!CoCPath(lpPath)) 251 | { 252 | return FALSE; 253 | } 254 | 255 | LPCTSTR lpExe = is64Bit ? _T("coc.exe ") : _T("coc32.exe "); 256 | SIZE_T szLen = is64Bit ? 8 : 10; 257 | _tcsncat(lpPath, lpExe, szLen); 258 | 259 | return TRUE; 260 | } 261 | 262 | BOOL CoCLibPath(LPTSTR lpPath, BOOL is64Bit) 263 | { 264 | if (!CoCPath(lpPath)) 265 | { 266 | return FALSE; 267 | } 268 | 269 | LPCTSTR lpLib = is64Bit ? _T("connect-or-cut.dll") : _T("connect-or-cut32.dll"); 270 | SIZE_T szLen = is64Bit ? 19 : 21; 271 | _tcsncat(lpPath, lpLib, szLen); 272 | 273 | return TRUE; 274 | } 275 | 276 | BOOL LoadCoCLibrary(HANDLE hProcess, BOOL is64Bit) 277 | { 278 | TCHAR szCoCPath[MAX_PATH] = { 0 }; 279 | 280 | if (!CoCLibPath(szCoCPath, is64Bit)) 281 | { 282 | return FALSE; 283 | } 284 | 285 | if (!LoadProcessLibrary(hProcess, szCoCPath)) 286 | { 287 | return FALSE; 288 | } 289 | 290 | return TRUE; 291 | } 292 | 293 | static inline PSTR ExeWithoutQuotes(PSTR lpExe) 294 | { 295 | PSTR exe = lpExe; 296 | 297 | if (exe) 298 | { 299 | if (exe[0] == '"') 300 | { 301 | exe++; 302 | size_t len = strlen(exe); 303 | if (len > 0 && exe[len - 1] == '"') 304 | { 305 | exe[len - 1] = '\0'; 306 | } 307 | } 308 | } 309 | 310 | return exe; 311 | } 312 | 313 | // 314 | // Creates the process specified in lpArgs, using passed function. 315 | // 316 | // Before doing so, ensure we will be able to inject connect-or-cut 317 | // by comparing our bitness with the one of the process to run. If they differ 318 | // then we rerun the appropriate coc.exe (or coc32.exe). If they don't then 319 | // we inject using regular mechanism. 320 | // 321 | BOOL CreateProcessThenInject(stCreateProcess *lpArgs, BOOL *isConsole) 322 | { 323 | if (lpArgs == NULL || lpArgs->fn == NULL) 324 | { 325 | SetLastError(ERROR_INVALID_PARAMETER); 326 | return FALSE; 327 | } 328 | 329 | BOOL isThisWow64 = FALSE; 330 | if (!IsWow64Process(GetCurrentProcess(), &isThisWow64)) 331 | { 332 | return FALSE; 333 | } 334 | 335 | PSTR exe = GetImageName(lpArgs); 336 | BOOL is64Bit = FALSE; 337 | 338 | if (!CheckNtImage(ExeWithoutQuotes(exe), &is64Bit, isConsole)) 339 | { 340 | DWORD dwLastError = GetLastError(); 341 | LocalFree(exe); 342 | SetLastError(dwLastError); 343 | return FALSE; 344 | } 345 | 346 | LocalFree(exe); 347 | if (isThisWow64 != !is64Bit) 348 | { 349 | // Need to rerun with coc.exe (or coc32.exe), so we'll alter the command-line 350 | TCHAR szCoCPath[MAX_PATH] = { 0 }; 351 | if (!CoCExePath(szCoCPath, is64Bit)) 352 | { 353 | return FALSE; 354 | } 355 | 356 | _tcscat(szCoCPath, lpArgs->lpCommandLine); 357 | 358 | // Store to restore later 359 | LPTSTR lpCommandLine = lpArgs->lpCommandLine; 360 | LPCTSTR lpApplicationName = lpArgs->lpApplicationName; 361 | lpArgs->lpCommandLine = szCoCPath; 362 | lpArgs->lpApplicationName = NULL; 363 | BOOL bCreateProcess = (*lpArgs->fn)(lpArgs->lpApplicationName, lpArgs->lpCommandLine, lpArgs->lpProcessAttributes, 364 | lpArgs->lpThreadAttributes, lpArgs->bInheritHandles, lpArgs->dwCreationFlags, 365 | lpArgs->lpEnvironment, lpArgs->lpCurrentDirectory, lpArgs->lpStartupInfo, lpArgs->lpProcessInformation); 366 | lpArgs->lpCommandLine = lpCommandLine; 367 | lpArgs->lpApplicationName = lpApplicationName; 368 | 369 | return bCreateProcess; 370 | } 371 | else 372 | { 373 | BOOL bNeedsResume = !(((lpArgs->dwCreationFlags & CREATE_SUSPENDED) == CREATE_SUSPENDED)); 374 | lpArgs->dwCreationFlags |= CREATE_SUSPENDED; 375 | BOOL bCreateProcess = (*lpArgs->fn)(lpArgs->lpApplicationName, lpArgs->lpCommandLine, lpArgs->lpProcessAttributes, 376 | lpArgs->lpThreadAttributes, lpArgs->bInheritHandles, lpArgs->dwCreationFlags, 377 | lpArgs->lpEnvironment, lpArgs->lpCurrentDirectory, lpArgs->lpStartupInfo, lpArgs->lpProcessInformation); 378 | if (bNeedsResume) 379 | { 380 | lpArgs->dwCreationFlags &= ~CREATE_SUSPENDED; 381 | } 382 | 383 | if (bCreateProcess) 384 | { 385 | if (!LoadCoCLibrary(lpArgs->lpProcessInformation->hProcess, is64Bit)) 386 | { 387 | // Injection failed. In that case we report the error and let the 388 | // process continue. 389 | MessageBoxError(GetLastError(), _T("LoadCoCLibrary")); 390 | } 391 | 392 | if (bNeedsResume && ResumeThread(lpArgs->lpProcessInformation->hThread) == -1) 393 | { 394 | // Unable to resume the main thread. We terminate the process to 395 | // avoid it from being in a hung state. 396 | DWORD dwLastError = GetLastError(); 397 | TerminateProcess(lpArgs->lpProcessInformation->hProcess, 0xdeadbeef); 398 | WaitForSingleObject(lpArgs->lpProcessInformation->hProcess, INFINITE); 399 | SetLastError(dwLastError); 400 | return FALSE; 401 | } 402 | 403 | } 404 | 405 | return bCreateProcess; 406 | } 407 | } 408 | 409 | static TCHAR* MoveBeforeFirstArgument(TCHAR *lpCommandLine) 410 | { 411 | TCHAR* p = lpCommandLine; 412 | BOOL bInQuotes = FALSE; 413 | size_t uiBackslashes = 0; 414 | 415 | while (*p != _T('\0')) 416 | { 417 | if (*p == _T('\\')) 418 | { 419 | uiBackslashes++; 420 | } 421 | else if (*p == _T('"')) 422 | { 423 | if (uiBackslashes % 2 == 0) 424 | { 425 | bInQuotes = !bInQuotes; 426 | } 427 | 428 | uiBackslashes = 0; 429 | } 430 | else if (*p == _T(' ')) 431 | { 432 | uiBackslashes = 0; 433 | 434 | if (!bInQuotes) 435 | break; 436 | } 437 | else 438 | { 439 | uiBackslashes = 0; 440 | } 441 | 442 | p++; 443 | } 444 | 445 | return p; 446 | } 447 | 448 | static PSTR StringFromUnicode(LPCTSTR lpSource, size_t dstLen) 449 | { 450 | size_t converted; 451 | PSTR dst = (PSTR) LocalAlloc(LMEM_ZEROINIT, dstLen); 452 | wcstombs_s(&converted, dst, dstLen, lpSource, _TRUNCATE); 453 | return dst; 454 | } 455 | 456 | PSTR GetImageName(stCreateProcess *lpArgs) 457 | { 458 | if (lpArgs == NULL) 459 | { 460 | SetLastError(ERROR_INVALID_PARAMETER); 461 | return NULL; 462 | } 463 | 464 | if (lpArgs->lpApplicationName) 465 | { 466 | return StringFromUnicode(lpArgs->lpApplicationName, (_tcslen(lpArgs->lpApplicationName) + 1) * sizeof(TCHAR)); 467 | } 468 | else 469 | { 470 | LPTSTR lpFirstArgument = MoveBeforeFirstArgument(lpArgs->lpCommandLine); 471 | return StringFromUnicode(lpArgs->lpCommandLine, lpFirstArgument + 1 - lpArgs->lpCommandLine); 472 | } 473 | } -------------------------------------------------------------------------------- /inject.h: -------------------------------------------------------------------------------- 1 | /* inject.h -- Injects connect-or-cut DLL into a new Windows process. 2 | * 3 | * Copyright Ⓒ 2018-2019 Thomas Girard 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * * Neither the name of the author nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | */ 34 | 35 | #pragma once 36 | 37 | #define _CRT_SECURE_NO_WARNINGS 38 | #include 39 | #define WIN32_LEAN_AND_MEAN 40 | #include 41 | 42 | typedef BOOL(WINAPI *pfnCreateProcess) (LPCTSTR lpApplicationName, 43 | LPTSTR lpCommandLine, 44 | LPSECURITY_ATTRIBUTES lpProcessAttributes, 45 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 46 | BOOL bInheritHandles, 47 | DWORD dwCreationFlags, 48 | LPVOID lpEnvironment, 49 | LPCTSTR lpCurrentDirectory, 50 | LPSTARTUPINFO lpStartupInfo, 51 | LPPROCESS_INFORMATION lpProcessInformation); 52 | 53 | typedef struct stCreateProcess { 54 | pfnCreateProcess fn; 55 | LPCTSTR lpApplicationName; 56 | LPTSTR lpCommandLine; 57 | LPSECURITY_ATTRIBUTES lpProcessAttributes; 58 | LPSECURITY_ATTRIBUTES lpThreadAttributes; 59 | BOOL bInheritHandles; 60 | DWORD dwCreationFlags; 61 | LPVOID lpEnvironment; 62 | LPCTSTR lpCurrentDirectory; 63 | LPSTARTUPINFO lpStartupInfo; 64 | LPPROCESS_INFORMATION lpProcessInformation; 65 | } stCreateProcess; 66 | 67 | BOOL CreateProcessThenInject(stCreateProcess *lpArgs, BOOL *isConsole); 68 | 69 | BOOL LoadCoCLibrary(HANDLE hProcess, BOOL is64Bit); 70 | 71 | PSTR GetImageName(stCreateProcess *lpArgs); -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Version.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /resource1.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Version.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /sys_queue.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 1991, 1993 3 | * The Regents of the University of California. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 4. Neither the name of the University nor the names of its contributors 14 | * may be used to endorse or promote products derived from this software 15 | * without specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | * 29 | * @(#)queue.h 8.5 (Berkeley) 8/20/94 30 | * $FreeBSD$ 31 | */ 32 | 33 | #ifndef _SYS_QUEUE_H_ 34 | #define _SYS_QUEUE_H_ 35 | 36 | //#include 37 | 38 | /* 39 | * This file defines four types of data structures: singly-linked lists, 40 | * singly-linked tail queues, lists and tail queues. 41 | * 42 | * A singly-linked list is headed by a single forward pointer. The elements 43 | * are singly linked for minimum space and pointer manipulation overhead at 44 | * the expense of O(n) removal for arbitrary elements. New elements can be 45 | * added to the list after an existing element or at the head of the list. 46 | * Elements being removed from the head of the list should use the explicit 47 | * macro for this purpose for optimum efficiency. A singly-linked list may 48 | * only be traversed in the forward direction. Singly-linked lists are ideal 49 | * for applications with large datasets and few or no removals or for 50 | * implementing a LIFO queue. 51 | * 52 | * A singly-linked tail queue is headed by a pair of pointers, one to the 53 | * head of the list and the other to the tail of the list. The elements are 54 | * singly linked for minimum space and pointer manipulation overhead at the 55 | * expense of O(n) removal for arbitrary elements. New elements can be added 56 | * to the list after an existing element, at the head of the list, or at the 57 | * end of the list. Elements being removed from the head of the tail queue 58 | * should use the explicit macro for this purpose for optimum efficiency. 59 | * A singly-linked tail queue may only be traversed in the forward direction. 60 | * Singly-linked tail queues are ideal for applications with large datasets 61 | * and few or no removals or for implementing a FIFO queue. 62 | * 63 | * A list is headed by a single forward pointer (or an array of forward 64 | * pointers for a hash table header). The elements are doubly linked 65 | * so that an arbitrary element can be removed without a need to 66 | * traverse the list. New elements can be added to the list before 67 | * or after an existing element or at the head of the list. A list 68 | * may be traversed in either direction. 69 | * 70 | * A tail queue is headed by a pair of pointers, one to the head of the 71 | * list and the other to the tail of the list. The elements are doubly 72 | * linked so that an arbitrary element can be removed without a need to 73 | * traverse the list. New elements can be added to the list before or 74 | * after an existing element, at the head of the list, or at the end of 75 | * the list. A tail queue may be traversed in either direction. 76 | * 77 | * For details on the use of these macros, see the queue(3) manual page. 78 | * 79 | * Below is a summary of implemented functions where: 80 | * + means the macro is available 81 | * - means the macro is not available 82 | * s means the macro is available but is slow (runs in O(n) time) 83 | * 84 | * SLIST LIST STAILQ TAILQ 85 | * _HEAD + + + + 86 | * _CLASS_HEAD + + + + 87 | * _HEAD_INITIALIZER + + + + 88 | * _ENTRY + + + + 89 | * _CLASS_ENTRY + + + + 90 | * _INIT + + + + 91 | * _EMPTY + + + + 92 | * _FIRST + + + + 93 | * _NEXT + + + + 94 | * _PREV - + - + 95 | * _LAST - - + + 96 | * _FOREACH + + + + 97 | * _FOREACH_FROM + + + + 98 | * _FOREACH_SAFE + + + + 99 | * _FOREACH_FROM_SAFE + + + + 100 | * _FOREACH_REVERSE - - - + 101 | * _FOREACH_REVERSE_FROM - - - + 102 | * _FOREACH_REVERSE_SAFE - - - + 103 | * _FOREACH_REVERSE_FROM_SAFE - - - + 104 | * _INSERT_HEAD + + + + 105 | * _INSERT_BEFORE - + - + 106 | * _INSERT_AFTER + + + + 107 | * _INSERT_TAIL - - + + 108 | * _CONCAT s s + + 109 | * _REMOVE_AFTER + - + - 110 | * _REMOVE_HEAD + - + - 111 | * _REMOVE s + s + 112 | * _SWAP + + + + 113 | * 114 | */ 115 | #ifdef QUEUE_MACRO_DEBUG 116 | #warn Use QUEUE_MACRO_DEBUG_TRACE and / or QUEUE_MACRO_DEBUG_TRASH 117 | #define QUEUE_MACRO_DEBUG_TRACE 118 | #define QUEUE_MACRO_DEBUG_TRASH 119 | #endif 120 | 121 | #ifdef QUEUE_MACRO_DEBUG_TRACE 122 | /* Store the last 2 places the queue element or head was altered */ 123 | struct qm_trace { 124 | unsigned long lastline; 125 | unsigned long prevline; 126 | const char *lastfile; 127 | const char *prevfile; 128 | }; 129 | 130 | #define TRACEBUF struct qm_trace trace; 131 | #define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } , 132 | 133 | #define QMD_TRACE_HEAD(head) do { \ 134 | (head)->trace.prevline = (head)->trace.lastline; \ 135 | (head)->trace.prevfile = (head)->trace.lastfile; \ 136 | (head)->trace.lastline = __LINE__; \ 137 | (head)->trace.lastfile = __FILE__; \ 138 | } while (0) 139 | 140 | #define QMD_TRACE_ELEM(elem) do { \ 141 | (elem)->trace.prevline = (elem)->trace.lastline; \ 142 | (elem)->trace.prevfile = (elem)->trace.lastfile; \ 143 | (elem)->trace.lastline = __LINE__; \ 144 | (elem)->trace.lastfile = __FILE__; \ 145 | } while (0) 146 | 147 | #else /* !QUEUE_MACRO_DEBUG_TRACE */ 148 | #define QMD_TRACE_ELEM(elem) 149 | #define QMD_TRACE_HEAD(head) 150 | #define TRACEBUF 151 | #define TRACEBUF_INITIALIZER 152 | #endif /* QUEUE_MACRO_DEBUG_TRACE */ 153 | 154 | #ifdef QUEUE_MACRO_DEBUG_TRASH 155 | #define TRASHIT(x) do {(x) = (void *)-1;} while (0) 156 | #define QMD_IS_TRASHED(x) ((x) == (void *)(intptr_t)-1) 157 | #else /* !QUEUE_MACRO_DEBUG_TRASH */ 158 | #define TRASHIT(x) 159 | #define QMD_IS_TRASHED(x) 0 160 | #endif /* QUEUE_MACRO_DEBUG_TRASH */ 161 | 162 | #if defined(QUEUE_MACRO_DEBUG_TRACE) || defined(QUEUE_MACRO_DEBUG_TRASH) 163 | #define QMD_SAVELINK(name, link) void **name = (void *)&(link) 164 | #else /* !QUEUE_MACRO_DEBUG_TRACE && !QUEUE_MACRO_DEBUG_TRASH */ 165 | #define QMD_SAVELINK(name, link) 166 | #endif /* QUEUE_MACRO_DEBUG_TRACE || QUEUE_MACRO_DEBUG_TRASH */ 167 | 168 | #ifdef __cplusplus 169 | /* 170 | * In C++ there can be structure lists and class lists: 171 | */ 172 | #define QUEUE_TYPEOF(type) type 173 | #else 174 | #define QUEUE_TYPEOF(type) struct type 175 | #endif 176 | 177 | /* 178 | * Singly-linked List declarations. 179 | */ 180 | #define SLIST_HEAD(name, type) \ 181 | struct name { \ 182 | struct type *slh_first; /* first element */ \ 183 | } 184 | 185 | #define SLIST_CLASS_HEAD(name, type) \ 186 | struct name { \ 187 | class type *slh_first; /* first element */ \ 188 | } 189 | 190 | #define SLIST_HEAD_INITIALIZER(head) \ 191 | { NULL } 192 | 193 | #define SLIST_ENTRY(type) \ 194 | struct { \ 195 | struct type *sle_next; /* next element */ \ 196 | } 197 | 198 | #define SLIST_CLASS_ENTRY(type) \ 199 | struct { \ 200 | class type *sle_next; /* next element */ \ 201 | } 202 | 203 | /* 204 | * Singly-linked List functions. 205 | */ 206 | #if (defined(_KERNEL) && defined(INVARIANTS)) 207 | #define QMD_SLIST_CHECK_PREVPTR(prevp, elm) do { \ 208 | if (*(prevp) != (elm)) \ 209 | panic("Bad prevptr *(%p) == %p != %p", \ 210 | (prevp), *(prevp), (elm)); \ 211 | } while (0) 212 | #else 213 | #define QMD_SLIST_CHECK_PREVPTR(prevp, elm) 214 | #endif 215 | 216 | #define SLIST_CONCAT(head1, head2, type, field) do { \ 217 | QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1); \ 218 | if (curelm == NULL) { \ 219 | if ((SLIST_FIRST(head1) = SLIST_FIRST(head2)) != NULL) \ 220 | SLIST_INIT(head2); \ 221 | } else if (SLIST_FIRST(head2) != NULL) { \ 222 | while (SLIST_NEXT(curelm, field) != NULL) \ 223 | curelm = SLIST_NEXT(curelm, field); \ 224 | SLIST_NEXT(curelm, field) = SLIST_FIRST(head2); \ 225 | SLIST_INIT(head2); \ 226 | } \ 227 | } while (0) 228 | 229 | #define SLIST_EMPTY(head) ((head)->slh_first == NULL) 230 | 231 | #define SLIST_FIRST(head) ((head)->slh_first) 232 | 233 | #define SLIST_FOREACH(var, head, field) \ 234 | for ((var) = SLIST_FIRST((head)); \ 235 | (var); \ 236 | (var) = SLIST_NEXT((var), field)) 237 | 238 | #define SLIST_FOREACH_FROM(var, head, field) \ 239 | for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ 240 | (var); \ 241 | (var) = SLIST_NEXT((var), field)) 242 | 243 | #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ 244 | for ((var) = SLIST_FIRST((head)); \ 245 | (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ 246 | (var) = (tvar)) 247 | 248 | #define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ 249 | for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ 250 | (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ 251 | (var) = (tvar)) 252 | 253 | #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ 254 | for ((varp) = &SLIST_FIRST((head)); \ 255 | ((var) = *(varp)) != NULL; \ 256 | (varp) = &SLIST_NEXT((var), field)) 257 | 258 | #define SLIST_INIT(head) do { \ 259 | SLIST_FIRST((head)) = NULL; \ 260 | } while (0) 261 | 262 | #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ 263 | SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ 264 | SLIST_NEXT((slistelm), field) = (elm); \ 265 | } while (0) 266 | 267 | #define SLIST_INSERT_HEAD(head, elm, field) do { \ 268 | SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ 269 | SLIST_FIRST((head)) = (elm); \ 270 | } while (0) 271 | 272 | #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) 273 | 274 | #define SLIST_REMOVE(head, elm, type, field) do { \ 275 | QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ 276 | if (SLIST_FIRST((head)) == (elm)) { \ 277 | SLIST_REMOVE_HEAD((head), field); \ 278 | } \ 279 | else { \ 280 | QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head); \ 281 | while (SLIST_NEXT(curelm, field) != (elm)) \ 282 | curelm = SLIST_NEXT(curelm, field); \ 283 | SLIST_REMOVE_AFTER(curelm, field); \ 284 | } \ 285 | TRASHIT(*oldnext); \ 286 | } while (0) 287 | 288 | #define SLIST_REMOVE_AFTER(elm, field) do { \ 289 | SLIST_NEXT(elm, field) = \ 290 | SLIST_NEXT(SLIST_NEXT(elm, field), field); \ 291 | } while (0) 292 | 293 | #define SLIST_REMOVE_HEAD(head, field) do { \ 294 | SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ 295 | } while (0) 296 | 297 | #define SLIST_REMOVE_PREVPTR(prevp, elm, field) do { \ 298 | QMD_SLIST_CHECK_PREVPTR(prevp, elm); \ 299 | *(prevp) = SLIST_NEXT(elm, field); \ 300 | TRASHIT((elm)->field.sle_next); \ 301 | } while (0) 302 | 303 | #define SLIST_SWAP(head1, head2, type) do { \ 304 | QUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1); \ 305 | SLIST_FIRST(head1) = SLIST_FIRST(head2); \ 306 | SLIST_FIRST(head2) = swap_first; \ 307 | } while (0) 308 | 309 | /* 310 | * Singly-linked Tail queue declarations. 311 | */ 312 | #define STAILQ_HEAD(name, type) \ 313 | struct name { \ 314 | struct type *stqh_first;/* first element */ \ 315 | struct type **stqh_last;/* addr of last next element */ \ 316 | } 317 | 318 | #define STAILQ_CLASS_HEAD(name, type) \ 319 | struct name { \ 320 | class type *stqh_first; /* first element */ \ 321 | class type **stqh_last; /* addr of last next element */ \ 322 | } 323 | 324 | #define STAILQ_HEAD_INITIALIZER(head) \ 325 | { NULL, &(head).stqh_first } 326 | 327 | #define STAILQ_ENTRY(type) \ 328 | struct { \ 329 | struct type *stqe_next; /* next element */ \ 330 | } 331 | 332 | #define STAILQ_CLASS_ENTRY(type) \ 333 | struct { \ 334 | class type *stqe_next; /* next element */ \ 335 | } 336 | 337 | /* 338 | * Singly-linked Tail queue functions. 339 | */ 340 | #define STAILQ_CONCAT(head1, head2) do { \ 341 | if (!STAILQ_EMPTY((head2))) { \ 342 | *(head1)->stqh_last = (head2)->stqh_first; \ 343 | (head1)->stqh_last = (head2)->stqh_last; \ 344 | STAILQ_INIT((head2)); \ 345 | } \ 346 | } while (0) 347 | 348 | #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) 349 | 350 | #define STAILQ_FIRST(head) ((head)->stqh_first) 351 | 352 | #define STAILQ_FOREACH(var, head, field) \ 353 | for((var) = STAILQ_FIRST((head)); \ 354 | (var); \ 355 | (var) = STAILQ_NEXT((var), field)) 356 | 357 | #define STAILQ_FOREACH_FROM(var, head, field) \ 358 | for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ 359 | (var); \ 360 | (var) = STAILQ_NEXT((var), field)) 361 | 362 | #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ 363 | for ((var) = STAILQ_FIRST((head)); \ 364 | (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ 365 | (var) = (tvar)) 366 | 367 | #define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ 368 | for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ 369 | (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ 370 | (var) = (tvar)) 371 | 372 | #define STAILQ_INIT(head) do { \ 373 | STAILQ_FIRST((head)) = NULL; \ 374 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 375 | } while (0) 376 | 377 | #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ 378 | if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ 379 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 380 | STAILQ_NEXT((tqelm), field) = (elm); \ 381 | } while (0) 382 | 383 | #define STAILQ_INSERT_HEAD(head, elm, field) do { \ 384 | if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ 385 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 386 | STAILQ_FIRST((head)) = (elm); \ 387 | } while (0) 388 | 389 | #define STAILQ_INSERT_TAIL(head, elm, field) do { \ 390 | STAILQ_NEXT((elm), field) = NULL; \ 391 | *(head)->stqh_last = (elm); \ 392 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 393 | } while (0) 394 | 395 | #define STAILQ_LAST(head, type, field) \ 396 | (STAILQ_EMPTY((head)) ? NULL : \ 397 | __containerof((head)->stqh_last, \ 398 | QUEUE_TYPEOF(type), field.stqe_next)) 399 | 400 | #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) 401 | 402 | #define STAILQ_REMOVE(head, elm, type, field) do { \ 403 | QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ 404 | if (STAILQ_FIRST((head)) == (elm)) { \ 405 | STAILQ_REMOVE_HEAD((head), field); \ 406 | } \ 407 | else { \ 408 | QUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head); \ 409 | while (STAILQ_NEXT(curelm, field) != (elm)) \ 410 | curelm = STAILQ_NEXT(curelm, field); \ 411 | STAILQ_REMOVE_AFTER(head, curelm, field); \ 412 | } \ 413 | TRASHIT(*oldnext); \ 414 | } while (0) 415 | 416 | #define STAILQ_REMOVE_AFTER(head, elm, field) do { \ 417 | if ((STAILQ_NEXT(elm, field) = \ 418 | STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ 419 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 420 | } while (0) 421 | 422 | #define STAILQ_REMOVE_HEAD(head, field) do { \ 423 | if ((STAILQ_FIRST((head)) = \ 424 | STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ 425 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 426 | } while (0) 427 | 428 | #define STAILQ_SWAP(head1, head2, type) do { \ 429 | QUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1); \ 430 | QUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last; \ 431 | STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ 432 | (head1)->stqh_last = (head2)->stqh_last; \ 433 | STAILQ_FIRST(head2) = swap_first; \ 434 | (head2)->stqh_last = swap_last; \ 435 | if (STAILQ_EMPTY(head1)) \ 436 | (head1)->stqh_last = &STAILQ_FIRST(head1); \ 437 | if (STAILQ_EMPTY(head2)) \ 438 | (head2)->stqh_last = &STAILQ_FIRST(head2); \ 439 | } while (0) 440 | 441 | 442 | /* 443 | * List declarations. 444 | */ 445 | #define LIST_HEAD(name, type) \ 446 | struct name { \ 447 | struct type *lh_first; /* first element */ \ 448 | } 449 | 450 | #define LIST_CLASS_HEAD(name, type) \ 451 | struct name { \ 452 | class type *lh_first; /* first element */ \ 453 | } 454 | 455 | #define LIST_HEAD_INITIALIZER(head) \ 456 | { NULL } 457 | 458 | #define LIST_ENTRY(type) \ 459 | struct { \ 460 | struct type *le_next; /* next element */ \ 461 | struct type **le_prev; /* address of previous next element */ \ 462 | } 463 | 464 | #define LIST_CLASS_ENTRY(type) \ 465 | struct { \ 466 | class type *le_next; /* next element */ \ 467 | class type **le_prev; /* address of previous next element */ \ 468 | } 469 | 470 | /* 471 | * List functions. 472 | */ 473 | 474 | #if (defined(_KERNEL) && defined(INVARIANTS)) 475 | /* 476 | * QMD_LIST_CHECK_HEAD(LIST_HEAD *head, LIST_ENTRY NAME) 477 | * 478 | * If the list is non-empty, validates that the first element of the list 479 | * points back at 'head.' 480 | */ 481 | #define QMD_LIST_CHECK_HEAD(head, field) do { \ 482 | if (LIST_FIRST((head)) != NULL && \ 483 | LIST_FIRST((head))->field.le_prev != \ 484 | &LIST_FIRST((head))) \ 485 | panic("Bad list head %p first->prev != head", (head)); \ 486 | } while (0) 487 | 488 | /* 489 | * QMD_LIST_CHECK_NEXT(TYPE *elm, LIST_ENTRY NAME) 490 | * 491 | * If an element follows 'elm' in the list, validates that the next element 492 | * points back at 'elm.' 493 | */ 494 | #define QMD_LIST_CHECK_NEXT(elm, field) do { \ 495 | if (LIST_NEXT((elm), field) != NULL && \ 496 | LIST_NEXT((elm), field)->field.le_prev != \ 497 | &((elm)->field.le_next)) \ 498 | panic("Bad link elm %p next->prev != elm", (elm)); \ 499 | } while (0) 500 | 501 | /* 502 | * QMD_LIST_CHECK_PREV(TYPE *elm, LIST_ENTRY NAME) 503 | * 504 | * Validates that the previous element (or head of the list) points to 'elm.' 505 | */ 506 | #define QMD_LIST_CHECK_PREV(elm, field) do { \ 507 | if (*(elm)->field.le_prev != (elm)) \ 508 | panic("Bad link elm %p prev->next != elm", (elm)); \ 509 | } while (0) 510 | #else 511 | #define QMD_LIST_CHECK_HEAD(head, field) 512 | #define QMD_LIST_CHECK_NEXT(elm, field) 513 | #define QMD_LIST_CHECK_PREV(elm, field) 514 | #endif /* (_KERNEL && INVARIANTS) */ 515 | 516 | #define LIST_CONCAT(head1, head2, type, field) do { \ 517 | QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1); \ 518 | if (curelm == NULL) { \ 519 | if ((LIST_FIRST(head1) = LIST_FIRST(head2)) != NULL) { \ 520 | LIST_FIRST(head2)->field.le_prev = \ 521 | &LIST_FIRST((head1)); \ 522 | LIST_INIT(head2); \ 523 | } \ 524 | } else if (LIST_FIRST(head2) != NULL) { \ 525 | while (LIST_NEXT(curelm, field) != NULL) \ 526 | curelm = LIST_NEXT(curelm, field); \ 527 | LIST_NEXT(curelm, field) = LIST_FIRST(head2); \ 528 | LIST_FIRST(head2)->field.le_prev = &LIST_NEXT(curelm, field); \ 529 | LIST_INIT(head2); \ 530 | } \ 531 | } while (0) 532 | 533 | #define LIST_EMPTY(head) ((head)->lh_first == NULL) 534 | 535 | #define LIST_FIRST(head) ((head)->lh_first) 536 | 537 | #define LIST_FOREACH(var, head, field) \ 538 | for ((var) = LIST_FIRST((head)); \ 539 | (var); \ 540 | (var) = LIST_NEXT((var), field)) 541 | 542 | #define LIST_FOREACH_FROM(var, head, field) \ 543 | for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ 544 | (var); \ 545 | (var) = LIST_NEXT((var), field)) 546 | 547 | #define LIST_FOREACH_SAFE(var, head, field, tvar) \ 548 | for ((var) = LIST_FIRST((head)); \ 549 | (var) && ((tvar) = LIST_NEXT((var), field), 1); \ 550 | (var) = (tvar)) 551 | 552 | #define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ 553 | for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ 554 | (var) && ((tvar) = LIST_NEXT((var), field), 1); \ 555 | (var) = (tvar)) 556 | 557 | #define LIST_INIT(head) do { \ 558 | LIST_FIRST((head)) = NULL; \ 559 | } while (0) 560 | 561 | #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 562 | QMD_LIST_CHECK_NEXT(listelm, field); \ 563 | if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ 564 | LIST_NEXT((listelm), field)->field.le_prev = \ 565 | &LIST_NEXT((elm), field); \ 566 | LIST_NEXT((listelm), field) = (elm); \ 567 | (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ 568 | } while (0) 569 | 570 | #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 571 | QMD_LIST_CHECK_PREV(listelm, field); \ 572 | (elm)->field.le_prev = (listelm)->field.le_prev; \ 573 | LIST_NEXT((elm), field) = (listelm); \ 574 | *(listelm)->field.le_prev = (elm); \ 575 | (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ 576 | } while (0) 577 | 578 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 579 | QMD_LIST_CHECK_HEAD((head), field); \ 580 | if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ 581 | LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ 582 | LIST_FIRST((head)) = (elm); \ 583 | (elm)->field.le_prev = &LIST_FIRST((head)); \ 584 | } while (0) 585 | 586 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 587 | 588 | #define LIST_PREV(elm, head, type, field) \ 589 | ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \ 590 | __containerof((elm)->field.le_prev, \ 591 | QUEUE_TYPEOF(type), field.le_next)) 592 | 593 | #define LIST_REMOVE(elm, field) do { \ 594 | QMD_SAVELINK(oldnext, (elm)->field.le_next); \ 595 | QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ 596 | QMD_LIST_CHECK_NEXT(elm, field); \ 597 | QMD_LIST_CHECK_PREV(elm, field); \ 598 | if (LIST_NEXT((elm), field) != NULL) \ 599 | LIST_NEXT((elm), field)->field.le_prev = \ 600 | (elm)->field.le_prev; \ 601 | *(elm)->field.le_prev = LIST_NEXT((elm), field); \ 602 | TRASHIT(*oldnext); \ 603 | TRASHIT(*oldprev); \ 604 | } while (0) 605 | 606 | #define LIST_SWAP(head1, head2, type, field) do { \ 607 | QUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1); \ 608 | LIST_FIRST((head1)) = LIST_FIRST((head2)); \ 609 | LIST_FIRST((head2)) = swap_tmp; \ 610 | if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ 611 | swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ 612 | if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ 613 | swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ 614 | } while (0) 615 | 616 | /* 617 | * Tail queue declarations. 618 | */ 619 | #define TAILQ_HEAD(name, type) \ 620 | struct name { \ 621 | struct type *tqh_first; /* first element */ \ 622 | struct type **tqh_last; /* addr of last next element */ \ 623 | TRACEBUF \ 624 | } 625 | 626 | #define TAILQ_CLASS_HEAD(name, type) \ 627 | struct name { \ 628 | class type *tqh_first; /* first element */ \ 629 | class type **tqh_last; /* addr of last next element */ \ 630 | TRACEBUF \ 631 | } 632 | 633 | #define TAILQ_HEAD_INITIALIZER(head) \ 634 | { NULL, &(head).tqh_first, TRACEBUF_INITIALIZER } 635 | 636 | #define TAILQ_ENTRY(type) \ 637 | struct { \ 638 | struct type *tqe_next; /* next element */ \ 639 | struct type **tqe_prev; /* address of previous next element */ \ 640 | TRACEBUF \ 641 | } 642 | 643 | #define TAILQ_CLASS_ENTRY(type) \ 644 | struct { \ 645 | class type *tqe_next; /* next element */ \ 646 | class type **tqe_prev; /* address of previous next element */ \ 647 | TRACEBUF \ 648 | } 649 | 650 | /* 651 | * Tail queue functions. 652 | */ 653 | #if (defined(_KERNEL) && defined(INVARIANTS)) 654 | /* 655 | * QMD_TAILQ_CHECK_HEAD(TAILQ_HEAD *head, TAILQ_ENTRY NAME) 656 | * 657 | * If the tailq is non-empty, validates that the first element of the tailq 658 | * points back at 'head.' 659 | */ 660 | #define QMD_TAILQ_CHECK_HEAD(head, field) do { \ 661 | if (!TAILQ_EMPTY(head) && \ 662 | TAILQ_FIRST((head))->field.tqe_prev != \ 663 | &TAILQ_FIRST((head))) \ 664 | panic("Bad tailq head %p first->prev != head", (head)); \ 665 | } while (0) 666 | 667 | /* 668 | * QMD_TAILQ_CHECK_TAIL(TAILQ_HEAD *head, TAILQ_ENTRY NAME) 669 | * 670 | * Validates that the tail of the tailq is a pointer to pointer to NULL. 671 | */ 672 | #define QMD_TAILQ_CHECK_TAIL(head, field) do { \ 673 | if (*(head)->tqh_last != NULL) \ 674 | panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ 675 | } while (0) 676 | 677 | /* 678 | * QMD_TAILQ_CHECK_NEXT(TYPE *elm, TAILQ_ENTRY NAME) 679 | * 680 | * If an element follows 'elm' in the tailq, validates that the next element 681 | * points back at 'elm.' 682 | */ 683 | #define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ 684 | if (TAILQ_NEXT((elm), field) != NULL && \ 685 | TAILQ_NEXT((elm), field)->field.tqe_prev != \ 686 | &((elm)->field.tqe_next)) \ 687 | panic("Bad link elm %p next->prev != elm", (elm)); \ 688 | } while (0) 689 | 690 | /* 691 | * QMD_TAILQ_CHECK_PREV(TYPE *elm, TAILQ_ENTRY NAME) 692 | * 693 | * Validates that the previous element (or head of the tailq) points to 'elm.' 694 | */ 695 | #define QMD_TAILQ_CHECK_PREV(elm, field) do { \ 696 | if (*(elm)->field.tqe_prev != (elm)) \ 697 | panic("Bad link elm %p prev->next != elm", (elm)); \ 698 | } while (0) 699 | #else 700 | #define QMD_TAILQ_CHECK_HEAD(head, field) 701 | #define QMD_TAILQ_CHECK_TAIL(head, headname) 702 | #define QMD_TAILQ_CHECK_NEXT(elm, field) 703 | #define QMD_TAILQ_CHECK_PREV(elm, field) 704 | #endif /* (_KERNEL && INVARIANTS) */ 705 | 706 | #define TAILQ_CONCAT(head1, head2, field) do { \ 707 | if (!TAILQ_EMPTY(head2)) { \ 708 | *(head1)->tqh_last = (head2)->tqh_first; \ 709 | (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ 710 | (head1)->tqh_last = (head2)->tqh_last; \ 711 | TAILQ_INIT((head2)); \ 712 | QMD_TRACE_HEAD(head1); \ 713 | QMD_TRACE_HEAD(head2); \ 714 | } \ 715 | } while (0) 716 | 717 | #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) 718 | 719 | #define TAILQ_FIRST(head) ((head)->tqh_first) 720 | 721 | #define TAILQ_FOREACH(var, head, field) \ 722 | for ((var) = TAILQ_FIRST((head)); \ 723 | (var); \ 724 | (var) = TAILQ_NEXT((var), field)) 725 | 726 | #define TAILQ_FOREACH_FROM(var, head, field) \ 727 | for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ 728 | (var); \ 729 | (var) = TAILQ_NEXT((var), field)) 730 | 731 | #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ 732 | for ((var) = TAILQ_FIRST((head)); \ 733 | (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ 734 | (var) = (tvar)) 735 | 736 | #define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ 737 | for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ 738 | (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ 739 | (var) = (tvar)) 740 | 741 | #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ 742 | for ((var) = TAILQ_LAST((head), headname); \ 743 | (var); \ 744 | (var) = TAILQ_PREV((var), headname, field)) 745 | 746 | #define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ 747 | for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ 748 | (var); \ 749 | (var) = TAILQ_PREV((var), headname, field)) 750 | 751 | #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ 752 | for ((var) = TAILQ_LAST((head), headname); \ 753 | (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ 754 | (var) = (tvar)) 755 | 756 | #define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ 757 | for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ 758 | (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ 759 | (var) = (tvar)) 760 | 761 | #define TAILQ_INIT(head) do { \ 762 | TAILQ_FIRST((head)) = NULL; \ 763 | (head)->tqh_last = &TAILQ_FIRST((head)); \ 764 | QMD_TRACE_HEAD(head); \ 765 | } while (0) 766 | 767 | #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 768 | QMD_TAILQ_CHECK_NEXT(listelm, field); \ 769 | if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ 770 | TAILQ_NEXT((elm), field)->field.tqe_prev = \ 771 | &TAILQ_NEXT((elm), field); \ 772 | else { \ 773 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 774 | QMD_TRACE_HEAD(head); \ 775 | } \ 776 | TAILQ_NEXT((listelm), field) = (elm); \ 777 | (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ 778 | QMD_TRACE_ELEM(&(elm)->field); \ 779 | QMD_TRACE_ELEM(&(listelm)->field); \ 780 | } while (0) 781 | 782 | #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ 783 | QMD_TAILQ_CHECK_PREV(listelm, field); \ 784 | (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ 785 | TAILQ_NEXT((elm), field) = (listelm); \ 786 | *(listelm)->field.tqe_prev = (elm); \ 787 | (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ 788 | QMD_TRACE_ELEM(&(elm)->field); \ 789 | QMD_TRACE_ELEM(&(listelm)->field); \ 790 | } while (0) 791 | 792 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ 793 | QMD_TAILQ_CHECK_HEAD(head, field); \ 794 | if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ 795 | TAILQ_FIRST((head))->field.tqe_prev = \ 796 | &TAILQ_NEXT((elm), field); \ 797 | else \ 798 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 799 | TAILQ_FIRST((head)) = (elm); \ 800 | (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ 801 | QMD_TRACE_HEAD(head); \ 802 | QMD_TRACE_ELEM(&(elm)->field); \ 803 | } while (0) 804 | 805 | #define TAILQ_INSERT_TAIL(head, elm, field) do { \ 806 | QMD_TAILQ_CHECK_TAIL(head, field); \ 807 | TAILQ_NEXT((elm), field) = NULL; \ 808 | (elm)->field.tqe_prev = (head)->tqh_last; \ 809 | *(head)->tqh_last = (elm); \ 810 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 811 | QMD_TRACE_HEAD(head); \ 812 | QMD_TRACE_ELEM(&(elm)->field); \ 813 | } while (0) 814 | 815 | #define TAILQ_LAST(head, headname) \ 816 | (*(((struct headname *)((head)->tqh_last))->tqh_last)) 817 | 818 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) 819 | 820 | #define TAILQ_PREV(elm, headname, field) \ 821 | (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) 822 | 823 | #define TAILQ_REMOVE(head, elm, field) do { \ 824 | QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ 825 | QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ 826 | QMD_TAILQ_CHECK_NEXT(elm, field); \ 827 | QMD_TAILQ_CHECK_PREV(elm, field); \ 828 | if ((TAILQ_NEXT((elm), field)) != NULL) \ 829 | TAILQ_NEXT((elm), field)->field.tqe_prev = \ 830 | (elm)->field.tqe_prev; \ 831 | else { \ 832 | (head)->tqh_last = (elm)->field.tqe_prev; \ 833 | QMD_TRACE_HEAD(head); \ 834 | } \ 835 | *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ 836 | TRASHIT(*oldnext); \ 837 | TRASHIT(*oldprev); \ 838 | QMD_TRACE_ELEM(&(elm)->field); \ 839 | } while (0) 840 | 841 | #define TAILQ_SWAP(head1, head2, type, field) do { \ 842 | QUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first; \ 843 | QUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last; \ 844 | (head1)->tqh_first = (head2)->tqh_first; \ 845 | (head1)->tqh_last = (head2)->tqh_last; \ 846 | (head2)->tqh_first = swap_first; \ 847 | (head2)->tqh_last = swap_last; \ 848 | if ((swap_first = (head1)->tqh_first) != NULL) \ 849 | swap_first->field.tqe_prev = &(head1)->tqh_first; \ 850 | else \ 851 | (head1)->tqh_last = &(head1)->tqh_first; \ 852 | if ((swap_first = (head2)->tqh_first) != NULL) \ 853 | swap_first->field.tqe_prev = &(head2)->tqh_first; \ 854 | else \ 855 | (head2)->tqh_last = &(head2)->tqh_first; \ 856 | } while (0) 857 | 858 | #endif /* !_SYS_QUEUE_H_ */ 859 | -------------------------------------------------------------------------------- /tcpcontest.c: -------------------------------------------------------------------------------- 1 | /* tcpcontest -- simple connect(2) test program 2 | * 3 | * Copyright Ⓒ 2017-2019 Thomas Girard 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * * Neither the name of the author nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | */ 34 | 35 | #ifdef _WIN32 36 | #define _CRT_SECURE_NO_WARNINGS 37 | #include 38 | #define WIN32_LEAN_AND_MEAN 39 | #include 40 | #include 41 | #include 42 | #pragma comment(lib, "Ws2_32.lib") 43 | #define close closesocket 44 | #else 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #define SOCKET int 51 | #define gai_strerrorA gai_strerror 52 | #endif 53 | 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | 60 | static const char *me; 61 | 62 | static int 63 | usage (int retcode) 64 | { 65 | FILE *out = retcode ? stderr : stdout; 66 | fprintf (out, "Usage: %s HOST PORT\n", me); 67 | fprintf (out, "Invoke connect() on HOST:PORT and return call value\n"); 68 | exit (retcode); 69 | } 70 | 71 | int 72 | main (int argc, char *argv[]) 73 | { 74 | me = argv[0]; 75 | 76 | if (argc != 3) 77 | { 78 | usage (EXIT_FAILURE); 79 | } 80 | 81 | #ifdef _WIN32 82 | WSADATA wsaData; 83 | int err; 84 | 85 | err = WSAStartup(MAKEWORD(2, 2), &wsaData); 86 | 87 | if (err != 0) 88 | { 89 | fprintf(stderr, "WSAStartup: %d\n", err); 90 | exit(err); 91 | } 92 | #endif 93 | 94 | struct addrinfo hints; 95 | struct addrinfo *result, *rp; 96 | 97 | memset (&hints, 0, sizeof (struct addrinfo)); 98 | hints.ai_family = AF_UNSPEC; 99 | hints.ai_socktype = SOCK_STREAM; 100 | hints.ai_flags = 0; 101 | hints.ai_protocol = IPPROTO_TCP; 102 | 103 | int addr = getaddrinfo (argv[1], argv[2], &hints, &result); 104 | int rc = EXIT_FAILURE; 105 | 106 | if (addr != 0) 107 | { 108 | fprintf (stderr, "getaddrinfo: %s\n", gai_strerrorA (addr)); 109 | exit (rc); 110 | } 111 | 112 | for (rp = result; rp != NULL; rp = rp->ai_next) 113 | { 114 | SOCKET s = socket (rp->ai_family, rp->ai_socktype, 115 | rp->ai_protocol); 116 | if (s == -1) 117 | { 118 | continue; 119 | } 120 | 121 | char str[INET6_ADDRSTRLEN]; 122 | void *sinaddr = NULL; 123 | if (rp->ai_family == AF_INET) 124 | { 125 | struct sockaddr_in *sinp = (struct sockaddr_in *) rp->ai_addr; 126 | sinaddr = &sinp->sin_addr; 127 | } 128 | else 129 | { 130 | struct sockaddr_in6 *sin6p = (struct sockaddr_in6 *) rp->ai_addr; 131 | sinaddr = &sin6p->sin6_addr; 132 | } 133 | 134 | if (inet_ntop(rp->ai_family, sinaddr, str, INET6_ADDRSTRLEN) == NULL) { 135 | perror("inet_ntop"); 136 | exit(EXIT_FAILURE); 137 | } 138 | 139 | rc = connect (s, rp->ai_addr, (int) rp->ai_addrlen); 140 | 141 | if (rc == 0) 142 | { 143 | close (s); 144 | printf ("connect to %s is OK\n", str); 145 | break; 146 | } 147 | else if (rc == -1) 148 | { 149 | #ifndef _WIN32 150 | printf ("connect to %s is KO: errno is %d (%s)\n", str, errno, strerror(errno)); 151 | #else 152 | printf ("connect to %s is KO: errno is %d\n", str, WSAGetLastError()); 153 | #endif 154 | close (s); 155 | } 156 | else 157 | { 158 | printf ("connect to %s is OK?: return code is %d\n", str, rc); 159 | close (s); 160 | } 161 | } 162 | 163 | freeaddrinfo (result); 164 | 165 | #ifdef _WIN32 166 | WSACleanup (); 167 | #endif 168 | 169 | exit (rc); 170 | } 171 | -------------------------------------------------------------------------------- /testsuite: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Small testsuite to make sure parsing works and rules are applied 4 | # 5 | 6 | ecount=0 7 | 8 | _warn() { 9 | cat >&2 <&1 >/dev/null | grep ALLOW >/dev/null 58 | _footer 59 | } 60 | 61 | BLOCK() { 62 | _header BLOCK $* 63 | _run $* -l block 2>&1 >/dev/null | grep BLOCK >/dev/null 64 | _footer 65 | } 66 | 67 | ABORT_ON() { 68 | _header "abort on" $* 69 | _run $* -l error 2>&1 >/dev/null | grep ERROR >/dev/null 70 | _footer 71 | } 72 | 73 | 74 | WD="`dirname $0`" 75 | 76 | test -f "$WD/tcpcontest" || _die "Missing tcpcontest helper program!" 77 | 78 | ALLOW host ::1 port 50 with args -a ::1 -b \'*\' 79 | ALLOW host ::1 port 50 with args -a [::1] -b \'*\' 80 | ALLOW host ::1 port 50 with args -a [::1]:50 -b \'*\' 81 | BLOCK host ::1 port 50 with args -a [::1]:49 -b \'*\' 82 | ALLOW host ::1 port 21 with args -a [::1]:ftp -b \'*\' 83 | ALLOW host a::f port 50 with args -a a::f -b \'*\' 84 | BLOCK host a::f port 50 with args -b [a::f]:50 85 | ALLOW host 2001:db8:0:1:1:1:1:1 port 50 with args -a 2001:db8:0:1:1:1:1:1 -b \'*\' 86 | ALLOW host 127.0.0.1 port 50 with args -a 127.0.0.1:50 -b \'*\' 87 | ALLOW host 127.0.0.1 port 50 with args -a 127.0.0.1 -b \'*\' 88 | BLOCK host 127.0.0.1 port 50 with args -a 127.0.0.1:49 -b \'*\' 89 | ALLOW host 127.0.0.1 port 50 with args 90 | ALLOW host 127.0.0.1 port 50 with args -a \'*\' 91 | BLOCK host 127.0.0.1 port 50 with args -b \'*\' 92 | ALLOW host localhost port 50 with args -a localhost -b \'*\' 93 | BLOCK host localhost port 50 with args -a localhost:49 -b \'*\' 94 | ALLOW host 127.0.0.1 port 80 with args -a ::ffff:7f00:1 -b \'*\' 95 | BLOCK host 127.0.0.2 port 80 with args -a ::ffff:7f00:1 -b \'*\' 96 | ALLOW host 127.0.0.1 port 80 with args -a [::ffff:7f00:1]:80 -b \'*\' 97 | BLOCK host 127.0.0.1 port 80 with args -a [::ffff:7f00:1]:81 -b \'*\' 98 | ALLOW host ::ffff:7f00:1 port 80 with args -a 127.0.0.1 -b \'*\' 99 | BLOCK host ::ffff:7f00:2 port 80 with args -a 127.0.0.1 -b \'*\' 100 | ALLOW host ::ffff:7f00:1 port 80 with args -a 127.0.0.1:80 -b \'*\' 101 | BLOCK host ::ffff:7f00:1 port 80 with args -a 127.0.0.1:81 -b \'*\' 102 | ALLOW host www.google.com port 80 with args -d -a \'*.google.com\' -a \'*.1e100.net\' -b \'*\' 103 | #ALLOW host lwn.net port 80 with args -a lwn.net:80 -b \'*\' 104 | ABORT_ON host www.google.com port 80 with args -a \'*.google.com\' -a \'*.1e100.net\' -b \'*\' 105 | ABORT_ON host ::1 port 80 with args -a ab::g 106 | ABORT_ON host ::1 port 80 with args -a :::1 107 | ABORT_ON host ::1 port 80 with args -a a::b::a 108 | ABORT_ON host ::1 port 80 with args -a fffff:: 109 | ABORT_ON host localhost port 80 with args -a 256.168.10.192 110 | ABORT_ON host ::1 port 80 with args -a 256:fffff:: 111 | 112 | 113 | # CIDR notation test 114 | 115 | ## IPv4 116 | 117 | ### Standard 118 | ALLOW host 127.0.0.1 port 80 with args -a 127.0.0.1 -b \'*\' 119 | ALLOW host 127.0.0.1 port 80 with args -a 127.0.0.1/24 -b \'*\' 120 | BLOCK host 127.0.1.1 port 80 with args -a 127.0.0.1/24 -b \'*\' 121 | BLOCK host 127.0.0.1 port 80 with args -b 127.0.0.1 122 | 123 | ### With port 124 | ALLOW host 127.0.0.1 port 80 with args -a 127.0.0.0/24:80 -b \'*\' 125 | BLOCK host 127.0.0.1 port 80 with args -a 127.0.0.0/24:81 -b \'*\' 126 | BLOCK host 127.0.1.0 port 80 with args -a 127.0.0.0/24:80 -b \'*\' 127 | BLOCK host 127.0.0.1 port 80 with args -b 127.0.0.0/24:80 128 | ALLOW host 127.0.0.0 port 21 with args -a 127.0.0.0/24:ftp -b \'*\' 129 | 130 | ### Invalid syntax 131 | ABORT_ON host 127.0.0.1 port 80 with args -a 127.0.0.1/-1 -b \'*\' 132 | ABORT_ON host 127.0.0.1 port 80 with args -a 127.0.0.1/33 -b \'*\' 133 | ABORT_ON host 127.0.0.1 port 80 with args -a 127.0.0.1/a -b \'*\' 134 | ABORT_ON host 127.0.0.1 port 80 with args -a 127.0.0.1/ -b \'*\' 135 | ABORT_ON host 127.0.0.1 port 80 with args -a 127.0.0.1/24/24 -b \'*\' 136 | ABORT_ON host 127.0.0.1 port 80 with args -a 127.0.0.1:80/24 -b \'*\' 137 | ABORT_ON host 127.0.0.1 port 80 with args -a 127.0.0/24.1 -b \'*\' 138 | 139 | ### 32bit mask 140 | ALLOW host 127.0.0.1 port 80 with args -a 127.0.0.1/32 -b \'*\' 141 | BLOCK host 127.0.0.2 port 80 with args -a 127.0.0.1/32 -b \'*\' 142 | BLOCK host 127.0.0.1 port 80 with args -a 0.0.0.0/32 -b \'*\' 143 | BLOCK host 127.0.0.1 port 80 with args -a 255.255.255.255/32 -b \'*\' 144 | 145 | ### 31bit mask 146 | BLOCK host 127.0.0.1 port 80 with args -a 127.0.0.2/31 -b \'*\' 147 | ALLOW host 127.0.0.2 port 80 with args -a 127.0.0.2/31 -b \'*\' 148 | ALLOW host 127.0.0.3 port 80 with args -a 127.0.0.2/31 -b \'*\' 149 | BLOCK host 127.0.0.4 port 80 with args -a 127.0.0.2/31 -b \'*\' 150 | 151 | ### 1bit mask 152 | ALLOW host 1.0.0.0 port 80 with args -a 127.0.0.0/1 -b \'*\' 153 | ALLOW host 127.255.255.255 port 80 with args -a 127.0.0.0/1 -b \'*\' 154 | #BLOCK host 128.0.0.1 port 80 with args -a 127.0.0.0/1 -b \'*\' 155 | BLOCK host 255.255.255.255 port 80 with args -a 127.0.0.0/1 -b \'*\' 156 | 157 | ### 0bit mask 158 | ALLOW host 0.0.0.1 port 80 with args -a 127.0.0.0/0 -b \'*\' 159 | ALLOW host 0.0.1.0 port 80 with args -a 127.0.0.0/0 -b \'*\' 160 | ALLOW host 0.1.0.0 port 80 with args -a 127.0.0.0/0 -b \'*\' 161 | ALLOW host 1.0.0.0 port 80 with args -a 127.0.0.0/0 -b \'*\' 162 | ALLOW host 127.255.255.255 port 80 with args -a 127.0.0.0/0 -b \'*\' 163 | #ALLOW host 128.0.0.1 port 80 with args -a 127.0.0.0/0 -b \'*\' 164 | ALLOW host 255.255.255.255 port 80 with args -a 127.0.0.0/0 -b \'*\' 165 | ALLOW host 127.0.0.1 port 80 with args -a 0.0.0.0/0 -b \'*\' 166 | ALLOW host 127.0.0.1 port 80 with args -a 255.255.255.255/0 -b \'*\' 167 | 168 | ## IPv6 169 | 170 | ### Standard 171 | ALLOW host ::1 port 80 with args -a ::1 -b \'*\' 172 | ALLOW host ::1 port 80 with args -a ::1/24 -b \'*\' 173 | ALLOW host ::1 port 80 with args -a [::1/24] -b \'*\' 174 | BLOCK host a::1 port 80 with args -a ::1/24 -b \'*\' 175 | BLOCK host ::1 port 80 with args -b ::1 176 | 177 | ### With port 178 | ALLOW host ::1 port 80 with args -a [::1/24]:80 -b \'*\' 179 | BLOCK host ::1 port 80 with args -a [::1/24]:81 -b \'*\' 180 | BLOCK host a::1 port 80 with args -a [::1/24]:80 -b \'*\' 181 | BLOCK host ::1 port 80 with args -b [::1/24]:80 182 | ALLOW host ::1 port 21 with args -a [::1/24]:ftp -b \'*\' 183 | 184 | ### Invalid syntax 185 | ABORT_ON host ::1 port 80 with args -a ::1/-1 -b \'*\' 186 | ABORT_ON host ::1 port 80 with args -a ::1/129 -b \'*\' 187 | ABORT_ON host ::1 port 80 with args -a ::1/a -b \'*\' 188 | ABORT_ON host ::1 port 80 with args -a ::1/ -b \'*\' 189 | ABORT_ON host ::1 port 80 with args -a ::1/24/24 -b \'*\' 190 | ABORT_ON host ::1 port 80 with args -a [::1]/24 -b \'*\' 191 | ABORT_ON host ::1 port 80 with args -a [::1/24/24] -b \'*\' 192 | ABORT_ON host ::1 port 80 with args -a [::1/24]:80:80 -b \'*\' 193 | ABORT_ON host ::1 port 80 with args -a [::1]:80/24 -b \'*\' 194 | ABORT_ON host ::1 port 80 with args -a [:1:/24:1] -b \'*\' 195 | ABORT_ON host ::1 port 80 with args -a /24[::1] -b \'*\' 196 | 197 | ### 128bit mask 198 | ALLOW host ::1 port 80 with args -a ::1/128 -b \'*\' 199 | BLOCK host ::2 port 80 with args -a ::1/128 -b \'*\' 200 | BLOCK host ::1 port 80 with args -a ::0/128 -b \'*\' 201 | BLOCK host ::1 port 80 with args -a 2001:db8:0:1:1:1:1:1/128 -b \'*\' 202 | ALLOW host 2001:db8:0:1:1:1:1:1 port 80 with args -a 2001:db8:0:1:1:1:1:1/128 -b \'*\' 203 | 204 | ### 127bit mask 205 | BLOCK host ::1 port 80 with args -a ::2/127 -b \'*\' 206 | ALLOW host ::2 port 80 with args -a ::2/127 -b \'*\' 207 | ALLOW host ::3 port 80 with args -a ::2/127 -b \'*\' 208 | BLOCK host ::4 port 80 with args -a ::2/127 -b \'*\' 209 | 210 | ### 1bit mask 211 | BLOCK host ::0 port 80 with args -a 8000::/1 -b \'*\' 212 | BLOCK host 7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff port 80 with args -a 8000::/1 -b \'*\' 213 | ALLOW host 8000:: port 80 with args -a 8000::/1 -b \'*\' 214 | ALLOW host ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff port 80 with args -a 8000::/1 -b \'*\' 215 | 216 | ### 0bit mask 217 | ALLOW host 2001:db8:0:1:1:1:1:1 port 80 with args -a 8000::/0 -b \'*\' 218 | ALLOW host ::0 port 80 with args -a 8000::/0 -b \'*\' 219 | ALLOW host ::1 port 80 with args -a 8000::/0 -b \'*\' 220 | ALLOW host ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff port 80 with args -a 8000::/0 -b \'*\' 221 | ALLOW host ::1 port 80 with args -a ::0/0 -b \'*\' 222 | ALLOW host ::1 port 80 with args -a ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/0 -b \'*\' 223 | 224 | ## Rule IP is IPv4 mapping 225 | 226 | ### With port 227 | ALLOW host 127.1.1.1 port 80 with args -a [::ffff:7f01:101/128]:80 -b \'*\' 228 | BLOCK host 127.1.1.1 port 80 with args -a [::ffff:7f01:101/128]:81 -b \'*\' 229 | 230 | ### 128bit mask 231 | ALLOW host 127.1.1.1 port 80 with args -a ::ffff:7f01:101/128 -b \'*\' 232 | BLOCK host 127.1.1.2 port 80 with args -a ::ffff:7f01:101/128 -b \'*\' 233 | BLOCK host 128.1.1.1 port 80 with args -a ::ffff:7f01:101/128 -b \'*\' 234 | 235 | ### 127bit mask 236 | BLOCK host 127.0.0.1 port 80 with args -a ::ffff:7f00:2/127 -b \'*\' 237 | ALLOW host 127.0.0.2 port 80 with args -a ::ffff:7f00:2/127 -b \'*\' 238 | ALLOW host 127.0.0.3 port 80 with args -a ::ffff:7f00:2/127 -b \'*\' 239 | BLOCK host 127.0.0.4 port 80 with args -a ::ffff:7f00:2/127 -b \'*\' 240 | 241 | ### 97bit mask 242 | ALLOW host 1.0.0.0 port 80 with args -a ::ffff:7f01:101/97 -b \'*\' 243 | ALLOW host 127.255.255.255 port 80 with args -a ::ffff:7f01:101/97 -b \'*\' 244 | BLOCK host 128.0.0.0 port 80 with args -a ::ffff:7f01:101/97 -b \'*\' 245 | 246 | ### 96bit mask 247 | ALLOW host 127.0.0.1 port 80 with args -a ::ffff:7f01:101/96 -b \'*\' 248 | ALLOW host 128.1.1.2 port 80 with args -a ::ffff:7f01:101/96 -b \'*\' 249 | 250 | ### These are not treated as ipv4 mapped address because the rule side is masked. 251 | BLOCK host 127.0.0.1 port 80 with args -a ::ffff:7f00:1/0 -b \'*\' 252 | BLOCK host 127.0.0.1 port 80 with args -a ::ffff:7f00:1/95 -b \'*\' 253 | 254 | ## Connect IP is IPv4 mapping 255 | 256 | ### With port 257 | ALLOW host ::ffff:7f01:101 port 80 with args -a 127.1.1.1/32:80 -b \'*\' 258 | BLOCK host ::ffff:7f01:101 port 80 with args -a 127.1.1.1/32:81 -b \'*\' 259 | 260 | ### 32bit mask 261 | ALLOW host ::ffff:7f01:101 port 80 with args -a 127.1.1.1/32 -b \'*\' 262 | BLOCK host ::ffff:7f01:101 port 80 with args -a 127.1.1.2/32 -b \'*\' 263 | BLOCK host ::ffff:7f01:101 port 80 with args -a 128.1.1.1/32 -b \'*\' 264 | BLOCK host ::ffff:7f01:102 port 80 with args -a 127.1.1.1/32 -b \'*\' 265 | 266 | ### 31bit mask 267 | BLOCK host ::ffff:7f01:102 port 80 with args -a 127.1.1.1/31 -b \'*\' 268 | ALLOW host ::ffff:7f01:102 port 80 with args -a 127.1.1.2/31 -b \'*\' 269 | ALLOW host ::ffff:7f01:102 port 80 with args -a 127.1.1.3/31 -b \'*\' 270 | BLOCK host ::ffff:7f01:102 port 80 with args -a 127.1.1.4/31 -b \'*\' 271 | 272 | ### 1bit mask 273 | ALLOW host ::ffff:7f01:101 port 80 with args -a 127.1.1.1/1 -b \'*\' 274 | ALLOW host ::ffff:7f01:101 port 80 with args -a 127.1.1.2/1 -b \'*\' 275 | ALLOW host ::ffff:7f01:101 port 80 with args -a 127.1.2.1/1 -b \'*\' 276 | ALLOW host ::ffff:7f01:101 port 80 with args -a 127.2.1.1/1 -b \'*\' 277 | BLOCK host ::ffff:7f01:101 port 80 with args -a 128.1.1.1/1 -b \'*\' 278 | 279 | ### 0bit mask 280 | ALLOW host ::ffff:101:101 port 80 with args -a 127.1.1.1/0 -b \'*\' 281 | ALLOW host ::ffff:ffff:ffff port 80 with args -a 127.1.1.1/0 -b \'*\' 282 | 283 | ## hostname 284 | 285 | ### Invalid syntax 286 | ABORT_ON host localhost port 80 with args -a localhost/24 -b \'*\' 287 | 288 | 289 | if test $ecount -gt 0; then 290 | _die "$ecount test(s) failed!" 291 | else 292 | echo "All tests passed!" 293 | fi 294 | -------------------------------------------------------------------------------- /testsuite.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding(PositionalBinding=$false)] 2 | param () 3 | 4 | $scriptPath = Split-Path -parent $MyInvocation.MyCommand.Definition 5 | cd $scriptPath 6 | 7 | $LogTargets = @{"stderr" = 1; "syslog" = 2; "file" = 4} 8 | $LogLevels = @{"silent" = 0; "error" = 1; "block" = 2; "allow" = 3; "debug" = 4} 9 | 10 | # TODO: Sanity checks 11 | 12 | $ecount = 0 13 | 14 | function Test-Rule([string]$machine, [string]$port, [string]$level, [string]$allow, [string]$block) { 15 | # We need to reset variables before starting, since they are kept in the PS session 16 | $env:COC_ALLOW=$allow 17 | $env:COC_BLOCK=$block 18 | $env:COC_LOG_LEVEL=$LogLevels[$level] 19 | $env:COC_LOG_TARGET=$LogTargets["stderr"] 20 | Write-Host -NoNewline "Checking if we $level connection to ${machine}:$port with rules: {allow=$allow; block=$block} " 21 | $found=&{ .\coc.exe .\tcpcontest.exe $machine $port } 2>&1 | Select-String $level.ToUpper() 22 | if (!$found) { 23 | Write-Host "KO" 24 | $script:ecount++ 25 | } else { 26 | Write-Host "OK" 27 | } 28 | } 29 | 30 | function Test-Allow([string]$machine, [string]$port, [string]$allow, [string]$block) { 31 | Test-Rule -machine $machine -port $port -level "allow" -allow $allow -block $block 32 | } 33 | 34 | function Test-Block([string]$machine, [string]$port, [string]$allow, [string]$block) { 35 | Test-Rule -machine $machine -port $port -level "block" -allow $allow -block $block 36 | } 37 | 38 | Test-Allow -machine 127.0.0.1 -port 80 -allow 127.0.0.1 -block "*" 39 | Test-Allow -machine ::1 -port 50 -allow ::1 -block "*" 40 | Test-Allow -machine ::1 -port 50 -allow [::1] -block "*" 41 | Test-Allow -machine ::1 -port 50 -allow [::1]:50 -block "*" 42 | Test-Block -machine ::1 -port 50 -allow [::1]:49 -block "*" 43 | Test-Allow -machine ::1 -port 21 -allow [::1]:ftp -block "*" 44 | Test-Allow -machine a::f -port 50 -allow a::f -block "*" 45 | Test-Block -machine a::f -port 50 -block [a::f]:50 46 | Test-Allow -machine 2001:db8:0:1:1:1:1:1 -port 50 -allow 2001:db8:0:1:1:1:1:1 -block "*" 47 | Test-Allow -machine 127.0.0.1 -port 50 -allow 127.0.0.1:50 -block "*" 48 | Test-Allow -machine 127.0.0.1 -port 50 -allow 127.0.0.1 -block "*" 49 | Test-Block -machine 127.0.0.1 -port 50 -allow 127.0.0.1:49 -block "*" 50 | Test-Allow -machine 127.0.0.1 -port 50 51 | Test-Allow -machine 127.0.0.1 -port 50 -allow "*" 52 | Test-Block -machine 127.0.0.1 -port 50 -block "*" 53 | Test-Allow -machine localhost -port 50 -allow localhost -block "*" 54 | Test-Block -machine localhost -port 50 -allow localhost:49 -block "*" 55 | Test-Allow -machine 127.0.0.1 -port 80 -allow ::ffff:7f00:1 -block "*" 56 | Test-Block -machine 127.0.0.2 -port 80 -allow ::ffff:7f00:1 -block "*" 57 | Test-Allow -machine 127.0.0.1 -port 80 -allow [::ffff:7f00:1]:80 -block "*" 58 | Test-Block -machine 127.0.0.1 -port 80 -allow [::ffff:7f00:1]:81 -block "*" 59 | Test-Allow -machine ::ffff:7f00:1 -port 80 -allow 127.0.0.1 -block "*" 60 | Test-Block -machine ::ffff:7f00:2 -port 80 -allow 127.0.0.1 -block "*" 61 | Test-Allow -machine ::ffff:7f00:1 -port 80 -allow 127.0.0.1:80 -block "*" 62 | Test-Block -machine ::ffff:7f00:1 -port 80 -allow 127.0.0.1:81 -block "*" 63 | #Test-Allow -machine www.google.com -port 80 with args -d -a \'*.google.com\' -a \'*.1e100.net\' -block "*" 64 | 65 | 66 | if ($ecount -gt 0) { 67 | Write-Host -ForegroundColor Red "$ecount test(s) failed!" 68 | exit 1 69 | } else { 70 | Write-Host "All tests passed!" 71 | } 72 | -------------------------------------------------------------------------------- /vs2015/coc/Version.aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgg/connect-or-cut/21b859ac707dee6a91e88ed834a16423dfd52538/vs2015/coc/Version.aps -------------------------------------------------------------------------------- /vs2015/coc/Version.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgg/connect-or-cut/21b859ac707dee6a91e88ed834a16423dfd52538/vs2015/coc/Version.rc -------------------------------------------------------------------------------- /vs2015/coc/coc.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {09519461-063E-4EDA-A2DC-DBF87697C11A} 23 | Win32Proj 24 | coc 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 32.exe 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 32.exe 82 | 83 | 84 | false 85 | 86 | 87 | 88 | 89 | 90 | Level3 91 | Disabled 92 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 93 | 94 | 95 | Console 96 | true 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | Level3 106 | Disabled 107 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 108 | 109 | 110 | Console 111 | true 112 | 113 | 114 | 115 | 116 | Level3 117 | 118 | 119 | MaxSpeed 120 | true 121 | true 122 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 123 | 124 | 125 | Console 126 | true 127 | true 128 | true 129 | 130 | 131 | 132 | 133 | 134 | 135 | Level3 136 | 137 | 138 | MaxSpeed 139 | true 140 | true 141 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 142 | 143 | 144 | Console 145 | true 146 | true 147 | true 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | true 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /vs2015/coc/coc.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | 29 | 30 | Header Files 31 | 32 | 33 | 34 | 35 | Resource Files 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /vs2015/coc/coc.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | C:\Windows\System32\cmd.exe C:\Users\Thoma\Source\Repos\connect-or-cut\Debug\connect-or-cut.dll 5 | WindowsLocalDebugger 6 | 7 | 8 | "C:\Windows\System32\cmd.exe" /c "echo toto" 9 | WindowsLocalDebugger 10 | 11 | 12 | C:\Users\Thoma\Source\Repos\connect-or-cut\Debug\tcpcontest.exe 13 | WindowsLocalDebugger 14 | 15 | -------------------------------------------------------------------------------- /vs2015/coc/error.c: -------------------------------------------------------------------------------- 1 | /* error.c -- perror() + exit() for Windows. 2 | * 3 | * Copyright Ⓒ 2018-2019 Thomas Girard 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * * Neither the name of the author nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | /* 41 | * This function is based on: 42 | * https://msdn.microsoft.com/en-us/library/windows/desktop/ms680582(v=vs.85).aspx 43 | */ 44 | void ErrorExit(LPTSTR lpszFunction) 45 | { 46 | LPVOID lpError; 47 | LPVOID lpDisplayBuf; 48 | DWORD dwLastError = GetLastError(); 49 | 50 | FormatMessage( 51 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 52 | FORMAT_MESSAGE_FROM_SYSTEM | 53 | FORMAT_MESSAGE_IGNORE_INSERTS, 54 | NULL, dwLastError, 55 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 56 | (LPTSTR) &lpError, 0, NULL); 57 | 58 | lpDisplayBuf = (LPVOID) LocalAlloc( 59 | LMEM_ZEROINIT, 60 | (_tcslen((LPTSTR) lpError) + _tcslen(lpszFunction) + 14) * sizeof(TCHAR)); 61 | StringCchPrintf( 62 | (LPTSTR) lpDisplayBuf, 63 | LocalSize(lpDisplayBuf) / sizeof(TCHAR), 64 | _T("%s failed: %s"), 65 | lpszFunction, lpError); 66 | 67 | MessageBox(NULL, (LPCTSTR)lpDisplayBuf, _T("connect-or-cut"), MB_OK | MB_ICONERROR); 68 | 69 | LocalFree(lpError); 70 | LocalFree(lpDisplayBuf); 71 | ExitProcess(dwLastError); 72 | } 73 | -------------------------------------------------------------------------------- /vs2015/coc/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Version.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /vs2015/tcpcontest/Version.aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgg/connect-or-cut/21b859ac707dee6a91e88ed834a16423dfd52538/vs2015/tcpcontest/Version.aps -------------------------------------------------------------------------------- /vs2015/tcpcontest/Version.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgg/connect-or-cut/21b859ac707dee6a91e88ed834a16423dfd52538/vs2015/tcpcontest/Version.rc -------------------------------------------------------------------------------- /vs2015/tcpcontest/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Version.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /vs2015/tcpcontest/tcpcontest.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {1FF3631D-851D-4356-BB77-68F9B0E75C37} 35 | Win32Proj 36 | tcpcontest 37 | 8.1 38 | 39 | 40 | 41 | Application 42 | true 43 | v140 44 | Unicode 45 | 46 | 47 | Application 48 | false 49 | v140 50 | true 51 | Unicode 52 | 53 | 54 | Application 55 | true 56 | v140 57 | Unicode 58 | 59 | 60 | Application 61 | false 62 | v140 63 | true 64 | Unicode 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | true 86 | 32.exe 87 | 88 | 89 | true 90 | 91 | 92 | false 93 | 94 | 95 | false 96 | 97 | 98 | 99 | 100 | 101 | Level3 102 | Disabled 103 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | 105 | 106 | Console 107 | true 108 | 109 | 110 | 111 | 112 | 113 | 114 | Level3 115 | Disabled 116 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 117 | 118 | 119 | Console 120 | true 121 | 122 | 123 | 124 | 125 | Level3 126 | 127 | 128 | MaxSpeed 129 | true 130 | true 131 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 132 | 133 | 134 | Console 135 | true 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | Level3 143 | 144 | 145 | MaxSpeed 146 | true 147 | true 148 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 149 | 150 | 151 | Console 152 | true 153 | true 154 | true 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /vs2015/tcpcontest/tcpcontest.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | 28 | 29 | Resource Files 30 | 31 | 32 | 33 | 34 | 35 | --------------------------------------------------------------------------------