├── LICENSE ├── README.md ├── bridge.jpg ├── contrib ├── nginx.add.conf ├── openssl.cnf ├── proxywars.md └── pw.jpg ├── logo.jpg └── src ├── Makefile ├── Makefile.android ├── Makefile.android.aarch64 ├── config.cc ├── config.h ├── crashc.cc ├── crashd.cc ├── csession.cc ├── dh.cc ├── dh2048.cc ├── disguise.cc ├── disguise.h ├── global.cc ├── global.h ├── iobox.cc ├── iobox.h ├── log.cc ├── log.h ├── misc.cc ├── misc.h ├── missing.cc ├── missing.h ├── net.cc ├── net.h ├── newdh ├── pty.cc ├── pty.h ├── pty98.cc ├── server.cc ├── server.h ├── session.cc ├── session.h └── ssession.cc /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CRypted Admin SHell 2 | =================== 3 | 4 |

5 | 6 |

7 | 8 | An SSH alternative featuring: 9 | 10 | * IPv6 ready 11 | * lightweight, straight forward and extensible protocol using TLS 1.3 12 | or optionally DTLS 1.2 or QUIC as transport layer 13 | * man-in-the-middle safe due to its authentication mechanism 14 | which involves the servers host key into the auth process 15 | * built-in traffic blinding against timing and packet-size info-leak attacks 16 | * not relying on any system auth frameworks such as PAM 17 | * can be entirely run as user, no need to setup config files 18 | * passive/active connects on both ends with most flexible 19 | local/remote address+port binding possibilities 20 | * built-in SOCKS5 client side support when doing active connects 21 | * easy to port to embedded systems such as routers 22 | * quiet/hidden mode for secret administration and take-back 23 | functionality for owned boxes 24 | * trigger-mode via syslog, mail or other files if requested 25 | * emergency mode to extract all necessary key files from the running binary 26 | * may be started as a CGI with all above functionality, command 27 | switches passed via query-string 28 | * integrated tcp-wrapper-like D/DoS protection 29 | * intentionally not passing local $ENV to remote to avoid info leaks 30 | * supports Perfect Forward Secrecy via DH Kex 31 | * can forward TCP *and* UDP sockets to remote 32 | * SOCKS4 and SOCKS5 support to forward browser sessions to remote 33 | * messenger proxy support 34 | * proxying based on SNI 35 | * SNI hiding mode 36 | * Disguise Filters to mask as different kind of software to global observers 37 | * can use UDP transport mode with DTLS and added reliability and flow-control 38 | layer 39 | * transparent roaming support with DTLS client sessions 40 | * suspend/resume support with DTLS client sessions 41 | 42 | 43 | If you came here for censorship circumvention - once everything is done and working - go to 44 | [proxywars contrib](https://github.com/stealth/crash/blob/master/contrib/proxywars.md) 45 | to learn about how to create WA/TG messenger proxy setups in censorship environments. 46 | 47 | 48 | Build 49 | ----- 50 | 51 | Build requires *OpenSSL* version >= `1.1` or `3.0` or compatible *LibreSSL* (`3.6.1` tested). 52 | Inside the cloned git repo: 53 | 54 | ``` 55 | $ make -C src 56 | ``` 57 | 58 | On BSD systems you need to install *GNU make* and type `gmake` instead. 59 | 60 | If you have a particular *OpenSSL* or *LibreSSL* setup, check the `Makefile` and 61 | set the appropriate `$SSL` variable. *crash* builds also nicely with *LibreSSL* and *BoringSSL*. 62 | 63 | For Android, edit the `Makefile.android` or `Makefile.android.aarch64` to reflect 64 | your NDK and *BoringSSL* install and use these. The build was tested with `android-ndk-r17b`. 65 | 66 | On OSX you want to install *OpenSSL* via `brew install openssl@1.1` or by hand before `make`. 67 | 68 | On Windows you need to install [cygwin](https://cygwin.com/install.html) and select 69 | the appropriate `gcc, gcc-g++, perl, openssl (1.1.1), libssl (1.1.1), libssl-devel (1.1.1), make` 70 | and `git` packages before the build in order to clone and `make` this repo. Make sure 71 | your openssl versions for the tool itself, the runtime libs and devel package are all 72 | the same. 73 | 74 | *crash* was successfully tested on *Linux, FreeBSD, NetBSD, OpenSolaris, OSX and Android*. 75 | 76 | After that, to generate the required server and authentication keys: 77 | 78 | ``` 79 | $ make -C src keys 80 | ``` 81 | 82 | or see further instructions in this document. If you want to use _ephemeral keying_ 83 | (aka [PFS](https://en.wikipedia.org/wiki/Perfect_Forward_Secrecy)), invoke 84 | 85 | ``` 86 | $ cd src; ./newdh 87 | ``` 88 | 89 | before `make` in order to generate DH parameters before the build. Thats not strictly necessary 90 | as of TLS 1.3, since the Kex will most likely chose one of the ECDH variants, but if you customize 91 | your setup, it is recommended to generate your own DH params. 92 | 93 | If you want to use the experimental QUIC transport mode, you need to install `OpenSSL >= 3.5.0-beta1` 94 | and adjust the `Makefile` to `HAVE_QUIC` and the *OpenSSL* install path, similar to that which 95 | is commented out. You possibly also need to adjust the path of the openssl binary inside `newdh` 96 | if you were not using *OpenSSL3* already, and invoke `newdh` before the build. 97 | 98 | 99 | Legacy builds 100 | ------------- 101 | 102 | If `make` detects that TLSv1.3 is not available on the system or `TLS_COMPAT_DOWNGRADE` is 103 | defined, the binaries are built with TLSv1.2 only. This is to allow using it on legacy 104 | systems when no other options are available. Obviosuly, the built binaries are not 105 | compatible to normal builds, but include full support of all other features. 106 | 107 | 108 | OpenSSL3 builds 109 | --------------- 110 | 111 | The *OpenSSL 3* API is quite different from the *OpenSSL-1.1* API. In order to make 112 | use of *OpenSSL 3*, you have to edit `Makefile` and `newdh` to reflect your path setup 113 | for your *OpenSSL* install. Invoking `newdh` is mandatory, unlike for the 1.1 builds. After 114 | that you just do `make` and everything should be the same as with 1.1. 115 | 116 | *proudly sponsored by:* 117 |

118 | 119 | 120 | 121 |

122 | 123 | 124 | Run 125 | --- 126 | 127 | *crash* does not need any config or option files to run. Its easy and 128 | straight forward to use. Anything can be enabled/disabled by 129 | runtime switches: 130 | 131 | ``` 132 | stealth@linux ~> ./crashd -h 133 | 134 | crypted admin shell (C) 2024 Sebastian Krahmer https://github.com/stealth/crash 135 | 136 | 137 | Usage: ./crashd [-U] [-q] [-a] [-6] [-D] [-H host] [-p port] [-A auth keys] 138 | [-k server key-file] [-c server X509 cert] [-L [ip]:port] [-S SNI] 139 | [-t trigger-file] [-m trigger message] [-e] [-g good IPs] [-N] [-R] 140 | [-x socks5://[ip]:port] [-G method:prefix:action] [-w] 141 | 142 | -a -- always login if authenticated, despite false/nologin shells 143 | -U -- run as user (e.g. turn off setuid() calls) if invoked as such 144 | -e -- extract key and certfile from the binary itself (no -k/-c needed) 145 | -q -- quiet mode, turns off logging and utmp entries 146 | -6 -- use IPv6 rather than IPv4 147 | -w -- setproctitle to `[kthreadd]` (must be last arg!) 148 | -H -- host to connect to; if omitted: passive connect (default) 149 | -p -- port to connect to when active connect; default is 2222 150 | -L -- local [ip]:port used for binding ([0.0.0.0]:2222) 151 | -g -- file containing list of good IP/IP6's in D/DoS case (default off) 152 | -A -- authorized-key file for users if starts with '/'; folder inside ~ 153 | containing authorized_keys file otherwise; 'self' means to use 154 | blob-extraction (see -e); default is .crash 155 | -k -- servers key file; default is ./serverkey.priv 156 | -c -- X509 certificate-file that belongs to serverkey (-k); 157 | default is ./serverkey.pub 158 | -t -- watch triggerfile for certain message (-m) before connect/listen 159 | -m -- wait with connect/listen until message in file (-t) is seen 160 | -N -- disable TCP/UDP port forwarding 161 | -D -- use DTLS transport (requires -S) 162 | -x -- use this SOCKS5 proxy when using active connect 163 | -R -- allow clients to roam sessions 164 | -G -- Traffic Disguise Filters, check docu 165 | -S -- SNI to hide behind 166 | 167 | ``` 168 | 169 | Most of it is pretty self-explaining. *crashd* can run as user. `-U` lets *crashd* 170 | skip `setuid()` calls, effectively being able to run as user. In this case, it only accepts 171 | logins to that user then by checking login name's `uid` against current `euid`. 172 | Both, *crashc* and *crashd* can use active and passive connects. Whenever 173 | a host-argument `-H` is given, it uses active connect to this host 174 | and the belonging port `-p`. It also accepts `-L` which specifies the local address and port it has 175 | to bind to, either before doing active connect (`-H`) or passively (no `-H` given). 176 | This way - from TCP point view - client and server role may be reversed, while still having 177 | *crashd* as the shell server. 178 | If `-w` is used it forks itself as **[kthreadd]** and tries to wrap around its 179 | `pid` to be somewhere around the system daemons. As `-w` is overwriting main()'s `argv` array, 180 | it must appear last in the option list, otherwise option processing will not work 181 | correctly. You can set the process name as `TITLE` def inside the `Makefile`. 182 | 183 | For testing, when you did `make keys` (next section), you can just run 184 | 185 | ``` 186 | src $ ./crashd -U -L [127.0.0.1]:2222 187 | ``` 188 | (or omit `-L` paramater to bind to the default port on any address) and 189 | 190 | ``` 191 | src $ ./crashc -v -K none -i authkey.priv -H 127.0.0.1 -p 2222 -l $USER 192 | ``` 193 | 194 | 195 | Key setup 196 | --------- 197 | 198 | Unless you want to use SNI-hiding (see section below), you can type straight ahead: 199 | 200 | ``` 201 | $ make -C src keys 202 | ``` 203 | 204 | But you can also do it by hand. To generate a X509 certificate containing the server key: 205 | 206 | ``` 207 | $ umask 066 208 | $ openssl genrsa -out serverkey.priv 4096 209 | $ openssl req -new -x509 -nodes -sha1 -key serverkey.priv -out serverkey.pub 210 | ``` 211 | 212 | To extract the public key in a form *crashc* can use it as a hostkey for comparison: 213 | 214 | ``` 215 | $ openssl x509 -in serverkey.pub -pubkey -noout > HK_127.0.0.1 216 | ``` 217 | 218 | So you have `HK_127.0.0.1` as the known-hosts keyfile for this host. 219 | As an alternative, you can use *crashc* with `-v` upon connect to 220 | extract the pubkey. But note that this might already be a key presented to 221 | you during an attack. So only do that if you know that the connection is 222 | not tampered with (e.g. single user on localhost). 223 | 224 | Unless you use SNI hiding (see section below), the values you enter for *Country-Name, 225 | Email, CN* etc. do not matter since *crashc* is not validating the X509. It just 226 | compares the public key value it obtained from the server with the key it has in its 227 | local key-store belonging to that server (similar to *SSH*). 228 | The server key is not encrypted since *crashd* is usually started 229 | via init scripts. Instead, the key file must have proper permissions 230 | so only appropriate users can read it (mode 0600). You can, if you like, 231 | also encrypt the server key but then you have to enter a pass-phrase 232 | whenever *crashd* is started. 233 | 234 | To generate a public/private RSA keypair for your authentication: 235 | 236 | ``` 237 | $ openssl genrsa -out authkey.priv -aes256 4096 238 | $ openssl rsa -in authkey.priv -pubout -out authkey.pub 239 | ``` 240 | 241 | Copy `authkey.pub` to `~/.crash/authorized_keys` on the remote box, and 242 | use `authkey.priv` for the `crashc -i` argument. Note, that upon authentication 243 | you will be asked for the pass-phrase to unlock your private key that is 244 | stored locally. The pass-phrase will not travel the network. 245 | 246 | Auth-Key sizes larger than *7500 bit* must not be used; 247 | the tokens do not fit into the auth handshake otherwise. 248 | 249 | *crashc* is using the `.crash/` subdir by default to check for 250 | already seen server keys. If you connect to a host via `-H $host -p $port`, 251 | a keyfile of form `.crash/HK_$host:$port` is looked up unless you specify an 252 | absolute path to a known keyfile. 253 | 254 | Hostkeys 255 | -------- 256 | 257 | By default, *crashc* will compare server hostkeys to the local key cache 258 | that is found inside the `.crash/` subdir of CWD. You may override the path of the cache 259 | folder by using the `-K` switch. For example by using `-K ~/.crash/`, you use the folder 260 | inside your home directory. If the pathname does not end with a slash, it is treated as 261 | a filename instead of a directory. If a cache directory is used instead of an 262 | filename, each hostkey is expected to be found inside the folder as of the name `HK_$HOST:$PORT` 263 | where `$HOST` is the `-H` argument and `$PORT` the `-p` argument. If using `-v` 264 | the server hostkey will be printed on `stderr` and may be pasted to the cache folder 265 | into the `HK_$HOST:$PORT` file. 266 | 267 | Hostkey checking may be suppressed by using `-K none`. 268 | 269 | The crash auth protocol incorporates the server host key when signing authentication 270 | requests. This way its not strictly necessary to check server host keys as 271 | you know it from SSH password authentication. Two things have to be considered 272 | if host-key checks are suppressed with `-K none ` though: 273 | 274 | * The user-name will potentially leak to a MiM server 275 | 276 | This is not an issue if you use a system user-name such as `root`. 277 | 278 | * The MiM could sort of phish you, by showing you a fake-shell where 279 | you think it belongs to your real server. This could be used to 280 | wait for `su` and similar commands and to record sensitive information 281 | as you type on the MiM shell. 282 | 283 | To conquer this, you have to make sure you are indeed on your real shell 284 | when you see the prompt. This can be achieved by echoing a secret token 285 | to the tty upon login, for example via one of the `.profile` or `.bashrc` 286 | files. As the MiM cannot know this token, you can be sure you have a 287 | confidential and untampered session when you see this token upon login; 288 | even if you omit the host-key check. 289 | 290 | 291 | Term setup 292 | ---------- 293 | 294 | As *crashc* is not transfering env vars to the remote side for a reason, keep in mind 295 | that certain stuff is unset, such as `$TERM`. I.e. if you want to run an editor on the remote 296 | shell and started *crashc* from within an xterm, you have to `TERM=xterm vi file` in order 297 | to have a useful editing session. Likewise for other programs that you expect to work and 298 | require specific environment setup. 299 | 300 | CGI 301 | --- 302 | 303 | *crashd* automatically detects whether it has been invoked as a CGI by 304 | a web-server by checking `QUERY_STRING` environment variable. It parses 305 | and converts the query-string into arguments it understands. It 306 | does not translate `%2F` etc characters! They should not be needed, 307 | since spaces, '(' and other weird characters do not make sense when 308 | calling crashd. Arguments that don't have a parameter such as `-U` 309 | have to be given `=1` argument to enable it, such as in: 310 | 311 | ``` 312 | http://127.0.0.1/cgi-bin/crashd?-K=/path/to/serverkey.pem& \ 313 | -C=/path/to/pubkey.x509&-p=1234&-A=/tmp/.crash/authorized_keys&-U=1&-a=1 314 | ``` 315 | 316 | which invokes *crashd* on the host 127.0.0.1 as user (probably "wwwrun" or whatever 317 | the web-server is running as). 318 | 319 | For pen-testing or for emergency case, *crashd* has the `-e` option. 320 | If `-e` is used, it extracts the server key-file and the X509 certificate 321 | from the ELF binary file, which have to be appended before using `-e`: 322 | 323 | ``` 324 | $ cat serverkey.priv>>crashd 325 | $ cat serverkey.pub>>crashd 326 | $ cat authkey.pub>>crashd 327 | ``` 328 | 329 | The order of appended keys is important. 330 | 331 | If you give `-A self` instead of a valid authentication directory or file, 332 | *crashd* also extracts the user-key used for authentication from its binary. 333 | The keys are extracted from the binary at runtime and stored in temp files 334 | of pattern `/tmp/sshXXXXXX` (`/data/local/tmp/sshXXXXXX` on Android). 335 | Make sure to erase them securely upon last login, since they contain private keying 336 | material. 337 | 338 | This is useful in pen-tests where you cannot upload arbitrary amount of files 339 | or you do not know the exact pathname of the upload storage: 340 | 341 | ``` 342 | $ curl 'http://127.0.0.1/cgi-bin/crashd?-A=self&-U=1&-e=1&-a=1' 343 | ``` 344 | 345 | `-a` is needed since most likely the *wwwrun* user has a `/bin/false` shell, 346 | which `-a` ignores. 347 | *crashd* is using `mkstemp()` to store the key files temporarily (just see above), with 348 | mode 0444 (world readable) since it needs to access authentication 349 | files as user. So be warned that, if you have users, they may read 350 | the private key used during SSL handshake. After all, its just an 351 | emergency mode. Stripping the *crashd* binary is not possible after 352 | appending the keys, or they will get lost. 353 | Back-connect etc. also work in CGI mode as well. 354 | If using that, client should use `-K` switch to tell client which key to use 355 | to authenticate the server. 356 | 357 | 358 | TCP and UDP port forward 359 | ------------------------ 360 | 361 | *crash* uses the same network engine as [psc](https://github.com/stealth/psc). Therefore 362 | you may use the same `-U` and `-T` parameters as known from *psc* and which are similar 363 | to those of *OpenSSH's* `-L` parameter. It will bind to `lport` and will forward connections 364 | to `[ip]:rport`, initiating the connection from the remote host. The same works for UDP 365 | packets, which is not possible with SSH. 366 | 367 | If you are interested in messenger proxy setups in copland countries, you can check `contrib` 368 | folder. 369 | 370 | 371 | SOCKS4 and SOCKS5 support 372 | ------------------------- 373 | 374 | *crash* also supports forwarding of TCP connections via *SOCKS4* (`-4 port`) and *SOCKS5* 375 | (`-5 port`). This sets up *port* as SOCKS port for TCP connections, so for instance you 376 | can browse remote networks via *crashc* sessions without the need to open any other 377 | connection during a pentest. If you pass `-N` to *crashc*, it enables DNS name resolution 378 | on the remote side, so you can also use chrome with it. But be warned: There is a privacy 379 | problem with browsers that try to resolve a sequence of DNS names upon startup that 380 | is not under your control. Also, if your remote side has a broken DNS setup, your typing 381 | shell may block for several seconds if DNS reply packets are missing. There are no good 382 | async resolver functions which are embeddable and portable so I had to rely on 383 | `getaddrinfo()` in the single thread at the price of possible blockings for several seconds 384 | if DNS problems exist. Thats why name resolving has to be enabled explicitly. *crashd* 385 | tries to minimize this potential problem with DNS lookup caches though, so in most 386 | situation it should just work painlessly. 387 | If you pass `-X IP-address` (must come before any other proxy argument), you can bind your local proxy 388 | to an address different from `127.0.0.1`, so you can share the proxy in your local network. 389 | 390 | There is also a client side SOCKS5 support available when using *crashc* with `-x`. 391 | 392 | 393 | Proxying based on SNI 394 | --------------------- 395 | 396 | In some circumstances you might want to change the endpoint of the proxy session based 397 | on a SNI that you receive in the TLS `ClientHello`. For convenience, *crashc* integrates 398 | support for that by using `-Y lport:SNI:[ip]:rport` which listens on `lport` and forwards the 399 | given `SNI` to `ip:rport`. A fallback of `default` SNI can be given so that any non-matches or 400 | missing SNIs will be forwarded to that destination. 401 | 402 | 403 | DTLS transport 404 | -------------- 405 | 406 | *crash* allows to use all of its trickery above also on a DTLS 1.2 transport layer based 407 | on UDP. I have added basic flow control and reliability, so you can even xfer files and use 408 | port forwarding as with TLS 1.3. The reason for adding DTLS is that some countries have 409 | TCP egress filters that only allow incoming connections. It is harder for censors to tell 410 | which UDP packets establish an outgoing connection, as there is nothing like a "connection" 411 | with UDP. With DTLS sessions, which are established by the `-D` switch on both sides, a SNI 412 | is mandatory. 413 | When forwarding UDP ports on DTLS sessions, make sure you will not send UDP payloads larger than 414 | 1320 bytes across the sockets, as it is necessary in UDP case to keep enough room for headers 415 | and record layer without the need to fragment the packet, as DTLS honors packet boundaries 416 | (there is nothing like a stream as in TCP, just datagrams). 417 | DTLS mode is still experimental (although working stable) and will switch to DTLS 1.3 as soon 418 | as it is implemented widely (DTLS 1.3 RFC was just finished 2022). 419 | 420 | 421 | Suspend/Resume/Roaming 422 | ---------------------- 423 | 424 | This is an experimental feature, although working stable. 425 | 426 | When using DTLS sessions and *crashd* is started with `-R`, you will get the following: 427 | 428 | * transparent roaming of the client sessions - including existing SOCKS connections - which 429 | allows to switch underlying physical layer, VPN, Interface, NAT or IP address without 430 | even noticing it 431 | * *crashc* may be terminated via `SIGTERM`, so it will dump the session to a ticket 432 | file (`-t`) which can later be resumed from by passing the correct dst IP:port and ticket 433 | but w/o the need to authenticate again (no `-i`) - with full roaming support 434 | 435 | In the 2nd case, **the ticket file will not be encrypted**, so make sure you never leak it. 436 | This allows you to switch off your laptop and continue working from elsewhere or even 437 | share the ticket to another admin who then continues your session. 438 | 439 | One thing is special with regards to bound server ports when using roaming: Due to 440 | UDP internals, the next open session for a followup "connect" will be on the next 441 | free port in the range of `[port, port + 1000]` and not on the same port as when using TCP. 442 | This needs to be as with roaming we cannot actually call `connect()` to virtually create a 443 | connected tuple, as the next session packet can arrive from anywhere - not just from the 444 | originating IP as happens with TCP. So when you start the server with `-p 2222` and one 445 | roaming session already exists, the next one needs to "connect" to port `2223`. If the 446 | session at port `2222` is finished (not suspended, but really finished), port `2222` 447 | will become available again to the next client. 448 | 449 | Suspend/Resume does not work yet with *LibreSSL* builds, but roaming does. 450 | 451 | 452 | Mitigating traffic analysis 453 | --------------------------- 454 | 455 | Traffic analysis mitigation has differant goals: 456 | 457 | * make it hard to find out actual typing sequences and potential info leaks about whats being typed 458 | * make it hard for a global observer to track connection streams across packet mixes or hubs 459 | * make it hard for censors to identify/distinguish crash sessions from a set of "legit" connections 460 | 461 | It is not possible to reach all three goals at the same time, e.g. you want randomized packet sizes 462 | to make it hard for observers to know you are using a *crash* session, but this will also make 463 | your packet stream unique across mixes. 464 | 465 | Completely mitigating traffic analysis for a capable (global) observer is very hard. 466 | It would require many crash users so to sink all individual packets in a swarm and 467 | make it impossible to find patterns that could be used to track individual users across 468 | packet mixes. It would also require a fixed packet size for *all* packets as well as a 469 | constant delay between the sends to make all connections look equal. Even then, there's 470 | still the problem of the overall amount of traffic sent that may be measured and used 471 | to track individuals. As having constant size and delays would make the connection 472 | feel slow or even unusable, *crash* lets you choose between traffic policies which are 473 | controlled by `-R ` at client side. *Level* is an integer with the following meaning: 474 | 475 | * 0: disable all padding of payloads and do not inject random traffic 476 | 477 | * 1: pad payload to rand size up to 1320 byte boundary, no injects 478 | * 2: pad payload to rand size up to 1320 byte boundary, random injects client side 479 | * 3: pad payload to rand size up to 1320 byte boundary, random injects with server responses 480 | 481 | * 4: pad payload to the next 256, 512, 1024 or 1320 byte boundary, no injects (default) 482 | * 5: pad payload to the next 256, 512, 1024 or 1320 byte boundary, random injects client side 483 | * 6: pad payload to the next 256, 512, 1024 or 1320 byte boundary, random injects with server 484 | responses 485 | 486 | * 7: pad payload to 1320 byte boundary, no injects 487 | * 8: pad payload to 1320 byte boundary, random injects client side 488 | * 9: pad payload to 1320 byte boundary, random injects with server responses 489 | 490 | *Factor* is a multiply factor `1..100` that adds as many NOP packets per real packet in order 491 | to make it harder to match amount of input traffic to output traffic on proxy hosts. 492 | 493 | 1320 is crashds internally used MSS. The values were chosen in a way so that sent data fits most 494 | likely into a single packet. Note however that these are the packet sizes (plus the TLS record size) 495 | as it is passed to the TCP stack. TCP will decide itself how it will send the segments. There is 496 | no way to enforce 'TCP packet sizes', but this does not matter as the deps to the actual payload 497 | size is already blurred. 498 | 499 | A higher *Level* does not automatically mean a better analysis mitigation. You have to chose the best 500 | `-R ` depending on your personal needs. 501 | 502 | In DTLS mode there are always ping packets in order to implement synchronization and flow control. 503 | 504 | If you live in a country with restrictive egress filtering, it may be helpful to test how long 505 | connections can survive. Note that due to `-4` and `-U` which allows to proxy TCP *and* UDP (DNS) 506 | to a remote site, *crash* may be used as a [shadowsocks](https://shadowsocks.org) alternative that requires 507 | basically no setup and just needs a user-shell behind egress. 508 | 509 | If you think that all of this is paranoia, go get some product sheets for devices that 510 | detect and classify SSH traffic by behavioral analysis. 511 | 512 | 513 | Hiding by SNI 514 | ------------- 515 | 516 | By default, the *crashd* will show a banner upon connect to tell the peer major and minor version 517 | numbers. Censorship countries might block addresses which show banners they dislike. To combat this, 518 | *crash* allows for a TLS-only mode that is indistinguishable from a HTTPS session. Just start 519 | *crashd* with `-S` and give a semi-secret name (Server Name Indicator, SNI). Only clients that also 520 | use the correct `-S` parameter will reach the gate for authentication at all. Other TLS sessions 521 | will just be rejected. *Note that the SNI travels the network in plain-text and that `-S` is not meant 522 | for authentication.* The only reason for SNI hiding is to hide the *crash* banner from probing/crawling. 523 | You may also use SNI proxies such as [sshttp](https://github.com/stealth/sshttp) to hide *crash* even 524 | deeper and to forward all non-correct SNI connects to some web-site. This way you may hide your server 525 | behind neutral web-sites from aggressively probing/blocking censors. 526 | 527 | In order for probing to not reveal that you are running *crash* by checking the X509 certificate 528 | details, you should use reasonable values for *Country Name*, *City* etc. when asked for it during 529 | the `make keys` process. For instance it would make no sense to setup a pro-regime web-site 530 | to hide behind and enter anti-regime values for the X509 specific naming. 531 | 532 | Inside the `contrib` folder you will find a nginx config file that you can integrate into 533 | your setup along with comments how you would create a connect from outside to your nginx server 534 | in order to have a *crash* session based on a SNI that you chose. 535 | 536 | 537 | Disguise Filters 538 | ---------------- 539 | 540 | Taking the feature of SNI hiding one step further. Some countries use network data gathered at 541 | their border routers to scan destination machines and check whether the content or software there 542 | could pose a threat to their leaders. It is therefore not good to always tell anyone openly 543 | that a *crashd* is running on a certain port, even if the peer shows up with the right SNI, 544 | as the SNI could have been sniffed by a global observer. Entering *Disguise Filters*. 545 | 546 | Upon connect, you have to show up with a correct (pre-)secret in order to start a *crash* session. 547 | This can't be known by an observer as its hidden inside the TLS stream (unlike the SNI). If 548 | the secret is not correct, *crashd* disguises as another - innocent looking - software. 549 | 550 | Currently, there is only one Disguise Filter, `redirect1`, which masks as a web server 551 | sending a redirect of your choice. Disguise Filters always also require `-S`: 552 | 553 | 554 | ``` 555 | $ ./crashd -L [0.0.0.0]:4433 -c serverkey.pub -k serverkey.priv \ 556 | -G redirect1:mydirtysecret:https://www.ccc.de -S localhost 557 | ``` 558 | 559 | So only those who know can start a shell session: 560 | 561 | ``` 562 | $ ./crashc -H 127.0.0.1 -p 4433 -l stealth -i authkey.priv -S localhost -G mydirtysecret 563 | ``` 564 | 565 | All others, e.g. `curl https://localhost:4433 -k -v -L` will be redirected to `https://www.ccc.de`. 566 | 567 | (where `localhost` was just chosen for testing to make curl have the right SNI) 568 | When a Disguise Filter is triggered, you will see it in the logs. This also allows admins to 569 | have shell servers reachable from outside which just map to the legit web server when not 570 | prompted with the correct (pre-) secret. 571 | For sure; for a disguise to work against censors with a large dick, your story has to be 572 | perfect, i.e. the CNs etc. of the certificate have to look legit, even better signed by 573 | a legit CA and the redirect has to look reasonable. 574 | 575 | 576 | File up/download 577 | ---------------- 578 | 579 | Although there is nothing like `sftp` for *crash*, it may be used for file up/downloads. 580 | 581 | In order to upload a file: 582 | ``` 583 | ~ $ crashc -H host -i authkey.priv -l root -c 'dd of=/path/on/remote status=progress' < local.file 584 | ``` 585 | 586 | Or to download a file: 587 | ``` 588 | ~ $ crashc -H host -i authkey.priv -l root -c 'dd if=/path/on/remote status=progress' > local.file 589 | ``` 590 | 591 | Note that in the download case you must not specify the `-v` switch since this would add 592 | the verbose output to the `local.file`. For `-c` commands, *crash* will forward `stdout` and 593 | `stderr` separated to the local tty's fd 1 and 2, so above commands add a nice progress bar 594 | during the xfer. 595 | 596 | 597 | MTU/MSS 598 | ------- 599 | 600 | *crash* is assuming a MTU of 1500 and using a MSS of 1320, so that TLS record layer and some other 601 | meta data fits into this MTU and even into network devices with smaller MTU ~1400, which should fit with most 602 | VPN setups etc.. If you are using DTLS mode (that is UDP) and a VPN or whatever with a much smaller MTU, 603 | you might want to compile *crash* with lower values which you can change in `misc.h`. If you are using TCP, 604 | i.e. not using the `-D` switch, these values do not matter for you. 605 | 606 | 607 | DoS mitigation 608 | -------------- 609 | 610 | *crashd* includes some sort of D/DoS protection. Only one connection per second 611 | is allowed per IP, except if the IP is listed (or the network it belongs to) 612 | in a good-IP file given with -g at startup. 613 | Per default no good IPs are assigned. Network-address-goodness only works with 614 | IPv4 yet. A simple good-IP file may look like this: 615 | 616 | 617 | # sample good-IP file 618 | 192.168.3.1 619 | 192.168.2.0 620 | 10.0.0.0 621 | fe80:216::1234 622 | # end of file 623 | 624 | Together with the interval timer for hanging un-authenticated 625 | connections this allows to have no more than 12 'hanging' 626 | crashd's at the same time, still allowing you to login 627 | if you are listed in good-IPs and your underlying TCP/IP stack 628 | is not already trashed. 629 | 630 |

631 | 632 | 633 | 634 |

635 | 636 | 637 | -------------------------------------------------------------------------------- /bridge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/crash/338cc70f445997e2c408d6ae8ee0543fa1d8f6e3/bridge.jpg -------------------------------------------------------------------------------- /contrib/nginx.add.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Add this to your nginx.conf and restart. 3 | # 4 | # It will forward the crashc TLS session non-terminating to crashd 5 | # running on localhost, if you connect from outside to your nginx server 6 | # at port 4433 with SNI `good.sni.example.com` (change SNI as needed to bypass 7 | # censorship devices). 8 | # 9 | # It requires nginx to be built with SNI and stream support (nginx -V), but thats 10 | # mostly the case for distro packages. 11 | # 12 | # Change nginx port and certificates etc. as needed to integrate with your legit 13 | # looking pro-regime website to serve a standard innocent web-page when 14 | # web clients connect. 15 | # 16 | # credit goes to: https://gist.github.com/kekru/c09dbab5e78bf76402966b13fa72b9d2 17 | # 18 | 19 | stream { 20 | 21 | map $ssl_preread_server_name $targetBackend { 22 | good.sni.example.com 127.0.0.1:2222; 23 | # other.sni.example.com 127.0.0.1:2223; 24 | } 25 | 26 | server { 27 | listen 4433; 28 | 29 | proxy_connect_timeout 1s; 30 | proxy_timeout 100000s; 31 | resolver 1.1.1.1; 32 | 33 | proxy_pass $targetBackend; 34 | ssl_preread on; 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /contrib/openssl.cnf: -------------------------------------------------------------------------------- 1 | HOME = . 2 | RANDFILE = $ENV::HOME/.rnd 3 | 4 | 5 | #################################################################### 6 | [ req ] 7 | default_bits = 4096 8 | default_keyfile = privkey.pem 9 | distinguished_name = req_distinguished_name 10 | attributes = req_attributes 11 | 12 | [ req_distinguished_name ] 13 | countryName = Country Name (2 letter code) 14 | countryName_default = DE 15 | countryName_min = 2 16 | countryName_max = 2 17 | 18 | stateOrProvinceName = Province 19 | stateOrProvinceName_default = JWD 20 | 21 | localityName = City 22 | localityName_default = Karl Marx Stadt 23 | 24 | 0.organizationName = Organization 25 | 0.organizationName_default = crash users 26 | 27 | organizationalUnitName = Unit name 28 | organizationalUnitName_default = Steelworker 29 | 30 | commonName = Common Name 31 | commonName_default = noname 32 | commonName_max = 64 33 | 34 | emailAddress = Mail 35 | emailAddress_default = nomail 36 | emailAddress_max = 64 37 | 38 | [ req_attributes ] 39 | challengePassword = A challenge password 40 | challengePassword_min = 4 41 | challengePassword_max = 20 42 | 43 | unstructuredName = An optional company name 44 | 45 | 46 | -------------------------------------------------------------------------------- /contrib/proxywars.md: -------------------------------------------------------------------------------- 1 | # WA / Telegram proxy setup 2 | 3 |

4 | 5 |

6 | 7 | How to get messenger connectivity if your Internet is dead in copland. This covers 8 | *Telegram* and *Whatsapp* messengers to connect from this **Inside** filtered network. 9 | 10 | ## Outside 11 | 12 | All methods described here require a VPS or server instance running **Outside** the censored network. 13 | In the ideal case this has been setup before blocking rules were tightened. It may also require 14 | help from Outside by volunteers. If only the target IP space of your messenger provider, e.g. 15 | the Meta network is blocked, you can directly jump to the `Proxy setup` section. If there is 16 | more blocking, the next sections describe techniques to bypass these. 17 | 18 | ## TCP blocking 19 | 20 | If just outgoing TCP connections are blocked, try using `-D` for DTLS that runs on UDP. You can also 21 | try reverse connect by *crash* server from outside into the censored network by setting up cron scripts 22 | or similar on the VPS that periodically connects to you. This is the case that works for Iran. 23 | 24 | ## SNI blocking 25 | 26 | Some censorship white-list connections to regime websites by checking the SNI of the outgoing 27 | connection. You can use [sniprobe](https://github.com/c-skills/sniprobe) to find out which. 28 | Then you can try connecting to your VPS by setting up your instances and using `-S` with that SNI. 29 | 30 | You can go one step further as to setup a regime friendly website that praises the leader to 31 | fool them to have connections from Inside allowed to this outside host. On this innocent looking 32 | website you run a SNI proxy such as [sshttp](https://github.com/stealth/sshttp) that multiplexes 33 | another SNI of your choice to a *crash* instance running behind the https port of that website. 34 | Censorship equipment sees all connections looking like a https session to a pro regime website. 35 | 36 | You can read more on the SNI case by [our THC friends](https://blog.thc.org/the-iran-firewall-a-preliminary-report). 37 | 38 | ## ICMP or DNS tunneling 39 | 40 | If nothing of above works, you may try to resolve DNS or ping your VPS. If that works, you can setup 41 | a ICMP or DNS tunnel via [fraud-bridge](https://github.com/stealth/fraud-bridge). You can reach 42 | your VPS via `1.2.3.5` then. 43 | 44 | 45 | # Proxy setup 46 | 47 | ## Whatsapp 48 | 49 | There is an [official WA docu](https://github.com/WhatsApp/proxy) that uses a bit overblown docker setup with `haproxy`. 50 | To boil it down - if you read all the configs - the messenger app is just expecting a simple port forward from the proxy 51 | to `g.whatsapp.net:5222`. Meta seems to use DNS based load balancing so you may get different addresses 52 | than me, but for me it resolves to `185.60.217.54` or `2a03:2880:f276:d0:face:b00c:0:7260` in IPv6 case. 53 | Thats for the `Chat port`. For `Media port` they are using `mmg.whatsapp.net:443` which resolves to 54 | `57.144.111.32` or `2a03:2880:f32e:122:face:b00c:0:167`. 55 | 56 | Given that you manage to establish a connection to your VPS in the steps before, you do: 57 | 58 | ``` 59 | $ crashc -X 192.168.0.123 -S yourSNI -D -H $VPS -T 1235:[185.60.217.54]:5222 \ 60 | -T 1236:[57.144.111.32]:443 -l user -i authkey.priv -v 61 | ``` 62 | 63 | or 64 | 65 | ``` 66 | $ crashc -X 192.168.0.123 -S yourSNI -D -H $VPS -T 1235:[2a03:2880:f276:d0:face:b00c:0:7260]:5222 \ 67 | -T 1236:[2a03:2880:f32e:122:face:b00c:0:167]:443 -l user -i authkey.priv -v 68 | 69 | ``` 70 | 71 | (using DTLS in this example and WA's IPv6 endpoint in the 2nd case) 72 | 73 | In your WA messenger, go to `Settings` -> `Storage and data` -> `Proxy settings` and set it to: 74 | 75 | * Proxy host: `192.168.0.123` 76 | * Chat port: `1235` and leave the `Use TLS` box unchecked. No worries - you will still get encrypted comms. 77 | * Media port: `1236` 78 | 79 | It assumes that your phone is connected via Wifi and using the same `192.168.0.0` network 80 | as your *crashc* session which runs on the machine that has a LAN (wired or Wifi) IP of `192.168.0.123`. 81 | 82 | ## Telegram 83 | 84 | With *Telegram* it is similar as easy. Will just use 85 | 86 | ``` 87 | $ crashc -X 192.168.0.123 -S yourSNI -D -H $VPS -5 1235 -l user -i authkey.priv -v 88 | ``` 89 | 90 | To setup a SOCKS5 proxy and configure it in the app as `Settings` -> `Data and Storage` -> `Proxy Settings` -> `Add Proxy` 91 | as SOCKS5 with `192.168.0.123` and port `1235`. 92 | 93 | ## Signal 94 | 95 | There is a [docu for Signal](https://github.com/signalapp/Signal-TLS-Proxy), using `nginx`. 96 | 97 | Unfortunately *Signal* is using TLS-in-TLS tunneling with SNI proxying in the inner tunnel which 98 | requires sort of more complex setup that can't be handled by *crash* alone. 99 | 100 | ## DNS 101 | 102 | If DNS is blocked or filtered, you can forward DNS lookups via `-X 192.168.0.123 -U 53:[8.8.8.8]:53` 103 | and configure `192.168.0.123` as your DNS resolver. It requires to run *crashc* as root, since it needs 104 | to bind to the privileged port 53. DNS forwarding is usually not needed for the messenger proxy case, 105 | since the proxy is configured as an IP address. Sometimes though, external resources need to be accessed/resolved. 106 | 107 | ## Public Access 108 | 109 | If you are a volunteer from Outside and want to donate public *crash* endpoints on your VPS without actually 110 | giving shell access to strangers, you can setup a dedicated user and set his shell to `/bin/cat` (manually 111 | editing `/etc/passwd` or via `chsh` but in this case you need to add `/bin/cat` to `/etc/shells`). 112 | 113 | You can then setup the *crash* keys as normal and distribute them to people Inside and asking them 114 | at which IP they want to have your active *crashd* connections terminated if they can't connect themselfes to 115 | Outside, or tell them how they can connect to your VPS. 116 | 117 | To avoid abuse, you might want to apply firewalling rules of that country's netrange and also filter outgoing 118 | connections to be only possible to the messenger service' networks/ports. 119 | 120 | -------------------------------------------------------------------------------- /contrib/pw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/crash/338cc70f445997e2c408d6ae8ee0543fa1d8f6e3/contrib/pw.jpg -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stealth/crash/338cc70f445997e2c408d6ae8ee0543fa1d8f6e3/logo.jpg -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | 6 | INC= 7 | LIBS= 8 | CXXFLAGS= 9 | DEFS=-DTITLE="\"[kthreadd]\"" 10 | 11 | POSIX=-D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600 12 | 13 | # comment in if you want to have server and client support TLS 1.2 in order 14 | # to use aged systems that have no TLS 1.3 libcrypto support, which is otherwise 15 | # the minimum TLS version 16 | #DEFS+=-DTLS_COMPAT_DOWNGRADE 17 | 18 | # Do not enable. !For debugging only! 19 | # -DUSE_CCIPHERS="\"NULL@SECLEVEL=0\"" -DUSE_SCIPHERS="\"NULL@SECLEVEL=0\"" 20 | 21 | # comment out if you want DTLSv1_listen() "DoS protection" for OpenSSL-3 or LibreSSL-3 22 | # do not comment out if you want suspend/resume 23 | DEFS+=-DNO_DTLS_LISTEN 24 | 25 | # enable experimental QUIC transport 26 | #DEFS+=-DHAVE_QUIC 27 | 28 | #SSL=/opt/ssl/openssl-3.5.0-beta1 29 | 30 | ifeq ($(shell uname), Linux) 31 | LIBS+=-lssl -lcrypto -Wl,--rpath=$(SSL)/lib -Wl,--rpath=$(SSL)/lib64 32 | DEFS+=-DHAVE_UNIX98 33 | DEFS+=$(POSIX) 34 | else ifeq ($(shell uname), Solaris) 35 | LIBS+=-lssl -lcrypto -lsocket -lnsl 36 | else ifeq ($(shell uname), Darwin) 37 | SSL=/usr/local/opt/openssl\@1.1 38 | LIBS+=-lssl -lcrypto 39 | DEFS+=-DHAVE_UNIX98 40 | else ifeq ($(shell uname), OpenBSD) 41 | LIBS+=-lssl -lcrypto -lutil 42 | else ifeq ($(shell uname), NetBSD) 43 | LIBS+=-lssl -lcrypto -lutil 44 | else ifeq ($(shell uname), FreeBSD) 45 | LIBS+=-lssl -lcrypto -lutil 46 | # Cygwin 47 | else 48 | LIBS+=-lssl -lcrypto -lutil 49 | DEFS+=$(POSIX) 50 | endif 51 | 52 | 53 | CXX?=c++ 54 | CXXSTD?=c++11 55 | INC+=-I$(SSL)/include 56 | CXXFLAGS+=-Wall -O2 -std=$(CXXSTD) -pedantic $(INC) $(DEFS) 57 | 58 | LIBS+=-L$(SSL)/lib -L$(SSL)/lib64 59 | LDFLAGS=-lpthread 60 | LD=$(CXX) 61 | STRIP=strip 62 | 63 | CNF=../contrib/openssl.cnf 64 | 65 | all: crashd crashc 66 | 67 | clean: 68 | rm -rf *.o 69 | 70 | keys: 71 | umask 066 72 | @echo "***** Generating Keys ****" 73 | @echo 74 | @echo "Whatever you enter for Country/Organization etc is not important (press enter)." 75 | @echo "Just the passphrase is important" 76 | @echo 77 | @echo "**************************" 78 | sleep 3 79 | OPENSSL_CONF=$(CNF) openssl genrsa -out serverkey.priv 4096 80 | OPENSSL_CONF=$(CNF) openssl req -new -x509 -nodes -sha1 -key serverkey.priv -out serverkey.pub 81 | OPENSSL_CONF=$(CNF) openssl x509 -in serverkey.pub -pubkey -noout > HK_127.0.0.1 82 | OPENSSL_CONF=$(CNF) openssl genrsa -out authkey.priv -aes256 4096 83 | OPENSSL_CONF=$(CNF) openssl rsa -in authkey.priv -pubout -out authkey.pub 84 | @echo 85 | @echo 86 | @echo "Your serverkey is in serverkey.{priv,pub} and authentication (user-) key in" 87 | @echo "authkey.{priv,pub}. Copy authkey.pub to ~/.crash/authorized_keys on remote" 88 | @echo "server and use '-i authkey.priv' on the client to connect to it" 89 | @echo 90 | @echo "Your known-host key which belongs to serverkey.priv is in HK_127.0.0.1" 91 | @echo "and you can use it with '-K HK_127.0.0.1' on the client (127.0.0.1 as example)" 92 | @echo 93 | @echo "For example you can start './crashd' as root on localhost and use" 94 | @echo "'./crashc -K ./HK_127.0.0.1 -H 127.0.0.1 -l user -i authkey.priv'" 95 | @echo "to login." 96 | @echo 97 | 98 | crashc: net.o misc.o crashc.o config.o session.o csession.o global.o missing.o 99 | $(LD) net.o misc.o crashc.o config.o pty.o global.o pty98.o session.o csession.o missing.o $(LIBS) $(LDFLAGS) -o crashc 100 | $(STRIP) crashc 101 | 102 | crashd: server.o session.o ssession.o net.o misc.o crashd.o config.o pty.o pty98.o global.o log.o dh.o iobox.o missing.o disguise.o 103 | $(LD) server.o session.o ssession.o net.o misc.o crashd.o config.o pty.o pty98.o global.o log.o dh.o iobox.o missing.o disguise.o $(LIBS) $(LDFLAGS) -o crashd 104 | $(STRIP) crashd 105 | 106 | missing.o: missing.cc 107 | $(CXX) $(CXXFLAGS) -c missing.cc 108 | 109 | iobox.o: iobox.cc 110 | $(CXX) $(CXXFLAGS) -c iobox.cc 111 | 112 | disguise.o: disguise.cc 113 | $(CXX) $(CXXFLAGS) -c disguise.cc 114 | 115 | server.o: server.cc 116 | $(CXX) $(CXXFLAGS) -c server.cc 117 | 118 | session.o: session.cc 119 | $(CXX) $(CXXFLAGS) -c session.cc 120 | 121 | ssession.o: ssession.cc 122 | $(CXX) $(CXXFLAGS) -c ssession.cc 123 | 124 | csession.o: csession.cc 125 | $(CXX) $(CXXFLAGS) -c csession.cc 126 | 127 | net.o: net.cc 128 | $(CXX) $(CXXFLAGS) -c net.cc 129 | 130 | misc.o: misc.cc 131 | $(CXX) $(CXXFLAGS) -c misc.cc 132 | 133 | config.o: config.cc 134 | $(CXX) $(CXXFLAGS) -c config.cc 135 | 136 | pty.o: pty.cc 137 | $(CXX) $(CXXFLAGS) -c pty.cc 138 | 139 | pty98.o: pty98.cc 140 | $(CXX) $(CXXFLAGS) -c pty98.cc 141 | 142 | global.o: global.cc 143 | $(CXX) $(CXXFLAGS) -c global.cc 144 | 145 | log.o: log.cc 146 | $(CXX) $(CXXFLAGS) -c log.cc 147 | 148 | crashd.o: crashd.cc 149 | $(CXX) $(CXXFLAGS) -c crashd.cc 150 | 151 | crashc.o: crashc.cc 152 | $(CXX) $(CXXFLAGS) -c crashc.cc 153 | 154 | dh.o: dh.cc dh2048.cc 155 | $(CXX) $(CXXFLAGS) -c dh.cc 156 | 157 | -------------------------------------------------------------------------------- /src/Makefile.android: -------------------------------------------------------------------------------- 1 | # setup your build environment to build for Android: 2 | # 1. Install NDK and clone boringssl git 3 | # 2. inside boringssl git, checkout the android version that you are using on your phone 4 | # 3. set path to your NDK and SSL path you are using as well as your $PLATFORM 5 | # 4. copy phones or emulators libssl.so and libcrypto.so to . 6 | 7 | PLATFORM=android-24 8 | NDK=/opt/android-ndk 9 | 10 | SSL=/tmp/boringssl 11 | 12 | PREFIX=$(NDK)/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi- 13 | SYSROOT=--sysroot=$(NDK)/platforms/$(PLATFORM)/arch-arm 14 | SYSROOT+=-isysroot $(NDK)/sysroot 15 | 16 | INC=-isystem $(NDK)/sysroot/usr/include/arm-linux-androideabi\ 17 | -I$(NDK)/sources/cxx-stl/gnu-libstdc++/include\ 18 | -I$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/include/\ 19 | -I$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/include\ 20 | -I$(SSL)/include 21 | 22 | LIB=-Wl,$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/libgnustl_static.a 23 | LIB+=-L . -lssl -lcrypto 24 | 25 | DEFS=-DANDROID 26 | 27 | CXX=$(PREFIX)gcc -fPIC -O2 -Wall -pedantic -std=c++11 $(SYSROOT) $(INC) $(DEFS) 28 | LD=$(PREFIX)gcc -pie $(SYSROOT) 29 | 30 | STRIP=$(PREFIX)strip 31 | 32 | 33 | all: crashd crashc 34 | 35 | clean: 36 | rm -rf *.o 37 | 38 | crashc: net.o misc.o crashc.o config.o global.o 39 | $(LD) net.o misc.o crashc.o config.o pty.o global.o pty98.o $(LIB) -o crashc 40 | $(STRIP) crashc 41 | 42 | crashd: server.o session.o net.o misc.o crashd.o config.o pty.o pty98.o global.o log.o dh.o iobox.o 43 | $(LD) server.o session.o net.o misc.o crashd.o config.o pty.o pty98.o global.o log.o dh.o iobox.o $(LIB) -o crashd 44 | $(STRIP) crashd 45 | 46 | iobox.o: iobox.cc 47 | $(CXX) $(CXXFLAGS) -c iobox.cc 48 | 49 | server.o: server.cc 50 | $(CXX) $(CXXFLAGS) -c server.cc 51 | 52 | session.o: session.cc 53 | $(CXX) $(CXXFLAGS) -c session.cc 54 | 55 | net.o: net.cc 56 | $(CXX) $(CXXFLAGS) -c net.cc 57 | 58 | misc.o: misc.cc 59 | $(CXX) $(CXXFLAGS) -c misc.cc 60 | 61 | config.o: config.cc 62 | $(CXX) $(CXXFLAGS) -c config.cc 63 | 64 | pty.o: pty.cc 65 | $(CXX) $(CXXFLAGS) -c pty.cc 66 | 67 | pty98.o: pty98.cc 68 | $(CXX) $(CXXFLAGS) -c pty98.cc 69 | 70 | global.o: global.cc 71 | $(CXX) $(CXXFLAGS) -c global.cc 72 | 73 | log.o: log.cc 74 | $(CXX) $(CXXFLAGS) -c log.cc 75 | 76 | crashd.o: crashd.cc 77 | $(CXX) $(CXXFLAGS) -c crashd.cc 78 | 79 | crashc.o: crashc.cc 80 | $(CXX) $(CXXFLAGS) -c crashc.cc 81 | 82 | dh.o: dh.cc dh2048.cc 83 | $(CXX) $(CXXFLAGS) -c dh.cc 84 | 85 | -------------------------------------------------------------------------------- /src/Makefile.android.aarch64: -------------------------------------------------------------------------------- 1 | # 2 | # setup your build environment to build for Android: 3 | # 1. Install NDK and clone boringssl git 4 | # 2. inside boringssl git, checkout the android version that you are using on your phone 5 | # 3. set path to your NDK and SSL path you are using as well as your $PLATFORM 6 | # 4. copy phones or emulators libssl.so and libcrypto.so to . 7 | 8 | PLATFORM=android-24 9 | NDK=/opt/android-ndk 10 | 11 | SSL=/tmp/boringssl 12 | 13 | PREFIX=$(NDK)/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android- 14 | SYSROOT=--sysroot=$(NDK)/platforms/$(PLATFORM)/arch-arm64/ 15 | SYSROOT+=-isysroot $(NDK)/sysroot 16 | 17 | INC=-isystem $(NDK)/sysroot/usr/include/aarch64-linux-android\ 18 | -I$(NDK)/sources/cxx-stl/gnu-libstdc++/include\ 19 | -I$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/include/\ 20 | -I$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/arm64-v8a/include\ 21 | -I$(SSL)/include 22 | 23 | LIB=-Wl,$(ROOT)/$(NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/arm64-v8a/libgnustl_static.a 24 | LIB+=-L . -lssl -lcrypto 25 | 26 | DEFS=-DANDROID 27 | 28 | CXX=$(PREFIX)gcc -fPIC -O2 -Wall -pedantic -std=c++11 $(SYSROOT) $(INC) $(DEFS) 29 | LD=$(PREFIX)gcc -pie $(SYSROOT) 30 | 31 | STRIP=$(PREFIX)strip 32 | 33 | 34 | all: crashd crashc 35 | 36 | clean: 37 | rm -rf *.o 38 | 39 | crashc: net.o misc.o crashc.o config.o global.o 40 | $(LD) net.o misc.o crashc.o config.o pty.o global.o pty98.o $(LIB) -o crashc 41 | $(STRIP) crashc 42 | 43 | crashd: server.o session.o net.o misc.o crashd.o config.o pty.o pty98.o global.o log.o dh.o iobox.o 44 | $(LD) server.o session.o net.o misc.o crashd.o config.o pty.o pty98.o global.o log.o dh.o iobox.o $(LIB) -o crashd 45 | $(STRIP) crashd 46 | 47 | iobox.o: iobox.cc 48 | $(CXX) $(CXXFLAGS) -c iobox.cc 49 | 50 | server.o: server.cc 51 | $(CXX) $(CXXFLAGS) -c server.cc 52 | 53 | session.o: session.cc 54 | $(CXX) $(CXXFLAGS) -c session.cc 55 | 56 | net.o: net.cc 57 | $(CXX) $(CXXFLAGS) -c net.cc 58 | 59 | misc.o: misc.cc 60 | $(CXX) $(CXXFLAGS) -c misc.cc 61 | 62 | config.o: config.cc 63 | $(CXX) $(CXXFLAGS) -c config.cc 64 | 65 | pty.o: pty.cc 66 | $(CXX) $(CXXFLAGS) -c pty.cc 67 | 68 | pty98.o: pty98.cc 69 | $(CXX) $(CXXFLAGS) -c pty98.cc 70 | 71 | global.o: global.cc 72 | $(CXX) $(CXXFLAGS) -c global.cc 73 | 74 | log.o: log.cc 75 | $(CXX) $(CXXFLAGS) -c log.cc 76 | 77 | crashd.o: crashd.cc 78 | $(CXX) $(CXXFLAGS) -c crashd.cc 79 | 80 | crashc.o: crashc.cc 81 | $(CXX) $(CXXFLAGS) -c crashc.cc 82 | 83 | dh.o: dh.cc dh2048.cc 84 | $(CXX) $(CXXFLAGS) -c dh.cc 85 | 86 | -------------------------------------------------------------------------------- /src/config.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "misc.h" 6 | 7 | namespace config 8 | { 9 | bool verbose = 0, silent = 0, v6 = 0, uid_change = 1, wrap = 0, 10 | always_login = 0, extract_blob = 0, no_hk_check = 0, no_net = 0, socks5_dns = 0, allow_roam = 0; 11 | 12 | uint32_t traffic_flags = crash::TRAFFIC_PAD1, traffic_multiply = 0; 13 | 14 | std::string keyfile = "./serverkey.priv", certfile = "./serverkey.pub"; 15 | std::string host = "", port = "2222", laddr = "0.0.0.0", lport = "2222", local_proxy_ip = "127.0.0.1", sni = "", ticket = ""; 16 | 17 | std::string transport = "tls1"; 18 | 19 | // If ends with "/", its interpreted as current 20 | // subdirectory that contains the known server pubkeys; 21 | // otherwise as a absolute path for the file containing the 22 | // server pubkey 23 | std::string server_keys = ".crash/"; 24 | 25 | // Two meanings: for server: 26 | // If starts with "/", its interpreted as the absolute path 27 | // of the file containing the auth-key (be CAREFULL since this 28 | // may open root-hole, depending on permission of the file). 29 | // otherwise, its interpreted as ~/$user_keys/authorized_keys 30 | // where ~ depends on the user that wants to authenticate 31 | // for client: this should be the absolute path of the file 32 | // containing the private key used to authenticate against 33 | // the server 34 | std::string user_keys = ".crash"; 35 | 36 | std::string user = ""; 37 | std::string cmd = ""; 38 | 39 | // trigger-file / trigger-message 40 | std::string tfile = "/var/log/messages", tmsg = ""; 41 | 42 | std::string good_ip_file = ""; 43 | 44 | std::string disguise_method = "", disguise_secret = "", disguise_action = ""; 45 | 46 | std::string socks5_connect_proxy = "", socks5_connect_proxy_port = ""; 47 | 48 | std::map tcp_listens, udp_listens; 49 | 50 | std::map> sni2node; 51 | 52 | int socks5_port = -1, socks5_fd = -1, socks4_port = -1, socks4_fd = -1; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef crash_config_h 2 | #define crash_config_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace config 10 | { 11 | extern bool verbose, silent, v6, uid_change, wrap, 12 | always_login, extract_blob, no_hk_check, no_net, socks5_dns, allow_roam; 13 | 14 | extern uint32_t traffic_flags, traffic_multiply; 15 | 16 | extern std::string keyfile, certfile, host, port, laddr, lport, local_proxy_ip, sni, transport, ticket; 17 | extern std::string server_keys, user_keys, user, cmd; 18 | 19 | extern std::string tfile, tmsg, good_ip_file; 20 | 21 | extern std::string disguise_method, disguise_secret, disguise_action; 22 | 23 | extern std::string socks5_connect_proxy, socks5_connect_proxy_port; 24 | 25 | extern std::map tcp_listens, udp_listens; 26 | 27 | extern std::map> sni2node; 28 | 29 | extern int socks5_port, socks5_fd, socks4_port, socks4_fd; 30 | } 31 | 32 | #endif 33 | 34 | -------------------------------------------------------------------------------- /src/crashc.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "config.h" 43 | #include "session.h" 44 | #include "global.h" 45 | 46 | 47 | using namespace std; 48 | using namespace crash; 49 | 50 | 51 | void help(const char *p) 52 | { 53 | printf("\nUsage:\t%s [-6] [-v] [-H host] [-p port] [-L [ip]:port] [-i auth keyfile]\n" 54 | "\t [-K server key/s] [-c cmd] [-S SNI] [-DQ] [-X IP] [-U lport:[ip]:rport]\n" 55 | "\t [-T lport:[ip]:rport] [-Y lport:SNI:[ip]:rport [-4 lport] [-5 lport]\n" 56 | "\t [-R b:m] [-N] [-x socks5://[ip]:port] [-t ticket] [-G str] <-l user>\n\n" 57 | "\t -6 -- use IPv6 instead of IPv4\n" 58 | "\t -v -- be verbose\n" 59 | "\t -H -- host to connect to; if omitted: passive (default)\n" 60 | "\t -p -- port to connect to; default is %s\n" 61 | "\t -L -- local [ip]:port used for binding (default [0.0.0.0]:0)\n" 62 | "\t -i -- private key used for authentication\n" 63 | "\t -K -- folder of known host keys if it ends with '/';\n" 64 | "\t absolute path of known-hosts file otherwise;\n" 65 | "\t 'none' to disable; default is %s\n" 66 | "\t -c -- command to execute on remote host\n" 67 | "\t -X -- if proxying is enabled, bind to this IP (default 127.0.0.1)\n" 68 | "\t -N -- enable DNS resolving in SOCKS5 proxy\n" 69 | "\t -Y -- forward TLS port lport with SNI to ip:rport on remote site\n" 70 | "\t -U -- forward UDP port lport to ip:rport on remote site\n" 71 | "\t -T -- forward TCP port lport to ip:rport on remote site\n" 72 | "\t -4 -- start SOCKS4 server on lport to forward TCP sessions\n" 73 | "\t -5 -- start SOCKS5 server on lport to forward TCP sessions\n" 74 | "\t -R -- traffic blinding level (0-9) and multiply factor, default 4:0\n" 75 | "\t -D -- use DTLS transport (requires -S)\n" 76 | "\t -Q -- use QUIC transport (requires -S)\n" 77 | "\t -S -- SNI to use\n" 78 | "\t -t -- ticket-file to use for suspend/resume\n" 79 | "\t -x -- use this SOCKS5 proxy when connecting\n" 80 | "\t -G -- Trafic Disguise Prefix (requires -S)\n" 81 | "\t -l -- user to login as (no default!)\n\n", 82 | p, config::port.c_str(), config::server_keys.c_str()); 83 | } 84 | 85 | 86 | void sig_int(int x) 87 | { 88 | return; 89 | } 90 | 91 | 92 | int main(int argc, char **argv) 93 | { 94 | struct sigaction sa; 95 | string ostr = ""; 96 | 97 | int c = 0; 98 | uint32_t traffic_policy = 4; 99 | char lport[16] = {0}, port_hex[16] = {0}, ip[128] = {0}, sni[128] = {0}; 100 | uint16_t rport = 0; 101 | 102 | // in client mode we do not bind to a specific port by default 103 | config::lport = "0"; 104 | 105 | while ((c = getopt(argc, argv, "6vhH:K:p:L:X:Y:l:i:c:R:T:U:5:4:S:x:QDNt:G:")) != -1) { 106 | switch (c) { 107 | case 'G': 108 | config::disguise_secret = optarg; 109 | break; 110 | case 't': 111 | config::ticket = optarg; 112 | break; 113 | case 'N': 114 | config::socks5_dns = 1; 115 | break; 116 | case 'Q': 117 | #ifdef HAVE_QUIC 118 | config::transport = "quic1"; 119 | #else 120 | fprintf(stderr, "QUIC mode not compiled in.\n"); 121 | exit(1); 122 | #endif 123 | break; 124 | case 'D': 125 | config::transport = "dtls1"; 126 | break; 127 | case '6': 128 | if (config::laddr == "0.0.0.0") 129 | config::laddr = "::"; 130 | config::v6 = 1; 131 | break; 132 | case 'H': 133 | config::host = optarg; 134 | break; 135 | case 'p': 136 | config::port = optarg; 137 | break; 138 | case 'L': 139 | if (sscanf(optarg, "[%127[^]]]:%15[0-9]", ip, lport) == 2) { 140 | config::laddr = ip; 141 | config::lport = lport; 142 | } 143 | break; 144 | case 'X': 145 | config::local_proxy_ip = optarg; 146 | break; 147 | case 'K': 148 | config::server_keys = optarg; 149 | if (config::server_keys == "none") 150 | config::no_hk_check = 1; 151 | break; 152 | case 'l': 153 | config::user = optarg; 154 | break; 155 | case 'i': 156 | config::user_keys = optarg; 157 | break; 158 | case 'c': 159 | config::cmd = optarg; 160 | break; 161 | case 'v': 162 | config::verbose = 1; 163 | break; 164 | case 'R': 165 | if (sscanf(optarg, "%u:%u", &traffic_policy, &config::traffic_multiply) != 2) { 166 | fprintf(stderr, "crashc: Expect level:factor style argument for -R. Check docu.\n"); 167 | return 1; 168 | } 169 | break; 170 | case 'T': 171 | if (sscanf(optarg, "%15[0-9]:[%127[^]]]:%hu", lport, ip, &rport) == 3) { 172 | snprintf(port_hex, sizeof(port_hex), "%04hx", rport); 173 | config::tcp_listens[lport] = string(ip) + "/" + string(port_hex) + "/"; 174 | ostr += "crashc: set up local TCP port " + string(lport) + " to proxy to " + string(ip) + ":" + to_string(rport) + " @ remote.\n"; 175 | } 176 | break; 177 | case 'U': 178 | if (sscanf(optarg, "%15[0-9]:[%127[^]]]:%hu", lport, ip, &rport) == 3) { 179 | snprintf(port_hex, sizeof(port_hex), "%04hx", rport); 180 | config::udp_listens[lport] = string(ip) + "/" + string(port_hex) + "/"; 181 | ostr += "crashc: set up local UDP port " + string(lport) + " to proxy to " + string(ip) + ":" + to_string(rport) + " @ remote.\n"; 182 | } 183 | break; 184 | case 'Y': 185 | if (sscanf(optarg, "%15[0-9]:%127[^:]:[%127[^]]]:%hu", lport, sni, ip, &rport) == 4) { 186 | config::sni2node[sni] = { ip, rport}; 187 | config::tcp_listens[lport] = "SNI"; 188 | ostr += "crashc: set up local TCP port " + string(lport) + " to proxy for SNI " + sni + " to " + string(ip) + ":" + to_string(rport) + " @ remote.\n"; 189 | } 190 | case '4': 191 | if (config::socks4_fd == -1) { 192 | config::socks4_port = strtoul(optarg, nullptr, 10); 193 | if ((config::socks4_fd = tcp_listen(config::local_proxy_ip, optarg)) > 0) 194 | ostr += "crashc: set up SOCKS4 port on " + string(optarg) + "\n"; 195 | } 196 | break; 197 | case '5': 198 | if (config::socks5_fd == -1) { 199 | config::socks5_port = strtoul(optarg, nullptr, 10); 200 | if ((config::socks5_fd = tcp_listen(config::local_proxy_ip, optarg)) > 0) 201 | ostr += "crashc: set up SOCKS5 port on " + string(optarg) + "\n"; 202 | } 203 | break; 204 | case 'S': 205 | config::sni = optarg; 206 | break; 207 | case 'x': 208 | if (sscanf(optarg, "socks5://[%127[^]]]:%hu", ip, &rport) == 2) { 209 | config::socks5_connect_proxy = ip; 210 | config::socks5_connect_proxy_port = to_string(rport); 211 | } 212 | break; 213 | default: 214 | help(*argv); 215 | return 0; 216 | } 217 | } 218 | 219 | memset(&sa, 0, sizeof(sa)); 220 | sa.sa_handler = sig_winch; 221 | sigaction(SIGWINCH, &sa, nullptr); 222 | sa.sa_handler = sig_int; 223 | sigaction(SIGINT, &sa, nullptr); 224 | sa.sa_handler = SIG_IGN; 225 | sigaction(SIGPIPE, &sa, nullptr); 226 | 227 | if (config::user.length() == 0 || ((config::transport == "dtls1" || config::transport == "quic1") && config::sni.empty()) || 228 | (config::transport == "dtls1" && config::socks5_connect_proxy.size() > 0)) { 229 | printf("\nMissing or invalid combination of options.\n"); 230 | help(*argv); 231 | return 1; 232 | } 233 | 234 | if (traffic_policy < 0 || traffic_policy > 9) 235 | traffic_policy = 4; 236 | if (config::traffic_multiply < 1 || config::traffic_multiply > 100) 237 | config::traffic_multiply = 1; 238 | 239 | switch (traffic_policy) { 240 | case 0: 241 | config::traffic_flags = TRAFFIC_NOPAD; 242 | break; 243 | case 1: 244 | config::traffic_flags = TRAFFIC_PADRND; 245 | break; 246 | case 2: 247 | config::traffic_flags = TRAFFIC_PADRND|TRAFFIC_INJECT|TRAFFIC_PING_IGN; 248 | break; 249 | case 3: 250 | config::traffic_flags = TRAFFIC_PADRND|TRAFFIC_INJECT; 251 | break; 252 | 253 | case 4: 254 | config::traffic_flags = TRAFFIC_PAD1; 255 | break; 256 | case 5: 257 | config::traffic_flags = TRAFFIC_PAD1|TRAFFIC_INJECT|TRAFFIC_PING_IGN; 258 | break; 259 | case 6: 260 | config::traffic_flags = TRAFFIC_PAD1|TRAFFIC_INJECT; 261 | break; 262 | 263 | case 7: 264 | config::traffic_flags = TRAFFIC_PADMAX; 265 | break; 266 | case 8: 267 | config::traffic_flags = TRAFFIC_PADMAX|TRAFFIC_INJECT|TRAFFIC_PING_IGN; 268 | break; 269 | case 9: 270 | config::traffic_flags = TRAFFIC_PADMAX|TRAFFIC_INJECT; 271 | } 272 | 273 | // DTLS (UDP) always needs to inject SQ packets on timeout for RX/TX sync 274 | if (config::transport == "dtls1") 275 | config::traffic_flags |= TRAFFIC_INJECT; 276 | 277 | if (config::verbose) { 278 | fprintf(stderr, "\ncrypted admin shell (C) 2025 Sebastian Krahmer https://github.com/stealth/crash\n\n%s\n", ostr.c_str()); 279 | fprintf(stderr, "crashc: starting crypted administration shell\n"); 280 | if (!config::host.empty()) 281 | fprintf(stderr, "crashc: connecting to [%s]:%s ...\n\n", config::host.c_str(), config::port.c_str()); 282 | else 283 | fprintf(stderr, "crashc: listen for back-connect on [%s]:%s ...\n\n", config::laddr.c_str(), config::lport.c_str()); 284 | } 285 | client_session csess(config::ticket, config::transport, config::sni, config::disguise_secret); 286 | if (csess.setup() < 0) { 287 | fprintf(stderr, "crashc: %s\n", csess.why()); 288 | return 1; 289 | } 290 | 291 | if (csess.handle() < 0) { 292 | fprintf(stderr, "crashc: %s\n", csess.why()); 293 | return 1; 294 | } 295 | 296 | if (config::verbose) { 297 | if (csess.suspended()) 298 | fprintf(stderr, "\ncrashc: suspended.\n"); 299 | fprintf(stderr, "crashc: closing connection.\n"); 300 | } 301 | 302 | if (!global::input_received) 303 | fprintf(stderr, "crashc: No input received. Error. Auth failure?\n"); 304 | 305 | return 0; 306 | } 307 | 308 | -------------------------------------------------------------------------------- /src/crashd.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | extern "C" { 43 | #include 44 | } 45 | #include "server.h" 46 | #include "log.h" 47 | #include "misc.h" 48 | #include "global.h" 49 | #include "config.h" 50 | 51 | 52 | using namespace std; 53 | using namespace crash; 54 | 55 | 56 | void help(const char *p) 57 | { 58 | printf("\nUsage:\t%s [-U] [-q] [-a] [-6] [-DQ] [-H host] [-p port] [-A auth keys]\n" 59 | "\t [-k server key-file] [-c server X509 cert] [-L [ip]:port] [-S SNI]\n" 60 | "\t [-t trigger-file] [-m trigger message] [-e] [-g good IPs] [-N] [-R]\n" 61 | "\t [-x socks5://[ip]:port] [-G method:prefix:action] [-w]\n\n" 62 | "\t -a -- always login if authenticated, despite false/nologin shells\n" 63 | "\t -U -- run as user (e.g. turn off setuid() calls) if invoked as such\n" 64 | "\t -e -- extract key and certfile from the binary itself (no -k/-c needed)\n" 65 | "\t -q -- quiet mode, turns off logging and utmp entries\n" 66 | "\t -6 -- use IPv6 rather than IPv4\n" 67 | "\t -w -- setproctitle to `%s` (must be last arg!)\n" 68 | "\t -H -- host to connect to; if omitted: passive connect (default)\n" 69 | "\t -p -- port to connect to when active connect; default is %s\n" 70 | "\t -L -- local [ip]:port used for binding ([%s]:%s)\n" 71 | "\t -g -- file containing list of good IP/IP6's in D/DoS case (default off)\n" 72 | "\t -A -- authorized-key file for users if starts with '/'; folder inside ~\n" 73 | "\t containing authorized_keys file otherwise; 'self' means to use\n" 74 | "\t blob-extraction (see -e); default is %s\n" 75 | "\t -k -- servers key file; default is %s\n" 76 | "\t -c -- X509 certificate-file that belongs to serverkey (-k);\n" 77 | "\t default is %s\n" 78 | "\t -t -- watch triggerfile for certain message (-m) before connect/listen\n" 79 | "\t -m -- wait with connect/listen until message in file (-t) is seen\n" 80 | "\t -N -- disable TCP/UDP port forwarding\n" 81 | "\t -D -- use DTLS transport (requires -S)\n" 82 | "\t -Q -- use QUIC transport (requires -S)\n" 83 | "\t -x -- use this SOCKS5 proxy when using active connect\n" 84 | "\t -R -- allow clients to roam sessions\n" 85 | "\t -G -- Traffic Disguise Filters, check docu\n" 86 | "\t -S -- SNI to hide behind\n\n", 87 | p, TITLE, config::port.c_str(), config::laddr.c_str(), config::lport.c_str(), config::user_keys.c_str(), config::keyfile.c_str(), 88 | config::certfile.c_str()); 89 | } 90 | 91 | 92 | void sig_chld(int) 93 | { 94 | pid_t pid = 0; 95 | while ((pid = waitpid(-1, nullptr, WNOHANG)) > 0) { 96 | // If its the real crashd w/o a session, we run as root, 97 | // so clean up leaving sessions 98 | if (getpid() == global::crashd_pid) 99 | Server::add_chld(pid); 100 | } 101 | return; 102 | } 103 | 104 | 105 | void sig_alarm(int) 106 | { 107 | // Only exit for the sessions which run in another process, 108 | // as the alarm handler is there to kill timed out login sessions 109 | if (getpid() == global::crashd_pid) 110 | return; 111 | exit(1); 112 | } 113 | 114 | 115 | int main(int argc, char **argv) 116 | { 117 | int c = 0, i = 0; 118 | char **orig_argv = argv; 119 | char *argv0 = strdup(argv[0]); 120 | int orig_argc = argc; 121 | 122 | // First of all, some ugly parsing, so it can be called 123 | // via CGI too! 124 | char *ptr1 = nullptr, *ptr2 = nullptr, *ptr3 = nullptr; 125 | if ((ptr1 = getenv("QUERY_STRING")) != nullptr) { 126 | setvbuf(stdout, nullptr, _IONBF, 0); 127 | printf("Content-Type: text/html\r\n\r\n"); 128 | argv = (char **)malloc(100 * sizeof(char *)); 129 | if (!argv) { 130 | printf("Out of memory!\n"); 131 | return 1; 132 | } 133 | i = 1; 134 | memset(argv, 0, 100 * sizeof(char *)); 135 | argv[0] = strdup("[nfsd]"); 136 | 137 | while (i < 20 && (ptr2 = strchr(ptr1, '&')) != nullptr) { 138 | *ptr2 = 0; 139 | ptr3 = strchr(ptr1, '='); 140 | if (!ptr3) 141 | return 1; 142 | *ptr3 = 0; 143 | argv[i++] = strdup(ptr1); 144 | 145 | // switches without argument must be passed like 146 | // -U=1 to the CGI, so we have way more easy parsing 147 | if (strcmp(ptr3 + 1, "1")) 148 | argv[i++] = strdup(ptr3 + 1); 149 | ptr1 = ptr2; 150 | ++ptr1; 151 | } 152 | 153 | // last key=value pair, w/o & 154 | if ((ptr2 = strchr(ptr1, '=')) != nullptr) { 155 | *ptr2 = 0; 156 | argv[i++] = strdup(ptr1); 157 | if (strcmp(ptr2 + 1, "1")) 158 | argv[i++] = strdup(ptr2 + 1); 159 | } 160 | argc = i; 161 | for (i = 0; i < argc; ++i) 162 | printf("%s\r\n\r\n", argv[i]); 163 | } 164 | 165 | printf("\ncrypted admin shell (C) 2025 Sebastian Krahmer https://github.com/stealth/crash\n\n"); 166 | 167 | char ip[128] = {0}, lport[16] = {0}, prefix[128] = {0}, action[256] = {0}; 168 | 169 | while ((c = getopt(argc, argv, "6qhH:p:A:t:m:k:c:L:g:QDUweaS:NRG:")) != -1) { 170 | switch (c) { 171 | case 'G': 172 | // The only method for now is "redirect1" 173 | if (sscanf(optarg, "redirect1:%127[^:]:%255c", prefix, action) == 2) { 174 | config::disguise_method = "redirect1"; 175 | config::disguise_secret = prefix; 176 | config::disguise_action = action; 177 | } 178 | break; 179 | case 'Q': 180 | #ifdef HAVE_QUIC 181 | config::transport = "quic1"; 182 | #else 183 | fprintf(stderr, "QUIC mode not compiled in.\n"); 184 | exit(1); 185 | #endif 186 | break; 187 | case 'D': 188 | config::transport = "dtls1"; 189 | break; 190 | case 'U': 191 | config::uid_change = 0; 192 | break; 193 | case 't': 194 | config::tfile = optarg; 195 | break; 196 | case 'm': 197 | config::tmsg = optarg; 198 | break; 199 | case 'a': 200 | config::always_login = 1; 201 | break; 202 | case '6': 203 | if (config::laddr == "0.0.0.0") 204 | config::laddr = "::"; 205 | config::v6 = 1; 206 | break; 207 | case 'q': 208 | config::silent = 1; 209 | break; 210 | case 'g': 211 | config::good_ip_file = optarg; 212 | break; 213 | case 'w': 214 | config::wrap = 1; 215 | for (i = 0; i < orig_argc; ++i) 216 | memset(orig_argv[i], 0, strlen(orig_argv[i])); 217 | strcpy(orig_argv[0], TITLE); 218 | setproctitle(TITLE); 219 | break; 220 | case 'H': 221 | config::host = optarg; 222 | break; 223 | case 'p': 224 | config::port = optarg; 225 | break; 226 | case 'L': 227 | if (sscanf(optarg, "[%127[^]]]:%15[0-9]", ip, lport) == 2) { 228 | config::laddr = ip; 229 | config::lport = lport; 230 | } 231 | break; 232 | case 'A': 233 | config::user_keys = optarg; 234 | break; 235 | case 'k': 236 | config::keyfile = optarg; 237 | break; 238 | case 'c': 239 | config::certfile = optarg; 240 | break; 241 | case 'e': 242 | config::extract_blob = 1; 243 | break; 244 | case 'S': 245 | config::sni = optarg; 246 | break; 247 | case 'N': 248 | config::no_net = 1; 249 | break; 250 | case 'R': 251 | config::allow_roam = 1; 252 | break; 253 | case 'x': 254 | if (sscanf(optarg, "socks5://[%127[^]]]:%15[0-9]", ip, lport) == 2) { 255 | config::socks5_connect_proxy = ip; 256 | config::socks5_connect_proxy_port = lport; 257 | } 258 | break; 259 | default: 260 | help(*orig_argv); 261 | return 0; 262 | } 263 | } 264 | 265 | if ((config::transport == "dtls1" || config::transport == "quic1") && config::sni.empty()) { 266 | printf("Config error. DTLS option requires SNI. Exiting.\n\n"); 267 | return 1; 268 | } 269 | 270 | if (config::transport == "tls1" && config::allow_roam) { 271 | printf("Config error. TCP/TLS sessions cannot roam. Exiting.\n\n"); 272 | return 1; 273 | } 274 | 275 | struct sigaction sa; 276 | memset(&sa, 0, sizeof(sa)); 277 | sa.sa_flags = SA_RESTART; 278 | sa.sa_handler = sig_chld; 279 | sigaction(SIGCHLD, &sa, nullptr); 280 | 281 | sa.sa_handler = SIG_IGN; 282 | sigaction(SIGPIPE, &sa, nullptr); 283 | 284 | sa.sa_handler = sig_alarm; 285 | sigaction(SIGALRM, &sa, nullptr); 286 | 287 | if (RAND_load_file("/dev/urandom", 256) != 256) 288 | RAND_load_file("/dev/random", 8); 289 | 290 | if (config::good_ip_file.length() > 0) 291 | read_good_ips(config::good_ip_file); 292 | 293 | if (fork() != 0) 294 | return 0; 295 | 296 | global::crashd_pid = getpid(); 297 | 298 | //chdir("/"); No, we like to have the possibility to find keyfiles etc in "." 299 | int max = sysconf(_SC_OPEN_MAX); 300 | for (i = 0; i <= max; ++i) 301 | close(i); 302 | open("/dev/null", O_RDWR|O_NOCTTY); 303 | dup2(0, 1); dup2(1, 2); 304 | setsid(); 305 | 306 | // If a message-trigger has been given 307 | if (config::tmsg.length() > 0) 308 | read_until(config::tfile.c_str(), config::tmsg.c_str()); 309 | 310 | // extract key/cert from ELF binary into tmp file 311 | if (config::extract_blob) { 312 | config::keyfile = extract_keys(argv0); 313 | config::certfile = config::keyfile; 314 | if (config::keyfile.size() == 0) 315 | return 1; 316 | if (config::user_keys == "self") 317 | config::user_keys = config::keyfile; 318 | } 319 | 320 | Server *server_instance{nullptr}; 321 | if (!(server_instance = new (nothrow) Server(config::transport, config::sni))) 322 | return 1; 323 | 324 | auto cleanup_func = [&](int fail) { 325 | if (fail) 326 | syslog().log(server_instance->why()); 327 | sa.sa_handler = SIG_IGN; 328 | sigaction(SIGCHLD, &sa, nullptr); 329 | delete server_instance; 330 | server_instance = nullptr; 331 | if (config::extract_blob) 332 | unlink(config::keyfile.c_str()); 333 | return fail; 334 | }; 335 | 336 | if (server_instance->setup() < 0) 337 | return cleanup_func(1); 338 | 339 | if (server_instance->loop() < 0) 340 | return cleanup_func(1); 341 | 342 | return cleanup_func(0); 343 | } 344 | 345 | -------------------------------------------------------------------------------- /src/dh.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2022 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "dh2048.cc" 42 | 43 | namespace crash { 44 | 45 | #if OPENSSL_VERSION_NUMBER < 0x30000000 46 | 47 | static DH *dh2048 = nullptr; 48 | 49 | 50 | DH *dh_callback(SSL *ssl, int is_exported, int keylen) 51 | { 52 | return dh2048; 53 | } 54 | 55 | #endif 56 | 57 | int enable_dh(SSL_CTX *ctx) 58 | { 59 | #if OPENSSL_VERSION_NUMBER >= 0x30000000 60 | 61 | EVP_PKEY *evp_dh = nullptr; 62 | std::unique_ptr bio{ 63 | BIO_new_mem_buf(pem_dh.c_str(), pem_dh.size()), 64 | BIO_free 65 | }; 66 | if (!bio.get() || PEM_read_bio_Parameters(bio.get(), &evp_dh) == nullptr) 67 | return 0; 68 | SSL_CTX_set0_tmp_dh_pkey(ctx, evp_dh); 69 | return 1; 70 | #else 71 | if ((dh2048 = get_dh2048()) != nullptr) { 72 | SSL_CTX_set_tmp_dh_callback(ctx, dh_callback); 73 | return 1; 74 | } 75 | return 0; 76 | #endif 77 | } 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/dh2048.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | DH *get_dh2048() 4 | { 5 | return NULL; 6 | } 7 | -------------------------------------------------------------------------------- /src/disguise.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "config.h" 38 | #include "misc.h" 39 | 40 | extern "C" { 41 | #include 42 | #include 43 | } 44 | 45 | namespace crash { 46 | 47 | 48 | using namespace std; 49 | 50 | // -G method:secret:param 51 | // as of now there is only "redirect1" disguise 52 | 53 | int disguise_filter(SSL *ssl) 54 | { 55 | 56 | if (config::disguise_method != "redirect1") 57 | return 1; 58 | 59 | string req = ""; 60 | req.reserve(1024); 61 | char buf[1024] = {0}; 62 | 63 | pollfd pfd = {0}; 64 | if ((pfd.fd = SSL_get_fd(ssl)) < 0) 65 | return -1; 66 | pfd.events = POLLIN; 67 | 68 | int pn = 0; 69 | 70 | timeval start = {0}, now = {0}; 71 | gettimeofday(&start, nullptr); 72 | 73 | bool http_complete = 0; 74 | string::size_type nlnl = string::npos; 75 | 76 | for (;;) { 77 | gettimeofday(&now, nullptr); 78 | 79 | // timeout -> invalid request? 80 | if (now.tv_sec - start.tv_sec > 3) 81 | break; 82 | 83 | if ((pn = poll(&pfd, 1, TCP_POLL_TO)) < 0) 84 | return -1; 85 | if (pn == 0) 86 | continue; 87 | 88 | ssize_t n = SSL_peek(ssl, buf, sizeof(buf)); 89 | switch (SSL_get_error(ssl, n)) { 90 | case SSL_ERROR_NONE: 91 | if (n > 0) 92 | req += string(buf, n); 93 | break; 94 | case SSL_ERROR_WANT_WRITE: 95 | case SSL_ERROR_WANT_READ: 96 | break; 97 | case SSL_ERROR_ZERO_RETURN: 98 | // fallthrough 99 | default: 100 | return -1; 101 | } 102 | 103 | // someone who knows, success 104 | if (req.find(config::disguise_secret) == 0) { 105 | 106 | // We just peeked, so really take these bytes from the layer (according to manpage) 107 | // SSL_read() must return it w/o error if SSL_peek() did so. 108 | SSL_read(ssl, buf, config::disguise_secret.size()); 109 | return 1; 110 | } 111 | 112 | // look for complete HTTP hdr 113 | if ((nlnl = req.rfind("\r\n\r\n")) != string::npos) { 114 | http_complete = 1; 115 | break; 116 | } 117 | } 118 | 119 | // should be some sort of HTTP request by now 120 | 121 | struct tm tm = {0}; 122 | char gmt_date[128] = {0}; 123 | gmtime_r(&now.tv_sec, &tm); 124 | strftime(gmt_date, sizeof(gmt_date), "%a, %d %b %Y %H:%M:%S GMT\r\n", &tm); 125 | 126 | string bad = "HTTP/1.1 400 Bad Request\r\nServer: nginx\r\n"; 127 | bad += "Date: " + string(gmt_date); 128 | bad += "Content-Type: text/html\r\nContent-Length: 150\r\nConnection: close\r\n\r\n" 129 | "\r\n400 Bad Request\r\n\r\n" 130 | "

400 Bad Request

\r\n
nginx
\r\n" 131 | "\r\n\r\n"; 132 | 133 | 134 | if (!http_complete) { 135 | SSL_write(ssl, bad.c_str(), bad.size()); 136 | return 0; 137 | } 138 | 139 | auto r = regex("^(POST|GET|HEAD) +/[^ ]* +HTTP/1\\.1\r\n"); 140 | if (regex_search(req, r) != 1) { 141 | SSL_write(ssl, bad.c_str(), bad.size()); 142 | return 0; 143 | } 144 | 145 | r = regex("\r\nHost: *[\\.a-zA-Z0-9-_:]+\r\n"); 146 | if (regex_search(req, r) != 1) { 147 | SSL_write(ssl, bad.c_str(), bad.size()); 148 | return 0; 149 | } 150 | 151 | // must exist 152 | string::size_type nl = req.find("\r\n"), prev_nl = string::npos; 153 | prev_nl = nl + 2; 154 | nl = req.find("\r\n", prev_nl); 155 | 156 | // Each line after initial GET must be of "Key: Value" form 157 | for (http_complete = 0; nl != string::npos;) { 158 | if (nl == nlnl) { 159 | http_complete = 1; 160 | break; 161 | } 162 | nl += 2; 163 | if (req.substr(prev_nl, nl - prev_nl).find(":") == string::npos) 164 | break; 165 | prev_nl = nl; 166 | nl = req.find("\r\n", nl); 167 | } 168 | 169 | if (!http_complete) { 170 | SSL_write(ssl, bad.c_str(), bad.size()); 171 | return 0; 172 | } 173 | 174 | string redir = "HTTP/1.1 301 Moved Permanently\r\nServer: nginx\r\n"; 175 | redir += "Date: " + string(gmt_date); 176 | redir += "Content-Type: text/html\r\nContent-Length: 138\r\nConnection: close\r\n" 177 | "Location: " + config::disguise_action + "\r\n\r\n" 178 | "\r\n301 Found\r\n\r\n" 179 | "

301 Found

\r\n
nginx
\r\n" 180 | "\r\n\r\n"; 181 | 182 | SSL_write(ssl, redir.c_str(), redir.size()); 183 | return 0; 184 | } 185 | 186 | } 187 | 188 | -------------------------------------------------------------------------------- /src/disguise.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | extern "C" { 34 | #include 35 | } 36 | 37 | namespace crash { 38 | 39 | 40 | int disguise_filter(SSL *ssl); 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/global.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | namespace global { 7 | 8 | volatile bool window_size_changed = 0; 9 | 10 | bool input_received = 0; 11 | 12 | pid_t crashd_pid = 0; 13 | 14 | std::map good_ips; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | #ifndef crash_global_h 2 | #define crash_global_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | namespace global { 10 | extern bool window_size_changed; 11 | extern bool input_received; 12 | extern pid_t crashd_pid; 13 | extern std::map good_ips; 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/iobox.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2022 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "pty.h" 39 | #include "iobox.h" 40 | 41 | 42 | iobox::iobox() 43 | { 44 | } 45 | 46 | 47 | iobox::~iobox() 48 | { 49 | if (d_mode == MODE_PTY) 50 | d_pty.close(); 51 | else if (d_mode == MODE_PIPE || d_mode == MODE_SOCKET) { 52 | close(d_in[0]); close(d_in[1]); 53 | close(d_out[0]); close(d_out[1]); 54 | close(d_err[0]); close(d_err[1]); 55 | } 56 | } 57 | 58 | 59 | int iobox::init_pty(uid_t u, gid_t g, mode_t m) 60 | { 61 | d_mode = MODE_PTY; 62 | 63 | if (d_pty.open() < 0) { 64 | d_serr = "iobox::init_pty::"; 65 | d_serr += d_pty.why(); 66 | return -1; 67 | } 68 | if (d_pty.grant(u, g, m) < 0) { 69 | d_serr = "iobox::init_pty::"; 70 | d_serr += d_pty.why(); 71 | return -1; 72 | } 73 | return 0; 74 | } 75 | 76 | 77 | int iobox::init_pipe() 78 | { 79 | d_mode = MODE_PIPE; 80 | 81 | if (pipe(d_in) < 0) { 82 | d_serr = "iobox::init_pipe:"; 83 | d_serr += strerror(errno); 84 | return -1; 85 | } 86 | if (pipe(d_out) < 0) { 87 | d_serr = "iobox::init_pipe:"; 88 | d_serr += strerror(errno); 89 | return -1; 90 | } 91 | if (pipe(d_err) < 0) { 92 | d_serr = "iobox::init_pipe:"; 93 | d_serr += strerror(errno); 94 | return -1; 95 | } 96 | return 0; 97 | } 98 | 99 | 100 | int iobox::init_socket() 101 | { 102 | d_mode = MODE_SOCKET; 103 | 104 | if (socketpair(PF_UNIX, SOCK_DGRAM, 0, d_in) < 0) { 105 | d_serr = "iobox::init_socket:"; 106 | d_serr += strerror(errno); 107 | return -1; 108 | } 109 | if (socketpair(PF_UNIX, SOCK_DGRAM, 0, d_out) < 0) { 110 | d_serr = "iobox::init_socket:"; 111 | d_serr += strerror(errno); 112 | return -1; 113 | } 114 | if (socketpair(PF_UNIX, SOCK_DGRAM, 0, d_err) < 0) { 115 | d_serr = "iobox::init_socket:"; 116 | d_serr += strerror(errno); 117 | return -1; 118 | } 119 | return 0; 120 | } 121 | 122 | 123 | int iobox::slave0() 124 | { 125 | if (d_mode == MODE_PTY) { 126 | return d_pty.slave(); 127 | } else { 128 | return d_in[0]; // read end for slave stdin 129 | } 130 | 131 | return -1; 132 | } 133 | 134 | 135 | int iobox::close_slave0() 136 | { 137 | if (d_mode == MODE_PTY) { 138 | return d_pty.close_slave(); 139 | } else { 140 | close(d_in[0]); 141 | d_in[0] = -1; 142 | return 0; 143 | } 144 | return -1; 145 | } 146 | 147 | 148 | int iobox::slave1() 149 | { 150 | if (d_mode == MODE_PTY) { 151 | return d_pty.slave(); 152 | } else { 153 | return d_out[1]; // write end for slave stdout 154 | } 155 | 156 | return -1; 157 | } 158 | 159 | 160 | int iobox::close_slave1() 161 | { 162 | if (d_mode == MODE_PTY) { 163 | return d_pty.close_slave(); 164 | } else { 165 | close(d_out[1]); 166 | d_out[1] = -1; 167 | return 0; 168 | } 169 | 170 | return -1; 171 | } 172 | 173 | 174 | int iobox::slave2() 175 | { 176 | if (d_mode == MODE_PTY) { 177 | return d_pty.slave(); 178 | } else { 179 | return d_err[1]; // write end for slave stderr 180 | } 181 | 182 | return -1; 183 | } 184 | 185 | 186 | int iobox::close_slave2() 187 | { 188 | if (d_mode == MODE_PTY) { 189 | return d_pty.close_slave(); 190 | } else { 191 | close(d_err[1]); 192 | d_err[1] = -1; 193 | return 0; 194 | } 195 | 196 | return -1; 197 | } 198 | 199 | 200 | int iobox::master0() 201 | { 202 | if (d_mode == MODE_PTY) { 203 | return d_pty.master(); 204 | } else { 205 | return d_in[1]; // write end for what appears on slave stdin 206 | } 207 | 208 | return -1; 209 | } 210 | 211 | 212 | int iobox::close_master0() 213 | { 214 | if (d_mode == MODE_PTY) { 215 | return d_pty.close_master(); 216 | } else { 217 | close(d_in[1]); 218 | d_in[1] = -1; 219 | return 0; 220 | } 221 | 222 | return -1; 223 | 224 | } 225 | 226 | 227 | int iobox::master1() 228 | { 229 | if (d_mode == MODE_PTY) { 230 | return d_pty.master(); 231 | } else { 232 | return d_out[0]; // read end for slave's stdout 233 | } 234 | 235 | return -1; 236 | } 237 | 238 | 239 | int iobox::close_master1() 240 | { 241 | if (d_mode == MODE_PTY) { 242 | return d_pty.close_master(); 243 | } else { 244 | close(d_out[0]); 245 | d_out[0] = -1; 246 | return 0; 247 | } 248 | 249 | return -1; 250 | } 251 | 252 | 253 | int iobox::master2() 254 | { 255 | if (d_mode == MODE_PTY) { 256 | return d_pty.master(); 257 | } else { 258 | return d_err[0]; // read end for slave's stderr 259 | } 260 | 261 | return -1; 262 | } 263 | 264 | 265 | int iobox::close_master2() 266 | { 267 | if (d_mode == MODE_PTY) { 268 | return d_pty.close_master(); 269 | } else { 270 | close(d_err[0]); 271 | d_err[0] = -1; 272 | return 0; 273 | } 274 | 275 | return -1; 276 | } 277 | 278 | 279 | int iobox::close_master() 280 | { 281 | if (d_mode == MODE_PTY) { 282 | return d_pty.close_master(); 283 | } else { 284 | close_master0(); 285 | close_master1(); 286 | close_master2(); 287 | return 0; 288 | } 289 | 290 | return -1; 291 | } 292 | 293 | 294 | int iobox::close_slave() 295 | { 296 | if (d_mode == MODE_PTY) { 297 | return d_pty.close_slave(); 298 | } else { 299 | close_slave0(); 300 | close_slave1(); 301 | close_slave2(); 302 | return 0; 303 | } 304 | 305 | return -1; 306 | } 307 | 308 | -------------------------------------------------------------------------------- /src/iobox.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2022 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef crash_iobox_h 34 | #define crash_iobox_h 35 | 36 | #include 37 | #include 38 | #include 39 | #include "pty.h" 40 | 41 | 42 | typedef enum { 43 | MODE_INVALID = 0, 44 | MODE_PTY = 1, 45 | MODE_PIPE = 2, 46 | MODE_SOCKET = 3 47 | } iobox_mode_t; 48 | 49 | 50 | /* an I/O layer abstraction class that could be a pty or a pipe, 51 | * depending on whether an pty needs to be allocated or not. This makes the 52 | * session loop much more readable. 53 | * In the pty case, slave0, slave1, slave2 are all the same as only one pty 54 | * is needed for IPC. In pipe case, theres a pipe pair for each slave0/master0, 55 | * slave1/master1, slave2/master2 so that the session loop can distinguish between 56 | * stdout and stderr writes of the child and can mux this through the crash client. 57 | * This is good for some programs which require stdout/stderr split to distinguish 58 | * output data from errors (such as opmsg). 59 | */ 60 | 61 | class iobox { 62 | 63 | int d_in[2]{-1, -1}, d_out[2]{-1, -1}, d_err[2]{-1, -1}; 64 | 65 | #ifdef HAVE_UNIX98 66 | pty98 d_pty; 67 | #else 68 | pty d_pty; 69 | #endif 70 | 71 | iobox_mode_t d_mode{MODE_INVALID}; 72 | 73 | std::string d_serr{""}; 74 | 75 | public: 76 | 77 | const char *why() { return d_serr.c_str(); } 78 | 79 | iobox(); 80 | 81 | ~iobox(); 82 | 83 | bool is_pty() { return d_mode == MODE_PTY; } 84 | 85 | int init_pipe(); 86 | 87 | int init_pty(uid_t, gid_t, mode_t); 88 | 89 | int init_socket(); 90 | 91 | int slave0(); 92 | 93 | int close_slave0(); 94 | 95 | int slave1(); 96 | 97 | int close_slave1(); 98 | 99 | int slave2(); 100 | 101 | int close_slave2(); 102 | 103 | int master0(); 104 | 105 | int close_master0(); 106 | 107 | int master1(); 108 | 109 | int close_master1(); 110 | 111 | int master2(); 112 | 113 | int close_master2(); 114 | 115 | int close_master(); 116 | 117 | int close_slave(); 118 | 119 | const std::string pts_name() 120 | { 121 | if (d_mode == MODE_PTY) 122 | return d_pty.sname(); 123 | return ""; 124 | } 125 | 126 | }; 127 | 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /src/log.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2022 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include "log.h" 42 | #include "config.h" 43 | 44 | #ifdef __FreeBSD__ 45 | #include 46 | #endif 47 | 48 | using namespace std; 49 | 50 | namespace crash { 51 | 52 | logger::logger() 53 | { 54 | openlog("crashd", LOG_NOWAIT|LOG_PID, LOG_DAEMON); 55 | } 56 | 57 | 58 | logger::~logger() 59 | { 60 | closelog(); 61 | } 62 | 63 | 64 | void logger::log(const string &msg) 65 | { 66 | if (!config::silent) 67 | ::syslog(LOG_NOTICE, "%s", msg.c_str()); 68 | } 69 | 70 | 71 | logger &syslog() 72 | { 73 | static logger L; 74 | return L; 75 | } 76 | 77 | 78 | #if (defined ANDROID) || (defined EMBEDDED) || (defined __OpenBSD__) 79 | void logger::login(const string &dev, const string &user, const string &host) 80 | { 81 | } 82 | 83 | 84 | void logger::logout(pid_t pid, const struct timeval &tv) 85 | { 86 | } 87 | 88 | 89 | #else 90 | 91 | #include 92 | 93 | void logger::login(const string &dev, const string &user, const string &host) 94 | { 95 | if (config::silent) 96 | return; 97 | 98 | struct utmpx utx; 99 | memset(&utx, 0, sizeof(utx)); 100 | 101 | utx.ut_pid = getpid(); 102 | utx.ut_type = USER_PROCESS; 103 | 104 | const char *ptr = nullptr; 105 | if (strstr(dev.c_str(), "/dev/")) 106 | ptr = dev.c_str() + 5; 107 | else 108 | ptr = dev.c_str(); 109 | 110 | snprintf(utx.ut_line, sizeof(utx.ut_line), "%s", ptr); 111 | snprintf(utx.ut_user, sizeof(utx.ut_user), "%s", user.c_str()); 112 | #ifdef __CYGWIN__ 113 | snprintf(utx.ut_id, sizeof(utx.ut_id), "%02x", utx.ut_pid); 114 | #else 115 | snprintf(utx.ut_id, sizeof(utx.ut_id), "%03x", utx.ut_pid); 116 | #endif 117 | 118 | timeval tv; 119 | gettimeofday(&tv, nullptr); 120 | utx.ut_tv.tv_sec = tv.tv_sec; 121 | utx.ut_tv.tv_usec = tv.tv_usec; 122 | 123 | setutxent(); 124 | pututxline(&utx); 125 | endutxent(); 126 | } 127 | 128 | 129 | void logger::logout(pid_t pid, const struct timeval &tv) 130 | { 131 | if (config::silent) 132 | return; 133 | 134 | struct utmpx utx; 135 | memset(&utx, 0, sizeof(utx)); 136 | 137 | utx.ut_pid = pid; 138 | utx.ut_type = DEAD_PROCESS; 139 | 140 | #ifdef __CYGWIN__ 141 | snprintf(utx.ut_id, sizeof(utx.ut_id), "%02x", utx.ut_pid); 142 | #else 143 | snprintf(utx.ut_id, sizeof(utx.ut_id), "%03x", utx.ut_pid); 144 | #endif 145 | 146 | utx.ut_tv.tv_sec = tv.tv_sec; 147 | utx.ut_tv.tv_usec = tv.tv_usec; 148 | 149 | setutxent(); 150 | 151 | struct utmpx *ent = nullptr; 152 | for (;;) { 153 | if (!(ent = getutxid(&utx))) 154 | break; 155 | if (ent->ut_pid == pid && ent->ut_type == USER_PROCESS) { 156 | pututxline(&utx); 157 | break; 158 | } 159 | } 160 | endutxent(); 161 | } 162 | 163 | #endif 164 | 165 | } 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2016 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef crash_log_h 34 | #define crash_log_h 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | namespace crash { 41 | 42 | class logger { 43 | 44 | public: 45 | logger(); 46 | 47 | ~logger(); 48 | 49 | void log(const std::string &); 50 | 51 | static void login(const std::string&, const std::string&, const std::string&); 52 | 53 | static void logout(pid_t, const struct timeval &); 54 | }; 55 | 56 | 57 | extern logger &syslog(); 58 | 59 | } 60 | 61 | #endif 62 | 63 | -------------------------------------------------------------------------------- /src/misc.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | extern "C" { 49 | #include 50 | } 51 | #include "config.h" 52 | #include "misc.h" 53 | #include "global.h" 54 | 55 | #ifdef __linux__ 56 | #include 57 | #endif 58 | 59 | 60 | using namespace std; 61 | 62 | namespace crash { 63 | 64 | 65 | string slen(unsigned short l) 66 | { 67 | char buf[32] = {0}; 68 | snprintf(buf, sizeof(buf) - 1, "%05hu", l); 69 | return buf; 70 | } 71 | 72 | 73 | int readn(int fd, void *buf, size_t len) 74 | { 75 | int o = 0, n; 76 | char *ptr = (char*)buf; 77 | 78 | while (len > 0) { 79 | if ((n = read(fd, ptr+o, len)) <= 0) 80 | return n; 81 | len -= n; 82 | o += n; 83 | } 84 | return o; 85 | } 86 | 87 | 88 | int writen(int fd, const void *buf, size_t len) 89 | { 90 | int o = 0, n = 0; 91 | const char *ptr = reinterpret_cast(buf); 92 | 93 | while (len > 0) { 94 | if ((n = write(fd, ptr + o, len)) <= 0) 95 | return n; 96 | len -= n; 97 | o += n; 98 | } 99 | return o; 100 | } 101 | 102 | 103 | int flush_fd(int fd, const string &buf) 104 | { 105 | string::size_type bs = buf.size(); 106 | 107 | if (bs == 0) 108 | return 0; 109 | 110 | ssize_t n = 0; 111 | size_t idx = 0; 112 | do { 113 | size_t wn = bs > 0x1000 ? 0x1000 : bs; 114 | if ((n = write(fd, buf.c_str() + idx, wn)) <= 0) 115 | return n; 116 | idx += n; 117 | bs -= n; 118 | } while (bs > 0); 119 | 120 | return idx; 121 | } 122 | 123 | 124 | size_t prepend_seq(sequence_t n, string &s) 125 | { 126 | size_t r = 0; 127 | 128 | // no sequencing for Re-sends and SQ packets. 129 | if (s.find("D:") == 6 || s.find("C:WS:") == 6 || s.find("C:T:") == 6 || 130 | s.find("C:U:") == 6 || s.find("C:CL") == 6 || s.find("C:PP") == 6) { 131 | char buf[32] = {0}; 132 | snprintf(buf, sizeof(buf) - 1, "%05hu:C:PN:%016llx:", (unsigned short)(6 + 17 + ((unsigned short)(s.size() & 0xffff))), n); 133 | s.insert(0, buf); 134 | r = 1; 135 | } 136 | 137 | return r; 138 | } 139 | 140 | 141 | size_t rnd_between(size_t min, size_t max) 142 | { 143 | size_t rnd = 0; 144 | if (min >= max || RAND_bytes(reinterpret_cast(&rnd), sizeof(size_t)) != 1) 145 | return min; 146 | rnd %= (max - min); 147 | return min + rnd; 148 | } 149 | 150 | 151 | size_t pad_nops(string &s, size_t PMAX_SIZE) 152 | { 153 | 154 | if (config::traffic_flags & TRAFFIC_NOPAD || PMAX_SIZE > MSS || PMAX_SIZE < QUIC_MSS || s.size() > MSS) 155 | return 0; 156 | 157 | const size_t nop_fix = 5 + 6; // %05hu:C:NO: 158 | 159 | const auto l = s.size(); 160 | size_t pads = 0; 161 | 162 | if (l + nop_fix >= PMAX_SIZE) 163 | return 0; 164 | 165 | char zeros[MSS] = {0}; 166 | 167 | if (config::traffic_flags & TRAFFIC_PADMAX) { 168 | pads = PMAX_SIZE - l - nop_fix; 169 | } else if (config::traffic_flags & TRAFFIC_PADRND) { 170 | pads = rnd_between(0, PMAX_SIZE - l - nop_fix); 171 | } else { 172 | if (l + nop_fix > 1024) 173 | pads = PMAX_SIZE - l - nop_fix; 174 | else if (l + nop_fix > 512) 175 | pads = 1024 - l - nop_fix; 176 | else if (l + nop_fix > 256) 177 | pads = 512 - l - nop_fix; 178 | else 179 | pads = 256 - l - nop_fix; 180 | } 181 | 182 | // Huh? 183 | if (pads <= 0 || nop_fix + pads > PMAX_SIZE) 184 | return 0; 185 | 186 | s += slen(6 + pads); 187 | s += ":C:NO:"; 188 | s += string(zeros, pads); 189 | 190 | return nop_fix + pads; 191 | } 192 | 193 | 194 | string ping_packet() 195 | { 196 | if (config::traffic_flags & TRAFFIC_PING_IGN) 197 | return "00010:C:PR:ping"; // use a ping-reply which is ignored 198 | else 199 | return "00010:C:PP:ping"; 200 | } 201 | 202 | 203 | void sig_winch(int x) 204 | { 205 | global::window_size_changed = 1; 206 | } 207 | 208 | 209 | void read_until(const char *path, const char *msg) 210 | { 211 | FILE *f = fopen(path, "r"); 212 | if (!f) 213 | return; 214 | setvbuf(f, nullptr, _IONBF, 0); 215 | fseek(f, 0, SEEK_END); 216 | long off = ftell(f); 217 | 218 | char buf[1024] = {0}; 219 | 220 | for (;;) { 221 | memset(buf, 0, sizeof(buf)); 222 | fseek(f, off, SEEK_SET); 223 | if (!fgets(buf, sizeof(buf), f)) 224 | ; // avoid gcc warning 225 | if (strstr(buf, msg)) 226 | break; 227 | off = ftell(f); 228 | 229 | // do not sleep if we have read something 230 | if (strlen(buf) == 0) 231 | sleep(3); 232 | } 233 | fclose(f); 234 | } 235 | 236 | 237 | string extract_keys(const char *blob) 238 | { 239 | // -----BEGIN RSA PRIV 240 | char pattern[] = {'-' - 1, '-' - 1, '-' - 1, '-' - 1, '-' - 1, 241 | 'B' - 1, 'E' - 1, 'G' - 1, 'I' - 1, 'N' - 1, ' ' - 1, 242 | 'R' - 1, 'S' - 1, 'A' - 1, ' ' - 1, 'P' - 1, 'R' - 1, 'I' - 1, 'V' - 1}; 243 | struct stat st; 244 | int fd = 0; 245 | unsigned int i = 0; 246 | #ifdef ANDROID 247 | char tfile[] = "/data/local/tmp/sshXXXXXX"; 248 | #else 249 | char tfile[] = "/tmp/sshXXXXXX"; 250 | #endif 251 | 252 | // un-rot 253 | for (i = 0; i < sizeof(pattern); ++i) 254 | ++pattern[i]; 255 | 256 | if (stat(blob, &st) < 0) 257 | return ""; 258 | char *data = new char[st.st_size]; 259 | if (!data) 260 | return ""; 261 | 262 | if ((fd = open(blob, O_RDONLY|O_NOCTTY)) < 0) { 263 | delete [] data; 264 | return ""; 265 | } 266 | 267 | if (read(fd, data, st.st_size) != st.st_size) { 268 | delete [] data; 269 | return ""; 270 | } 271 | close(fd); 272 | if ((fd = mkstemp(tfile)) < 0) { 273 | delete [] data; 274 | return ""; 275 | } 276 | fchmod(fd, 0444); 277 | 278 | for (i = 0; i < st.st_size - sizeof(pattern); ++i) { 279 | if (memcmp(data + i, pattern, sizeof(pattern)) == 0) 280 | break; 281 | } 282 | if (i == st.st_size - sizeof(pattern)) { 283 | delete [] data; 284 | close(fd); 285 | return ""; 286 | } 287 | 288 | if (write(fd, data + i, st.st_size - i) < 0) 289 | ; // avoid gcc warning 290 | close(fd); 291 | delete [] data; 292 | return string(tfile); 293 | } 294 | 295 | 296 | void read_good_ips(const string &path) 297 | { 298 | struct in_addr in; 299 | struct in6_addr in6; 300 | char buf[1024]; 301 | 302 | FILE *f = fopen(path.c_str(), "r"); 303 | if (!f) 304 | return; 305 | do { 306 | memset(buf, 0, sizeof(buf)); 307 | if (!fgets(buf, sizeof(buf), f)) 308 | break; 309 | strtok(buf, ";#\t \n"); 310 | if (inet_pton(AF_INET6, buf, &in6) == 1 || inet_pton(AF_INET, buf, &in) == 1) 311 | global::good_ips[buf] = 1; 312 | } while (!feof(f)); 313 | fclose(f); 314 | } 315 | 316 | 317 | // You might think that these functions are slow and its stupid to call 318 | // them. Remember that these are called only under a detected D/DoS condition 319 | // and in this case we accept only one connection per second anyway 320 | bool is_good_ip(const struct in_addr &in) 321 | { 322 | char dst[128]; 323 | if (inet_ntop(AF_INET, &in, dst, sizeof(dst)) == nullptr) 324 | return 0; 325 | if (global::good_ips.find(dst) != global::good_ips.end()) 326 | return 1; 327 | 328 | struct in_addr t = in; 329 | unsigned int n = 0xffffffff; 330 | for (int i = 1; i <= 24; ++i) { 331 | t.s_addr &= htonl(n< https_to_node(const char *chello, int bsize) 382 | { 383 | pair ret = {}; 384 | 385 | const unsigned char *ptr = reinterpret_cast(chello), *end = ptr + bsize; 386 | 387 | // TLS record; Handshake, version, len 388 | if (end - ptr <= 5 || bsize <= 5) 389 | return ret; 390 | ptr += 5; 391 | 392 | // ClientHello? 393 | if (*ptr != 1) 394 | return ret; 395 | ++ptr; 396 | 397 | auto it = config::sni2node.find("default"); 398 | if (it != config::sni2node.end()) 399 | ret = it->second; 400 | 401 | if (end - ptr <= 5 + 32 + 1) // record + Random + session_id len 402 | return ret; 403 | ptr += 5 + 32; 404 | 405 | uint8_t sessid_len = *ptr; 406 | ++ptr; 407 | if (end - ptr <= sessid_len) 408 | return ret; 409 | ptr += sessid_len; 410 | 411 | if (end - ptr <= 2) // cipher suite len 412 | return ret; 413 | uint16_t clen = ua_uint16_ntohs(ptr); 414 | ptr += 2; 415 | if (end - ptr <= clen) 416 | return ret; 417 | ptr += clen; 418 | 419 | if (end - ptr <= 1) // compression len 420 | return ret; 421 | clen = *ptr; 422 | ++ptr; 423 | if (end - ptr <= clen) 424 | return ret; 425 | ptr += clen; 426 | 427 | if (end - ptr <= 2) // Extensions len (sum of all Ex.) 428 | return ret; 429 | 430 | // skip Extensions len and iterate over each extension until we find SNI 431 | ptr += 2; 432 | 433 | for (; ptr < end;) { 434 | if (end - ptr <= 2) // Ex. Type 435 | break; 436 | uint16_t etype = ua_uint16_ntohs(ptr); 437 | ptr += 2; 438 | if (end - ptr <= 2) // Ex. Len 439 | break; 440 | clen = ua_uint16_ntohs(ptr); 441 | ptr += 2; 442 | if (end - ptr <= clen) 443 | break; 444 | 445 | // servername Ex. found? Go deeper to parse SNI Ex. (what a stupid protocol) 446 | // Theoretically there could be a lot of Server Name Types and list of hosts, but 447 | // we only allow "hostname" type and just one of them 448 | if (etype == 0) { 449 | if (end - ptr <= 2) 450 | break; 451 | clen = ua_uint16_ntohs(ptr); // Server Name List len 452 | ptr += 2; 453 | if (end - ptr <= clen || end - ptr <= 1) // 1 for Server Name Type 454 | break; 455 | if (*ptr != 0) // Server Name Type 0 -> Host Name 456 | break; 457 | ++ptr; 458 | if (end - ptr <= 2) 459 | break; 460 | clen = ua_uint16_ntohs(ptr); // hostname len 461 | ptr += 2; 462 | if (end - ptr < clen) 463 | break; 464 | string hostname = string(reinterpret_cast(ptr), clen); 465 | if (config::sni2node.count(hostname) > 0) 466 | ret = config::sni2node[hostname]; 467 | break; 468 | } 469 | ptr += clen; 470 | } 471 | 472 | return ret; 473 | } 474 | 475 | 476 | } 477 | 478 | -------------------------------------------------------------------------------- /src/misc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef crash_misc_h 34 | #define crash_misc_h 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | namespace crash { 45 | 46 | 47 | enum { 48 | STATE_INVALID = 0, 49 | STATE_PTY = 1, 50 | STATE_STDIN = 2, 51 | STATE_STDOUT = 3, 52 | STATE_STDERR = 4, 53 | STATE_SSL = 5, 54 | 55 | STATE_ACCEPT = 6, 56 | STATE_CONNECT = 7, 57 | STATE_CONNECTED = 8, 58 | STATE_CLOSING = 9, 59 | STATE_UDPCLIENT = 10, 60 | STATE_UDPSERVER = 11, 61 | STATE_SOCKS5_ACCEPT = 12, 62 | STATE_SOCKS5_AUTH1 = 13, 63 | STATE_SOCKS5_AUTH2 = 14, 64 | STATE_SOCKS4_ACCEPT = 15, 65 | STATE_SOCKS4_AUTH = 16, 66 | STATE_SNI_ACCEPT = 17, 67 | STATE_SNI_RCV = 18, 68 | 69 | CLOSING_TIME = 10, 70 | CONNECT_TIME = 30, 71 | UDP_CLOSING_TIME = 120, 72 | 73 | FDID_MAX = 65535, // id field of net cmds encoded as %04hx, so socket fds must not be larger 74 | 75 | // max prefix sizes 76 | SEQ_PSIZE = 28, // len:C:PN:.... packet numbering prefix for UDP dgrams 77 | DATA_PSIZE = 12, // len:D:I0: data I/O prefix 78 | NODE_PSIZE = 62, // len:C:T:N:IP/port/id/ net cmds 79 | 80 | MTU = 1500, 81 | MSS = 1320, // 'plain' with room for D/TLS record, transport and network hdrs + options 82 | QUIC_MSS = 1200 - 80, // 1200 as per RFC minus frame sizes. Must be < MSG_BSIZE 83 | STDIN_BSIZE = MSS - SEQ_PSIZE - DATA_PSIZE, // reduced size for QUIC plays no role, as QUIC is stream based although running on dgrams, so the prefix do not need to fit into a single dgram unlike for DTLS 84 | PTY_BSIZE = MSS - SEQ_PSIZE - DATA_PSIZE, 85 | SBUF_BSIZE = MSS - SEQ_PSIZE - NODE_PSIZE, 86 | MSG_BSIZE = MSS, // used in authenticate() to send single chunk of data to fit into one packet for efficiency 87 | CHUNK_SIZE = 16638, 88 | UDP_CHUNK_SIZE = MSS, 89 | QUIC_CHUNK_SIZE = CHUNK_SIZE, 90 | TCP_CHUNK_SIZE = CHUNK_SIZE, 91 | STDOUT_CHUNK_SIZE = 10*1024*1024, 92 | RBUF_BSIZE = 2*CHUNK_SIZE, 93 | 94 | NETCMD_SEND_ALLOW = 1, 95 | 96 | TRAFFIC_NOPAD = 0x00001, 97 | TRAFFIC_PAD1 = 0x00002, 98 | TRAFFIC_PADRND = 0x00004, 99 | TRAFFIC_PADMAX = 0x00008, 100 | TRAFFIC_PING_IGN = 0x01000, 101 | TRAFFIC_INJECT = 0x10000, 102 | 103 | TCP_POLL_TO = 1000, 104 | UDP_POLL_TO = 450, 105 | 106 | MAX_OVEC_SIZE = 32, 107 | MAX_NAME_LEN = 39 // maximum len of SOCKS5 DNS names that we accept 108 | }; 109 | 110 | struct state { 111 | time_t time{0}; 112 | int fd{-1}; 113 | int state{STATE_INVALID}; 114 | std::string ibuf{""}, rnode{""}; 115 | 116 | // TX buffer 117 | std::vector ovec; 118 | 119 | // deque of { UDP id, data } of UDP datagrams in out queue 120 | std::deque> odgrams; 121 | 122 | std::string::size_type tx_len{0}; 123 | }; 124 | 125 | 126 | // at least 64bit in any data model 127 | using sequence_t = unsigned long long; 128 | 129 | 130 | std::string slen(unsigned short); 131 | 132 | int writen(int fd, const void *buf, size_t len); 133 | 134 | int readn(int fd, void *buf, size_t len); 135 | 136 | int flush_fd(int, const std::string &); 137 | 138 | size_t prepend_seq(sequence_t, std::string &); 139 | 140 | size_t rnd_between(size_t, size_t); 141 | 142 | size_t pad_nops(std::string &, size_t); 143 | 144 | std::string ping_packet(); 145 | 146 | void sig_winch(int); 147 | 148 | int read_keys_from_file(const std::string &); 149 | 150 | void read_until(const char *, const char *); 151 | 152 | std::string extract_keys(const char *); 153 | 154 | void read_good_ips(const std::string &); 155 | 156 | bool is_good_ip(const struct in_addr &); 157 | 158 | bool is_good_ip(const struct in6_addr &); 159 | 160 | bool is_nologin(const std::string &); 161 | 162 | void setproctitle(const std::string &); 163 | 164 | std::pair https_to_node(const char *, int); 165 | 166 | } 167 | 168 | #endif 169 | 170 | -------------------------------------------------------------------------------- /src/missing.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "missing.h" 5 | 6 | extern "C" { 7 | #include 8 | #include 9 | #include 10 | #include 11 | } 12 | 13 | 14 | namespace crash { 15 | 16 | 17 | int EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b) 18 | { 19 | #if OPENSSL_VERSION_NUMBER >= 0x30000000L 20 | return ::EVP_PKEY_eq(a, b); 21 | #else 22 | return ::EVP_PKEY_cmp(a, b); 23 | #endif 24 | } 25 | 26 | 27 | int BIO_ADDR_rawmake(BIO_ADDR *ap, int family, const void *where, size_t wherelen, unsigned short port) 28 | { 29 | #ifdef LIBRESSL_VERSION_NUMBER 30 | if (family == AF_INET) { 31 | memset(&ap->sa_in, 0, sizeof(ap->sa_in)); 32 | ap->sa_in.sin_family = family; 33 | ap->sa_in.sin_port = port; 34 | memcpy(&ap->sa_in.sin_addr, where, sizeof(ap->sa_in.sin_addr)); 35 | return 0; 36 | } else if (family == AF_INET6) { 37 | memset(&ap->sa_in6, 0, sizeof(ap->sa_in)); 38 | ap->sa_in6.sin6_family = family; 39 | ap->sa_in6.sin6_port = port; 40 | memcpy(&ap->sa_in6.sin6_addr, where, sizeof(ap->sa_in6.sin6_addr)); 41 | return 0; 42 | } 43 | return -1; 44 | #else 45 | return ::BIO_ADDR_rawmake(ap, family, where, wherelen, port); 46 | #endif 47 | } 48 | 49 | BIO_ADDR *BIO_ADDR_new() 50 | { 51 | #ifdef LIBRESSL_VERSION_NUMBER 52 | return new (std::nothrow) BIO_ADDR; 53 | #else 54 | return ::BIO_ADDR_new(); 55 | #endif 56 | } 57 | 58 | 59 | void BIO_ADDR_free(BIO_ADDR *a) 60 | { 61 | #ifdef LIBRESSL_VERSION_NUMBER 62 | delete a; 63 | #else 64 | return ::BIO_ADDR_free(a); 65 | #endif 66 | } 67 | 68 | 69 | const SSL_METHOD *OSSL_QUIC_client_method() 70 | { 71 | #ifdef HAVE_QUIC 72 | return ::OSSL_QUIC_client_method(); 73 | #else 74 | return nullptr; 75 | #endif 76 | } 77 | 78 | 79 | const SSL_METHOD *OSSL_QUIC_server_method() 80 | { 81 | #ifdef HAVE_QUIC 82 | return ::OSSL_QUIC_server_method(); 83 | #else 84 | return nullptr; 85 | #endif 86 | } 87 | 88 | 89 | int SSL_set_blocking_mode(SSL *ssl, int b) 90 | { 91 | #ifdef HAVE_QUIC 92 | return ::SSL_set_blocking_mode(ssl, b); 93 | #else 94 | return 1; // success 95 | #endif 96 | } 97 | 98 | 99 | int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos, unsigned int plen) 100 | { 101 | #ifdef HAVE_QUIC 102 | return ::SSL_set_alpn_protos(ssl, protos, plen); 103 | #else 104 | return 0; // success 105 | #endif 106 | } 107 | 108 | 109 | int SSL_set1_initial_peer_addr(SSL *ssl, const BIO_ADDR *addr) 110 | { 111 | #ifdef HAVE_QUIC 112 | return ::SSL_set1_initial_peer_addr(ssl, addr); 113 | #else 114 | return 1; // success 115 | #endif 116 | } 117 | 118 | 119 | int SSL_handle_events(SSL *ssl) 120 | { 121 | #ifdef HAVE_QUIC 122 | return ::SSL_handle_events(ssl); 123 | #else 124 | return 1; // success 125 | #endif 126 | } 127 | 128 | int SSL_net_read_desired(SSL *ssl) 129 | { 130 | #ifdef HAVE_QUIC 131 | return ::SSL_net_read_desired(ssl); 132 | #else 133 | return 0; 134 | #endif 135 | } 136 | 137 | 138 | int SSL_net_write_desired(SSL *ssl) 139 | { 140 | #ifdef HAVE_QUIC 141 | return ::SSL_net_write_desired(ssl); 142 | #else 143 | return 0; 144 | #endif 145 | } 146 | 147 | 148 | SSL *SSL_new_listener(SSL_CTX *ctx, uint64_t flags) 149 | { 150 | #ifdef HAVE_QUIC 151 | return ::SSL_new_listener(ctx, flags); 152 | #else 153 | return nullptr; 154 | #endif 155 | } 156 | 157 | 158 | int SSL_listen(SSL *ssl) 159 | { 160 | #ifdef HAVE_QUIC 161 | return ::SSL_listen(ssl); 162 | #else 163 | return 1; // success 164 | #endif 165 | } 166 | 167 | 168 | void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, 169 | int (*cb) (SSL *ssl, 170 | const unsigned char **out, 171 | unsigned char *outlen, 172 | const unsigned char *in, 173 | unsigned int inlen, 174 | void *arg), 175 | void *arg) 176 | { 177 | #ifdef HAVE_QUIC 178 | return ::SSL_CTX_set_alpn_select_cb(ctx, cb, arg); 179 | #else 180 | return; 181 | #endif 182 | } 183 | 184 | 185 | SSL *SSL_accept_connection(SSL *ssl, uint64_t flags) 186 | { 187 | #ifdef HAVE_QUIC 188 | return ::SSL_accept_connection(ssl, flags); 189 | #else 190 | return nullptr; 191 | #endif 192 | } 193 | 194 | 195 | int SSL_stream_conclude(SSL *ssl, uint64_t flags) 196 | { 197 | #ifdef HAVE_QUIC 198 | return ::SSL_stream_conclude(ssl, flags); 199 | #else 200 | return 1; // success 201 | #endif 202 | } 203 | 204 | 205 | int SSL_get_event_timeout(SSL *ssl, struct timeval *tv, int *is_infinite) 206 | { 207 | #ifdef HAVE_QUIC 208 | return ::SSL_get_event_timeout(ssl, tv, is_infinite); 209 | #else 210 | return 1; // success 211 | #endif 212 | } 213 | 214 | } 215 | 216 | -------------------------------------------------------------------------------- /src/missing.h: -------------------------------------------------------------------------------- 1 | #ifndef crash_missing_h 2 | #define crash_missing_h 3 | 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef HAVE_QUIC 15 | #include 16 | #endif 17 | 18 | } 19 | 20 | namespace crash { 21 | 22 | #if OPENSSL_VERSION_NUMBER > 0x10100000L && !(defined LIBRESSL_VERSION_NUMBER) && !(defined BORINGSSL_API_VERSION) 23 | #define EVP_MD_CTX_delete EVP_MD_CTX_free 24 | #else 25 | #define EVP_MD_CTX_delete EVP_MD_CTX_destroy 26 | #endif 27 | 28 | #if OPENSSL_VERSION_NUMBER >= 0x30000000L || LIBRESSL_VERSION_NUMBER >= 0x30000000L 29 | #ifndef NO_DTLS_LISTEN 30 | #define HAVE_DTLS_LISTEN 31 | #endif 32 | #endif 33 | 34 | #ifdef LIBRESSL_VERSION_NUMBER 35 | 36 | // dirty, but libressl is behind with their API 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | union BIO_ADDR { 43 | struct sockaddr sa; 44 | struct sockaddr_in sa_in; 45 | struct sockaddr_in6 sa_in6; 46 | }; 47 | 48 | #endif 49 | 50 | int BIO_ADDR_rawmake(BIO_ADDR *, int, const void *, size_t, unsigned short); 51 | 52 | BIO_ADDR *BIO_ADDR_new(); 53 | 54 | void BIO_ADDR_free(BIO_ADDR *); 55 | 56 | int EVP_PKEY_cmp(const EVP_PKEY *, const EVP_PKEY *); 57 | 58 | const SSL_METHOD *OSSL_QUIC_client_method(); 59 | 60 | const SSL_METHOD *OSSL_QUIC_server_method(); 61 | 62 | int SSL_set_blocking_mode(SSL *, int); 63 | 64 | int SSL_set_alpn_protos(SSL *, const unsigned char *, unsigned int); 65 | 66 | int SSL_set1_initial_peer_addr(SSL *, const BIO_ADDR *); 67 | 68 | int SSL_handle_events(SSL *); 69 | 70 | int SSL_net_read_desired(SSL *); 71 | 72 | int SSL_net_write_desired(SSL *); 73 | 74 | SSL *SSL_new_listener(SSL_CTX *, uint64_t); 75 | 76 | int SSL_listen(SSL *); 77 | 78 | void SSL_CTX_set_alpn_select_cb(SSL_CTX *, int (*) (SSL *ssl, const unsigned char **, unsigned char *, const unsigned char *, unsigned int, void *), void *); 79 | 80 | SSL *SSL_accept_connection(SSL *, uint64_t); 81 | 82 | int SSL_stream_conclude(SSL *, uint64_t); 83 | 84 | int SSL_get_event_timeout(SSL *, struct timeval *, int *); 85 | 86 | enum { 87 | #ifdef HAVE_QUIC 88 | LISTENER_FLAG_NO_VALIDATE = SSL_LISTENER_FLAG_NO_VALIDATE 89 | #else 90 | LISTENER_FLAG_NO_VALIDATE = 0 91 | #endif 92 | }; 93 | 94 | } 95 | 96 | #endif 97 | 98 | -------------------------------------------------------------------------------- /src/net.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2023 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include "net.h" 50 | #include "misc.h" 51 | 52 | 53 | using namespace std; 54 | 55 | namespace crash { 56 | 57 | 58 | Socket::Socket(int pf, int type = SOCK_STREAM) 59 | { 60 | create(pf, type); 61 | } 62 | 63 | 64 | void Socket::create(int pf, int type) 65 | { 66 | // PF_ and AF_ constants *might* be equal 67 | if (pf == PF_INET || pf == AF_INET) 68 | d_family = AF_INET; 69 | else if (pf == PF_INET6 || pf == AF_INET6) 70 | d_family = AF_INET6; 71 | else 72 | d_family = 0; 73 | 74 | d_type = type; 75 | 76 | if ((d_sock_fd = socket(d_family, d_type, 0)) < 0) { 77 | d_error = "Socket::socket:"; 78 | d_error += strerror(errno); 79 | return; 80 | } 81 | 82 | if (d_type == SOCK_STREAM) { 83 | int one = 1; 84 | socklen_t len = sizeof(one); 85 | setsockopt(d_sock_fd, IPPROTO_TCP, TCP_NODELAY, &one, len); 86 | } else { 87 | int r = 2*1024*1024, s = r/4; 88 | setsockopt(d_sock_fd, SOL_SOCKET, SO_RCVBUF, &r, sizeof(r)); 89 | setsockopt(d_sock_fd, SOL_SOCKET, SO_SNDBUF, &s, sizeof(s)); 90 | } 91 | } 92 | 93 | 94 | int Socket::recycle() 95 | { 96 | close(d_sock_fd); 97 | create(d_family, d_type); 98 | return is_good() ? 0 : -1; 99 | } 100 | 101 | 102 | Socket::~Socket() 103 | { 104 | close(d_sock_fd); 105 | } 106 | 107 | 108 | int Socket::blisten(const string &laddr, const string &lport, bool do_listen, bool do_reuse) 109 | { 110 | int r = 0; 111 | 112 | if (do_reuse) { 113 | int one = 1; 114 | setsockopt(d_sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); 115 | #ifdef SO_REUSEPORT 116 | one = 1; 117 | setsockopt(d_sock_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); 118 | #endif 119 | } 120 | 121 | struct addrinfo hint = {0}, *tai = nullptr; 122 | 123 | hint.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; 124 | hint.ai_socktype = d_type; 125 | hint.ai_family = d_family; 126 | 127 | if ((r = getaddrinfo(laddr.c_str(), lport.c_str(), &hint, &tai)) != 0) { 128 | d_error = "Socket::blisten::getaddrinfo:"; 129 | d_error += gai_strerror(r); 130 | return -1; 131 | } 132 | 133 | unique_ptr ai(tai, freeaddrinfo); 134 | 135 | if (::bind(d_sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) { 136 | d_error = "Socket::bind:"; 137 | d_error += strerror(errno); 138 | return -1; 139 | } 140 | 141 | if (d_type == SOCK_STREAM && do_listen) { 142 | if (listen(d_sock_fd, 12) < 0) { 143 | d_error = "Socket::listen:"; 144 | d_error += strerror(errno); 145 | return -1; 146 | } 147 | } 148 | 149 | return d_sock_fd; 150 | } 151 | 152 | 153 | int Socket::socks5(const string &proxy, const string &port) 154 | { 155 | 156 | if (d_type != SOCK_STREAM) 157 | return -1; 158 | 159 | d_socks5_proxy = proxy; 160 | d_socks5_port = port; 161 | 162 | if (port.empty()) 163 | d_socks5_port = "5555"; 164 | 165 | return 0; 166 | } 167 | 168 | 169 | int Socket::connect(const string &host, const string &port) 170 | { 171 | int r = 0; 172 | struct addrinfo hint = {0}, *tai = nullptr; 173 | 174 | hint.ai_flags = AI_NUMERICSERV; 175 | hint.ai_socktype = d_type; 176 | hint.ai_family = d_family; 177 | 178 | string next_host = host, next_port = port; 179 | 180 | if (!d_socks5_proxy.empty()) { 181 | 182 | if (host.size() > 255) { 183 | d_error = "Socket::connect::host name size too large for SOCKS5 request"; 184 | return -1; 185 | } 186 | hint.ai_socktype = SOCK_STREAM; 187 | next_host = d_socks5_proxy; 188 | next_port = d_socks5_port; 189 | } 190 | 191 | if ((r = getaddrinfo(next_host.c_str(), next_port.c_str(), &hint, &tai)) != 0) { 192 | d_error = "Socket::connect::getaddrinfo:"; 193 | d_error += gai_strerror(r); 194 | return -1; 195 | } 196 | 197 | unique_ptr ai(tai, freeaddrinfo); 198 | 199 | if (::connect(d_sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) { 200 | d_error = "Socket::connect:"; 201 | d_error += strerror(errno); 202 | return -1; 203 | } 204 | 205 | if (!d_socks5_proxy.empty()) { 206 | 207 | // SOCKS5 greeting 208 | struct alignas(1) { 209 | uint8_t vers, nauth, auth; 210 | } s5_g = {5, 1, 0}, s5_g_resp = {0, 0, 0}; 211 | 212 | if (writen(d_sock_fd, &s5_g, sizeof(s5_g)) != sizeof(s5_g)) { 213 | d_error = "Socket::connect::write (SOCKS5):"; 214 | d_error += strerror(errno); 215 | return -1; 216 | } 217 | 218 | // auth not supported 219 | if (read(d_sock_fd, &s5_g_resp, 2) != 2 || s5_g_resp.nauth != 0) { 220 | d_error = "Socket::connect::read (SOCKS5):"; 221 | d_error += strerror(errno); 222 | return -1; 223 | } 224 | 225 | struct socks5_req s5_c = {0}, s5_c_resp = {0}; 226 | 227 | s5_c.vers = 0x5; 228 | s5_c.cmd = 1; 229 | s5_c.atype = 3; // DNS name, may also be IP string 230 | 231 | s5_c.name.nlen = (uint8_t)host.size(); 232 | memcpy(s5_c.name.name, host.c_str(), s5_c.name.nlen); 233 | uint16_t p = htons((uint16_t)stoul(port)); 234 | memcpy(s5_c.name.name + s5_c.name.nlen, &p, sizeof(p)); 235 | 236 | ssize_t req_len = 5*sizeof(uint8_t) + s5_c.name.nlen + sizeof(uint16_t); 237 | if (writen(d_sock_fd, &s5_c, req_len) != req_len) { 238 | d_error = "Socket::connect::write (SOCKS5):"; 239 | d_error += strerror(errno); 240 | return -1; 241 | } 242 | 243 | // len for IPv4 bound SOCKS servers (see below) 244 | ssize_t resp_len = 4*sizeof(uint8_t); // + sizeof(s5_c_resp.v4); 245 | 246 | // Workaround for buggy OpenSSH SOCKS5 server: Even if bound to IPv6 address, 247 | // it will answer with a bound address of type IPv4. So we can't pre-compute the 248 | // required response len :( 249 | //if (d_family == AF_INET6) 250 | // resp_len = 4*sizeof(uint8_t) + sizeof(s5_c_resp.v6); 251 | 252 | if (read(d_sock_fd, &s5_c_resp, resp_len) != resp_len || s5_c_resp.cmd != 0) { 253 | d_error = "Socket::connect::read (SOCKS5):"; 254 | d_error += strerror(errno); 255 | return -1; 256 | } 257 | 258 | ssize_t remain = 0; 259 | if (s5_c_resp.atype == 1) // IPv4 260 | remain = sizeof(s5_c_resp.v4); 261 | else 262 | remain = sizeof(s5_c_resp.v6); 263 | 264 | char dummy[sizeof(s5_c_resp.v4) + sizeof(s5_c_resp.v6)] = {0}; 265 | if (read(d_sock_fd, dummy, remain) != remain) { 266 | d_error = "Socket::connect::read (SOCKS5):"; 267 | d_error += strerror(errno); 268 | return -1; 269 | } 270 | } 271 | 272 | return d_sock_fd; 273 | } 274 | 275 | 276 | // maps "IP/port/ID/" string to actual socket, so that we know 277 | // which socket the tagged cmd data belongs to, which carries IP/port pair in front 278 | map tcp_nodes2sock, udp_nodes2sock; 279 | 280 | 281 | static int listen(int type, const string &ip, const string &port) 282 | { 283 | int r = 0, sock_fd = -1; 284 | 285 | addrinfo hint = {0}, *tai = nullptr; 286 | hint.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; 287 | hint.ai_socktype = type; 288 | 289 | if ((r = getaddrinfo(ip.c_str(), port.c_str(), &hint, &tai)) < 0) 290 | return -1; 291 | 292 | unique_ptr ai(tai, freeaddrinfo); 293 | 294 | if ((sock_fd = socket(ai->ai_family, type, 0)) < 0) 295 | return -1; 296 | 297 | nonblock(sock_fd); 298 | 299 | int one = 1; 300 | #ifdef SO_REUSEPORT 301 | setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); 302 | one = 1; 303 | #endif 304 | setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); 305 | 306 | if (::bind(sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) 307 | return -1; 308 | if (type == SOCK_STREAM) { 309 | if (::listen(sock_fd, 12) < 0) 310 | return -1; 311 | } 312 | 313 | return sock_fd; 314 | } 315 | 316 | 317 | int udp_listen(const string &ip, const string &port) 318 | { 319 | return listen(SOCK_DGRAM, ip, port); 320 | } 321 | 322 | 323 | int tcp_listen(const string &ip, const string &port) 324 | { 325 | return listen(SOCK_STREAM, ip, port); 326 | } 327 | 328 | 329 | static map resolv_cache; 330 | 331 | 332 | static int connect(int type, const string &name, const string &port) 333 | { 334 | 335 | // if cache has grown largely, drop it and make new 336 | if (resolv_cache.size() > 1024) { 337 | for (const auto &it : resolv_cache) 338 | freeaddrinfo(it.second); 339 | resolv_cache.clear(); 340 | } 341 | 342 | int r = 0, sock_fd = -1, one = 1; 343 | socklen_t len = sizeof(one); 344 | 345 | addrinfo hint = {0}, *tai = nullptr; 346 | hint.ai_socktype = type; 347 | hint.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV; 348 | 349 | bool can_free = 1; 350 | 351 | if ((r = getaddrinfo(name.c_str(), port.c_str(), &hint, &tai)) != 0) { 352 | 353 | can_free = 0; 354 | 355 | string key = name + ":" + port; 356 | 357 | auto it = resolv_cache.find(key); 358 | 359 | if (it == resolv_cache.end()) { 360 | 361 | hint.ai_flags = AI_NUMERICSERV; 362 | if ((r = getaddrinfo(name.c_str(), port.c_str(), &hint, &tai)) != 0) 363 | return -1; 364 | 365 | resolv_cache[key] = tai; 366 | 367 | } else 368 | tai = it->second; 369 | } 370 | 371 | if ((sock_fd = socket(tai->ai_family, type, 0)) < 0) { 372 | if (can_free) 373 | freeaddrinfo(tai); 374 | return -1; 375 | } 376 | 377 | if (type == SOCK_STREAM) 378 | setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, &one, len); 379 | 380 | nonblock(sock_fd); 381 | 382 | if (::connect(sock_fd, tai->ai_addr, tai->ai_addrlen) < 0 && errno != EINPROGRESS) { 383 | close(sock_fd); 384 | if (can_free) 385 | freeaddrinfo(tai); 386 | return -1; 387 | } 388 | 389 | return sock_fd; 390 | } 391 | 392 | 393 | int udp_connect(const string &ip, const string &port) 394 | { 395 | return connect(SOCK_DGRAM, ip, port); 396 | } 397 | 398 | 399 | int tcp_connect(const string &ip, const string &port) 400 | { 401 | return connect(SOCK_STREAM, ip, port); 402 | } 403 | 404 | 405 | void nonblock(int fd) 406 | { 407 | int flags = fcntl(fd, F_GETFL); 408 | fcntl(fd, F_SETFL, flags|O_NONBLOCK); 409 | } 410 | 411 | 412 | } 413 | 414 | -------------------------------------------------------------------------------- /src/net.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006-2023 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | #ifndef crash_net_h 33 | #define crash_net_h 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include "misc.h" 43 | 44 | 45 | namespace crash { 46 | 47 | class Socket { 48 | protected: 49 | int d_sock_fd{-1}, d_family{PF_INET}, d_type{SOCK_STREAM}; 50 | 51 | std::string d_socks5_proxy{""}, d_socks5_port{""}; 52 | std::string d_error{""}; 53 | 54 | void create(int, int); 55 | 56 | public: 57 | 58 | Socket(int, int); 59 | 60 | virtual ~Socket(); 61 | 62 | int recycle(); 63 | 64 | int socks5(const std::string &, const std::string &); 65 | 66 | int connect(const std::string &, const std::string &); 67 | 68 | int blisten(const std::string &, const std::string &, bool do_listen = 1, bool do_reuse = 1); 69 | 70 | bool is_good() { return d_sock_fd >= 0; }; 71 | 72 | const char *why() { return d_error.c_str(); }; 73 | }; 74 | 75 | 76 | // TCP connections can use the socket fd as id, as it uniquely identifies the connection. 77 | // UDP sockets receive all datagrams on the same fd, so we cannot use the fd as an connection-id and therefore 78 | // need to map the dgram's originating struct sockaddr {} (implemented as string blob) to an unqiue id, 79 | // so that we know where to send replies to when we receive data for that id. 80 | class udp_node2id { 81 | 82 | std::map d_id2node; 83 | std::map d_node2id; 84 | 85 | enum { max_id = 0xffff }; 86 | 87 | uint16_t d_next_id{0}; 88 | 89 | public: 90 | 91 | uint16_t put(const std::string &addr) 92 | { 93 | // if origin already exists, take this ID 94 | auto it = d_node2id.find(addr); 95 | if (it != d_node2id.end()) 96 | return it->second; 97 | 98 | // if there are free IDs, pick a new one 99 | if (d_node2id.size() <= max_id) { 100 | d_node2id[addr] = d_next_id; 101 | d_id2node[d_next_id] = addr; 102 | return d_next_id++; 103 | } 104 | 105 | // otherwise flush all mappings (possibly corrupt outstanding UDP sessions) 106 | d_node2id.clear(); 107 | d_id2node.clear(); 108 | d_next_id = 0; 109 | 110 | d_node2id[addr] = d_next_id; 111 | d_id2node[d_next_id] = addr; 112 | return d_next_id++; 113 | } 114 | 115 | std::string get(uint16_t id) 116 | { 117 | std::string ret = ""; 118 | auto it = d_id2node.find(id); 119 | if (it != d_id2node.end()) 120 | ret = it->second; 121 | 122 | return ret; 123 | } 124 | 125 | void del(uint16_t id) 126 | { 127 | auto it = d_id2node.find(id); 128 | if (it != d_id2node.end()) { 129 | auto it2 = d_node2id.find(it->second); // must exist 130 | d_node2id.erase(it2); 131 | d_id2node.erase(it); 132 | } 133 | } 134 | }; 135 | 136 | 137 | extern std::map tcp_nodes2sock, udp_nodes2sock; 138 | 139 | int tcp_listen(const std::string &, const std::string &); 140 | 141 | int udp_listen(const std::string &, const std::string &); 142 | 143 | int tcp_connect(const std::string &, const std::string &); 144 | 145 | int udp_connect(const std::string &, const std::string &); 146 | 147 | void nonblock(int); 148 | 149 | 150 | struct alignas(1) v4_tuple { 151 | in_addr dst; 152 | uint16_t dport; 153 | } __attribute__((packed)); 154 | 155 | 156 | struct alignas(1) v6_tuple { 157 | in6_addr dst; 158 | uint16_t dport; 159 | } __attribute__((packed)); 160 | 161 | 162 | struct alignas(1) name_tuple { 163 | uint8_t nlen; 164 | char name[255 + 2]; // +2 for dst port 165 | } __attribute__((packed)); 166 | 167 | 168 | struct alignas(1) socks5_req { 169 | uint8_t vers, cmd, mbz, atype; 170 | union { 171 | v4_tuple v4; 172 | v6_tuple v6; 173 | name_tuple name; 174 | }; 175 | } __attribute__((packed)); 176 | 177 | 178 | struct alignas(1) socks4_req { 179 | uint8_t ver, cmd; 180 | uint16_t dport; 181 | uint32_t dst; 182 | uint8_t id; 183 | } __attribute__((packed)); 184 | 185 | 186 | } 187 | 188 | #endif 189 | 190 | -------------------------------------------------------------------------------- /src/newdh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # set path if a different openssl binary (e.g. openssl3) should be used 4 | my $openssl = "openssl"; 5 | 6 | print "Generating 2048 bit DH parameters ...\n\n"; 7 | 8 | my $version = `$openssl version`; 9 | 10 | if ($version =~ /OpenSSL 3\./) { 11 | unlink("dh2048.cc"); 12 | open(O,">>dh2048.cc") or die $!; 13 | print O "#include \n\nstd::string pem_dh = R\"(\n"; 14 | system("$openssl dhparam -5 -outform pem 2048 >> dh2048.cc"); 15 | print O "\n)\";\n"; 16 | close O; 17 | } else { 18 | system("$openssl dhparam -5 -noout -C 2048 > dh2048.cc"); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/pty.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2001-2022 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include "pty.h" 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | using namespace std; 45 | 46 | pty::pty(const pty &rhs) 47 | { 48 | if (this == &rhs) 49 | return; 50 | _slave = dup(rhs._slave); _master = dup(rhs._master); 51 | m = rhs.m; s = rhs.s; 52 | } 53 | 54 | pty &pty::operator=(const pty &rhs) 55 | { 56 | if (this == &rhs) 57 | return *this; 58 | _slave = dup(rhs._slave); _master = dup(rhs._master); 59 | m = rhs.m; s = rhs.s; 60 | return *this; 61 | } 62 | 63 | 64 | // Open master+slave terminal 65 | // returns 0 on sucess, -1 on failure 66 | int pty::open() 67 | { 68 | #ifdef OLD_BSD_PTY 69 | char ptys[] = "/dev/ptyXY"; 70 | const char *it1 = "pqrstuvwxyzPQRST", 71 | *it2 = "0123456789abcdef"; 72 | 73 | for (; *it1 != 0; ++it1) { 74 | ptys[8] = *it1; 75 | for (; *it2 != 0; ++it2) { 76 | ptys[9] = *it2; 77 | 78 | // do master-part 79 | if ((_master = ::open(ptys, O_RDWR|O_NOCTTY)) < 0) { 80 | if (errno == ENOENT) { 81 | serr = strerror(errno); 82 | return -1; 83 | } else 84 | continue; 85 | } 86 | m = ptys; 87 | ptys[5] = 't'; 88 | 89 | // master finished. Do slave-part. 90 | if ((_slave = ::open(ptys, O_RDWR|O_NOCTTY)) < 0) { 91 | ::close(_master); 92 | serr = strerror(errno); 93 | return -1; 94 | } 95 | s = ptys; 96 | return 0; 97 | } 98 | } 99 | 100 | // out of terminals 101 | serr = "Out of ptys."; 102 | return -1; 103 | 104 | #else 105 | if ((_master = posix_openpt(O_RDWR|O_NOCTTY)) < 0) { 106 | serr = strerror(errno); 107 | return -1; 108 | } 109 | 110 | grantpt(_master); 111 | unlockpt(_master); 112 | s = ptsname(_master); 113 | 114 | // master finished. Do slave-part. 115 | if ((_slave = ::open(s.c_str(), O_RDWR|O_NOCTTY)) < 0) { 116 | ::close(_master); 117 | serr = strerror(errno); 118 | return -1; 119 | } 120 | 121 | return 0; 122 | #endif 123 | } 124 | 125 | 126 | int pty::close_slave() 127 | { 128 | ::close(_slave); 129 | _slave = -1; 130 | return 0; 131 | } 132 | 133 | 134 | int pty::close_master() 135 | { 136 | ::close(_master); 137 | _master = -1; 138 | return 0; 139 | } 140 | 141 | 142 | int pty::close() 143 | { 144 | if (_master > -1) { 145 | ::close(_master); 146 | _master = -1; 147 | } 148 | if (_slave > -1) { 149 | ::close(_slave); 150 | _slave = -1; 151 | } 152 | return 0; 153 | } 154 | 155 | int pty::grant(uid_t u, gid_t g, mode_t mode) 156 | { 157 | #ifdef OLD_BSD_PTY 158 | if (fchmod(_master, mode) < 0 || fchmod(_slave, mode) < 0) { 159 | serr = strerror(errno); 160 | return -1; 161 | } 162 | if (fchown(_master, u, g) < 0 || fchown(_slave, u, g) < 0) { 163 | serr = strerror(errno); 164 | return -1; 165 | } 166 | #else 167 | if (fchmod(_slave, mode) < 0) { 168 | serr = strerror(errno); 169 | return -1; 170 | } 171 | if (fchown(_slave, u, g) < 0) { 172 | serr = strerror(errno); 173 | return -1; 174 | } 175 | #endif 176 | return 0; 177 | } 178 | 179 | const char *pty::why() 180 | { 181 | return serr.c_str(); 182 | } 183 | 184 | -------------------------------------------------------------------------------- /src/pty.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2001-2022 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef crash_pty_h 34 | #define crash_pty_h 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | using namespace std; 43 | 44 | // A BSD 4.3+ PTY API. 45 | class pty { 46 | protected: 47 | // file-descriptors for terminal 48 | int _master{-1}, _slave{-1}; 49 | 50 | // names of device-files 51 | string m{""}, s{""}, serr{""}; 52 | public: 53 | pty() 54 | { 55 | } 56 | 57 | virtual ~pty() 58 | { 59 | close(); 60 | } 61 | 62 | // Copy-constructor 63 | pty(const pty &rhs); 64 | 65 | // Assign-operator 66 | pty &operator=(const pty &rhs); 67 | 68 | // open master+slave terminal 69 | virtual int open(); 70 | 71 | // close both 72 | int close(); 73 | 74 | int close_master(); 75 | 76 | int close_slave(); 77 | 78 | int master() { return _master; } 79 | 80 | int slave() { return _slave; } 81 | 82 | string mname() { return m; } 83 | 84 | string sname() { return s; } 85 | 86 | // do chown, chmod 87 | virtual int grant(uid_t, gid_t, mode_t); 88 | 89 | const char* why(); 90 | }; 91 | 92 | class pty98 : public pty { 93 | public: 94 | pty98() : pty() {} 95 | 96 | virtual ~pty98() {} 97 | 98 | 99 | pty98(const pty98 &); 100 | 101 | pty98 &operator=(const pty98 &); 102 | 103 | virtual int open(); 104 | 105 | virtual int grant(uid_t, gid_t, mode_t); 106 | }; 107 | 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /src/pty98.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2001-2009 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | #include "pty.h" 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #ifdef __sun__ 44 | #include 45 | #include 46 | #include 47 | #endif 48 | 49 | pty98::pty98(const pty98 &rhs) 50 | : pty(rhs) 51 | { 52 | } 53 | 54 | pty98 &pty98::operator=(const pty98 &rhs) 55 | { 56 | pty::operator=(rhs); 57 | return *this; 58 | } 59 | 60 | int pty98::open() 61 | { 62 | #ifdef HAVE_UNIX98 63 | m = "/dev/ptmx"; 64 | 65 | if ((_master = ::open(m.c_str(), O_RDWR|O_NOCTTY)) < 0) { 66 | serr = strerror(errno); 67 | return -1; 68 | } 69 | if (grantpt(_master) < 0) { 70 | ::close(_master); 71 | serr = strerror(errno); 72 | return -1; 73 | } 74 | 75 | unlockpt(_master); 76 | #ifdef __linux__ 77 | char buf[1024]; 78 | memset(buf, 0, sizeof(buf)); 79 | ptsname_r(_master, buf, sizeof(buf)); 80 | s = buf; 81 | #else 82 | s = ptsname(_master); 83 | #endif 84 | 85 | if ((_slave = ::open(s.c_str(), O_RDWR|O_NOCTTY)) < 0) { 86 | ::close(_master); 87 | serr = strerror(errno); 88 | return -1; 89 | } 90 | #ifdef __sun__ 91 | ioctl(_slave, I_PUSH, "ptem"); 92 | ioctl(_slave, I_PUSH, "ldterm"); 93 | ioctl(_slave, I_PUSH, "ttcompat"); 94 | #endif 95 | 96 | #endif 97 | return 0; 98 | } 99 | 100 | 101 | int pty98::grant(uid_t u, gid_t g, mode_t mode) 102 | { 103 | if (fchmod(_slave, mode) < 0) { 104 | serr = strerror(errno); 105 | return -1; 106 | } 107 | if (fchown(_slave, u, g) < 0) { 108 | serr = strerror(errno); 109 | return -1; 110 | } 111 | return 0; 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/server.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2024 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include "session.h" 50 | #include "server.h" 51 | #include "config.h" 52 | #include "log.h" 53 | #include "net.h" 54 | #include "misc.h" 55 | 56 | // needed for HAVE_DTLS_LISTEN 57 | #include "missing.h" 58 | 59 | 60 | using namespace std; 61 | using namespace crash; 62 | 63 | 64 | extern "C" { 65 | #include 66 | #include 67 | #include 68 | } 69 | 70 | namespace crash { 71 | 72 | // class static definitions 73 | unsigned short Server::d_min_time_between_reconnect = 1; 74 | 75 | // We can't use std::mutex b/c try_lock() in the same thread is undefined and 76 | // can't use std::recursive_mutex b/c try_lock() would succeed, so we have to use our own 77 | // kind of locking from within signal handlers and we will use cmpxchg on atomics for that. 78 | atomic Server::sigchld_mtx{0}; 79 | 80 | // We can't use maps or vectors, as they'd use the heap and we will work inside sig handler 81 | int Server::sigchld_idx{-1}; 82 | pair Server::sigchld_pids[Server::MAX_CHLDS]; 83 | 84 | 85 | #ifdef USE_SCIPHERS 86 | string ciphers = USE_SCIPHERS; 87 | #else 88 | string ciphers = "!LOW:!EXP:!MD5:!CAMELLIA:!RC4:!MEDIUM:!DES:!3DES:!ADH:kDHE:RSA:AES256:AESGCM:SHA256:SHA384:@STRENGTH"; 89 | #endif 90 | 91 | extern int enable_dh(SSL_CTX *); 92 | 93 | 94 | map dtls_cookies; 95 | 96 | int cookie_generate_cb(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len) 97 | { 98 | int fd = 0; 99 | 100 | if ((fd = SSL_get_fd(ssl)) < 0) 101 | return 1; 102 | 103 | unsigned char ck[16] = {0}; 104 | RAND_bytes(ck, sizeof(ck)); 105 | dtls_cookies.emplace(fd, string(reinterpret_cast(ck), sizeof(ck))); 106 | memcpy(cookie, ck, sizeof(ck)); 107 | *cookie_len = sizeof(ck); 108 | return 1; 109 | } 110 | 111 | 112 | int cookie_verify_cb(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len) 113 | { 114 | int fd = SSL_get_fd(ssl); 115 | 116 | if (fd < 0 || cookie_len > 16) 117 | return 0; 118 | if (cookie_len == 0) 119 | return 1; 120 | auto it = dtls_cookies.find(fd); 121 | 122 | if (it == dtls_cookies.end() || string(reinterpret_cast(cookie), cookie_len) != it->second) 123 | return 0; 124 | return 1; 125 | } 126 | 127 | 128 | Server::Server(const std::string &t, const std::string &sni) 129 | : d_transport(t), d_sni(sni) 130 | { 131 | } 132 | 133 | 134 | Server::~Server() 135 | { 136 | if (d_ssl_ctx) 137 | SSL_CTX_free(d_ssl_ctx); 138 | delete d_sock; 139 | } 140 | 141 | 142 | // called from signal handler 143 | void Server::add_chld(pid_t p) 144 | { 145 | int unlocked = 0; 146 | 147 | struct timeval tv; 148 | tv.tv_sec = time(nullptr); 149 | tv.tv_usec = 0; 150 | 151 | // Can't acquire lock? Just return 152 | if (!sigchld_mtx.compare_exchange_strong(unlocked, 1)) 153 | return; 154 | 155 | if (sigchld_idx < (MAX_CHLDS - 1)) 156 | sigchld_pids[++sigchld_idx] = {p, tv}; 157 | 158 | sigchld_mtx.store(unlocked); 159 | } 160 | 161 | 162 | void Server::release_chlds() 163 | { 164 | int unlocked = 0; 165 | 166 | // This is not spinning, as we are not concurrent with other threads, but only 167 | // with outselfes if a signal is delivered. Therefore, when inside a signal handler and the 168 | // mtx is hold, we will never be here. So the line below could actually just be an 169 | // atomic set to 1, since the wile loop will always succeed at the first try. 170 | while (!sigchld_mtx.compare_exchange_strong(unlocked, 1)) 171 | ; 172 | 173 | for (;sigchld_idx >= 0; --sigchld_idx) 174 | logger::logout(sigchld_pids[sigchld_idx].first, sigchld_pids[sigchld_idx].second); 175 | 176 | sigchld_mtx.store(unlocked); 177 | } 178 | 179 | 180 | int Server::setup() 181 | { 182 | SSL_library_init(); 183 | SSL_load_error_strings(); 184 | OpenSSL_add_all_algorithms(); 185 | OpenSSL_add_all_digests(); 186 | if (d_transport == "dtls1") 187 | d_ssl_method = DTLS_server_method(); 188 | else if (d_transport == "quic1") 189 | d_ssl_method = OSSL_QUIC_server_method(); 190 | else 191 | d_ssl_method = TLS_server_method(); 192 | 193 | if (!d_ssl_method) { 194 | d_err = "Server::setup::D/TLS_server_method:"; 195 | d_err += ERR_error_string(ERR_get_error(), nullptr); 196 | return -1; 197 | } 198 | 199 | if ((d_ssl_ctx = SSL_CTX_new(d_ssl_method)) == nullptr) { 200 | d_err = "Server::setup::SSL_CTX_new:"; 201 | d_err += ERR_error_string(ERR_get_error(), nullptr); 202 | return -1; 203 | } 204 | if (SSL_CTX_use_certificate_file(d_ssl_ctx, config::certfile.c_str(), 205 | SSL_FILETYPE_PEM) != 1) { 206 | d_err = "Server::setup::SSL_CTX_use_certificate():"; 207 | d_err += ERR_error_string(ERR_get_error(), nullptr); 208 | return -1; 209 | } 210 | if (SSL_CTX_use_PrivateKey_file(d_ssl_ctx, config::keyfile.c_str(), 211 | SSL_FILETYPE_PEM) != 1) { 212 | d_err = "Server::setup::SSL_CTX_use_PrivateKey_file():"; 213 | d_err += ERR_error_string(ERR_get_error(), nullptr); 214 | return -1; 215 | } 216 | 217 | long op = SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE|SSL_OP_NO_TICKET|SSL_OP_NO_QUERY_MTU; 218 | 219 | if (d_transport == "dtls1") { 220 | if (SSL_CTX_set_session_id_context(d_ssl_ctx, reinterpret_cast("crashd"), 6) != 1) { 221 | d_err = "Server::setup::SSL_CTX_set_session_id_context():"; 222 | d_err += ERR_error_string(ERR_get_error(), nullptr); 223 | return -1; 224 | } 225 | SSL_CTX_set_timeout(d_ssl_ctx, 60*60*24*365); // 1y 226 | SSL_CTX_set_session_cache_mode(d_ssl_ctx, SSL_SESS_CACHE_NO_AUTO_CLEAR|SSL_SESS_CACHE_SERVER); 227 | } 228 | 229 | #ifdef SSL_OP_NO_COMPRESSION 230 | op |= SSL_OP_NO_COMPRESSION; 231 | #endif 232 | 233 | if ((unsigned long)(SSL_CTX_set_options(d_ssl_ctx, op) & op) != (unsigned long)op) { 234 | d_err = "Server::setup::SSL_CTX_set_options():"; 235 | d_err += ERR_error_string(ERR_get_error(), nullptr); 236 | return -1; 237 | } 238 | 239 | #if !(defined TLS1_3_VERSION) or defined TLS_COMPAT_DOWNGRADE 240 | #warning "TLS1_3_VERSION not defined! Building compat version to support aged systems. This produces island-binaries incompatible to normal builds." 241 | int min_vers = TLS1_2_VERSION; 242 | #else 243 | int min_vers = TLS1_3_VERSION; 244 | #endif 245 | 246 | if (d_transport == "dtls1") { 247 | min_vers = DTLS1_2_VERSION; 248 | #ifdef HAVE_DTLS_LISTEN 249 | SSL_CTX_set_cookie_generate_cb(d_ssl_ctx, cookie_generate_cb); 250 | SSL_CTX_set_cookie_verify_cb(d_ssl_ctx, cookie_verify_cb); 251 | #endif 252 | } 253 | 254 | if (d_transport != "quic1" && SSL_CTX_set_min_proto_version(d_ssl_ctx, min_vers) != 1) { 255 | d_err = "Server::setup::SSL_CTX_set_min_proto_version():"; 256 | d_err += ERR_error_string(ERR_get_error(), nullptr); 257 | return -1; 258 | } 259 | 260 | // check for DHE and enable it if there are parameters 261 | string::size_type dhe = ciphers.find("kDHE"); 262 | if (dhe != string::npos) { 263 | if (enable_dh(d_ssl_ctx) != 1) 264 | ciphers.erase(dhe, 4); 265 | } 266 | 267 | if (d_transport != "quic1" && SSL_CTX_set_cipher_list(d_ssl_ctx, ciphers.c_str()) != 1) { 268 | d_err = "Server::setup::SSL_CTX_set_cipher_list:"; 269 | d_err += ERR_error_string(ERR_get_error(), nullptr); 270 | return -1; 271 | } 272 | 273 | if (config::v6) 274 | d_sock = new (nothrow) Socket(PF_INET6, d_transport == "tls1" ? SOCK_STREAM : SOCK_DGRAM); 275 | else 276 | d_sock = new (nothrow) Socket(PF_INET, d_transport == "tls1" ? SOCK_STREAM : SOCK_DGRAM); 277 | 278 | if (!d_sock || !d_sock->is_good()) { 279 | d_err = "Server::setup::new:"; 280 | return -1; 281 | } 282 | 283 | return 0; 284 | } 285 | 286 | 287 | int Server::loop() 288 | { 289 | pid_t pid = 0; 290 | int peer_fd = -1, r = 0; 291 | struct sockaddr *from = nullptr; 292 | struct sockaddr_in from4; 293 | struct sockaddr_in6 from6; 294 | socklen_t flen = 0; 295 | string msg = ""; 296 | char dst[128] = {0}; 297 | time_t now = time(nullptr) - 42, last_accept = 0; 298 | unsigned short port = 0; 299 | int type = d_transport == "tls1" ? SOCK_STREAM : SOCK_DGRAM; 300 | 301 | if (config::v6) { 302 | from = reinterpret_cast(&from6); 303 | flen = sizeof(from6); 304 | } else { 305 | from = reinterpret_cast(&from4); 306 | flen = sizeof(from4); 307 | } 308 | 309 | if (!d_sock || !d_sock->is_good()) { 310 | d_err = "Server::loop: Server not initialized!"; 311 | return -1; 312 | } 313 | 314 | // No host -> passive 315 | if (config::host.empty()) { 316 | if ((d_sock_fd = d_sock->blisten(config::laddr, config::lport)) < 0) { 317 | d_err = "Server::loop::"; 318 | d_err += d_sock->why(); 319 | return -1; 320 | } 321 | 322 | for (;;) { 323 | 324 | // if any ... 325 | Server::release_chlds(); 326 | 327 | // In silent mode, we just sleep on the socket, but in non-silent mode we want regular interval 328 | // checks whether utmpx entries need to be removed. 329 | if (!config::silent) { 330 | pollfd pfd = { d_sock_fd, POLLIN, 0 }; 331 | 332 | if (poll(&pfd, 1, TCP_POLL_TO) != 1) 333 | continue; 334 | } 335 | 336 | if (type == SOCK_DGRAM) { 337 | char c = 0; 338 | last_accept = now; 339 | if (recvfrom(d_sock_fd, &c, 1, MSG_PEEK, from, &flen) <= 0) 340 | continue; 341 | 342 | now = time(nullptr); 343 | 344 | // TLS Record Layer: ContentType == handshake (22), or QUIC 345 | if (now - last_accept <= d_min_time_between_reconnect || (c != 22 && (c & 0xc0) != 0xc0)) { 346 | char buf[4096] = {0}; 347 | recv(d_sock_fd, buf, sizeof(buf), 0); 348 | continue; 349 | } 350 | 351 | // dup() it, to kind of emulate accept() and we do not need to handle DGRAM differently in child 352 | peer_fd = dup(d_sock_fd); 353 | 354 | // in UDP mode, set the address where data is sent from and received from 355 | // via read/write and all other datagrams are ignored on receipt. When roaming is allowed, 356 | // the underlying BIO will handle it 357 | if (!config::allow_roam && connect(peer_fd, from, flen) != 0) { 358 | msg = "Failed to set default dst of new UDP connection."; 359 | syslog().log(msg); 360 | close(peer_fd); 361 | continue; 362 | } 363 | 364 | } else { 365 | last_accept = now; 366 | peer_fd = accept(d_sock_fd, from, &flen); 367 | if (peer_fd < 0) 368 | continue; 369 | 370 | // brand new D/DoS protection :-) 371 | now = time(nullptr); 372 | if (now - last_accept <= d_min_time_between_reconnect) { 373 | if (config::v6) { 374 | if (!is_good_ip(from6.sin6_addr)) { 375 | close(peer_fd); 376 | continue; 377 | } 378 | } else { 379 | if (!is_good_ip(from4.sin_addr)) { 380 | close(peer_fd); 381 | continue; 382 | } 383 | } 384 | } 385 | } 386 | 387 | if ((pid = fork()) == 0) { 388 | close(d_sock_fd); 389 | 390 | if (config::v6) { 391 | inet_ntop(AF_INET6, &from6.sin6_addr, dst, sizeof(dst)); 392 | port = ntohs(from6.sin6_port); 393 | } else { 394 | inet_ntop(AF_INET, &from4.sin_addr, dst, sizeof(dst)); 395 | port = ntohs(from4.sin_port); 396 | } 397 | msg = "New connection from ["; 398 | msg += dst; 399 | snprintf(dst, sizeof(dst), "]:%hu", port); 400 | msg += dst; 401 | syslog().log(msg); 402 | 403 | server_session *s = new (nothrow) server_session(peer_fd, d_transport, d_sni, string(reinterpret_cast(from), flen)); 404 | if (!s) { 405 | syslog().log("out of memory"); 406 | close(peer_fd); 407 | exit(1); 408 | } 409 | if (s->handle(d_ssl_ctx) < 0) { 410 | string l = "FAIL: "; 411 | l += s->why(); 412 | syslog().log(l); 413 | } 414 | syslog().log("closing connection"); 415 | delete s; 416 | exit(0); 417 | } 418 | 419 | close(peer_fd); 420 | if (type == SOCK_STREAM) 421 | continue; 422 | 423 | // DGRAM sockets must be closed and re-bound, as the connect() on dup-ed peer_fd also changed state 424 | // of d_sock_fd. recycle() handles that. 425 | if (d_sock->recycle() < 0) { 426 | d_err = "Server::loop::"; 427 | d_err += d_sock->why(); 428 | return -1; 429 | } 430 | 431 | auto lp = stoul(config::lport); 432 | for (auto p = lp;; ++p) { 433 | 434 | if (p - lp > 1000) { 435 | sleep(1); 436 | p = lp; 437 | } 438 | 439 | // when roaming is allowed, the next server session to be opened will be on lport+1, 440 | // as we were not calling connect() on the socket to fix the peer IP and therefore 441 | // we would get duped packets if multiple clients try to share same IP:port destination 442 | // without us fixing them by connect(). The loop makes sure we will be re-using original lport 443 | // if it is free again and cycle through a list of [lport, lport + 1000] 444 | 445 | if ((d_sock_fd = d_sock->blisten(config::laddr, to_string(p), 1, !config::allow_roam)) < 0) { 446 | if (!config::allow_roam) { 447 | d_err = "Server::loop::"; 448 | d_err += d_sock->why(); 449 | return -1; 450 | } 451 | } else 452 | break; // success 453 | } 454 | } 455 | 456 | // target host was given -> active connect 457 | } else if (d_transport == "tls1") { 458 | if (d_sock->blisten(config::laddr, config::lport, 0) < 0) { 459 | d_err = "Server::loop::"; 460 | d_err += d_sock->why(); 461 | return -1; 462 | } 463 | if (!config::socks5_connect_proxy.empty()) 464 | d_sock->socks5(config::socks5_connect_proxy, config::socks5_connect_proxy_port); 465 | if ((d_sock_fd = d_sock->connect(config::host, config::port)) < 0) { 466 | d_err = "Server::loop::"; 467 | d_err += d_sock->why(); 468 | return -1; 469 | } 470 | server_session *s = new (nothrow) server_session(d_sock_fd, d_transport, d_sni, ""); 471 | if (s) { 472 | if ((r = s->handle(d_ssl_ctx)) < 0) { 473 | d_err = "Server::loop::"; 474 | d_err += s->why(); 475 | } 476 | } 477 | delete s; 478 | } else { 479 | d_err = "Server::loop: Not possible to use active connect as server in UDP mode."; 480 | r = -1; 481 | } 482 | 483 | Server::release_chlds(); 484 | 485 | return r; 486 | } 487 | 488 | } 489 | 490 | -------------------------------------------------------------------------------- /src/server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2022 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | #ifndef crash_server_h 33 | #define crash_server_h 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include "net.h" 43 | #include "log.h" 44 | 45 | extern "C" { 46 | #include 47 | #include 48 | } 49 | 50 | namespace crash { 51 | 52 | class Server { 53 | 54 | int d_sock_fd{-1}; 55 | Socket *d_sock{nullptr}; 56 | std::string d_transport{"tls1"}, d_sni{""}, d_err{""}; 57 | 58 | #if OPENSSL_VERSION_NUMBER >= 0x10000000L 59 | const SSL_METHOD *d_ssl_method{nullptr}; 60 | #else 61 | SSL_METHOD *d_ssl_method{nullptr}; 62 | #endif 63 | SSL_CTX *d_ssl_ctx{nullptr}; 64 | 65 | std::map d_connects; 66 | static unsigned short d_min_time_between_reconnect; 67 | 68 | static std::atomic sigchld_mtx; 69 | static int sigchld_idx; 70 | enum { MAX_CHLDS = 32 }; 71 | static std::pair sigchld_pids[MAX_CHLDS]; 72 | 73 | public: 74 | 75 | Server(const std::string &, const std::string &); 76 | 77 | ~Server(); 78 | 79 | // These need to be in Server{} because its running privileged, so 80 | // the release cann ::logout() the PIDs from the utmx files 81 | static void add_chld(pid_t); 82 | 83 | static void release_chlds(); 84 | 85 | const char *why() { return d_err.c_str(); }; 86 | 87 | int setup(); 88 | 89 | int loop(); 90 | }; 91 | 92 | } 93 | 94 | #endif 95 | 96 | -------------------------------------------------------------------------------- /src/session.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2024 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | extern "C" { 41 | #include 42 | #include 43 | #include 44 | } 45 | 46 | #include "net.h" 47 | #include "config.h" 48 | #include "session.h" 49 | #include "missing.h" 50 | 51 | 52 | namespace crash { 53 | 54 | 55 | session::session(const string &t, const string &sni) 56 | : d_transport_str(t), d_sni(sni) 57 | { 58 | if (d_transport_str == "dtls1") { 59 | d_transport = transport_t::TRANSPORT_DTLS; 60 | d_type = SOCK_DGRAM; 61 | d_chunk_size = UDP_CHUNK_SIZE; 62 | d_poll_to.max = d_poll_to.next = UDP_POLL_TO; 63 | d_bio_peer = BIO_ADDR_new(); 64 | } 65 | 66 | if (d_transport_str == "quic1") { 67 | d_transport = transport_t::TRANSPORT_QUIC; 68 | d_type = SOCK_DGRAM; 69 | d_chunk_size = QUIC_CHUNK_SIZE; 70 | d_poll_to.max = d_poll_to.next = TCP_POLL_TO; 71 | d_bio_peer = BIO_ADDR_new(); 72 | d_mss = QUIC_MSS; 73 | } 74 | 75 | d_now = time(nullptr); 76 | } 77 | 78 | 79 | session::~session() 80 | { 81 | if (d_bio_peer) 82 | crash::BIO_ADDR_free(d_bio_peer); 83 | 84 | // OK to free d_bio, as we up_refed +1 for ourself just 85 | // after creating and BIO_free_all() as called on SSL_free() 86 | // will only free (downref) its copies 87 | BIO_free(d_bio); 88 | 89 | if (quic()) 90 | crash::SSL_stream_conclude(d_ssl, 0); 91 | 92 | if (d_ssl) { 93 | SSL_shutdown(d_ssl); 94 | SSL_free(d_ssl); 95 | } 96 | if (d_pubkey) 97 | EVP_PKEY_free(d_pubkey); 98 | if (d_privkey) 99 | EVP_PKEY_free(d_privkey); 100 | 101 | if (stream()) 102 | shutdown(d_peer_fd, SHUT_RDWR); 103 | 104 | if (d_fd2state) { 105 | for (int i = 3; i <= d_max_fd; ++i) { 106 | if (d_fd2state[i].state != STATE_INVALID) 107 | close(i); 108 | } 109 | delete [] d_fd2state; 110 | } 111 | 112 | delete [] d_pfds; 113 | 114 | close(d_peer_fd); 115 | } 116 | 117 | 118 | int session::tx_add_mult(int fd, const string &s) 119 | { 120 | tx_add(fd, s); 121 | 122 | // If traffic multiplier is configured, do so for data packets 123 | if (config::traffic_multiply > 1 && d_fd2state[fd].state == STATE_SSL) { 124 | for (uint32_t i = 1; i < config::traffic_multiply; ++i) { 125 | string np = ""; 126 | 127 | if (pad_nops(np, d_mss) > 0) 128 | tx_add(fd, np); 129 | } 130 | } 131 | 132 | return 0; 133 | } 134 | 135 | 136 | int session::tx_add(int fd, const string &s) 137 | { 138 | d_fd2state[fd].tx_len += s.size(); 139 | 140 | if (d_fd2state[fd].state == STATE_SSL && dtls()) { 141 | d_fd2state[fd].ovec.push_back(s); 142 | } else { 143 | if (d_fd2state[fd].ovec.empty()) 144 | d_fd2state[fd].ovec.push_back(s); 145 | else 146 | d_fd2state[fd].ovec[0] += s; 147 | } 148 | 149 | return 0; 150 | } 151 | 152 | 153 | int session::tx_remove(int fd, string::size_type n) 154 | { 155 | 156 | if (d_fd2state[fd].ovec.empty()) 157 | return 0; 158 | 159 | if (d_fd2state[fd].state == STATE_SSL && dtls()) { 160 | return 0; // No removing of DTLS dgram data. It was already removed by tx_string(). 161 | } else { 162 | d_fd2state[fd].ovec[0].erase(0, n); 163 | } 164 | 165 | if (n <= d_fd2state[fd].tx_len) 166 | d_fd2state[fd].tx_len -= n; 167 | else 168 | d_fd2state[fd].tx_len = 0; 169 | 170 | return 0; 171 | } 172 | 173 | 174 | string::size_type session::tx_size(int fd) 175 | { 176 | return d_fd2state[fd].tx_len; 177 | } 178 | 179 | 180 | session::strview session::tx_string(int fd, sequence_t &seq, string &bk_str, string::size_type max) 181 | { 182 | bk_str.clear(); 183 | seq = 0; 184 | 185 | strview sv = bk_str; 186 | 187 | if (d_fd2state[fd].ovec.empty()) 188 | return sv; 189 | 190 | // only SSL sockets need special treatments of dgram/stream/sequenced packets, 191 | // other out-buffers such as for pty, stdout etc just get the plain data 192 | if (d_fd2state[fd].state == STATE_SSL) { 193 | 194 | // Only DTLS needs own sequencing and retries 195 | if (!dtls()) { 196 | // Only pad if since last padding new payload data was added to queue. 197 | // As there is only one socket (d_peer_fd) where we pad outgoing data, 198 | // one variable (d_last_ssl_qlen) is sufficient and we don't need to have 199 | // a variable inside d_fd2state. 200 | if (d_last_ssl_qlen < d_fd2state[fd].ovec[0].size()) 201 | d_fd2state[fd].tx_len += pad_nops(d_fd2state[fd].ovec[0], d_mss); 202 | 203 | if (d_fd2state[fd].ovec[0].size() > max) 204 | sv = { d_fd2state[fd].ovec[0].c_str(), max }; 205 | else 206 | sv = { d_fd2state[fd].ovec[0].c_str(), d_fd2state[fd].ovec[0].size() }; 207 | } else { 208 | bool nop_only = 0; 209 | auto it = d_fd2state[fd].ovec.begin(); 210 | for (; it != d_fd2state[fd].ovec.end(); ++it) { 211 | 212 | // SQ-packets, already sequenced packets (via d_tx_map<> resend) and pure NOPs only as single dgrams 213 | if (it->find("C:SQ:") == 6 || it->find("C:PN:") == 6 || it->find("C:NO:") == 6) { 214 | if (bk_str.empty()) { 215 | if (it->find("C:NO:") == 6) 216 | nop_only = 1; 217 | bk_str = *it++; 218 | } 219 | break; 220 | } 221 | if (bk_str.size() + it->size() + SEQ_PSIZE <= max) 222 | bk_str += *it; 223 | else 224 | break; 225 | } 226 | 227 | // In DTLS case the data is immediately removed from queueing by tx_string(), 228 | // since SSL socket will be blocking and not writing partial 229 | // (it is either written at once or not at all) and the final dgram will be kept 230 | // in d_tx_map<> for a possible resend. This makes modding the ovec[] with :PN:... 231 | // and pads spanning multiple vector entries unnecessary. 232 | d_fd2state[fd].tx_len -= bk_str.size(); 233 | d_fd2state[fd].ovec.erase(d_fd2state[fd].ovec.begin(), it); 234 | 235 | // If a seq# was added, pass it to upper layer. It may be a resend from d_tx_map<>, 236 | // in which case no new seq# is added and 'seq' stays 0 to signal this. 237 | if (prepend_seq(d_flow.tx_sequence, bk_str) > 0) 238 | seq = d_flow.tx_sequence; 239 | 240 | // If it was a single NOP pkt, its already padded 241 | if (!nop_only) 242 | pad_nops(bk_str, d_mss); 243 | 244 | // DTLS case needs to have strview of backing string returned, as content was removed from ovec 245 | return sv = bk_str; 246 | } 247 | } else { 248 | if (d_fd2state[fd].ovec[0].size() > max) 249 | sv = { d_fd2state[fd].ovec[0].c_str(), max }; 250 | else 251 | sv = { d_fd2state[fd].ovec[0].c_str(), d_fd2state[fd].ovec[0].size() }; 252 | } 253 | 254 | return sv; 255 | } 256 | 257 | 258 | string session::tx_string_and_clear(int fd, string::size_type max) 259 | { 260 | sequence_t seq = 0; 261 | string bk_str; 262 | bk_str.reserve(2*CHUNK_SIZE); 263 | 264 | if (tx_empty(fd)) 265 | return bk_str; 266 | 267 | if (max == 0) 268 | max = tx_size(fd); 269 | 270 | auto sv = tx_string(fd, seq, bk_str, max); 271 | 272 | // if backing string is empty after return, it means we have SOCK_STREAM based optimization 273 | // and need to copy the strview, as we are meant to return a string that survives 274 | // the clearance of ovec buffers 275 | if (bk_str.empty()) 276 | bk_str = string(sv.c_str(), sv.size()); 277 | 278 | // now we have the str, it can be cleared 279 | tx_clear(fd); 280 | 281 | // no move(), RVO 282 | return bk_str; 283 | } 284 | 285 | 286 | bool session::tx_empty(int fd) 287 | { 288 | return d_fd2state[fd].tx_len == 0; 289 | } 290 | 291 | 292 | void session::tx_clear(int fd) 293 | { 294 | d_fd2state[fd].tx_len = 0; 295 | d_fd2state[fd].ovec.clear(); 296 | } 297 | 298 | 299 | bool session::tx_can_add(int fd) 300 | { 301 | // only send new dgrams if peer acknowledged us ours within a certain window (peer's RX# is our TX#) 302 | return (tls() || quic() || (d_flow.tx_sequence - d_flow.last_rx_seen <= MAX_OVEC_SIZE)); 303 | } 304 | 305 | 306 | bool session::tx_must_add_sq(int fd) 307 | { 308 | if (d_fd2state[fd].state == STATE_INVALID) 309 | return 0; 310 | 311 | // if we did not send a SQ pkt for quite some time, force one 312 | if (d_flow.tx_sequence - d_flow.last_tx_sq_added > MAX_OVEC_SIZE) 313 | return 1; 314 | 315 | return 0; 316 | } 317 | 318 | 319 | int session::tx_add_sq(int fd) 320 | { 321 | if (d_fd2state[fd].state == STATE_INVALID) 322 | return 0; 323 | 324 | char buf[64] = {0}; 325 | snprintf(buf, sizeof(buf) - 1, "%05hu:C:SQ:%016llx:%016llx:", (unsigned short)(6 + 34), d_flow.rx_sequence, d_flow.tx_sequence); 326 | 327 | tx_add(fd, buf); 328 | d_pfds[fd].events |= POLLOUT; 329 | 330 | d_flow.last_rx_acked = d_flow.rx_sequence; 331 | d_flow.last_tx_sq_added = d_flow.tx_sequence; 332 | 333 | return 1; 334 | } 335 | 336 | 337 | // cmd handler that is common for client and server sessions 338 | int session::handle_input(int i) 339 | { 340 | string &cmd = d_fd2state[i].ibuf; 341 | 342 | if (cmd.size() < 7) 343 | return 0; 344 | 345 | unsigned short l = 0; 346 | if (sscanf(cmd.c_str(), "%05hu:", &l) != 1) 347 | return 0; 348 | size_t len = l; 349 | 350 | if (len < 6) 351 | return -1; 352 | if (cmd.size() < 5 + len) // 5bytes %05hu + :C:... 353 | return 0; 354 | 355 | if (dtls() && cmd.find("C:SQ:", 6) == 6) { 356 | sequence_t peer_rx = 0, peer_tx = 0; 357 | if (sscanf(cmd.c_str() + 5, ":C:SQ:%016llx:%016llx:", &peer_rx, &peer_tx) == 2) { 358 | 359 | // up to seq# 'peer_rx' has been received, so remove from our TX map 360 | for (; d_flow.last_rx_seen < peer_rx; ++d_flow.last_rx_seen) 361 | d_tx_map.erase(d_flow.last_rx_seen); 362 | 363 | // peer is missing some of our packets? 364 | if (peer_rx < d_flow.tx_sequence) { 365 | 366 | // re-send some of the missing packets, if any 367 | for (sequence_t i = peer_rx; i < d_flow.tx_sequence && i - peer_rx <= MAX_OVEC_SIZE/2; ++i) { 368 | tx_add(d_peer_fd, d_tx_map[i]); 369 | d_pfds[d_peer_fd].events |= POLLOUT; 370 | } 371 | } 372 | } 373 | 374 | // packet seq number as added by prepend_seq() 375 | } else if (dtls() && cmd.find("C:PN:", 6) == 6) { 376 | sequence_t seq = 0; 377 | if (sscanf(cmd.c_str() + 5, ":C:PN:%016llx:", &seq) == 1) { 378 | 379 | // keep seq#s that are to be expected in future and try to put some in order 380 | // that may be found in d_rx_map<> 381 | if (seq > d_flow.rx_sequence) { 382 | 383 | // :PN: packets wrap the next data packet completely with its len 384 | d_rx_map[seq] = cmd.substr(0, 5 + len); 385 | cmd.erase(0, 5 + len); 386 | 387 | // maybe the expected seq# was already put to the d_rx_map<> ? 388 | if (d_rx_map.count(d_flow.rx_sequence) > 0) { 389 | cmd.insert(0, move(d_rx_map[d_flow.rx_sequence])); 390 | d_rx_map.erase(d_flow.rx_sequence); 391 | } 392 | return 1; 393 | 394 | // the exact next expected seq#, increase and continue processing 395 | } else if (seq == d_flow.rx_sequence) { 396 | 397 | // Acknowledge so far, if we received more than half of the window w/o acking them. 398 | // If we miss this code path due to missing :PN: pkts, the main poll loop will trigger sending 399 | // acks by timeouts. 400 | if (d_flow.rx_sequence - d_flow.last_rx_acked >= MAX_OVEC_SIZE/2) 401 | tx_add_sq(d_peer_fd); 402 | 403 | ++d_flow.rx_sequence; 404 | d_rx_map.erase(seq); // erase from map in case it was kept 405 | 406 | // Check rx map for possibly next packet to process along and 407 | // erase from map if any. Insert right behind currently processed packet in this case. 408 | auto next = d_rx_map.find(d_flow.rx_sequence); 409 | if (next != d_rx_map.end()) { 410 | cmd.insert(5 + len, move(next->second)); 411 | d_rx_map.erase(next); 412 | } 413 | 414 | // :PN: packets wrap the entire packet with its len, so let remove the fixed :PN: seq# hdr 415 | // to obtain data packet for next processing loop. 416 | len = SEQ_PSIZE - 5; 417 | 418 | // remove duped packets from queue 419 | } else { 420 | ; // done by 'cmd.erase(0, 5 + len);' at end of the function 421 | } 422 | } 423 | // ping request 424 | } else if (cmd.find("C:PP:", 6) == 6) { 425 | const string echo = cmd.substr(5 + 6, len - 6); 426 | tx_add(d_peer_fd, slen(6 + echo.size()) + ":C:PR:" + echo); 427 | d_pfds[d_peer_fd].events |= POLLOUT; 428 | } else if (cmd.find("C:T:", 6) == 6 || cmd.find("C:U:", 6) == 6) { 429 | 430 | // also remove cmd from buffer it net handler returns error 431 | net_cmd_handler(cmd); 432 | 433 | } else if (cmd.find("C:PR:", 6) == 6) { 434 | ; // ignore ping replies 435 | } else if (cmd.find("C:NO:", 6) == 6) { 436 | ; // ignore nops 437 | 438 | // disable traffic padding 439 | } else if (cmd.find("C:P0:", 6) == 6) { 440 | config::traffic_flags |= TRAFFIC_NOPAD; 441 | } else if (cmd.find("C:P1:", 6) == 6) { 442 | config::traffic_flags &= ~TRAFFIC_NOPAD; 443 | config::traffic_flags |= TRAFFIC_PADRND; 444 | } else if (cmd.find("C:P4:", 6) == 6) { 445 | config::traffic_flags &= ~TRAFFIC_NOPAD; 446 | config::traffic_flags |= TRAFFIC_PAD1; 447 | // enable maximum padding 448 | } else if (cmd.find("C:P9:", 6) == 6) { 449 | config::traffic_flags &= ~TRAFFIC_NOPAD; 450 | config::traffic_flags |= TRAFFIC_PADMAX; 451 | 452 | } else { 453 | 454 | // Valid len/data packet but no command handled. Do not erase it, it may be for the derived 455 | // class. Let them check and erase. 456 | return 1; 457 | } 458 | 459 | // One command was handled above. Erase this particular cmd. 460 | cmd.erase(0, 5 + len); 461 | 462 | // There may be more cmds in the ibuf. Pass to derived class. 463 | return 1; 464 | } 465 | 466 | 467 | /* 468 | * C:T:N:IP/port/ID/ -> open new TCP connection to IP:port 469 | * C:T:C:IP/port/ID/ -> connection to IP:port is estabished on remote side 470 | * C:T:S:IP/port/ID/data -> send data to IP:port 471 | * C:T:R:IP/port/ID/data -> data received from IP:port on remote side 472 | * C:T:F:IP/port/ID/ -> close connection belonging to IP:port 473 | * 474 | * C:U:S:IP/port/ID/data -> send UDP datagram to IP:port 475 | * C:U:R:IP/port/ID/data -> received UDP datagram from IP:port on remote side 476 | * 477 | */ 478 | int session::net_cmd_handler(const string &cmd) 479 | { 480 | char C[16] = {0}, proto[16] = {0}, op[16] = {0}, host[128] = {0}; 481 | uint16_t port = 0, id = 0; 482 | unsigned short len = 0; 483 | int sock = -1; 484 | 485 | // ID is the logical channel to distinguish between multiple same host:port connections. 486 | // The accepted socket fd of the local part is unique and good for it. FDID_MAX ensures it can be encoded as %hx. 487 | if (sscanf(cmd.c_str(), "%05hu:%15[^:]:%15[^:]:%15[^:]:%127[^/]/%04hx/%04hx/", &len, C, proto, op, host, &port, &id) != 7) 488 | return 0; 489 | 490 | auto slash = cmd.find("/"); 491 | const string node = string(host) + cmd.substr(slash, 11); 492 | 493 | if (len < 7 + node.size() || len > cmd.size() - 5) 494 | return 0; 495 | 496 | if (C[0] != 'C' || (proto[0] != 'T' && proto[0] != 'U')) 497 | return -1; 498 | 499 | // open new non-blocking connection 500 | if (cmd.find("C:T:N:", 6) == 6 && (d_net_cmd_flags & NETCMD_SEND_ALLOW)) { 501 | if ((sock = tcp_connect(host, to_string(port))) < 0) 502 | return -1; 503 | 504 | d_pfds[sock].revents = 0; 505 | d_pfds[sock].events = POLLOUT; 506 | d_pfds[sock].fd = sock; 507 | 508 | d_fd2state[sock].fd = sock; 509 | d_fd2state[sock].state = STATE_CONNECT; 510 | d_fd2state[sock].odgrams.clear(); 511 | d_fd2state[sock].rnode = node; 512 | d_fd2state[sock].time = d_now; 513 | tx_clear(sock); 514 | 515 | tcp_nodes2sock[node] = sock; 516 | 517 | // non-blocking connect() got ready 518 | } else if (cmd.find("C:T:C:", 6) == 6) { 519 | auto it = tcp_nodes2sock.find(node); 520 | if (it == tcp_nodes2sock.end()) 521 | return -1; 522 | sock = it->second; 523 | 524 | d_pfds[sock].events = POLLIN; 525 | 526 | d_fd2state[sock].fd = sock; 527 | d_fd2state[sock].state = STATE_CONNECTED; 528 | d_fd2state[sock].odgrams.clear(); 529 | d_fd2state[sock].time = d_now; 530 | tx_clear(sock); 531 | 532 | // finish connection 533 | } else if (cmd.find("C:T:F:", 6) == 6) { 534 | auto it = tcp_nodes2sock.find(node); 535 | if (it == tcp_nodes2sock.end()) 536 | return -1; 537 | sock = it->second; 538 | tcp_nodes2sock.erase(it); 539 | 540 | // flush any remaining data 541 | flush_fd(sock, tx_string_and_clear(sock)); 542 | 543 | // sock will be closed in main poll() loop via timeout 544 | shutdown(sock, SHUT_RDWR); 545 | d_pfds[sock].fd = -1; 546 | d_pfds[sock].events = 0; 547 | 548 | d_fd2state[sock].state = STATE_CLOSING; 549 | d_fd2state[sock].odgrams.clear(); 550 | d_fd2state[sock].time = d_now; 551 | 552 | // Send or receive data. No NETCMD_SEND_ALLOW check, since the node will not be in 553 | // the tcp_nodes2sock map in the first place, as there was no tcp_connect() and no map 554 | // insertion. 555 | } else if (cmd.find("C:T:S:", 6) == 6 || cmd.find("C:T:R:", 6) == 6) { 556 | auto it = tcp_nodes2sock.find(node); 557 | if (it == tcp_nodes2sock.end()) 558 | return -1; 559 | sock = it->second; 560 | tx_add(sock, cmd.substr(5 + 7 + node.size(), len - 7 - node.size())); // strip off data part 561 | d_pfds[sock].events |= POLLOUT; 562 | d_fd2state[sock].time = d_now; 563 | 564 | } else if (cmd.find("C:U:S:", 6) == 6 || cmd.find("C:U:R:", 6) == 6) { 565 | auto it = udp_nodes2sock.find(node); 566 | if (it == udp_nodes2sock.end()) { 567 | if (!(d_net_cmd_flags & NETCMD_SEND_ALLOW)) 568 | return 0; 569 | if ((sock = udp_connect(host, to_string(port))) < 0) 570 | return -1; 571 | udp_nodes2sock[node] = sock; 572 | 573 | // Just fill rnode part in server side. client main loop expects ID/ part not to be 574 | // appended 575 | d_fd2state[sock].rnode = node; 576 | d_fd2state[sock].state = STATE_UDPCLIENT; 577 | d_fd2state[sock].fd = sock; 578 | } else 579 | sock = it->second; 580 | 581 | d_pfds[sock].revents = 0; 582 | d_pfds[sock].fd = sock; 583 | d_pfds[sock].events = POLLIN; 584 | 585 | if (cmd.size() > 5 + 7 + node.size()) { 586 | d_fd2state[sock].odgrams.push_back({id, cmd.substr(5 + 7 + node.size(), len - 7 - node.size())}); // strip off data part (startes after "%05hu:C:U:S") 587 | d_pfds[sock].events |= POLLOUT; 588 | } 589 | d_fd2state[sock].time = d_now; 590 | } 591 | 592 | return 1; 593 | } 594 | 595 | 596 | } 597 | 598 | -------------------------------------------------------------------------------- /src/session.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009-2025 Sebastian Krahmer. 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 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 | * 3. All advertising materials mentioning features or use of this software 14 | * must display the following acknowledgement: 15 | * This product includes software developed by Sebastian Krahmer. 16 | * 4. The name Sebastian Krahmer may not be used to endorse or promote 17 | * products derived from this software without specific prior written 18 | * permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef crash_session_h 34 | #define crash_session_h 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include "net.h" 47 | #include "misc.h" 48 | #include "iobox.h" 49 | #include "missing.h" 50 | 51 | extern "C" { 52 | #include 53 | #include 54 | #include 55 | #include 56 | } 57 | 58 | 59 | namespace crash { 60 | 61 | 62 | class session { 63 | 64 | protected: 65 | 66 | enum class transport_t : uint8_t { 67 | TRANSPORT_TLS = 1, 68 | TRANSPORT_DTLS = 2, 69 | TRANSPORT_QUIC = 3 70 | }; 71 | 72 | bool dgram() 73 | { 74 | return d_type == SOCK_DGRAM; 75 | } 76 | 77 | bool stream() 78 | { 79 | return d_type == SOCK_STREAM; 80 | } 81 | 82 | bool quic() 83 | { 84 | return d_transport == transport_t::TRANSPORT_QUIC; 85 | } 86 | 87 | bool tls() 88 | { 89 | return d_transport == transport_t::TRANSPORT_TLS; 90 | } 91 | 92 | bool dtls() 93 | { 94 | return d_transport == transport_t::TRANSPORT_DTLS; 95 | } 96 | 97 | 98 | std::string d_err{""}; 99 | std::string d_transport_str{"tls1"}, d_sni{""}, d_ticket_file{""}; 100 | 101 | // keep sent packets in DGRAM case in a tx map for requested resends 102 | // and keep rx packets that arrived out of order for later processing 103 | std::map d_tx_map, d_rx_map; 104 | 105 | SSL *d_ssl{nullptr}; 106 | 107 | BIO *d_bio{nullptr}; 108 | BIO_ADDR *d_bio_peer{nullptr}; 109 | 110 | EVP_PKEY *d_pubkey{nullptr}, *d_privkey{nullptr}; 111 | 112 | state *d_fd2state{nullptr}; 113 | 114 | pollfd *d_pfds{nullptr}; 115 | 116 | string::size_type d_last_ssl_qlen = 0; 117 | 118 | struct { 119 | // next TX seq# to come in the next send, next RX seq# to be expected on receive 120 | sequence_t tx_sequence{1}, rx_sequence{1}; 121 | 122 | // last acknowledged RX seq# by peer, detected lost packet with seq# 123 | sequence_t last_rx_seen{0}, packet_loss{0}; 124 | 125 | // last seq# that we acked to peer, last tx seq# that we injected a SQ pkt 126 | sequence_t last_rx_acked{0}, last_tx_sq_added{0}; 127 | 128 | void reset() 129 | { 130 | tx_sequence = 1; 131 | rx_sequence = 1; 132 | last_rx_seen = 0; 133 | packet_loss = 0; 134 | last_rx_acked = 0; 135 | last_tx_sq_added = 0; 136 | } 137 | 138 | } d_flow; 139 | 140 | struct { 141 | int max{TCP_POLL_TO}, next{TCP_POLL_TO}, min{100}; 142 | } d_poll_to; 143 | 144 | time_t d_now{0}; 145 | 146 | size_t d_mss{MSS}; 147 | 148 | uint32_t d_net_cmd_flags{0}; 149 | 150 | int d_peer_fd{-1}, d_family{AF_INET}, d_type{SOCK_STREAM}, d_max_fd{0}; 151 | 152 | unsigned int d_chunk_size{TCP_CHUNK_SIZE}; 153 | 154 | uint16_t d_major{3}, d_minor{1000}; // keep in-sync with server_session::d_banner 155 | 156 | transport_t d_transport{transport_t::TRANSPORT_TLS}; 157 | 158 | // whether suspend signal was received, which needs different 159 | // connection teardown in destructor 160 | bool d_suspended{0}; 161 | 162 | // my own stringview for optimization of tx_string() in C++11 that doesn't have it yet 163 | class strview { 164 | const char *d_cstr{nullptr}; 165 | std::string::size_type d_size{0}; 166 | public: 167 | 168 | // not owning 169 | strview(const std::string &s) { d_cstr = s.c_str(); d_size = s.size(); } 170 | 171 | strview(const char *cptr, std::string::size_type l) { d_cstr = cptr; d_size = l; } 172 | 173 | const char *c_str() const { return d_cstr; } 174 | 175 | const std::string::size_type size() const { return d_size; } 176 | }; 177 | 178 | int tx_add(int, const std::string &); 179 | 180 | int tx_add_mult(int, const std::string &); 181 | 182 | int tx_remove(int i, string::size_type); 183 | 184 | strview tx_string(int fd, std::string &bk_str) 185 | { 186 | sequence_t s = 0; 187 | return tx_string(fd, s, bk_str); 188 | } 189 | 190 | strview tx_string(int, sequence_t &, std::string &, std::string::size_type max = CHUNK_SIZE); 191 | 192 | std::string tx_string_and_clear(int, std::string::size_type max = 0); 193 | 194 | std::string::size_type tx_size(int); 195 | 196 | void tx_clear(int); 197 | 198 | bool tx_empty(int); 199 | 200 | bool tx_can_add(int); 201 | 202 | int tx_add_sq(int); 203 | 204 | bool tx_must_add_sq(int); 205 | 206 | virtual int suspend(const std::string &) = 0; 207 | 208 | virtual int authenticate() = 0; 209 | 210 | virtual int handle_input(int) = 0; 211 | 212 | int net_cmd_handler(const std::string &); 213 | 214 | public: 215 | 216 | session(const std::string &, const std::string &); 217 | 218 | virtual ~session(); 219 | 220 | const char *why() 221 | { 222 | return d_err.c_str(); 223 | } 224 | 225 | bool suspended() { return d_suspended; } 226 | }; 227 | 228 | 229 | class client_session : public session { 230 | 231 | udp_node2id d_udp_node2id; 232 | 233 | std::string d_sbanner{""}, d_disguise_prefix{""}; 234 | 235 | struct termios d_tattr, d_old_tattr; 236 | 237 | SSL_CTX *d_ssl_ctx{nullptr}; 238 | 239 | #if OPENSSL_VERSION_NUMBER >= 0x10000000L 240 | const SSL_METHOD *d_ssl_method{nullptr}; 241 | #else 242 | SSL_METHOD *d_ssl_method{nullptr}; 243 | #endif 244 | 245 | bool d_has_tty{0}; 246 | 247 | protected: 248 | 249 | int send_window_size(); 250 | 251 | int check_server_key(); 252 | 253 | virtual int suspend(const std::string &) override; 254 | 255 | virtual int authenticate() override; 256 | 257 | virtual int handle_input(int) override; 258 | 259 | public: 260 | client_session(const std::string &, const std::string &, const std::string &, const std::string &); 261 | 262 | ~client_session(); 263 | 264 | int setup(); 265 | 266 | int handle(); 267 | }; 268 | 269 | 270 | 271 | class server_session : public session { 272 | 273 | SSL_CTX *d_ssl_ctx0{nullptr}; // not owning 274 | 275 | std::string d_user{""}, d_cmd{""}, d_home{""}, d_shell{""}, d_peer_ip{""}; 276 | std::string d_banner{"1000 crashd-3.1000 OK\r\n"}; // keep in sync with d_major and d_minor 277 | 278 | iobox d_iob; 279 | 280 | int d_stdin_closed{0}; 281 | uid_t d_final_uid{0xffff}; 282 | 283 | protected: 284 | 285 | #if defined LIBRESSL_VERSION_NUMBER || defined BORINGSSL_API_VERSION 286 | char d_dlisten_param[128]{0}; 287 | #else 288 | BIO_ADDR *d_dlisten_param{nullptr}; 289 | #endif 290 | 291 | virtual int suspend(const std::string &) override; 292 | 293 | virtual int authenticate() override; 294 | 295 | virtual int handle_input(int) override; 296 | 297 | public: 298 | 299 | server_session(int, const std::string &, const std::string &, const std::string &); 300 | 301 | ~server_session(); 302 | 303 | int handle(SSL_CTX *); 304 | 305 | }; 306 | 307 | } 308 | 309 | #endif 310 | 311 | --------------------------------------------------------------------------------