├── downstream ├── debian │ ├── compat │ ├── docs │ ├── rules │ ├── changelog │ └── control └── ucspi-tools.rb ├── read0.sh ├── read6.sh ├── write.sh ├── .gitignore ├── client.cf ├── config.mk ├── server.cf ├── .travis.yml ├── GNUmakefile ├── ca.cf ├── http ├── httppc.1 ├── dprintf.c ├── http_parser.h ├── Makefile ├── tlss.1 ├── findport.c ├── tests.mk ├── socks.1 ├── sockc.1 ├── README.md ├── tlsc.1 ├── httppc.c ├── https.c ├── ucspi-tee.c ├── http_parser.c ├── httpc.c ├── sslc.c ├── ftpc.c ├── test.sh ├── tlss.c ├── tcps.c ├── tcpc.c ├── sockc.c ├── socks.c ├── tap-functions └── tlsc.c /downstream/debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /downstream/debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /read0.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | cat >"$1" 6 | -------------------------------------------------------------------------------- /read6.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | cat <&6 >"$1" 6 | -------------------------------------------------------------------------------- /write.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | /usr/bin/env >&7 6 | -------------------------------------------------------------------------------- /downstream/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | export PREFIX=/usr 3 | %: 4 | dh $@ 5 | -------------------------------------------------------------------------------- /downstream/debian/changelog: -------------------------------------------------------------------------------- 1 | ucspi-tools (1.1-1) unstable; urgency=low 2 | 3 | * Initial release 4 | 5 | -- Jan Klemkow Tue, 14 Jan 2015 11:46:51 -0600 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | *.core 4 | *.key 5 | *.csr 6 | *.crt 7 | *.out 8 | sockc 9 | tlsc 10 | tlss 11 | sslc 12 | tcpc 13 | tcps 14 | httppc 15 | httpc 16 | https 17 | ftpc 18 | *.tar.gz 19 | *.p12 20 | tags 21 | debian 22 | -------------------------------------------------------------------------------- /client.cf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | prompt = no 3 | distinguished_name = req_distinguished_name 4 | 5 | [ req_distinguished_name ] 6 | C = DE 7 | ST = Test State or Province 8 | L = Test Locality 9 | O = Organization Name 10 | OU = Organizational Unit Name 11 | CN = user 12 | emailAddress = user@localhost 13 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # paths 2 | PREFIX ?= /usr/local/ 3 | BINDIR = ${DESTDIR}${PREFIX}/bin/ 4 | MANDIR = ${DESTDIR}${PREFIX}/man/ 5 | MAN1DIR = ${MANDIR}/man1/ 6 | 7 | VERSION = 1.7 8 | 9 | # compiler 10 | CFLAGS += -std=c99 -pedantic -Wall -Wextra 11 | CFLAGS += -D_XOPEN_SOURCE=700 12 | CFLAGS += -D_BSD_SOURCE 13 | -------------------------------------------------------------------------------- /server.cf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | prompt = no 3 | distinguished_name = req_distinguished_name 4 | 5 | [ req_distinguished_name ] 6 | C = DE 7 | ST = Test State or Province 8 | L = Test Locality 9 | O = Organization Name 10 | OU = Organizational Unit Name 11 | CN = localhost 12 | emailAddress = admin@localhost 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | script: 3 | - make 4 | - make test POSIXLY_CORRECT=true 5 | before_install: 6 | - sudo apt-get -qq update 7 | - sudo apt-get install -q libbsd-dev 8 | - git clone https://github.com/libressl-portable/portable.git libressl 9 | - (cd libressl;./autogen.sh && ./configure && make check && sudo make install && sudo ldconfig) 10 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | OS := $(shell uname -s) 2 | 3 | # GNU/Linux 4 | ifeq "$(OS)" "Linux" 5 | CFLAGS += -DUSE_LIBBSD 6 | CFLAGS += -D_GNU_SOURCE 7 | CFLAGS += `pkg-config --cflags libbsd` 8 | LDLIBS += `pkg-config --libs libbsd` 9 | endif 10 | 11 | # MacOSX 12 | ifeq "$(OS)" "Darwin" 13 | CFLAGS += -D_DARWIN_C_SOURCE 14 | LDFLAGS += -lcrypto `pkg-config --libs libtls libssl` 15 | endif 16 | 17 | include Makefile 18 | -------------------------------------------------------------------------------- /ca.cf: -------------------------------------------------------------------------------- 1 | [req] 2 | prompt = no 3 | distinguished_name = req_distinguished_name 4 | 5 | [req_distinguished_name] 6 | C = DE 7 | ST = Test State or Province 8 | L = Test Locality 9 | O = Organization Name 10 | OU = Organizational Unit Name 11 | CN = root 12 | emailAddress = admin@localhost 13 | 14 | [root] 15 | private_key = ca.key 16 | certificate = ca.crt 17 | database = ca.txt 18 | default_md = sha1 19 | default_crl_hours = 1 20 | 21 | [ca] 22 | default_ca = root 23 | -------------------------------------------------------------------------------- /downstream/debian/control: -------------------------------------------------------------------------------- 1 | Source: ucspi-tools 2 | Section: misc 3 | Priority: extra 4 | Maintainer: Jan Klemkow 5 | Build-Depends: debhelper (>= 8.0.0), libssl-dev 6 | Standards-Version: 3.9.3 7 | Homepage: https://github.com/younix/ucspi/blob/master/README.md 8 | 9 | Package: ucspi-tools 10 | Architecture: all 11 | Depends: ${misc:Depends} 12 | Description: The ucspi Tool Suite is bunch of tools to handle UCSPI connections. 13 | The UNIX Client/Server Program Interface Tool Suite is bunch of tools to handle UCSPI connections. 14 | -------------------------------------------------------------------------------- /http: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #printf "GET / HTTP/1.1\r\n" > /dev/fd/6 4 | #printf "Host: $1\r\n" > /dev/fd/6 5 | #printf "\r\n" > /dev/fd/6 6 | #cat < /dev/fd/7 7 | 8 | url=$1 9 | 10 | scheme=$(echo $url | sed -nEe 's%(.*)://[^/]*/.*%\1%p') 11 | host= $(echo $url | sed -nEe 's%.*://([^/]*)/.*%\1%p') 12 | uri= $(echo $url | sed -nEe 's%.*://[^/]*/(.*)%\1%p') 13 | 14 | #port=$(echo $host | sed -nEe 's/.*:([[:digit:]]*)$/\1/p') 15 | #port=$(echo $host | sed -nEe 's/.*:([[:digit:]]*)$/\1/p') 16 | port=80 17 | 18 | echo ./tcpclient $host 80 httpc $uri 19 | ./tcpclient $host $port ./httpc 20 | -------------------------------------------------------------------------------- /downstream/ucspi-tools.rb: -------------------------------------------------------------------------------- 1 | require "formula" 2 | 3 | class UcspiTools < Formula 4 | homepage "https://github.com/younix/ucspi/blob/master/README.md" 5 | url "https://github.com/younix/ucspi/tarball/968f1e7231c69624402ae8bf6903880a7491d51a" 6 | sha1 "643d88d906a51b6c45d3fa52636187932fb68b2b" 7 | version '2015-01-14' 8 | 9 | depends_on "pkg-config" => :build 10 | depends_on "ucspi-tcp" 11 | 12 | def install 13 | system "make", "install prefix=#{prefix}" 14 | end 15 | 16 | test do 17 | out = shell_output("#{bin}/socks 2>&1") 18 | assert out.include?("tcpclient PROXY-HOST PROXY-PORT socks HOST PORT PROGRAM [ARGS...]") 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /httppc.1: -------------------------------------------------------------------------------- 1 | .Dd September 21, 2016 2 | .Dt HTTPPC 1 3 | .Os 4 | .Sh NAME 5 | .Nm httppc 6 | .Nd HTTP proxy client 7 | .Sh SYNOPSIS 8 | .Nm tcpclient 9 | .Ar proxy-host 10 | .Ar proxy-port Nm httppc 11 | .Ar host 12 | .Ar port 13 | .Ar program 14 | .Op arguments 15 | .Sh DESCRIPTION 16 | The 17 | .Nm 18 | utility is an HTTP proxy handler for a UCSPI execchain. 19 | It communicates with tcpclient over pipes with filedescriptor 6 and 7. 20 | tcpclient connects to an HTTP proxy and delegates the further protocol 21 | handling to 22 | .Nm httppc . 23 | httppc talks with the HTTP proxy and initiates a TCP connection to 24 | .Ar host 25 | with 26 | .Ar port . 27 | After the connection initiation 28 | .Nm 29 | executes 30 | .Ar program 31 | with 32 | .Ar arguments . 33 | .Sh ENVIRONMENT 34 | .Bl -tag -width Ds 35 | .It Ev TCPLOCALHOST Ev TCPLOCALIP Ev TCPLOCALPORT 36 | These variables are delete. 37 | .It Ev TCPREMOTEHOST Ev TCPREMOTEPORT 38 | These variables are set to the hostname and port number of the server. 39 | .El 40 | .\".Sh EXIT STATUS 41 | .\".Sh EXAMPLES 42 | .Sh SEE ALSO 43 | .Xr sockc 1 , 44 | .Xr tcpclient 1 45 | .Sh STANDARDS 46 | RFC 7231, section 4.3.6. CONNECT 47 | .Sh AUTHORS 48 | This program was written by 49 | .An Jan Klemkow Aq Mt j.klemkow@wemelug.de . 50 | -------------------------------------------------------------------------------- /dprintf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifdef __sun__ 18 | #include 19 | #include 20 | #include 21 | 22 | void 23 | dprintf(int fd, const char *fmt, ...) 24 | { 25 | char buf[BUFSIZ]; 26 | va_list ap; 27 | int len; 28 | 29 | va_start(ap, fmt); 30 | len = vsnprintf(buf, sizeof buf, fmt, ap); 31 | va_end(ap); 32 | 33 | if (len >= (int)sizeof buf) 34 | len = sizeof buf; 35 | 36 | if (write(fd, buf, len) == -1) 37 | err(EXIT_FAILURE, "write"); 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /http_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef HTTP_PARSER_H 18 | #define HTTP_PARSER_H 19 | 20 | struct http_response { 21 | int code; 22 | size_t content_length; 23 | 24 | enum { 25 | HTTP_CONT_ENC_PLAIN = 0, 26 | HTTP_CONT_ENC_COMPRESS, 27 | HTTP_CONT_ENC_DEFLATE, 28 | HTTP_CONT_ENC_GZIP 29 | } content_encoding; 30 | 31 | enum { 32 | HTTP_TRANS_ENC_NONE = 0, 33 | HTTP_TRANS_ENC_CHUNKED 34 | } transfer_encoding; 35 | }; 36 | 37 | int http_read_line_fd(int fd, char *buf, size_t size); 38 | int http_read_line_fh(FILE *fh, char *buf, size_t size); 39 | int http_parse_code(char *buf, size_t size); 40 | int http_parse_line(struct http_response *head, char *buf); 41 | char *http_reason_phrase(int code); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | DISTNAME := ucspi-tools-${VERSION} 4 | TARBALL := ${DISTNAME}.tar.gz 5 | 6 | LIBS_TLS ?= -ltls `pkg-config --libs libssl` 7 | 8 | .PHONY: all test clean install 9 | .SUFFIXES: .c .o 10 | 11 | all: sockc tlsc tlss httppc httpc https ftpc tcpc tcps 12 | 13 | # HTTP 14 | httpc.o: http_parser.h 15 | http_parser.o: http_parser.h 16 | 17 | httpc: httpc.o http_parser.o 18 | $(CC) $(LDFLAGS) -o $@ httpc.o http_parser.o 19 | 20 | httppc: httppc.o http_parser.o 21 | $(CC) $(LDFLAGS) -o $@ httppc.o http_parser.o 22 | 23 | # TCP 24 | tcpc: tcpc.o 25 | $(CC) $(LDFLAGS) -o tcpc tcpc.o 26 | 27 | tcps: tcps.o 28 | $(CC) $(LDFLAGS) -o tcps tcps.o 29 | 30 | # SSL/TLS 31 | tlsc: tlsc.o 32 | $(CC) $(LDFLAGS) -o tlsc tlsc.o $(LIBS_TLS) 33 | 34 | tlss: tlss.o 35 | $(CC) $(LDFLAGS) -o tlss tlss.o $(LIBS_TLS) 36 | 37 | # Just for lagacy systems. Don't use this. 38 | #LIBS_SSL = `pkg-config --libs libssl openssl` 39 | #sslc: sslc.o 40 | # $(CC) $(LDFLAGS) -o sslc sslc.o $(LIBS_SSL) 41 | # 42 | #sslc.o: sslc.c 43 | # $(CC) $(CFLAGS) `pkg-config --cflags libssl` -o $@ -c sslc.c 44 | 45 | clean: 46 | rm -rf *.core *.o obj/* socks sockc tcpc tcps tlsc tlss sslc httpc \ 47 | httppc https ftpc findport ucspi-tools-* ucspi-tee *.key *.csr *.crt \ 48 | *.trace *.out 49 | 50 | install: all 51 | mkdir -p ${BINDIR} 52 | mkdir -p ${MAN1DIR} 53 | install -m 775 sockc ${BINDIR} 54 | install -m 775 tlsc ${BINDIR} 55 | install -m 775 tlss ${BINDIR} 56 | install -m 775 httppc ${BINDIR} 57 | install -m 444 sockc.1 ${MAN1DIR} 58 | install -m 444 tlsc.1 ${MAN1DIR} 59 | install -m 444 tlss.1 ${MAN1DIR} 60 | install -m 444 httppc.1 ${MAN1DIR} 61 | 62 | $(TARBALL): 63 | git archive --format=tar.gz --prefix=$(DISTNAME)/ HEAD -o $@ 64 | 65 | deb: $(TARBALL) 66 | #dh_make -y -s -i -f $(TARBALL) -p ucspi_${VERSION} 67 | #rm -f debian/*.ex debian/*.EX debian/README.* 68 | mkdir debian 69 | mv downstream/debian/* debian/ 70 | fakeroot debian/rules clean 71 | fakeroot debian/rules build 72 | fakeroot debian/rules binary 73 | debuild -b -us -uc 74 | 75 | include tests.mk 76 | -------------------------------------------------------------------------------- /tlss.1: -------------------------------------------------------------------------------- 1 | .Dd December 19, 2018 2 | .Dt TLSS 1 3 | .Os 4 | .Sh NAME 5 | .Nm tlss 6 | .Nd UCSPI TLS Server 7 | .Sh SYNOPSIS 8 | .Nm tcpserver Ar host Ar port Nm tlss 9 | .Op Fl C 10 | .Op Fl c Ar cert_file 11 | .Op Fl k Ar key_file 12 | .Op Fl p Ar ca_path 13 | .Op Fl f Ar ca_file 14 | .Ar program 15 | .Op args... 16 | .Sh DESCRIPTION 17 | The 18 | .Nm 19 | utility is a TLS server to be used in an UCSPI exec-chain. 20 | .Nm tcpserver 21 | spawns 22 | .Nm 23 | after a client connects. 24 | It initializes the server-side of a TLS tunnel and handles the TLS handshake. 25 | After the TLS connection is established 26 | .Nm 27 | spawns 28 | .Ar program . 29 | The spawned program is now able to handle its network communication in plain 30 | text. 31 | .Nm 32 | handle all the encryption and decryption of the communication. 33 | .Nm 34 | is also able to check a client side TLS certificate. 35 | The certificate verification can be controlled by to following options. 36 | The system CA certificates are used to verify the current connection 37 | certificate by default. 38 | The options are as follows: 39 | .Bl -tag -width Ds 40 | .It Fl h 41 | Show usage text. 42 | .It Fl C 43 | this option activates the client-side certificate check. 44 | The connection is terminated if the client does not provide a valid certificate. 45 | The client certificate is verified against the given CA store. 46 | .It Fl c Ar cert_file 47 | sets the servers certificate that is used to verify the server to the client. 48 | .It Fl k Ar key_file 49 | sets the servers private key. 50 | .It Fl p Ar ca_path 51 | sets a directory path containing files with CA certificates which are used to 52 | verify the clients certificate. 53 | .It Fl f Ar ca_file 54 | sets a file with CA certificates which are used to verify the clients 55 | certificate. 56 | .El 57 | .Sh ENVIRONMENT 58 | .Bl -tag -width Ds 59 | .It PROTO 60 | is set by 61 | .Nm 62 | to SSL to signal 63 | .Ar program 64 | that the connection is encrypted. 65 | .El 66 | .Sh EXIT STATUS 67 | .Ex -std 68 | .Sh SEE ALSO 69 | .Xr httppc 1 , 70 | .Xr sockc 1 , 71 | .Xr tcpserver 1 , 72 | .Xr tlsc 1 73 | .Sh AUTHORS 74 | .An -nosplit 75 | The 76 | .Nm 77 | program was written by 78 | .An Jan Klemkow Aq Mt j.klemkow@wemelug.de . 79 | -------------------------------------------------------------------------------- /findport.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jan Klemkow 3 | * Copyright (c) 2016 Alexander Bluhm 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | int 30 | main(int argc, char *argv[]) 31 | { 32 | int *s; 33 | size_t slen = 1; 34 | struct sockaddr_in addr; 35 | socklen_t addrlen = sizeof addr; 36 | const char *errstr = NULL; 37 | 38 | if (argc > 1) { 39 | slen = strtonum(argv[1], 1, UINT16_MAX, &errstr); 40 | if (errstr != NULL) 41 | err(EXIT_FAILURE, "strtonum"); 42 | } 43 | 44 | if ((s = calloc(slen, sizeof *s)) == NULL) 45 | err(EXIT_FAILURE, "calloc"); 46 | 47 | for (size_t i = 0; i < slen; i++) { 48 | if ((s[i] = socket(AF_INET, SOCK_STREAM, 0)) == -1) 49 | err(EXIT_FAILURE, "socket"); 50 | 51 | memset(&addr, 0, sizeof addr); 52 | addr.sin_family = AF_INET; 53 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 54 | addr.sin_port = 0; 55 | 56 | if (bind(s[i], (struct sockaddr *)&addr, addrlen) == -1) 57 | err(EXIT_FAILURE, "bind"); 58 | 59 | if (getsockname(s[i], (struct sockaddr *)&addr, &addrlen) == -1) 60 | err(EXIT_FAILURE, "getsockname"); 61 | 62 | printf("%u\n", ntohs(addr.sin_port)); 63 | } 64 | 65 | for (size_t i = 0; i < slen; i++) 66 | if (close(s[i]) == -1) 67 | err(EXIT_FAILURE, "close"); 68 | 69 | return EXIT_SUCCESS; 70 | } 71 | -------------------------------------------------------------------------------- /tests.mk: -------------------------------------------------------------------------------- 1 | # this Makefile creates files which are needed for tests 2 | 3 | KEYLEN=4096 4 | 5 | test: tcps tcpc tlss tlsc server.crt client.crt ca.crt 6 | ./test.sh 7 | 8 | # create server key ############################################################ 9 | client.key: 10 | openssl genrsa -out $@ ${KEYLEN} 11 | # create server certificate request 12 | client.csr: client.key client.cf 13 | openssl req -new -key client.key -config client.cf -out $@ 14 | # create ca-signed server certificate 15 | client.crt: client.csr ca.crt 16 | openssl x509 -req -in client.csr -out $@ \ 17 | -CAcreateserial -CAkey ca.key -CA ca.crt 18 | # create pkcs12 version of the client key for import browser 19 | client.p12: client.crt client.key 20 | openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out $@ 21 | 22 | # create server key ############################################################ 23 | server.key: 24 | openssl genrsa -out $@ ${KEYLEN} 25 | # create server certificate request 26 | server.csr: server.key server.cf 27 | openssl req -new -key server.key -config server.cf -out $@ 28 | # create ca-signed server certificate 29 | server.crt: server.csr ca.crt 30 | openssl x509 -req -in server.csr -out $@ \ 31 | -CAcreateserial -CAkey ca.key -CA ca.crt 32 | 33 | # create certificate authority ################################################# 34 | ca.key: 35 | openssl genrsa -out $@ ${KEYLEN} 36 | ca.crt: ca.key ca.cf 37 | openssl req -new -x509 -key ca.key -config ca.cf -out $@ 38 | 39 | # create certificate revocation list ########################################### 40 | ca.crl: ca.cf ca.key client_revoke.crt 41 | openssl ca -config ca.cf -gencrl -revoke client_revoke.crt -out $@ 42 | 43 | # create client revoke key ##################################################### 44 | client_revoke.key: 45 | openssl genrsa -out $@ ${KEYLEN} 46 | # create server certificate request 47 | client_revoke.csr: client_revoke.key client_revoke.cf 48 | openssl req -new -key client_revoke.key -config client_revoke.cf -out $@ 49 | # create ca-signed server certificate 50 | client_revoke.crt: client_revoke.csr ca.crt 51 | openssl x509 -req -in client_revoke.csr -out $@ \ 52 | -CAcreateserial -CAkey ca.key -CA ca.crt 53 | # create pkcs12 version of the client_revoke key for import browser 54 | client_revoke.p12: client_revoke.crt client_revoke.key 55 | openssl pkcs12 -export -clcerts -in client_revoke.crt \ 56 | -inkey client_revoke.key -out $@ 57 | -------------------------------------------------------------------------------- /socks.1: -------------------------------------------------------------------------------- 1 | .Dd $Mdocdate$ 2 | .Dt SOCKS 1 3 | .Sh NAME 4 | .Nm socks 5 | .Nd UCSPI socks proxy client 6 | .Sh SYNOPSIS 7 | .Nm tcpclient 8 | .Ar proxy-host 9 | .Ar proxy-port Nm socks 10 | .Ar host 11 | .Ar port 12 | .Ar program 13 | .Op arguments 14 | .Sh DESCRIPTION 15 | The 16 | .Nm 17 | utility is a socks proxy handler for a UCSPI execchain. 18 | It communicates with tcpclient over pipes with filedescriptor 6 and 7. 19 | tcpclient connects to the socks proxy and delegates the further protocol 20 | handling to 21 | .Nm socks . 22 | socks talks with the socks proxy and initiates a tcp connection to 23 | .Ar host 24 | with 25 | .Ar port . 26 | After connection initiation 27 | .Nm 28 | executes 29 | .Ar program 30 | with 31 | .Ar arguments . 32 | .Sh ENVIRONMENT 33 | The following environment variables are defined in the UCPSI specification. 34 | By the usage of 35 | .Nm socks 36 | there are two TCP connections. 37 | One from the 38 | .Nm tcpclient 39 | to the socks proxy and an other from the socks proxy to the server. 40 | The 41 | .Nm socks 42 | programm tries to handel this in a transparant way for the program. 43 | All variables which are set from tcpclient to describe the TCP connection 44 | between program and proxy are overwritten with the description of the TCP 45 | connection between proxy and server. 46 | Additionally variables are set to describe the connection between program 47 | and proxy. 48 | 49 | 50 | SOCKSLOCAL TCPLOCAL 51 | | | 52 | program <--> proxy <--> server 53 | | | 54 | SOCKSREMOTE TCPREMOTE 55 | 56 | .Bl -tag -width Ds 57 | .It Ev PROTO 58 | Is set to TCP. 59 | .It Ev TCPLOCALHOST Ev TCPLOCALIP Ev TCPLOCALPORT 60 | These variables are set to the hostname, IP address and port number of the 61 | local socks proxy address. 62 | .It Ev TCPREMOTEHOST Ev TCPREMOTEIP Ev TCPREMOTEPORT 63 | These variables are set to the hostname, IP address and port number of the 64 | server. 65 | .It Ev SOCKSLOCALHOST Ev SOCKSLOCALIP Ev SOCKSLOCALPORT 66 | These variables are set to the hostname, IP address and port number of the 67 | tcpclient local side. 68 | .It Ev SOCKSREMOTEHOST Ev SOCKSREMOTEIP Ev SOCKSREMOTEPORT 69 | These variables are set to the hostname, IP address and port number of the 70 | socks proxy server side. 71 | .El 72 | .\".Sh EXIT STATUS 73 | .\".Sh EXAMPLES 74 | .Sh SEE ALSO 75 | .Xr tcpclient 1 , 76 | .Xr socks 1 77 | .Sh STANDARDS 78 | RFC 1928, SOCKS version 5 79 | .Sh AUTHORS 80 | This program was written by Jan Klemkow. 81 | -------------------------------------------------------------------------------- /sockc.1: -------------------------------------------------------------------------------- 1 | .Dd September 21, 2016 2 | .Dt SOCKC 1 3 | .Os 4 | .Sh NAME 5 | .Nm sockc 6 | .Nd UCSPI SOCKS 5 proxy client 7 | .Sh SYNOPSIS 8 | .Nm tcpclient 9 | .Ar proxy-host 10 | .Ar proxy-port Nm sockc 11 | .Ar host 12 | .Ar port 13 | .Ar program 14 | .Op arguments 15 | .Sh DESCRIPTION 16 | The 17 | .Nm 18 | utility is a SOCKS 5 proxy handler for a UCSPI execchain. 19 | It communicates with tcpclient over pipes with filedescriptor 6 and 7. 20 | tcpclient connects to the SOCKS proxy and delegates the further protocol 21 | handling to 22 | .Nm sockc . 23 | sockc talks with the SOCKS proxy and initiates a tcp connection to 24 | .Ar host 25 | with 26 | .Ar port . 27 | After connection initiation 28 | .Nm 29 | executes 30 | .Ar program 31 | with 32 | .Ar arguments . 33 | .Sh ENVIRONMENT 34 | The following environment variables are defined in the UCPSI specification. 35 | By the usage of 36 | .Nm sockc 37 | there are two TCP connections. 38 | One from the 39 | .Nm tcpclient 40 | to the SOCKS proxy and an other from the SOCKS proxy to the server. 41 | The 42 | .Nm sockc 43 | programm tries to handel this in a transparant way for the program. 44 | All variables which are set from tcpclient to describe the TCP connection 45 | between program and proxy are overwritten with the description of the TCP 46 | connection between proxy and server. 47 | Additionally variables are set to describe the connection between program 48 | and proxy. 49 | .sp 50 | SOCKSLOCAL TCPLOCAL 51 | | | 52 | program <--> proxy <--> server 53 | | | 54 | SOCKSREMOTE TCPREMOTE 55 | .sp 56 | .Bl -tag -width Ds 57 | .It Ev PROTO 58 | Is set to TCP. 59 | .It Ev TCPLOCALHOST Ev TCPLOCALIP Ev TCPLOCALPORT 60 | These variables are set to the hostname, IP address and port number of the 61 | local SOCKS proxy address. 62 | .It Ev TCPREMOTEHOST Ev TCPREMOTEIP Ev TCPREMOTEPORT 63 | These variables are set to the hostname, IP address and port number of the 64 | server. 65 | .It Ev SOCKSLOCALHOST Ev SOCKSLOCALIP Ev SOCKSLOCALPORT 66 | These variables are set to the hostname, IP address and port number of the 67 | tcpclient local side. 68 | .It Ev SOCKSREMOTEHOST Ev SOCKSREMOTEIP Ev SOCKSREMOTEPORT 69 | These variables are set to the hostname, IP address and port number of the 70 | SOCKS proxy server side. 71 | .El 72 | .\".Sh EXIT STATUS 73 | .\".Sh EXAMPLES 74 | .Sh SEE ALSO 75 | .Xr httppc 1 , 76 | .Xr tcpclient 1 77 | .Sh STANDARDS 78 | RFC 1928, SOCKS version 5 79 | .Sh AUTHORS 80 | This program was written by 81 | .An Jan Klemkow Aq Mt j.klemkow@wemelug.de . 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/younix/ucspi.svg)](https://travis-ci.org/younix/ucspi) 2 | 3 | # ucspi-tools 4 | 5 | The **UNIX Client/Server Program Interface Tool Suite** is bunch of tools to 6 | handle UCSPI connections. 7 | 8 | ## sockc 9 | 10 | *sockc* is an ucpi SOCKS client. It handles the socks protocol transparently 11 | and establishes further connection through the corresponding SOCKS server. 12 | *sockc* supports SOCKS version 5. 13 | 14 | ## httppc 15 | 16 | *httppc* is an HTTP proxy client. It handles the HTTP protocol transparently 17 | and establishes a tunnel via the HTTP CONNECT method. 18 | 19 | ## tlsc 20 | 21 | *tlsc* establishes an TLS connection and builds an crypto interface between the 22 | network side and program side of the exec-chain. It depends on libtls from 23 | LibreSSL. 24 | 25 | ## tlss 26 | 27 | *tlss* accepts server side tls connections. It also uses libtls for encryption. 28 | 29 | ## sslc 30 | 31 | *sslc* is a legacy version of tlsc which just depends on plain old OpenSSL. It 32 | just contains rudiment certificate checks. 33 | 34 | ## httpc 35 | 36 | The HTTP client is just a stub for testing. It needs to be rewritten for 37 | productive use. 38 | 39 | ## examples 40 | 41 | Just open a tcp connection to google.de and make a fetch of the start page. 42 | 43 | ```shell 44 | tcpclient www.google.de 80 http www.google.de 45 | ``` 46 | 47 | Get the google index page over a local socks proxy: 48 | 49 | ```shell 50 | tcpclient 127.0.0.1 8080 socks www.google.de 80 ./http.sh www.google.de 51 | ``` 52 | 53 | If you have to use a socks proxy you could always use socks with the following 54 | alias: 55 | 56 | ```shellscript 57 | alias tcpclient="tcpclient 127.0.0.1 8080 socks" 58 | tcpclient www.google.de 80 59 | ``` 60 | 61 | ## TODO: 62 | * missing, but useful tools 63 | * smtp client 64 | * socks server 65 | * sockc 66 | * user authentication 67 | * server mode 68 | * udp 69 | * tlsc 70 | * Fingerprint accept 71 | * Revocation check 72 | * [OCSP](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol) 73 | * httpc 74 | * user authentication 75 | * support for different content encodings 76 | * keep-alive with queue of paths to download 77 | 78 | ## references 79 | * [ucspi](http://cr.yp.to/proto/ucspi.txt) 80 | * [ucspi-unix](http://untroubled.org/ucspi-unix/) 81 | * [ucspi-tcp](http://cr.yp.to/ucspi-tcp.html) 82 | * http server [fnord](http://www.fefe.de/fnord/) 83 | * [SOCKS Protocol Version 5](http://tools.ietf.org/html/rfc1928) 84 | * [RFC: Username/Password Authentication for SOCKS V5](https://tools.ietf.org/html/rfc1929) 85 | * [LibreSSL](http://www.libressl.org/) 86 | -------------------------------------------------------------------------------- /tlsc.1: -------------------------------------------------------------------------------- 1 | .Dd April 28, 2016 2 | .Dt TLSC 1 3 | .Os 4 | .Sh NAME 5 | .Nm tlsc 6 | .Nd UCSPI TLS Client 7 | .Sh SYNOPSIS 8 | .Nm tcpclient Ar host Ar port Nm tlsc 9 | .Op Fl hsCHTV 10 | .Op Fl F Ar fingerprint 11 | .Op Fl n Ar hostname 12 | .Op Fl c Ar cert_file 13 | .Op Fl k Ar key_file 14 | .Op Fl f Ar ca_file 15 | .Op Fl p Ar ca_path 16 | .Ar program 17 | .Op args... 18 | .Sh DESCRIPTION 19 | The 20 | .Nm 21 | utility is a TLS client to be used in an UCSPI exec-chains. 22 | .Nm 23 | initializes the TLS tunnel an checks the server side certificate. 24 | The certificate verification can be controlled by to following options. 25 | The system CA certificates are used to verify the current connection 26 | certificate by default. 27 | With 28 | .Fl f 29 | .Nm 30 | verifies the certificate chain based on one of the CA certificates stored in 31 | .Ar ca_file 32 | It is also possible to set a directory of CA certificate as trusted base with 33 | .Fl p . 34 | The options are as follows: 35 | .Bl -tag -width Ds 36 | .It Fl h 37 | Show usage text. 38 | .It Fl s 39 | Show the servers certificate information and exits. 40 | All certificate checks are deactivated in this case. 41 | .It Fl c Ar cert_file 42 | Uses first certificate in 43 | .Ar cert_file 44 | for client site authentication. 45 | .It Fl k Ar key_file 46 | Uses first private key in 47 | .Ar key_file 48 | for client site authentication. 49 | .It Fl f Ar cafile 50 | .Ar cafile 51 | is a file of CA certificates in PEM format. 52 | The file can contain several CA certificates. 53 | .It Fl p Ar capath 54 | .Ar capath 55 | is a directory containing CA certificates in PEM format. 56 | The files each contain one CA certificate. 57 | .It Fl n Ar hostname 58 | Uses 59 | .Ar hostname 60 | for hostname verification. 61 | .It Fl F Ar fingerprint 62 | sets the hash of the server certificate. 63 | If the hash does not match than the connection is about after handshake. 64 | (look at option 65 | .Fl s 66 | to get the fingerprint) 67 | .It Fl H 68 | Disables hostname verification. 69 | .It Fl C 70 | Disables certificate chain verification. 71 | .It Fl T 72 | Disables time verification. 73 | .It Fl V 74 | Disables certificate verification in general. 75 | .El 76 | .Sh ENVIRONMENT 77 | .Bl -tag -width Ds 78 | .It TLSC_FINGERPRINT 79 | sets fingerprint of the fingerprint. 80 | If the fingerprint does not match than the connection is about after the TLS 81 | handshake. 82 | (look at option 83 | .Fl s 84 | to get the fingerprint) 85 | .It TLSC_NO_VERIFICATION 86 | turns of all kind of certificate verification. 87 | .It TLSC_NO_HOST_VERIFICATION 88 | turns of verification of the certificate hostname. 89 | .It TLSC_NO_CERT_VERIFICATION 90 | turns of verification of the certificate chain. 91 | .It TLSC_NO_TIME_VERIFICATION 92 | turns of verification of the certificate validation time. 93 | .El 94 | .Sh EXIT STATUS 95 | .Ex -std 96 | .Sh SEE ALSO 97 | .Xr socks 1 , 98 | .Xr tcpclient 1 99 | .Sh AUTHORS 100 | .An -nosplit 101 | The 102 | .Nm 103 | program was written by 104 | .An Jan Klemkow Aq Mt j.klemkow@wemelug.de . 105 | -------------------------------------------------------------------------------- /httppc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "http_parser.h" 25 | 26 | #include "dprintf.c" 27 | 28 | #define READ_FD 6 29 | #define WRITE_FD 7 30 | 31 | void 32 | usage(void) 33 | { 34 | fprintf(stderr, "httppc host port prog\n"); 35 | exit(EXIT_FAILURE); 36 | } 37 | 38 | int 39 | main(int argc, char *argv[]) 40 | { 41 | int ch; 42 | char *basic = NULL; 43 | char buf[BUFSIZ]; 44 | 45 | while ((ch = getopt(argc, argv, "bh")) != -1) { 46 | switch (ch) { 47 | case 'b': 48 | if ((basic = strdup(optarg)) == NULL) 49 | err(EXIT_FAILURE, "strdup"); 50 | /* TODO: transform username and password into basic */ 51 | break; 52 | case 'h': 53 | default: 54 | usage(); 55 | /* NOTREACHED */ 56 | } 57 | } 58 | argc -= optind; 59 | argv += optind; 60 | 61 | if (argc < 3) 62 | usage(); 63 | 64 | char *host = *argv; argc--; argv++; 65 | char *port = *argv; argc--; argv++; 66 | char *prog = *argv; 67 | 68 | /* write HTTP request header */ 69 | dprintf(WRITE_FD, "CONNECT %s:%s HTTP/1.1\r\n", host, port); 70 | dprintf(WRITE_FD, "Host: %s:%s\r\n", host, port); 71 | if (basic != NULL) 72 | dprintf(WRITE_FD, "Proxy-Authorization: basic %s\r\n", basic); 73 | dprintf(WRITE_FD, "\r\n"); 74 | 75 | if (http_read_line_fd(READ_FD, buf, sizeof buf) == -1) 76 | errx(EXIT_FAILURE, "http_read_line_fd failed"); 77 | 78 | int code = http_parse_code(buf, sizeof buf); 79 | if (code != 200) 80 | errx(EXIT_FAILURE, "%d %s\n", code, http_reason_phrase(code)); 81 | 82 | do { 83 | if (http_read_line_fd(READ_FD, buf, sizeof buf) == -1) 84 | errx(EXIT_FAILURE, "http_read_line_fd failed"); 85 | } while (strcmp(buf, "\r\n") != 0); 86 | 87 | /* prepare environment variables */ 88 | /* remove all lost address information */ 89 | if (unsetenv("TCPLOCALIP") == -1) 90 | err(EXIT_FAILURE, "unsetenv"); 91 | if (unsetenv("TCPLOCALPORT") == -1) 92 | err(EXIT_FAILURE, "unsetenv"); 93 | if (unsetenv("TCPLOCALHOST") == -1) 94 | err(EXIT_FAILURE, "unsetenv"); 95 | if (unsetenv("TCPREMOTEIP") == -1) 96 | err(EXIT_FAILURE, "unsetenv"); 97 | 98 | /* set new address information */ 99 | if (setenv("TCPREMOTEHOST", host, 1) == -1) 100 | err(EXIT_FAILURE, "unsetenv"); 101 | if (setenv("TCPREMOTEPORT", port, 1) == -1) 102 | err(EXIT_FAILURE, "unsetenv"); 103 | 104 | /* start client program */ 105 | execvp(prog, argv); 106 | err(EXIT_FAILURE, "execvp: %s", prog); 107 | 108 | return EXIT_SUCCESS; 109 | } 110 | -------------------------------------------------------------------------------- /https.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define S_(x) #x 28 | #define S(x) S_(x) 29 | 30 | #define respond(str) do { \ 31 | fputs("HTTP/1.1 " str "\r\n\r\n", stdout); \ 32 | exit(EXIT_SUCCESS); \ 33 | } while (0) 34 | 35 | int 36 | main(void) 37 | { 38 | char buf[BUFSIZ]; 39 | char method[BUFSIZ+1]; 40 | char path[pathconf("/", _PC_PATH_MAX)+1]; 41 | char file[pathconf("/", _PC_PATH_MAX)+1]; 42 | char htdocs[pathconf("/", _PC_PATH_MAX)+1]; 43 | char resolved[pathconf("/", _PC_PATH_MAX)+1]; 44 | char host[sysconf(_SC_HOST_NAME_MAX)+1]; 45 | enum connection { KEEP_ALIVE, CLOSE } connection; 46 | struct stat sb; 47 | unsigned int major; 48 | unsigned int minor; 49 | size_t n; 50 | FILE *fh; 51 | 52 | strcpy(htdocs, "/var/www/htdocs"); 53 | 54 | #ifdef __OpenBSD__ 55 | if (unveil(htdocs, "r") == -1) 56 | respond("500 Internal Server Error"); 57 | if (pledge("stdio rpath", NULL) == -1) 58 | respond("500 Internal Server Error"); 59 | #endif 60 | next: 61 | connection = CLOSE; 62 | memset(host, 0, sizeof host); 63 | memset(&sb, 0, sizeof sb); 64 | memset(path, 0, sizeof path); 65 | memset(resolved, 0, sizeof resolved); 66 | 67 | /* parse method */ 68 | if (scanf("%" S(BUFSIZ) "s %" S(PATH_MAX) "s HTTP/%u.%u\r\n", 69 | method, path, &major, &minor) != 4) 70 | respond("400 Bad Request"); 71 | 72 | /* parse header fields */ 73 | while (fgets(buf, sizeof buf, stdin) != NULL && 74 | strcmp(buf, "\r\n") != 0) { 75 | if (strcmp(buf, "Connection: keep-alive\r\n") == 0) 76 | connection = KEEP_ALIVE; 77 | if (sscanf(buf, "Host: %" S(sysconf(_SC_HOST_NAME_MAX)+1) "s\r\n", host) == 1) 78 | continue; 79 | } 80 | 81 | /* check for default file */ 82 | if (strcmp(path, "/") == 0) 83 | strcpy(path, "index.html"); 84 | 85 | snprintf(file, sizeof file, "%s/%s/%s", htdocs, host, path); 86 | if (realpath(file, path) == NULL) { 87 | if (errno == ENOENT) 88 | respond("404 Not Found"); 89 | respond("400 Bad Request"); 90 | } 91 | /* check that realpath is inside htdocs */ 92 | if (strncmp(htdocs, file, strlen(htdocs)) != 0) 93 | respond("400 Bad Request"); 94 | 95 | if ((fh = fopen(path, "r")) == NULL) 96 | respond("400 Bad Request"); 97 | 98 | if (fstat(fileno(fh), &sb) == -1) 99 | respond("500 Internal Server Error"); 100 | 101 | /* response header */ 102 | fputs("HTTP/1.1 200 OK\r\n", stdout); 103 | printf("Content-Length: %lld\r\n", sb.st_size); 104 | fputs("\r\n", stdout); 105 | 106 | /* transfer body */ 107 | while ((n = fread(buf, sizeof *buf, sizeof buf, fh)) > 0) 108 | if (fwrite(buf, sizeof *buf, n, stdout) == 0) 109 | return EXIT_FAILURE; 110 | 111 | if (fclose(fh) == EOF) 112 | return EXIT_FAILURE; 113 | 114 | if (fflush(stdout) == EOF) 115 | err(EXIT_FAILURE, "fflush"); 116 | 117 | if (connection == KEEP_ALIVE) 118 | goto next; 119 | 120 | return EXIT_SUCCESS; 121 | } 122 | -------------------------------------------------------------------------------- /ucspi-tee.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #ifndef MAX 28 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 29 | #endif 30 | 31 | /* ucspi */ 32 | #define READ_FD 6 33 | #define WRITE_FD 7 34 | 35 | /* enviroment */ 36 | char **environ; 37 | 38 | static void 39 | usage(void) 40 | { 41 | fprintf(stderr, "ucspi-tee program [args]\n"); 42 | exit(EXIT_FAILURE); 43 | } 44 | 45 | int 46 | main(int argc, char *argv[], char *envp[]) 47 | { 48 | int ch; 49 | environ = envp; 50 | 51 | /* pipes to communicate with the front end */ 52 | int in = -1; 53 | int out = -1; 54 | 55 | /* pipes to communicate with the back end */ 56 | int sin = 6; 57 | int sout = 7; 58 | 59 | char *in_file = NULL; 60 | char *out_file = NULL; 61 | 62 | int in_fd = STDERR_FILENO; 63 | int out_fd = STDERR_FILENO; 64 | 65 | while ((ch = getopt(argc, argv, "f:p:Nh")) != -1) { 66 | switch (ch) { 67 | case 'i': 68 | if ((in_file = strdup(optarg)) == NULL) goto err; 69 | break; 70 | case 'o': 71 | if ((out_file = strdup(optarg)) == NULL) goto err; 72 | break; 73 | case 'h': 74 | default: 75 | usage(); 76 | /* NOTREACHED */ 77 | } 78 | } 79 | argc -= optind; 80 | argv += optind; 81 | 82 | if (argc < 1) 83 | usage(); 84 | 85 | /* fork front end program */ 86 | char *prog = argv[0]; 87 | # define PIPE_READ 0 88 | # define PIPE_WRITE 1 89 | int pi[2]; 90 | int po[2]; 91 | if (pipe(pi) < 0) goto err; 92 | if (pipe(po) < 0) goto err; 93 | switch (fork()) { 94 | case -1: 95 | err(EXIT_FAILURE, "fork"); 96 | case 0: /* start client program */ 97 | 98 | /* close unused pipe ends */ 99 | if (close(pi[PIPE_READ]) < 0) goto err; 100 | if (close(po[PIPE_WRITE]) < 0) goto err; 101 | 102 | /* 103 | * We have to move one descriptor cause po[] may 104 | * overlaps with descriptor 6 and 7. 105 | */ 106 | int po_read = 0; 107 | if ((po_read = dup(po[PIPE_READ])) < 0) goto err; 108 | if (close(po[PIPE_READ]) < 0) goto err; 109 | 110 | if (dup2(pi[PIPE_WRITE], WRITE_FD) < 0) goto err; 111 | if (dup2(po_read, READ_FD) < 0) goto err; 112 | 113 | if (close(pi[PIPE_WRITE]) < 0) goto err; 114 | if (close(po_read) < 0) goto err; 115 | execvpe(prog, argv, environ); 116 | err(EXIT_FAILURE, "execvpe: %s", prog); 117 | default: break; 118 | } 119 | 120 | /* close unused pipe ends */ 121 | if (close(pi[PIPE_WRITE]) < 0) goto err; 122 | if (close(po[PIPE_READ]) < 0) goto err; 123 | 124 | in = pi[PIPE_READ]; 125 | out = po[PIPE_WRITE]; 126 | 127 | for (;;) { 128 | char buf[BUFSIZ]; 129 | ssize_t n = 0; 130 | fd_set readfds; 131 | FD_ZERO(&readfds); 132 | FD_SET(in, &readfds); 133 | FD_SET(sin, &readfds); 134 | int max_fd = MAX(in, sin); 135 | if (select(max_fd+1, &readfds, NULL, NULL, NULL) == -1) 136 | goto err; 137 | 138 | if (FD_ISSET(sin, &readfds)) { 139 | if ((n = read(sin, buf, BUFSIZ)) < 0) goto err; 140 | if (n == 0) break; 141 | if (write(out, buf, n) < n) goto err; 142 | write(in_fd, "server:\n", 8); 143 | if (write(in_fd, buf, n) < n) goto err; 144 | } else if (FD_ISSET(in, &readfds)) { 145 | if ((n = read(in, buf, BUFSIZ)) < 0) goto err; 146 | if (n == 0) break; 147 | if (write(sout, buf, n) < n) goto err; 148 | write(in_fd, "client:\n", 8); 149 | if (write(out_fd, buf, n) < n) goto err; 150 | } 151 | } 152 | 153 | return EXIT_SUCCESS; 154 | err: 155 | perror("ucspi-tee"); 156 | return EXIT_FAILURE; 157 | } 158 | -------------------------------------------------------------------------------- /http_parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-2021 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "http_parser.h" 24 | 25 | /* string max compare */ 26 | #define strmcmp(a, b) \ 27 | strncmp((a), (b), strlen(b)) 28 | 29 | int 30 | http_read_line_fd(int fd, char *buf, size_t size) 31 | { 32 | size_t len = 0; 33 | char c; 34 | 35 | memset(buf, '\0', size); 36 | 37 | for (;;) { 38 | if (read(fd, &c, sizeof c) == -1) 39 | return -1; 40 | 41 | buf[len++] = c; 42 | 43 | if (len == size) 44 | return -1; 45 | 46 | if (strstr(buf, "\r\n")) 47 | break; 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | int 54 | http_read_line_fh(FILE *fh, char *buf, size_t size) 55 | { 56 | size_t len = 0; 57 | char c; 58 | 59 | memset(buf, '\0', size); 60 | 61 | for (;;) { 62 | if ((c = fgetc(fh)) == EOF) 63 | return -1; 64 | 65 | buf[len++] = c; 66 | 67 | if (len == size) 68 | return -1; 69 | 70 | if (strstr(buf, "\r\n")) 71 | break; 72 | } 73 | 74 | return 0; 75 | } 76 | 77 | int 78 | http_parse_code(char *buf, size_t size) 79 | { 80 | /* HTTP/1.1 200 OK */ 81 | int code; 82 | int old_errno = errno; 83 | 84 | if (strnlen(buf, size) < 12) 85 | return -1; 86 | 87 | errno = 0; 88 | code = strtol(buf + 9, NULL, 10); 89 | if (errno != 0) 90 | return -1; 91 | errno = old_errno; 92 | 93 | return code; 94 | } 95 | 96 | int 97 | http_parse_line(struct http_response *head, char *buf) 98 | { 99 | int old_errno = errno; 100 | 101 | if (strmcmp(buf, "Content-Length:") == 0) 102 | { 103 | errno = 0; 104 | head->content_length = strtol(buf + 16, NULL, 10); 105 | if (errno != 0) 106 | return -1; 107 | errno = old_errno; 108 | } 109 | else if (strmcmp(buf, "Content-Encoding:") == 0) 110 | { 111 | if (strstr(buf, "compress") != NULL) 112 | head->content_encoding = HTTP_CONT_ENC_COMPRESS; 113 | 114 | if (strstr(buf, "deflate") != NULL) 115 | head->content_encoding = HTTP_CONT_ENC_DEFLATE; 116 | 117 | if (strstr(buf, "gzip") != NULL) 118 | head->content_encoding = HTTP_CONT_ENC_GZIP; 119 | } 120 | else if (strmcmp(buf, "Transfer-Encoding:") == 0) 121 | { 122 | if (strstr(buf, "chunked") != NULL) 123 | head->transfer_encoding = HTTP_TRANS_ENC_CHUNKED; 124 | } 125 | 126 | return 0; 127 | } 128 | 129 | char * 130 | http_reason_phrase(int code) 131 | { 132 | switch (code) { 133 | case 100: return "Continue"; 134 | case 101: return "Switching Protocols"; 135 | case 200: return "OK"; 136 | case 201: return "Created"; 137 | case 202: return "Accepted"; 138 | case 203: return "Non-Authoritative Information"; 139 | case 204: return "No Content"; 140 | case 205: return "Reset Content"; 141 | case 206: return "Partial Content"; 142 | case 300: return "Multiple Choices"; 143 | case 301: return "Moved Permanently"; 144 | case 302: return "Found"; 145 | case 303: return "See Other"; 146 | case 304: return "Not Modified"; 147 | case 305: return "Use Proxy"; 148 | case 307: return "Temporary Redirect"; 149 | case 400: return "Bad Request"; 150 | case 401: return "Unauthorized"; 151 | case 402: return "Payment Required"; 152 | case 403: return "Forbidden"; 153 | case 404: return "Not Found"; 154 | case 405: return "Method Not Allowed"; 155 | case 406: return "Not Acceptable"; 156 | case 407: return "Proxy Authentication Required"; 157 | case 408: return "Request Timeout"; 158 | case 409: return "Conflict"; 159 | case 410: return "Gone"; 160 | case 411: return "Length Required"; 161 | case 412: return "Precondition Failed"; 162 | case 413: return "Payload Too Large"; 163 | case 414: return "URI Too Long"; 164 | case 415: return "Unsupported Media Type"; 165 | case 416: return "Range Not Satisfiable"; 166 | case 417: return "Expectation Failed"; 167 | case 426: return "Upgrade Required"; 168 | case 500: return "Internal Server Error"; 169 | case 501: return "Not Implemented"; 170 | case 502: return "Bad Gateway"; 171 | case 503: return "Service Unavailable"; 172 | case 504: return "Gateway Timeout"; 173 | case 505: return "HTTP Version Not Supported"; 174 | } 175 | return NULL; 176 | } 177 | -------------------------------------------------------------------------------- /httpc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2021 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "http_parser.h" 28 | 29 | #include "dprintf.c" 30 | 31 | #define READ_FD 6 32 | #define WRITE_FD 7 33 | 34 | static void 35 | usage(void) 36 | { 37 | fprintf(stderr, "httpc [-h] [-H HOST] [-o file] [URI]\n"); 38 | exit(EXIT_FAILURE); 39 | } 40 | 41 | void 42 | read_header(struct http_response *head, FILE *fh) 43 | { 44 | char buf[BUFSIZ]; 45 | 46 | memset(head, 0, sizeof *head); 47 | 48 | if (http_read_line_fh(fh , buf, sizeof buf) == -1) 49 | errx(EXIT_FAILURE, "http_read_line failed"); 50 | 51 | head->code = http_parse_code(buf, sizeof buf); 52 | if (head->code == -1) 53 | errx(EXIT_FAILURE, "unable to parse HTTP RETURN CODE"); 54 | 55 | if (head->code != 200) 56 | errx(EXIT_FAILURE, "http_parse_code...%d %s\n", head->code, 57 | http_reason_phrase(head->code)); 58 | 59 | /* read response header */ 60 | do { 61 | if (http_read_line_fh(fh, buf, sizeof buf) == -1) 62 | errx(EXIT_FAILURE, "http_read_line_fh failed"); 63 | 64 | if (http_parse_line(head, buf) == -1) 65 | errx(EXIT_FAILURE, "http_parse_line failed"); 66 | } while (strcmp(buf, "\r\n") != 0); 67 | } 68 | 69 | void 70 | read_content(size_t content_length, FILE *in, FILE *out) 71 | { 72 | char buf[BUFSIZ]; 73 | size_t size = sizeof buf; 74 | 75 | /* handle content */ 76 | for (;content_length > 0; content_length -= size) { 77 | if (content_length < size) 78 | size = content_length; 79 | 80 | if (fread(buf, size , 1, in) == 0) 81 | err(EXIT_FAILURE, "fread"); 82 | 83 | if (fwrite(buf, size, 1, out) == 0) 84 | err(EXIT_FAILURE, "fwrite"); 85 | } 86 | } 87 | 88 | void 89 | read_content_chunked(struct http_response *head, FILE *in, FILE *out) 90 | { 91 | size_t content_length = 0; 92 | char buf[BUFSIZ]; 93 | int old_errno = 0; 94 | 95 | do { 96 | /* read: chunk-size [chunk-ext] CRLF */ 97 | if (http_read_line_fh(in, buf, sizeof buf) == -1) 98 | errx(EXIT_FAILURE, "http_read_line failed"); 99 | 100 | /* parse: chunk-size */ 101 | old_errno = errno; errno = 0; 102 | content_length = strtol(buf, NULL, 16); 103 | if (errno != 0) 104 | errx(EXIT_FAILURE, "unreadable chunk size"); 105 | errno = old_errno; 106 | 107 | if (content_length > 0) { 108 | /* read: chunk-data */ 109 | read_content(content_length, in, out); 110 | 111 | /* read: CRLF */ 112 | if (http_read_line_fh(in , buf, sizeof buf) == -1) 113 | errx(EXIT_FAILURE, "http_read_line failed"); 114 | } else { 115 | /* read: trailer-part */ 116 | read_header(head, in); 117 | } 118 | } while (content_length > 0); 119 | } 120 | 121 | int 122 | main(int argc, char *argv[]) 123 | { 124 | int ch; 125 | int verbosity = 0; 126 | char *host = getenv("TCPREMOTEHOST"); 127 | char *file = NULL; 128 | char *uri = "/"; 129 | struct http_response head; 130 | FILE *fh = NULL; 131 | FILE *out = stdout; 132 | 133 | if (setvbuf(stdout, NULL, _IONBF, 0) != 0) 134 | err(EXIT_FAILURE, "setvbuf"); 135 | 136 | while ((ch = getopt(argc, argv, "H:o:gvh")) != -1) { 137 | switch (ch) { 138 | case 'H': 139 | if ((host = strdup(optarg)) == NULL) goto err; 140 | break; 141 | case 'o': 142 | if ((file = strdup(optarg)) == NULL) goto err; 143 | break; 144 | case 'v': 145 | verbosity++; 146 | break; 147 | case 'h': 148 | default: 149 | usage(); 150 | /* NOTREACHED */ 151 | } 152 | } 153 | argc -= optind; 154 | argv += optind; 155 | 156 | if (argc > 0) 157 | uri = argv[0]; 158 | 159 | /* write HTTP request header */ 160 | dprintf(WRITE_FD, "GET %s HTTP/1.1\r\n", uri); 161 | if (host != NULL) 162 | dprintf(WRITE_FD, "Host: %s\r\n", host); 163 | dprintf(WRITE_FD, "Accept-Encoding: gzip\r\n"); 164 | dprintf(WRITE_FD, "Connection: close\r\n"); 165 | dprintf(WRITE_FD, "\r\n"); 166 | 167 | if ((fh = fdopen(READ_FD, "r")) == NULL) 168 | err(EXIT_FAILURE, "fdopen"); 169 | 170 | if (file == NULL) 171 | file = basename(uri); 172 | 173 | read_header(&head, fh); 174 | 175 | if (head.content_encoding == HTTP_CONT_ENC_GZIP) { 176 | if ((out = popen("exec gunzip", "w")) == NULL) 177 | err(EXIT_FAILURE, "popen"); 178 | } 179 | 180 | if (head.transfer_encoding == HTTP_TRANS_ENC_CHUNKED) 181 | read_content_chunked(&head, fh, out); 182 | else 183 | read_content(head.content_length, fh, out); 184 | 185 | return EXIT_SUCCESS; 186 | err: 187 | perror("err: httpc:"); 188 | return EXIT_FAILURE; 189 | } 190 | -------------------------------------------------------------------------------- /sslc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #define SSL_NAME_LEN 256 28 | 29 | #ifndef MAX 30 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 31 | #endif 32 | 33 | /* enviroment */ 34 | char **environ; 35 | #if 0 36 | static bool 37 | check_hostname(SSL *ssl, const char *hostname) 38 | { 39 | X509 *cert; 40 | char buf[SSL_NAME_LEN]; 41 | if ((cert = SSL_get_peer_certificate(ssl)) == NULL) return false; 42 | 43 | X509_NAME_get_text_by_NID( 44 | X509_get_subject_name(cert), NID_commonName, buf, sizeof buf); 45 | 46 | buf[SSL_NAME_LEN - 1] = '\0'; 47 | fprintf(stderr, "cert hostname: '%s'\n", buf); 48 | 49 | return true; 50 | } 51 | #endif 52 | 53 | static void 54 | usage(void) 55 | { 56 | fprintf(stderr, "sslc [-hN] [-f CAFILE] [-p CAPATH] PROGRAM [ARGS]\n"); 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | int 61 | main(int argc, char *argv[], char *envp[]) 62 | { 63 | SSL *ssl = NULL; 64 | SSL_CTX *ssl_ctx = NULL; 65 | int ch, ret = 1; 66 | environ = envp; 67 | 68 | /* pipes to communicate with the front end */ 69 | int in = STDIN_FILENO; 70 | int out = STDOUT_FILENO; 71 | 72 | /* pipes to communicate with the back end */ 73 | int sin = 6; 74 | int sout = 7; 75 | 76 | char *ca_file = NULL; 77 | char *ca_path = NULL; 78 | int verify_mode = SSL_VERIFY_PEER; 79 | 80 | while ((ch = getopt(argc, argv, "f:p:Nh")) != -1) { 81 | switch (ch) { 82 | case 'f': 83 | if ((ca_file = strdup(optarg)) == NULL) goto err; 84 | break; 85 | case 'p': 86 | if ((ca_path = strdup(optarg)) == NULL) goto err; 87 | break; 88 | case 'N': 89 | verify_mode = SSL_VERIFY_NONE; 90 | break; 91 | case 'h': 92 | default: 93 | usage(); 94 | /* NOTREACHED */ 95 | } 96 | } 97 | argc -= optind; 98 | argv += optind; 99 | 100 | if (argc < 1) 101 | usage(); 102 | 103 | /* fork front end program */ 104 | char *prog = argv[0]; 105 | # define PIPE_READ 0 106 | # define PIPE_WRITE 1 107 | int pi[2]; 108 | int po[2]; 109 | if (pipe(pi) < 0) goto err; 110 | if (pipe(po) < 0) goto err; 111 | switch (fork()) { 112 | case 0: /* start client program */ 113 | if (close(pi[PIPE_READ]) < 0) goto err; 114 | if (close(po[PIPE_WRITE]) < 0) goto err; 115 | 116 | /* 117 | * We have to move one descriptor cause po[] may 118 | * overlaps with descriptor 6 and 7. 119 | */ 120 | int po_read = 0; 121 | if ((po_read = dup(po[PIPE_READ])) < 0) goto err; 122 | if (close(po[PIPE_READ]) < 0) goto err; 123 | 124 | if (dup2(pi[PIPE_WRITE], 7) < 0) goto err; 125 | if (dup2(po_read, 6) < 0) goto err; 126 | 127 | if (close(pi[PIPE_WRITE]) < 0) goto err; 128 | if (close(po_read) < 0) goto err; 129 | execve(prog, argv, environ); 130 | case -1: 131 | goto err; 132 | } 133 | 134 | in = pi[PIPE_READ]; 135 | out = po[PIPE_WRITE]; 136 | 137 | SSL_load_error_strings(); 138 | SSL_library_init(); 139 | if ((ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) goto err; 140 | 141 | /* prepare certificate checking */ 142 | if (ca_file != NULL || ca_path != NULL) 143 | SSL_CTX_load_verify_locations(ssl_ctx, ca_file, ca_path); 144 | else 145 | SSL_CTX_set_default_verify_paths(ssl_ctx); 146 | 147 | SSL_CTX_set_verify(ssl_ctx, verify_mode, NULL); 148 | 149 | if ((ssl = SSL_new(ssl_ctx)) == NULL) goto err; 150 | if (SSL_set_rfd(ssl, sin) == 0) goto err; 151 | if (SSL_set_wfd(ssl, sout) == 0) goto err; 152 | if ((ret = SSL_connect(ssl)) < 1) goto err; 153 | 154 | //check_hostname(ssl, "www.google.de"); 155 | 156 | for (;;) { 157 | int e; 158 | char buf[BUFSIZ]; 159 | ssize_t n = 0; 160 | fd_set readfds; 161 | FD_ZERO(&readfds); 162 | FD_SET(in, &readfds); 163 | FD_SET(sin, &readfds); 164 | int max_fd = MAX(in, sin); 165 | if (select(max_fd+1, &readfds, NULL, NULL, NULL) == -1) goto err; 166 | 167 | if (FD_ISSET(sin, &readfds)) { 168 | do { 169 | if ((n = SSL_read(ssl, buf, BUFSIZ)) <= 0) goto err; 170 | e = SSL_get_error(ssl, n); 171 | write(out, buf, n); 172 | } while (e == SSL_ERROR_WANT_READ || n == sizeof buf); 173 | } else if (FD_ISSET(in, &readfds)) { 174 | if ((n = read(in, buf, BUFSIZ)) <= 0) goto err; 175 | SSL_write(ssl, buf, n); 176 | } 177 | } 178 | 179 | return EXIT_SUCCESS; 180 | err: 181 | /* 182 | if (ret != 1) { 183 | int e = SSL_get_error(ssl, ret); 184 | fprintf(stderr, "sslc: ssl error: %d\n", e); 185 | ERR_print_errors_fp(stderr); 186 | } 187 | */ 188 | perror("sslc"); 189 | return EXIT_FAILURE; 190 | } 191 | -------------------------------------------------------------------------------- /ftpc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | FILE *in; 27 | FILE *out; 28 | bool debug = false; 29 | 30 | #define DEBUG(...) if (debug) warnx(__VA_ARGS__) 31 | 32 | int 33 | reply(char **str) 34 | { 35 | char line[BUFSIZ]; 36 | char replstr[5]; /* 123- */ 37 | int repl; /* reply code */ 38 | char ch; 39 | 40 | if (fgets(line, sizeof line, in) == NULL) 41 | err(EXIT_FAILURE, "fgets"); 42 | line[strcspn(line, "\r\n")] = '\0'; 43 | DEBUG("%s", line); 44 | 45 | if (sscanf(line, "%d%c", &repl, &ch) != 2) 46 | err(EXIT_FAILURE, "sscanf"); 47 | 48 | if (ch == ' ') 49 | goto out; 50 | 51 | if (ch != '-') 52 | err(EXIT_FAILURE, "protocol error: invaild reply string"); 53 | 54 | if (snprintf(replstr, sizeof replstr, "%d ", repl) != 5) 55 | err(EXIT_FAILURE, "protocol error: invaild reply string"); 56 | 57 | do { 58 | if (fgets(line, sizeof line, in) == NULL) 59 | err(EXIT_FAILURE, "fgets"); 60 | line[strcspn(line, "\r\n")] = '\0'; 61 | DEBUG("%s", line); 62 | } while (strncmp(line, replstr, 4)); 63 | out: 64 | if (str != NULL) 65 | *str = strdup(line); 66 | 67 | return repl; 68 | } 69 | 70 | int 71 | vcmd(char **replstr, const char *fmt, ...) 72 | { 73 | va_list ap; 74 | 75 | va_start(ap, fmt); 76 | vfprintf(out, fmt, ap); 77 | va_end(ap); 78 | 79 | fputs("\r\n", out); 80 | 81 | return reply(replstr); 82 | } 83 | 84 | FILE * 85 | pasv(const char *path, char dir) 86 | { 87 | FILE *fh; 88 | char cmd[BUFSIZ]; 89 | char *replstr; 90 | unsigned char addr[4]; 91 | unsigned char port[2]; 92 | unsigned short serv; 93 | 94 | if ((vcmd(&replstr, "PASV")) != 227) 95 | err(EXIT_FAILURE, "PASV"); 96 | DEBUG("pasv: %s", replstr); 97 | 98 | if (sscanf(replstr, "%*d %*[^(](%hhu,%hhu,%hhu,%hhu,%hhu,%hhu)", 99 | &addr[0], &addr[1], &addr[2], &addr[3], &port[0], &port[1]) != 6) 100 | err(EXIT_FAILURE, "parsing error: %s", replstr); 101 | free(replstr); 102 | 103 | serv = port[0] << 8 | port[1]; 104 | 105 | snprintf(cmd, sizeof cmd, "nc -N %hhu.%hhu.%hhu.%hhu %hu", 106 | addr[0], addr[1], addr[2], addr[3], serv); 107 | 108 | if (dir == '<' || dir == '>') 109 | snprintf(cmd + strlen(cmd), sizeof(cmd) - strlen(cmd), 110 | " %c '%s'", dir, path); 111 | DEBUG("cmd: %s", cmd); 112 | 113 | if ((fh = popen(cmd, "r")) == NULL) 114 | err(EXIT_FAILURE, "pasv: %s", cmd); 115 | 116 | return fh; 117 | } 118 | 119 | void 120 | usage(void) 121 | { 122 | fprintf(stderr, "ftpc [put|get|ls] path\n"); 123 | 124 | exit(EXIT_FAILURE); 125 | } 126 | 127 | int 128 | main(int argc, char *argv[]) 129 | { 130 | char *cmd = NULL; 131 | char *path = NULL; 132 | FILE *fh; 133 | char ch; 134 | 135 | while ((ch = getopt(argc, argv, "d")) != -1) { 136 | switch (ch) { 137 | case 'd': 138 | debug = true; 139 | break; 140 | default: 141 | usage(); 142 | } 143 | } 144 | argc -= optind; 145 | argv += optind; 146 | 147 | if (argc < 1) 148 | usage(); 149 | 150 | cmd = argv[0]; 151 | 152 | if (argc < 2) 153 | path = argv[1]; 154 | 155 | /* Open UCSPI fds as streams */ 156 | if ((in = fdopen(6, "r")) == NULL) 157 | err(EXIT_FAILURE, "fdopen"); 158 | if ((out = fdopen(7, "w")) == NULL) 159 | err(EXIT_FAILURE, "fdopen"); 160 | 161 | /* Set input and output streams line buffered */ 162 | setvbuf(in, NULL, _IOLBF, 0); 163 | setvbuf(out, NULL, _IOLBF, 0); 164 | 165 | if (reply(NULL) != 220) 166 | err(EXIT_FAILURE, "init error"); 167 | 168 | /* Login */ 169 | switch (vcmd(NULL, "USER %s", "anonymous")) { 170 | case 230: 171 | goto auth; 172 | case 331: 173 | goto pass; 174 | default: 175 | errx(EXIT_FAILURE, "user"); 176 | } 177 | 178 | pass: 179 | if (vcmd(NULL, "PASS %s", "mail@example.com") != 230) 180 | err(EXIT_FAILURE, "pass"); 181 | 182 | auth: /* Authenticated */ 183 | 184 | /* Set binary mode */ 185 | if (vcmd(NULL, "TYPE I") != 200) 186 | errx(EXIT_FAILURE, "TYPE"); 187 | 188 | 189 | if (strcmp(cmd, "put") == 0) { 190 | fh = pasv(path, '<'); 191 | 192 | if (vcmd(NULL, "STOR %s", path) != 150) 193 | errx(EXIT_FAILURE, "STOR"); 194 | } else if (strcmp(cmd, "get") == 0) { 195 | fh = pasv(path, '>'); 196 | 197 | if (vcmd(NULL, "RETR %s", path) != 150) 198 | errx(EXIT_FAILURE, "RETR"); 199 | } else if (strcmp(cmd, "ls") == 0) { 200 | char line[PATH_MAX]; 201 | 202 | fh = pasv(path, '|'); 203 | 204 | if (vcmd(NULL, "NLST") != 150) 205 | errx(EXIT_FAILURE, "RETR"); 206 | 207 | while (fgets(line, sizeof line, fh) != NULL) 208 | fputs(line, stdout); 209 | } else { 210 | errx(EXIT_FAILURE, "cmd %s is not implementet", cmd); 211 | } 212 | 213 | if (pclose(fh) != 0) 214 | err(EXIT_FAILURE, "pclose"); 215 | 216 | if (reply(NULL) != 226) 217 | errx(EXIT_FAILURE, "finish"); 218 | 219 | return EXIT_SUCCESS; 220 | } 221 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/ksh 2 | 3 | . ./tap-functions -u 4 | 5 | plan_tests 33 6 | 7 | # prepare 8 | expect_env() { 9 | file="$1" 10 | key="$2" 11 | expected="$3" 12 | output="$(grep "^$key=" "$file")" 13 | test "$key=$expected" = "$output" 14 | ok $? "Expected $key to be $expected (found $output)" 15 | } 16 | 17 | tmpdir=$(mktemp -d tests_XXXXXX) 18 | touch $tmpdir/tcps.log # prevent ENOENT in until grep loop later 19 | 20 | ######################################################################### 21 | # plain server to client communication # 22 | ######################################################################### 23 | ./tcps -d 127.0.0.1 0 /usr/bin/env 2>$tmpdir/tcps.log & 24 | 25 | # wait running server 26 | until grep -q '^listen: 127.0.0.1:' $tmpdir/tcps.log; do printf . && sleep 1; done 27 | SERVER_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcps.log | head -n 1) 28 | # start client 29 | ./tcpc -d 127.0.0.1 $SERVER_PORT ./read6.sh $tmpdir/env.txt 2>$tmpdir/tcpc.log 30 | CLIENT_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcpc.log | head -n 1) 31 | 32 | ok $? "plain connection server -> client" 33 | 34 | kill -9 %1 35 | 36 | # server side environment 37 | expect_env $tmpdir/env.txt "TCPREMOTEIP" "127.0.0.1" 38 | expect_env $tmpdir/env.txt "TCPREMOTEHOST" "localhost" 39 | expect_env $tmpdir/env.txt "TCPREMOTEPORT" "$CLIENT_PORT" 40 | expect_env $tmpdir/env.txt "TCPLOCALIP" "127.0.0.1" 41 | expect_env $tmpdir/env.txt "TCPLOCALHOST" "localhost" 42 | expect_env $tmpdir/env.txt "TCPLOCALPORT" "$SERVER_PORT" 43 | expect_env $tmpdir/env.txt "PROTO" "TCP" 44 | 45 | rm "$tmpdir/env.txt" 46 | 47 | ######################################################################### 48 | # plain client to server communication # 49 | ######################################################################### 50 | ./tcps -d 127.0.0.1 0 ./read0.sh "$tmpdir/env.txt" 2>$tmpdir/tcps.log & 51 | 52 | # wait running server 53 | until grep -q '^listen: 127.0.0.1:' $tmpdir/tcps.log; do :; done 54 | SERVER_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcps.log | head -n 1) 55 | 56 | ./tcpc -d 127.0.0.1 $SERVER_PORT ./write.sh 2>$tmpdir/tcpc.log 57 | CLIENT_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcpc.log | head -n 1) 58 | 59 | ok $? "plain connection client -> server" 60 | 61 | kill -9 %1 62 | 63 | # client side environment 64 | expect_env $tmpdir/env.txt "TCPREMOTEIP" "127.0.0.1" 65 | expect_env $tmpdir/env.txt "TCPREMOTEHOST" "localhost" 66 | expect_env $tmpdir/env.txt "TCPREMOTEPORT" "$SERVER_PORT" 67 | expect_env $tmpdir/env.txt "TCPLOCALIP" "127.0.0.1" 68 | expect_env $tmpdir/env.txt "TCPLOCALHOST" "localhost" 69 | expect_env $tmpdir/env.txt "TCPLOCALPORT" "$CLIENT_PORT" 70 | expect_env $tmpdir/env.txt "PROTO" "TCP" 71 | 72 | ######################################################################### 73 | # cert checks # 74 | ######################################################################### 75 | 76 | # These should have been created by 'make test' 77 | test -f ca.crt && test -f server.crt && test -f server.key && test -f client.crt && test -f client.key 78 | ok $? "Certificates and keys for running tests exist." 79 | # TODO: add more tests here 80 | #h=$(openssl x509 -outform der -in server.crt | sha256) 81 | #printf "SHA256:${h}\n" 82 | 83 | ######################################################################### 84 | # encrypted client to server communication # 85 | ######################################################################### 86 | ./tcps -d 127.0.0.1 0 \ 87 | ./tlss -f ca.crt -c server.crt -k server.key \ 88 | ./read0.sh "$tmpdir/env.txt" 2>$tmpdir/tcps.log & 89 | 90 | # wait running server 91 | until grep -q '^listen: 127.0.0.1:' $tmpdir/tcps.log; do printf . && sleep 1; done 92 | SERVER_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcps.log | head -n 1) 93 | 94 | ./tcpc -d 127.0.0.1 $SERVER_PORT \ 95 | ./tlsc -f ca.crt -c client.crt -k client.key \ 96 | ./write.sh 2>$tmpdir/tcpc.log 97 | 98 | CLIENT_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcpc.log | head -n 1) 99 | 100 | ok $? "tls connection client -> server" 101 | 102 | kill -9 %1 103 | 104 | # client side environment 105 | expect_env $tmpdir/env.txt "TCPREMOTEIP" "127.0.0.1" 106 | expect_env $tmpdir/env.txt "TCPREMOTEHOST" "localhost" 107 | expect_env $tmpdir/env.txt "TCPREMOTEPORT" "$SERVER_PORT" 108 | expect_env $tmpdir/env.txt "TCPLOCALIP" "127.0.0.1" 109 | expect_env $tmpdir/env.txt "TCPLOCALHOST" "localhost" 110 | expect_env $tmpdir/env.txt "TCPLOCALPORT" "$CLIENT_PORT" 111 | expect_env $tmpdir/env.txt "PROTO" "SSL" 112 | 113 | rm "$tmpdir/env.txt" 114 | 115 | ######################################################################### 116 | # encrypted server to client communication # 117 | ######################################################################### 118 | ./tcps -d 127.0.0.1 0 \ 119 | ./tlss -C -f ca.crt -c server.crt -k server.key \ 120 | /usr/bin/env 2>$tmpdir/tcps.log & 121 | 122 | # wait running server 123 | until grep -q '^listen: 127.0.0.1:' $tmpdir/tcps.log; do :; done 124 | SERVER_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcps.log | head -n 1) 125 | 126 | ./tcpc -d 127.0.0.1 $SERVER_PORT \ 127 | ./tlsc -f ca.crt -c client.crt -k client.key \ 128 | ./read6.sh "$tmpdir/env.txt" 2>$tmpdir/tcpc.log 129 | 130 | CLIENT_PORT=$(sed -ne 's/^listen: 127.0.0.1://p' $tmpdir/tcpc.log | head -n 1) 131 | 132 | ok $? "tls connection server -> client" 133 | 134 | kill -9 %1 135 | 136 | # server side environment 137 | expect_env $tmpdir/env.txt "TCPREMOTEIP" "127.0.0.1" 138 | expect_env $tmpdir/env.txt "TCPREMOTEHOST" "localhost" 139 | expect_env $tmpdir/env.txt "TCPREMOTEPORT" "$CLIENT_PORT" 140 | expect_env $tmpdir/env.txt "TCPLOCALIP" "127.0.0.1" 141 | expect_env $tmpdir/env.txt "TCPLOCALHOST" "localhost" 142 | expect_env $tmpdir/env.txt "TCPLOCALPORT" "$SERVER_PORT" 143 | expect_env $tmpdir/env.txt "PROTO" "SSL" 144 | 145 | rm "$tmpdir/env.txt" 146 | 147 | # clean up 148 | rm -rf $tmpdir 149 | 150 | # vim: set spell spelllang=en: 151 | -------------------------------------------------------------------------------- /tlss.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2018 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #ifndef MAX 28 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 29 | #endif 30 | 31 | /* ucspi */ 32 | #define READ_FD STDIN_FILENO 33 | #define WRITE_FD STDOUT_FILENO 34 | 35 | void 36 | usage(void) 37 | { 38 | fprintf(stderr, "tlss [-C] [-c cert_file] [-k key_file] [-p ca_path] " 39 | "[-f ca_file] prog [args]\n"); 40 | exit(EXIT_FAILURE); 41 | } 42 | 43 | int 44 | main(int argc, char *argv[]) 45 | { 46 | struct tls *tls = NULL; 47 | struct tls *cctx = NULL; 48 | struct tls_config *tls_config = NULL; 49 | int ch; 50 | 51 | #ifdef __OpenBSD__ 52 | if (pledge("stdio rpath proc exec", NULL) == -1) 53 | err(EXIT_FAILURE, "pledge"); 54 | #endif 55 | 56 | if (tls_init() == -1) 57 | err(EXIT_FAILURE, "tls_init"); 58 | 59 | if ((tls_config = tls_config_new()) == NULL) 60 | err(EXIT_FAILURE, "tls_config_new"); 61 | 62 | while ((ch = getopt(argc, argv, "Cc:k:p:f:r:")) != -1) { 63 | switch (ch) { 64 | case 'C': 65 | tls_config_verify_client(tls_config); 66 | break; 67 | case 'c': 68 | if (tls_config_set_cert_file(tls_config, optarg) == -1) 69 | err(EXIT_FAILURE, "tls_config_set_cert_file"); 70 | break; 71 | case 'k': 72 | if (tls_config_set_key_file(tls_config, optarg) == -1) 73 | err(EXIT_FAILURE, "tls_config_set_key_file"); 74 | break; 75 | case 'f': 76 | if (tls_config_set_ca_file(tls_config, optarg) == -1) 77 | err(EXIT_FAILURE, "tls_config_set_ca_file"); 78 | break; 79 | case 'p': 80 | if (tls_config_set_ca_path(tls_config, optarg) == -1) 81 | err(EXIT_FAILURE, "tls_config_set_ca_path"); 82 | break; 83 | case 'r': 84 | if (tls_config_set_crl_file(tls_config, optarg) == -1) 85 | err(EXIT_FAILURE, "tls_config_set_crl_file"); 86 | break; 87 | default: 88 | usage(); 89 | /* NOTREACHED */ 90 | } 91 | } 92 | argc -= optind; 93 | argv += optind; 94 | 95 | /* prepare libtls */ 96 | if ((tls = tls_server()) == NULL) 97 | err(EXIT_FAILURE, "tls_server"); 98 | 99 | if (tls_configure(tls, tls_config) == -1) 100 | errx(EXIT_FAILURE, "tls_configure: %s", tls_error(tls)); 101 | 102 | if (tls_accept_fds(tls, &cctx, STDIN_FILENO, STDOUT_FILENO) == -1) 103 | errx(EXIT_FAILURE, "tls_accept_fds: %s", tls_error(tls)); 104 | 105 | if (tls_handshake(cctx) == -1) 106 | errx(EXIT_FAILURE, "tls_handshake: %s", tls_error(cctx)); 107 | 108 | if (setenv("PROTO", "SSL", 1) == -1) 109 | err(EXIT_FAILURE, "setenv"); 110 | 111 | /* fork front end program */ 112 | char *prog = argv[0]; 113 | # define PIPE_READ 0 114 | # define PIPE_WRITE 1 115 | int pi[2]; /* input pipe */ 116 | int po[2]; /* output pipe */ 117 | if (pipe(pi) == -1) err(EXIT_FAILURE, "pipe"); 118 | if (pipe(po) == -1) err(EXIT_FAILURE, "pipe"); 119 | 120 | switch (fork()) { 121 | case -1: 122 | err(EXIT_FAILURE, "fork"); 123 | case 0: /* client program */ 124 | 125 | /* close non-using ends of pipes */ 126 | if (close(pi[PIPE_READ]) == -1) err(EXIT_FAILURE, "close"); 127 | if (close(po[PIPE_WRITE]) == -1) err(EXIT_FAILURE, "close"); 128 | 129 | /* move pipe end to ucspi defined fd numbers */ 130 | if (dup2(po[PIPE_READ], READ_FD) == -1) 131 | err(EXIT_FAILURE, "dup2"); 132 | if (dup2(pi[PIPE_WRITE], WRITE_FD) == -1) 133 | err(EXIT_FAILURE, "dup2"); 134 | 135 | if (close(po[PIPE_READ]) == -1) err(EXIT_FAILURE, "close"); 136 | if (close(pi[PIPE_WRITE]) == -1) err(EXIT_FAILURE, "close"); 137 | 138 | execv(prog, argv); 139 | err(EXIT_FAILURE, "execv: %s", prog); 140 | default: break; /* parent */ 141 | } 142 | 143 | #ifdef __OpenBSD__ 144 | if (pledge("stdio", NULL) == -1) 145 | err(EXIT_FAILURE, "pledge"); 146 | #endif 147 | 148 | /* close non-using ends of pipes */ 149 | if (close(pi[PIPE_WRITE]) == -1) err(EXIT_FAILURE, "close"); 150 | if (close(po[PIPE_READ]) == -1) err(EXIT_FAILURE, "close"); 151 | 152 | int in = pi[PIPE_READ]; 153 | int out = po[PIPE_WRITE]; 154 | 155 | /* communication loop */ 156 | for (;;) { 157 | int ret; 158 | char buf[BUFSIZ]; 159 | ssize_t sn = 0; 160 | fd_set readfds; 161 | FD_ZERO(&readfds); 162 | FD_SET(in, &readfds); 163 | FD_SET(READ_FD, &readfds); 164 | int max_fd = MAX(in, READ_FD); 165 | 166 | ret = select(max_fd+1, &readfds, NULL, NULL, NULL); 167 | if (ret == -1) 168 | err(EXIT_FAILURE, "select"); 169 | 170 | if (FD_ISSET(READ_FD, &readfds)) { 171 | do { 172 | again: 173 | sn = tls_read(cctx, buf, sizeof buf); 174 | if (sn == TLS_WANT_POLLIN || 175 | sn == TLS_WANT_POLLOUT) 176 | goto again; 177 | if (sn == -1) 178 | errx(EXIT_FAILURE, "tls_read: %s", 179 | tls_error(cctx)); 180 | if (sn == 0) 181 | return EXIT_SUCCESS; 182 | if (write(out, buf, sn) == -1) 183 | err(EXIT_FAILURE, "write"); 184 | } while (sn == sizeof buf); 185 | } else if (FD_ISSET(in, &readfds)) { 186 | if ((sn = read(in, buf, sizeof buf)) == -1) 187 | err(EXIT_FAILURE, "read"); 188 | if (sn == 0) /* EOF from inside */ 189 | goto out; 190 | /* XXX: unable to detect disconnect here */ 191 | if (tls_write(cctx, buf, sn) == -1) 192 | errx(EXIT_FAILURE, "tls_write: %s", 193 | tls_error(cctx)); 194 | } 195 | } 196 | 197 | out: 198 | tls_close(cctx); 199 | return EXIT_SUCCESS; 200 | } 201 | -------------------------------------------------------------------------------- /tcps.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2021 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #define MAXSOCK 10 31 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 32 | 33 | /* Set enviroment variable if value is not empty. */ 34 | #define set_env(name, value) \ 35 | if (strcmp((value), "") != 0) \ 36 | setenv((name), (value), 1) 37 | 38 | struct sock { 39 | int s; 40 | char ip[NI_MAXHOST]; 41 | char host[NI_MAXHOST]; 42 | char serv[NI_MAXSERV]; 43 | }; 44 | 45 | void 46 | start_prog(struct sock *sock, char *prog, char *argv[]) 47 | { 48 | int ecode = 0; 49 | int s = -1; 50 | struct sockaddr_storage addr; 51 | socklen_t len = sizeof addr; 52 | char ip[NI_MAXHOST] = ""; 53 | char host[NI_MAXHOST] = ""; 54 | char serv[NI_MAXSERV] = ""; 55 | 56 | if ((s = accept(sock->s, (struct sockaddr *)&addr, &len)) == -1) 57 | err(EXIT_FAILURE, "accept"); 58 | 59 | switch (fork()) { 60 | case -1: err(EXIT_FAILURE, "fork()"); /* error */ 61 | case 0: break; /* child */ 62 | default: /* parent */ 63 | if (close(s) == -1) 64 | err(EXIT_FAILURE, "close"); 65 | return; 66 | } 67 | 68 | /* get remote address information */ 69 | if ((ecode = getnameinfo((struct sockaddr *)&addr, len, ip, sizeof ip, 70 | serv, sizeof serv, NI_NUMERICHOST|NI_NUMERICSERV)) != 0) 71 | errx(EXIT_FAILURE, "getnameinfo: %s", gai_strerror(ecode)); 72 | 73 | if ((ecode = getnameinfo((struct sockaddr *)&addr, len, host, 74 | sizeof host, NULL, 0, 0)) != 0) 75 | errx(EXIT_FAILURE, "getnameinfo: %s", gai_strerror(ecode)); 76 | 77 | /* prepare enviroment */ 78 | set_env("TCPREMOTEIP" , ip); 79 | set_env("TCPREMOTEHOST", host); 80 | set_env("TCPREMOTEPORT", serv); 81 | 82 | set_env("TCPLOCALIP" , sock->ip); 83 | set_env("TCPLOCALHOST", sock->host); 84 | set_env("TCPLOCALPORT", sock->serv); 85 | set_env("PROTO", "TCP"); 86 | 87 | /* prepare file descriptors */ 88 | if (dup2(s, STDIN_FILENO) == -1) err(EXIT_FAILURE, "dup2"); 89 | if (dup2(s, STDOUT_FILENO) == -1) err(EXIT_FAILURE, "dup2"); 90 | if (close(s) == -1) err(EXIT_FAILURE, "close"); 91 | 92 | /* execute program */ 93 | execvp(prog, argv); 94 | err(EXIT_FAILURE, "execvp: %s", prog); 95 | } 96 | 97 | static void 98 | usage(void) 99 | { 100 | fprintf(stderr, "tcps [-46h] address port program [args]\n"); 101 | exit(EXIT_FAILURE); 102 | } 103 | 104 | int 105 | main(int argc, char *argv[]) 106 | { 107 | int ch; 108 | bool debug = false; 109 | 110 | struct addrinfo hints, *res, *res0; 111 | int error; 112 | int save_errno; 113 | struct sock sock[MAXSOCK]; 114 | int nsock; 115 | const char *cause = NULL; 116 | 117 | memset(&hints, 0, sizeof(hints)); 118 | hints.ai_family = PF_UNSPEC; 119 | hints.ai_socktype = SOCK_STREAM; 120 | hints.ai_flags = AI_PASSIVE; 121 | 122 | while ((ch = getopt(argc, argv, "46dh")) != -1) { 123 | switch (ch) { 124 | case '4': 125 | hints.ai_family = PF_INET; 126 | break; 127 | case '6': 128 | hints.ai_family = PF_INET6; 129 | break; 130 | case 'd': 131 | debug = true; 132 | break; 133 | case 'h': 134 | default: 135 | usage(); 136 | /* NOTREACHED */ 137 | } 138 | } 139 | argc -= optind; 140 | argv += optind; 141 | 142 | if (argc < 3) 143 | usage(); 144 | 145 | char *host = *argv; argv++; argc--; 146 | char *port = *argv; argv++; argc--; 147 | char *prog = *argv; 148 | 149 | if ((error = getaddrinfo(host, port, &hints, &res0)) != 0) 150 | errx(EXIT_FAILURE, "getaddrinfo: %s", gai_strerror(error)); 151 | 152 | nsock = 0; 153 | for (res = res0; res && nsock < MAXSOCK; res = res->ai_next) { 154 | sock[nsock].s = socket(res->ai_family, res->ai_socktype, 155 | res->ai_protocol); 156 | if (sock[nsock].s == -1) { 157 | cause = "socket"; 158 | continue; 159 | } 160 | 161 | if (bind(sock[nsock].s, res->ai_addr, res->ai_addrlen) == -1) { 162 | cause = "bind"; 163 | save_errno = errno; 164 | close(sock[nsock].s); 165 | errno = save_errno; 166 | continue; 167 | } 168 | 169 | if (listen(sock[nsock].s, 5) == -1) 170 | err(EXIT_FAILURE, "listen"); 171 | 172 | /* get really used address information */ 173 | if (getsockname(sock[nsock].s, res->ai_addr, &res->ai_addrlen) 174 | == -1) 175 | err(EXIT_FAILURE, "getsockname"); 176 | 177 | /* resolve local address information */ 178 | if ((error = getnameinfo(res->ai_addr, res->ai_addrlen, 179 | sock[nsock].ip , sizeof sock[nsock].ip, 180 | sock[nsock].serv, sizeof sock[nsock].serv, 181 | NI_NUMERICHOST|NI_NUMERICSERV)) != 0) 182 | errx(EXIT_FAILURE, "getnameinfo: %s", 183 | gai_strerror(error)); 184 | 185 | if ((error = getnameinfo(res->ai_addr, res->ai_addrlen, 186 | sock[nsock].host, sizeof sock[nsock].host, NULL, 0, 0)) !=0) 187 | errx(EXIT_FAILURE, "getnameinfo: %s", 188 | gai_strerror(error)); 189 | 190 | if (debug) 191 | fprintf(stderr, "listen: %s:%s\n", sock[nsock].ip, 192 | sock[nsock].serv); 193 | 194 | nsock++; 195 | } 196 | if (nsock == 0) 197 | err(EXIT_FAILURE, "%s", cause); 198 | freeaddrinfo(res0); 199 | 200 | /* select loop */ 201 | for (;;) { 202 | int max = 0; 203 | fd_set fdset; 204 | 205 | FD_ZERO(&fdset); 206 | 207 | for (int i = 0; i < nsock; i++) { 208 | FD_SET(sock[i].s, &fdset); 209 | max = MAX(max, sock[i].s); 210 | } 211 | 212 | if (select(max+1, &fdset, NULL, NULL, 0) == -1) 213 | err(EXIT_FAILURE, "select"); 214 | 215 | for (int i = 0; i < nsock; i++) 216 | if (FD_ISSET(sock[i].s, &fdset)) 217 | start_prog(&sock[i], prog, argv); 218 | } 219 | 220 | return EXIT_SUCCESS; 221 | } 222 | -------------------------------------------------------------------------------- /tcpc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2021 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /* Set enviroment variable if value is not empty. */ 33 | #define set_env(name, value) \ 34 | if (strcmp((value), "") != 0) \ 35 | setenv((name), (value), 1) 36 | 37 | int 38 | set_local_addr(int s, int family, char *local_addr_str, char *local_port_str) 39 | { 40 | struct sockaddr_storage ia; 41 | struct sockaddr_in *sa4 = (struct sockaddr_in *)&ia; 42 | struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&ia; 43 | socklen_t slen = 0; 44 | int local_port = 0; 45 | 46 | if (local_port_str != NULL) { 47 | local_port = strtol(local_port_str, NULL, 0); 48 | if (errno != 0) 49 | err(EXIT_FAILURE, "strtol"); 50 | } 51 | 52 | memset(&ia, 0, sizeof ia); 53 | ia.ss_family = family; 54 | 55 | switch (ia.ss_family) { 56 | case PF_INET: 57 | slen = sizeof *sa4; 58 | sa4->sin_port = htons(local_port); 59 | break; 60 | case PF_INET6: 61 | slen = sizeof *sa6; 62 | sa6->sin6_port = htons(local_port); 63 | break; 64 | default: 65 | errx(EXIT_FAILURE, "unknown protocol family: %d", family); 66 | } 67 | 68 | if (local_addr_str != NULL) { 69 | int ret = 0; 70 | ret = inet_pton(ia.ss_family, local_addr_str, 71 | ia.ss_family == PF_INET ? (void*)&sa4->sin_addr : 72 | (void*)&sa6->sin6_addr); 73 | if (ret == -1) 74 | err(EXIT_FAILURE, "inet_pton"); 75 | 76 | if (ret == 0) 77 | errx(EXIT_FAILURE, "unable to parse local ip address"); 78 | } 79 | 80 | return bind(s, (struct sockaddr *)&ia, slen); 81 | } 82 | 83 | void 84 | usage(void) 85 | { 86 | fprintf(stderr, "tcpclient [-4|6] [-Hh] host port program [args]\n"); 87 | exit(EXIT_FAILURE); 88 | } 89 | 90 | int 91 | main(int argc, char*argv[]) 92 | { 93 | struct addrinfo hints, *res, *res0; 94 | int error = 0; 95 | int save_errno; 96 | int s; 97 | int ch; 98 | char *argv0 = argv[0]; 99 | bool h_flag = true; 100 | bool debug = false; 101 | 102 | /* set some default values */ 103 | memset(&hints, 0, sizeof(hints)); 104 | hints.ai_family = PF_UNSPEC; 105 | hints.ai_socktype = SOCK_STREAM; 106 | 107 | char *local_addr_str = NULL; 108 | char *local_port_str = NULL; 109 | 110 | /* parsing command line arguments */ 111 | while ((ch = getopt(argc, argv, "46dHhi:p:")) != -1) { 112 | switch (ch) { 113 | case '4': 114 | if (hints.ai_family == AF_INET6) 115 | usage(); 116 | hints.ai_family = AF_INET; 117 | break; 118 | case '6': 119 | if (hints.ai_family == AF_INET) 120 | usage(); 121 | hints.ai_family = AF_INET6; 122 | break; 123 | case 'd': 124 | debug = true; 125 | break; 126 | case 'H': 127 | h_flag = false; 128 | break; 129 | case 'h': 130 | h_flag = true; 131 | break; 132 | case 'i': 133 | if ((local_addr_str = strdup(optarg)) == NULL) 134 | err(EXIT_FAILURE, "strdup"); 135 | break; 136 | case 'p': 137 | if ((local_port_str = strdup(optarg)) == NULL) 138 | err(EXIT_FAILURE, "strdup"); 139 | break; 140 | default: 141 | usage(); 142 | /* NOTREACHED */ 143 | } 144 | } 145 | argc -= optind; 146 | argv += optind; 147 | 148 | if (argc < 3) usage(); 149 | char *host = *argv; argv++; argc--; 150 | char *port = *argv; argv++; argc--; 151 | char *prog = *argv; 152 | 153 | error = getaddrinfo(host, port, &hints, &res0); 154 | if (error) 155 | errx(EXIT_FAILURE, "%s", gai_strerror(error)); 156 | s = -1; 157 | for (res = res0; res; res = res->ai_next) { 158 | s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 159 | if (s == -1) 160 | continue; 161 | 162 | /* set local address information */ 163 | if (local_addr_str != NULL || local_port_str != NULL) 164 | if (set_local_addr(s, res->ai_family, local_addr_str, 165 | local_port_str) == -1) { 166 | save_errno = errno; 167 | close(s); 168 | errno = save_errno; 169 | s = -1; 170 | continue; 171 | } 172 | 173 | if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { 174 | save_errno = errno; 175 | close(s); 176 | errno = save_errno; 177 | s = -1; 178 | continue; 179 | } 180 | break; /* okay we got one */ 181 | } 182 | if (s == -1) goto err; 183 | freeaddrinfo(res0); 184 | 185 | /* prepare environment variables */ 186 | char local_ip[NI_MAXHOST] = ""; 187 | char local_host[NI_MAXHOST] = ""; 188 | char local_port[NI_MAXSERV] = ""; 189 | char remote_ip[NI_MAXHOST] = ""; 190 | char remote_host[NI_MAXHOST] = ""; 191 | char remote_port[NI_MAXSERV] = ""; 192 | 193 | struct sockaddr_storage addr; 194 | socklen_t addrlen = sizeof addr; 195 | 196 | /* handle remote address information */ 197 | if (getpeername(s, (struct sockaddr*)&addr, &addrlen) == -1) 198 | err(EXIT_FAILURE, "getpeername"); 199 | 200 | if (h_flag) 201 | if ((error = getnameinfo((struct sockaddr *)&addr, addrlen, 202 | remote_host, sizeof remote_host, NULL, 0, 0)) != 0) 203 | errx(EXIT_FAILURE, "%s", gai_strerror(error)); 204 | 205 | if ((error = getnameinfo((struct sockaddr *)&addr, addrlen, remote_ip, 206 | sizeof remote_ip, remote_port, sizeof remote_port, 207 | NI_NUMERICHOST | NI_NUMERICSERV)) != 0) 208 | errx(EXIT_FAILURE, "%s", gai_strerror(error)); 209 | 210 | /* handle local address information */ 211 | if (getsockname(s, (struct sockaddr*)&addr, &addrlen) == -1) 212 | err(EXIT_FAILURE, "getsockname"); 213 | 214 | if (h_flag) 215 | if ((error = getnameinfo((struct sockaddr *)&addr, addrlen, 216 | local_host, sizeof local_host, NULL, 0, 0)) != 0) 217 | errx(EXIT_FAILURE, "%s", gai_strerror(error)); 218 | 219 | if ((error = getnameinfo((struct sockaddr *)&addr, addrlen, local_ip, 220 | sizeof local_ip, local_port, sizeof local_port, 221 | NI_NUMERICHOST | NI_NUMERICSERV)) != 0) 222 | errx(EXIT_FAILURE, "%s", gai_strerror(error)); 223 | 224 | if (debug) 225 | fprintf(stderr, "listen: %s:%s\n", local_ip, local_port); 226 | 227 | /* prepare enviroment */ 228 | set_env("TCPREMOTEIP" , remote_ip); 229 | set_env("TCPREMOTEPORT", remote_port); 230 | set_env("TCPREMOTEHOST", remote_host); 231 | set_env("TCPLOCALIP" , local_ip); 232 | set_env("TCPLOCALPORT" , local_port); 233 | set_env("TCPLOCALHOST" , local_host); 234 | set_env("PROTO", "TCP"); 235 | 236 | /* prepare file descriptors */ 237 | if (dup2(s, 6) == -1) err(EXIT_FAILURE, "dup2"); 238 | if (dup2(s, 7) == -1) err(EXIT_FAILURE, "dup2"); 239 | if (close(s) == -1) err(EXIT_FAILURE, "close"); 240 | 241 | execvp(prog, argv); 242 | err(EXIT_FAILURE, "execvp: %s", prog); 243 | err: 244 | perror(argv0); 245 | return EXIT_FAILURE; 246 | } 247 | -------------------------------------------------------------------------------- /sockc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2018 Jan Klemkow 3 | * Copyright (c) 2015 Stefan Thiemann 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifdef USE_LIBBSD 27 | # include 28 | #else 29 | # include 30 | #endif 31 | 32 | /* ucspi */ 33 | #define READ_FD 6 34 | #define WRITE_FD 7 35 | 36 | /* negotiation fields */ 37 | #define SOCKSv5 0x05 38 | #define RSV 0x00 39 | 40 | /* authentication methods */ 41 | #define NO_AUTH 0x00 42 | #define GSSAPI 0x01 /* not supported */ 43 | #define USRPASS 0x02 /* not supported */ 44 | #define NOT_ACC 0xFF 45 | 46 | /* address types */ 47 | #define IPv4 0x01 48 | #define BIND 0x03 49 | #define IPv6 0x04 50 | 51 | /* commands */ 52 | #define CMD_CONNECT 0x01 53 | #define CMD_BIND 0x02 /* not supported */ 54 | #define CMD_UDP_ASS 0x03 /* not supported */ 55 | 56 | struct nego { 57 | uint8_t ver; 58 | uint8_t nmethods; 59 | uint8_t method; 60 | }; 61 | 62 | struct nego_ans { 63 | uint8_t ver; 64 | uint8_t method; 65 | }; 66 | 67 | struct request { 68 | uint8_t ver; 69 | uint8_t cmd; 70 | uint8_t rsv; 71 | uint8_t atyp; 72 | union { 73 | uint8_t ip6[16]; 74 | uint8_t ip4[4]; 75 | struct { 76 | uint8_t len; 77 | char str[255]; 78 | } name; 79 | } addr; 80 | uint16_t port; 81 | }; 82 | 83 | static char * 84 | rep_mesg(uint8_t rep) 85 | { 86 | switch (rep) { 87 | case 0x00: return "succeeded"; 88 | case 0x01: return "general SOCKS server failure"; 89 | case 0x02: return "connection not allowed by ruleset"; 90 | case 0x03: return "Network unreachable"; 91 | case 0x04: return "Host unreachable"; 92 | case 0x05: return "Connection refused"; 93 | case 0x06: return "TTL expired"; 94 | case 0x07: return "Command not supported"; 95 | case 0x08: return "Address type not supported"; 96 | case 0x09: 97 | default: break; 98 | } 99 | 100 | return "unassigned"; 101 | } 102 | 103 | static void 104 | usage(void) 105 | { 106 | fputs("tcpclient proxyhost proxyport sockc host port prog [args...]\n", 107 | stderr); 108 | exit(EXIT_FAILURE); 109 | } 110 | 111 | int 112 | main(int argc, char *argv[]) 113 | { 114 | struct nego nego = {SOCKSv5, 1, NO_AUTH}; 115 | struct nego_ans nego_ans = {0, 0}; 116 | struct request request = {SOCKSv5, CMD_CONNECT, RSV, 0, {{0}}, 0}; 117 | struct request reply = {SOCKSv5, 0, RSV, 0, {{0}}, 0}; 118 | int ch, af = AF_INET6; 119 | 120 | while ((ch = getopt(argc, argv, "p:")) != -1) { 121 | switch (ch) { 122 | case 'h': 123 | default: 124 | usage(); 125 | /* NOTREACHED */ 126 | } 127 | } 128 | argc -= optind; 129 | argv += optind; 130 | 131 | if (argc < 3) usage(); 132 | char *host = *argv; argv++; argc--; 133 | char *port = *argv; argv++; argc--; 134 | char *prog = *argv; /* argv[0] == program name */ 135 | 136 | if (strlen(host) > 255) 137 | perror("sockc: hostname is too long"); 138 | 139 | /* parsing address argument */ 140 | if (inet_pton(AF_INET6, host, &request.addr.ip6) == 1) { 141 | af = AF_INET6; 142 | request.atyp = IPv6; 143 | } else if (inet_pton(AF_INET, host, &request.addr.ip4) == 1) { 144 | af = AF_INET; 145 | request.atyp = IPv4; 146 | } else if ((memcpy(request.addr.name.str, host, strlen(host))) != NULL){ 147 | af = AF_INET; 148 | request.atyp = BIND; 149 | } else { 150 | perror("could not handle address"); 151 | } 152 | 153 | /* parsing port number */ 154 | if ((request.port = htons((uint16_t)strtol(port, NULL, 0))) == 0) 155 | goto err; 156 | 157 | /* sockc: start negotiation */ 158 | if (write(WRITE_FD, &nego, sizeof nego) < 0) goto err; 159 | if (read(READ_FD, &nego_ans, sizeof nego_ans) < 0) goto err; 160 | 161 | if (nego_ans.method == NOT_ACC) 162 | perror("No acceptable authentication methods"); 163 | 164 | /* sockc: request for connection */ 165 | if (write(WRITE_FD, &request, 4) < 0) goto err; 166 | 167 | if (request.atyp == IPv6) { 168 | write(WRITE_FD, &request.addr.ip6, 16); 169 | } else if (request.atyp == IPv4) { 170 | write(WRITE_FD, &request.addr.ip4, 4); 171 | } else if (request.atyp == BIND) { 172 | request.addr.name.len = strlen(host); 173 | write(WRITE_FD, &request.addr.name.len, \ 174 | sizeof request.addr.name.len); 175 | write(WRITE_FD, request.addr.name.str, request.addr.name.len); 176 | } else { 177 | perror("this should not happen"); 178 | } 179 | 180 | /* sockc: send requested port */ 181 | if (write(WRITE_FD, &request.port, sizeof request.port) < 0) goto err; 182 | 183 | /* sockc: start analysing reply */ 184 | if (read(READ_FD, &reply, 4) < 0) goto err; 185 | 186 | if (reply.cmd != 0) 187 | perror(rep_mesg(reply.cmd)); 188 | 189 | /* read the bind address of the reply */ 190 | if (reply.atyp == IPv6) { 191 | af = AF_INET6; 192 | read(READ_FD, &reply.addr.ip6, sizeof reply.addr.ip6); 193 | } else if (reply.atyp == IPv4) { 194 | read(READ_FD, &reply.addr.ip4, sizeof reply.addr.ip4); 195 | } else if (reply.atyp == BIND) { 196 | read(READ_FD, &reply.addr.name.len, sizeof reply.addr.name.len); 197 | read(READ_FD, &reply.addr.name.str, reply.addr.name.len); 198 | } else { 199 | perror("sockc: unknown address type in reply"); 200 | } 201 | 202 | /* read the port of the replay */ 203 | read(READ_FD, &reply.port, sizeof reply.port); 204 | 205 | /* set ucspi enviroment variables */ 206 | char *tcp_remote_ip = getenv("TCPREMOTEIP"); 207 | char *tcp_remote_port = getenv("TCPREMOTEPORT"); 208 | char *tcp_remote_host = getenv("TCPREMOTEHOST"); 209 | char *tcp_local_ip = getenv("TCPLOCALIP"); 210 | char *tcp_local_port = getenv("TCPLOCALPORT"); 211 | char *tcp_local_host = getenv("TCPLOCALHOST"); 212 | 213 | #define SETENV(name, str) \ 214 | if ((str) != NULL && setenv((name), strdup(str), 1) == -1) \ 215 | perror("setenv") 216 | 217 | SETENV("SOCKSREMOTEIP" ,tcp_remote_ip); 218 | SETENV("SOCKSREMOTEPORT",tcp_remote_port); 219 | SETENV("SOCKSREMOTEHOST",tcp_remote_host); 220 | SETENV("SOCKSLOCALIP" ,tcp_local_ip); 221 | SETENV("SOCKSLOCALPORT" ,tcp_local_port); 222 | SETENV("SOCKSLOCALHOST" ,tcp_local_host); 223 | 224 | #undef SETENV 225 | 226 | char tmp[BUFSIZ]; 227 | if (request.atyp == IPv6 || request.atyp == IPv4) { 228 | inet_ntop(af, &request.addr, tmp, sizeof tmp); 229 | setenv("TCPREMOTEIP", tmp, 1); 230 | unsetenv("TCPREMOTEHOST"); 231 | } else { 232 | setenv("TCPREMOTEHOST", host, 1); 233 | unsetenv("TCPREMOTEIP"); 234 | } 235 | snprintf(tmp, sizeof tmp, "%d", ntohs(request.port)); 236 | setenv("TCPREMOTEPORT", tmp, 1); 237 | 238 | if (reply.atyp == IPv6 || reply.atyp == IPv4) { 239 | inet_ntop(af, &reply.addr, tmp, sizeof tmp); 240 | setenv("TCPLOCALIP", tmp, 1); 241 | unsetenv("TCPLOCALHOST"); 242 | } else { 243 | strlcpy(tmp, reply.addr.name.str, reply.addr.name.len); 244 | setenv("TCPLOCALHOST", tmp, 1); 245 | unsetenv("TCPLOCALIP"); 246 | } 247 | snprintf(tmp, sizeof tmp, "%d", ntohs(reply.port)); 248 | setenv("TCPLOCALPORT", tmp, 1); 249 | 250 | /* start client program */ 251 | execvp(prog, argv); 252 | err: 253 | perror("sockc"); 254 | return EXIT_FAILURE; 255 | } 256 | -------------------------------------------------------------------------------- /socks.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2021 Jan Klemkow 3 | * Copyright (c) 2015 Stefan Thiemann 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifdef USE_LIBBSD 27 | # include 28 | #else 29 | # include 30 | #endif 31 | 32 | /* enviroment */ 33 | extern char **environ; 34 | 35 | /* uscpi */ 36 | #define READ_FD STDIN_FILENO 37 | #define WRITE_FD STDOUT_FILENO 38 | 39 | /* Set enviroment variable if value is not empty. */ 40 | #define set_env(name, value) \ 41 | if (strcmp((value), "") != 0) \ 42 | setenv((name), (value), 1) 43 | 44 | /* negotiation fields */ 45 | #define SOCKSv5 0x05 46 | #define RSV 0x00 47 | 48 | /* authentication methods */ 49 | #define NO_AUTH 0x00 50 | #define GSSAPI 0x01 /* not supported */ 51 | #define USRPASS 0x02 /* not supported */ 52 | #define NOT_ACC 0xFF 53 | 54 | /* address types */ 55 | #define IPv4 0x01 56 | #define BIND 0x03 57 | #define IPv6 0x04 58 | 59 | /* commands */ 60 | #define CMD_CONNECT 0x01 61 | #define CMD_BIND 0x02 /* not supported */ 62 | #define CMD_UDP_ASS 0x03 /* not supported */ 63 | 64 | #define WRITE(fd, ptr, len) do { \ 65 | if (write((fd), (ptr), (len)) < (len)) \ 66 | goto err; \ 67 | } while(0); 68 | 69 | struct nego { 70 | uint8_t ver; 71 | uint8_t nmethods; 72 | uint8_t method; 73 | }; 74 | 75 | struct nego_ans { 76 | uint8_t ver; 77 | uint8_t method; 78 | }; 79 | 80 | struct request { 81 | uint8_t ver; 82 | uint8_t cmd; 83 | uint8_t rsv; 84 | uint8_t atyp; 85 | union { 86 | uint8_t ip6[16]; 87 | uint8_t ip4[4]; 88 | struct { 89 | uint8_t len; 90 | char str[255]; 91 | } name; 92 | } addr; 93 | uint16_t port; 94 | }; 95 | 96 | char * 97 | rep_mesg(uint8_t rep) 98 | { 99 | switch (rep) { 100 | case 0x00: return "succeeded"; 101 | case 0x01: return "general SOCKS server failure"; 102 | case 0x02: return "connection not allowed by ruleset"; 103 | case 0x03: return "Network unreachable"; 104 | case 0x04: return "Host unreachable"; 105 | case 0x05: return "Connection refused"; 106 | case 0x06: return "TTL expired"; 107 | case 0x07: return "Command not supported"; 108 | case 0x08: return "Address type not supported"; 109 | case 0x09: 110 | default: break; 111 | } 112 | 113 | return "unassigned"; 114 | } 115 | 116 | void 117 | usage(void) 118 | { 119 | fprintf(stderr, "tcpclient PROXY-HOST PROXY-PORT " 120 | "socks HOST PORT PROGRAM [ARGS...]\n"); 121 | exit(EXIT_FAILURE); 122 | } 123 | 124 | #if 0 125 | int 126 | read_request(struct request *request) 127 | { 128 | read(READ_FD, request, 4); 129 | 130 | if (request->atyp == IPv6) { 131 | read(READ_FD, &request->addr.ip6, 16); 132 | } else if (request->atyp == IPv4) { 133 | read(READ_FD, &request->addr.ip4, 4); 134 | } else if (request->atyp == BIND) { 135 | read(READ_FD, &request->addr.name.len, \ 136 | sizeof request->addr.name.len); 137 | read(READ_FD, request->addr.name.str, request->addr.name.len); 138 | } else { 139 | /* XXX: send err */ 140 | perror("this should not happen"); 141 | } 142 | 143 | /* socks: send requested port */ 144 | if (read(READ_FD, &request->port, sizeof request->port) == -1) goto err; 145 | 146 | return 0; 147 | err: 148 | return -1; 149 | } 150 | #endif 151 | 152 | int 153 | main(int argc, char *argv[], char *envp[]) 154 | { 155 | struct nego nego = {SOCKSv5, 1, NO_AUTH}; 156 | struct nego_ans nego_ans = {0, 0}; 157 | struct request request = {SOCKSv5, CMD_CONNECT, RSV, 0, {{0}}, 0}; 158 | struct request reply = {SOCKSv5, 0, RSV, 0, {{0}}, 0}; 159 | int ch, af = AF_INET6; 160 | 161 | environ = envp; 162 | while ((ch = getopt(argc, argv, "p:")) != -1) { 163 | switch (ch) { 164 | case 'h': 165 | default: 166 | usage(); 167 | /* NOTREACHED */ 168 | } 169 | } 170 | argc -= optind; 171 | argv += optind; 172 | 173 | if (argc < 3) usage(); 174 | char *host = *argv; argv++; argc--; 175 | char *port = *argv; argv++; argc--; 176 | char *prog = *argv; /* argv[0] == program name */ 177 | 178 | if (strlen(host) > 255) 179 | perror("socks: hostname is too long"); 180 | 181 | /* parsing address argument */ 182 | if (inet_pton(AF_INET6, host, &request.addr.ip6) == 1) { 183 | af = AF_INET6; 184 | request.atyp = IPv6; 185 | } else if (inet_pton(AF_INET, host, &request.addr.ip4) == 1) { 186 | af = AF_INET; 187 | request.atyp = IPv4; 188 | } else if ((memcpy(request.addr.name.str, host, strlen(host))) != NULL){ 189 | af = AF_INET; 190 | request.atyp = BIND; 191 | } else { 192 | perror("could not handle address"); 193 | } 194 | 195 | /* parsing port number */ 196 | if ((request.port = htons((uint16_t)strtol(port, NULL, 0))) == 0) goto err; 197 | 198 | /* socks: start negotiation */ 199 | if (write(WRITE_FD, &nego, sizeof nego) < 0) goto err; 200 | if (read(READ_FD, &nego_ans, sizeof nego_ans) < 0) goto err; 201 | 202 | if (nego_ans.method == NOT_ACC) 203 | perror("No acceptable authentication methods"); 204 | 205 | /* socks: request for connection */ 206 | if (write(WRITE_FD, &request, 4) < 0) goto err; 207 | 208 | if (request.atyp == IPv6) { 209 | write(WRITE_FD, &request.addr.ip6, 16); 210 | } else if (request.atyp == IPv4) { 211 | write(WRITE_FD, &request.addr.ip4, 4); 212 | } else if (request.atyp == BIND) { 213 | request.addr.name.len = strlen(host); 214 | write(WRITE_FD, &request.addr.name.len, \ 215 | sizeof request.addr.name.len); 216 | write(WRITE_FD, request.addr.name.str, request.addr.name.len); 217 | } else { 218 | perror("this should not happen"); 219 | } 220 | 221 | /* socks: send requested port */ 222 | if (write(WRITE_FD, &request.port, sizeof request.port) < 0) goto err; 223 | 224 | /* socks: start analysing reply */ 225 | if (read(READ_FD, &reply, 4) < 0) goto err; 226 | 227 | if (reply.cmd != 0) 228 | perror(rep_mesg(reply.cmd)); 229 | 230 | /* read the bind address of the reply */ 231 | if (reply.atyp == IPv6) { 232 | af = AF_INET6; 233 | read(READ_FD, &reply.addr.ip6, sizeof reply.addr.ip6); 234 | } else if (reply.atyp == IPv4) { 235 | read(READ_FD, &reply.addr.ip4, sizeof reply.addr.ip4); 236 | } else if (reply.atyp == BIND) { 237 | read(READ_FD, &reply.addr.name.len, sizeof reply.addr.name.len); 238 | read(READ_FD, &reply.addr.name.str, reply.addr.name.len); 239 | } else { 240 | perror("socks: unknown address type in reply"); 241 | } 242 | 243 | /* read the port of the replay */ 244 | read(READ_FD, &reply.port, sizeof reply.port); 245 | 246 | /* set ucspi enviroment variables */ 247 | char *tcp_remote_ip = getenv("TCPREMOTEIP"); 248 | char *tcp_remote_port = getenv("TCPREMOTEPORT"); 249 | char *tcp_remote_host = getenv("TCPREMOTEHOST"); 250 | char *tcp_local_ip = getenv("TCPLOCALIP"); 251 | char *tcp_local_port = getenv("TCPLOCALPORT"); 252 | char *tcp_local_host = getenv("TCPLOCALHOST"); 253 | 254 | set_env("SOCKSREMOTEIP", strdup(tcp_remote_ip)); 255 | set_env("SOCKSREMOTEPORT", strdup(tcp_remote_port)); 256 | set_env("SOCKSREMOTEHOST", strdup(tcp_remote_host)); 257 | set_env("SOCKSLOCALIP", strdup(tcp_local_ip)); 258 | set_env("SOCKSLOCALPORT", strdup(tcp_local_port)); 259 | set_env("SOCKSLOCALHOST", strdup(tcp_local_host)); 260 | 261 | char tmp[BUFSIZ]; 262 | if (request.atyp == IPv6 || request.atyp == IPv4) { 263 | inet_ntop(af, &request.addr, tmp, sizeof tmp); 264 | setenv("TCPREMOTEIP", tmp, 1); 265 | unsetenv("TCPREMOTEHOST"); 266 | } else { 267 | setenv("TCPREMOTEHOST", host, 1); 268 | unsetenv("TCPREMOTEIP"); 269 | } 270 | snprintf(tmp, sizeof tmp, "%d", ntohs(request.port)); 271 | setenv("TCPREMOTEPORT", tmp, 1); 272 | 273 | if (reply.atyp == IPv6 || reply.atyp == IPv4) { 274 | inet_ntop(af, &reply.addr, tmp, sizeof tmp); 275 | setenv("TCPLOCALIP", tmp, 1); 276 | unsetenv("TCPLOCALHOST"); 277 | } else { 278 | strlcpy(tmp, reply.addr.name.str, reply.addr.name.len); 279 | setenv("TCPLOCALHOST", tmp, 1); 280 | unsetenv("TCPLOCALIP"); 281 | } 282 | snprintf(tmp, sizeof tmp, "%d", ntohs(reply.port)); 283 | setenv("TCPLOCALPORT", tmp, 1); 284 | 285 | /* start client program */ 286 | execve(prog, argv, environ); 287 | err: 288 | perror("socks"); 289 | return EXIT_FAILURE; 290 | } 291 | -------------------------------------------------------------------------------- /tap-functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ksh 2 | 3 | # Copyright 2008 Patrick LeBoutillier 4 | # Copyright 2008 Aaron Kangas 5 | # Copyright 2015 Jan Klemkow 6 | # Licence: GPLv2 7 | # http://svn.solucorp.qc.ca/repos/solucorp/JTap/trunk/ 8 | 9 | _version='1.03' 10 | 11 | _plan_set=0 12 | _no_plan=0 13 | _skip_all=0 14 | _test_died=0 15 | _expected_tests=0 16 | _executed_tests=0 17 | _failed_tests=0 18 | TODO= 19 | 20 | usage() { 21 | cat <<'USAGE' 22 | tap-functions: A TAP-producing BASH library 23 | 24 | PLAN: 25 | plan_no_plan 26 | plan_skip_all [REASON] 27 | plan_tests NB_TESTS 28 | 29 | TEST: 30 | ok RESULT [NAME] 31 | okx COMMAND 32 | is RESULT EXPECTED [NAME] 33 | isnt RESULT EXPECTED [NAME] 34 | like RESULT PATTERN [NAME] 35 | unlike RESULT PATTERN [NAME] 36 | pass [NAME] 37 | fail [NAME] 38 | 39 | SKIP: 40 | skip [CONDITION] [REASON] [NB_TESTS=1] 41 | 42 | skip $feature_not_present "feature not present" 2 || { 43 | is $a "a" 44 | is $b "b" 45 | } 46 | 47 | TODO: 48 | Specify TODO mode by setting $TODO: 49 | TODO="not implemented yet" 50 | ok $result "some not implemented test" 51 | unset TODO 52 | 53 | OTHER: 54 | diag MSG 55 | 56 | EXAMPLE: 57 | #!/bin/bash 58 | 59 | . tap-functions 60 | 61 | plan_tests 7 62 | 63 | me=$USER 64 | is $USER $me "I am myself" 65 | like $HOME $me "My home is mine" 66 | like "`id`" $me "My id matches myself" 67 | 68 | /bin/ls $HOME 1>&2 69 | ok $? "/bin/ls $HOME" 70 | # Same thing using okx shortcut 71 | okx /bin/ls $HOME 72 | 73 | [[ "`id -u`" != "0" ]] 74 | i_am_not_root=$? 75 | skip $i_am_not_root "Must be root" || { 76 | okx ls /root 77 | } 78 | 79 | TODO="figure out how to become root..." 80 | okx [ "$HOME" == "/root" ] 81 | unset TODO 82 | USAGE 83 | exit 1 84 | } 85 | 86 | opt= 87 | set_u= 88 | while getopts "u" opt ; do 89 | case $opt in 90 | u) set_u=1 ;; 91 | *) usage ;; 92 | esac 93 | done 94 | shift $(( OPTIND - 1 )) 95 | # Don't allow uninitialized variables if requested 96 | [[ -n "$set_u" ]] && set -u 97 | unset opt set_u 98 | 99 | # Used to call _cleanup on shell exit 100 | trap _exit EXIT 101 | 102 | plan_no_plan() { 103 | (( _plan_set != 0 )) && "You tried to plan twice!" 104 | 105 | _plan_set=1 106 | _no_plan=1 107 | 108 | return 0 109 | } 110 | 111 | plan_skip_all() { 112 | local reason=${1:-''} 113 | 114 | (( _plan_set != 0 )) && _die "You tried to plan twice!" 115 | 116 | _print_plan 0 "Skip $reason" 117 | 118 | _skip_all=1 119 | _plan_set=1 120 | _exit 0 121 | 122 | return 0 123 | } 124 | 125 | plan_tests() { 126 | local tests=${1:?} 127 | 128 | (( _plan_set != 0 )) && _die "You tried to plan twice!" 129 | (( tests == 0 )) && _die "You said to run 0 tests! You've got to run something." 130 | 131 | _print_plan $tests 132 | _expected_tests=$tests 133 | _plan_set=1 134 | 135 | return $tests 136 | } 137 | 138 | _print_plan() { 139 | local tests=${1:?} 140 | local directive=${2:-''} 141 | 142 | echo -n "1..$tests" 143 | [[ -n "$directive" ]] && echo -n " # $directive" 144 | echo 145 | } 146 | 147 | pass() { 148 | local name=$1 149 | ok 0 "$name" 150 | } 151 | 152 | fail() { 153 | local name=$1 154 | ok 1 "$name" 155 | } 156 | 157 | # This is the workhorse method that actually 158 | # prints the tests result. 159 | ok() { 160 | local result=${1:?} 161 | local name=${2:-''} 162 | 163 | (( _plan_set == 0 )) && _die "You tried to run a test without a plan! Gotta have a plan." 164 | 165 | _executed_tests=$(( $_executed_tests + 1 )) 166 | 167 | if [[ -n "$name" ]] ; then 168 | if _matches "$name" "^[0-9]+$" ; then 169 | diag " You named your test '$name'. You shouldn't use numbers for your test names." 170 | diag " Very confusing." 171 | fi 172 | fi 173 | 174 | if (( result != 0 )) ; then 175 | echo -n "not " 176 | _failed_tests=$(( _failed_tests + 1 )) 177 | fi 178 | echo -n "ok $_executed_tests" 179 | 180 | if [[ -n "$name" ]] ; then 181 | local ename=$(echo "${name}" | sed -e 's/#/\\#/g') 182 | echo -n " - $ename" 183 | fi 184 | 185 | if [[ -n "$TODO" ]] ; then 186 | echo -n " # TODO $TODO" ; 187 | if (( result != 0 )) ; then 188 | _failed_tests=$(( _failed_tests - 1 )) 189 | fi 190 | fi 191 | 192 | echo 193 | if (( result != 0 )) ; then 194 | local file='tap-functions' 195 | local func= 196 | local line= 197 | 198 | local i=0 199 | local bt=$(caller $i) 200 | while _matches "$bt" "tap-functions" ; do 201 | i=$(( $i + 1 )) 202 | bt=$(caller $i) 203 | done 204 | local backtrace= 205 | eval $(caller $i | (read line func file ; echo "backtrace=\"$file:$func() at line $line.\"")) 206 | 207 | local t= 208 | [[ -n "$TODO" ]] && t="(TODO) " 209 | 210 | if [[ -n "$name" ]] ; then 211 | diag " Failed ${t}test '$name'" 212 | diag " in $backtrace" 213 | else 214 | diag " Failed ${t}test in $backtrace" 215 | fi 216 | fi 217 | 218 | return $result 219 | } 220 | 221 | okx() { 222 | local command="$@" 223 | 224 | local line= 225 | local command_status= 226 | diag "Output of '$command':" 227 | #exec 3>&1; # open by-pass on fd 3 228 | local ec=$({ $command; echo $?>3; } | while read line ; do 229 | diag "$line"; 230 | done; 3>&1) 231 | #ok ${PIPESTATUS[0]} "$command" 232 | ok $ec "$command" 233 | } 234 | 235 | _equals() { 236 | local result=${1:?} 237 | local expected=${2:?} 238 | 239 | if [[ "$result" == "$expected" ]] ; then 240 | return 0 241 | else 242 | return 1 243 | fi 244 | } 245 | 246 | _matches() { 247 | local result=${1:?} 248 | local pattern=${2:?} 249 | 250 | if [[ -z "$result" || -z "$pattern" ]] ; then 251 | return 1 252 | else 253 | echo "$result" | egrep "$pattern" >/dev/null 254 | fi 255 | } 256 | 257 | _is_diag() { 258 | local result=${1:?} 259 | local expected=${2:?} 260 | 261 | diag " got: '$result'" 262 | diag " expected: '$expected'" 263 | } 264 | 265 | is() { 266 | local result=${1:?} 267 | local expected=${2:?} 268 | local name=${3:-''} 269 | 270 | _equals "$result" "$expected" 271 | (( $? == 0 )) 272 | ok $? "$name" 273 | local r=$? 274 | (( r != 0 )) && _is_diag "$result" "$expected" 275 | return $r 276 | } 277 | 278 | isnt() { 279 | local result=${1:?} 280 | local expected=${2:?} 281 | local name=${3:-''} 282 | 283 | _equals "$result" "$expected" 284 | (( $? != 0 )) 285 | ok $? "$name" 286 | local r=$? 287 | (( r != 0 )) && _is_diag "$result" "$expected" 288 | return $r 289 | } 290 | 291 | like() { 292 | local result=${1:?} 293 | local pattern=${2:?} 294 | local name=${3:-''} 295 | 296 | _matches "$result" "$pattern" 297 | (( $? == 0 )) 298 | ok $? "$name" 299 | local r=$? 300 | (( r != 0 )) && diag " '$result' doesn't match '$pattern'" 301 | return $r 302 | } 303 | 304 | unlike() { 305 | local result=${1:?} 306 | local pattern=${2:?} 307 | local name=${3:-''} 308 | 309 | _matches "$result" "$pattern" 310 | (( $? != 0 )) 311 | ok $? "$name" 312 | local r=$? 313 | (( r != 0 )) && diag " '$result' matches '$pattern'" 314 | return $r 315 | } 316 | 317 | skip() { 318 | local condition=${1:?} 319 | local reason=${2:-''} 320 | local n=${3:-1} 321 | 322 | if (( condition == 0 )) ; then 323 | local i= 324 | for i in `jot $n 0`; do 325 | _executed_tests=$(( _executed_tests + 1 )) 326 | echo "ok $_executed_tests # skip: $reason" 327 | done 328 | return 0 329 | else 330 | return 331 | fi 332 | } 333 | 334 | diag() { 335 | local msg=${1:?} 336 | 337 | if [[ -n "$msg" ]] ; then 338 | echo "# $msg" 339 | fi 340 | 341 | return 1 342 | } 343 | 344 | _die() { 345 | local reason=${1:-''} 346 | 347 | echo "$reason" >&2 348 | _test_died=1 349 | _exit 255 350 | } 351 | 352 | BAIL_OUT() { 353 | local reason=${1:-''} 354 | 355 | echo "Bail out! $reason" >&2 356 | _exit 255 357 | } 358 | 359 | _cleanup() { 360 | local rc=0 361 | 362 | if (( _plan_set == 0 )) ; then 363 | diag "Looks like your test died before it could output anything." 364 | return $rc 365 | fi 366 | 367 | if (( _test_died != 0 )) ; then 368 | diag "Looks like your test died just after $_executed_tests." 369 | return $rc 370 | fi 371 | 372 | if (( _skip_all == 0 && _no_plan != 0 )) ; then 373 | _print_plan $_executed_tests 374 | fi 375 | 376 | local s= 377 | if (( _no_plan == 0 && _expected_tests < _executed_tests )) ; then 378 | s= ; (( _expected_tests > 1 )) && s=s 379 | local extra=$(( _executed_tests - _expected_tests )) 380 | diag "Looks like you planned $_expected_tests test$s but ran $extra extra." 381 | rc=-1 ; 382 | fi 383 | 384 | if (( _no_plan == 0 && _expected_tests > _executed_tests )) ; then 385 | s= ; (( _expected_tests > 1 )) && s=s 386 | diag "Looks like you planned $_expected_tests test$s but only ran $_executed_tests." 387 | fi 388 | 389 | if (( _failed_tests > 0 )) ; then 390 | s= ; (( _failed_tests > 1 )) && s=s 391 | diag "Looks like you failed $_failed_tests test$s of $_executed_tests." 392 | fi 393 | 394 | return $rc 395 | } 396 | 397 | _exit_status() { 398 | if (( _no_plan != 0 || _plan_set == 0 )) ; then 399 | return $_failed_tests 400 | fi 401 | 402 | if (( _expected_tests < _executed_tests )) ; then 403 | return $(( _executed_tests - _expected_tests )) 404 | fi 405 | 406 | return $(( _failed_tests + ( _expected_tests - _executed_tests ))) 407 | } 408 | 409 | _exit() { 410 | local rc=${1:-''} 411 | if [[ -z "$rc" ]] ; then 412 | _exit_status 413 | rc=$? 414 | fi 415 | 416 | _cleanup 417 | local alt_rc=$? 418 | (( alt_rc != 0 )) && rc=$alt_rc 419 | trap - EXIT 420 | exit $rc 421 | } 422 | -------------------------------------------------------------------------------- /tlsc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2018 Jan Klemkow 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #ifndef MAX 33 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 34 | #endif 35 | 36 | #define READ_FD 6 37 | #define WRITE_FD 7 38 | 39 | static void 40 | usage(void) 41 | { 42 | fprintf(stderr, 43 | "tlsc [-hCHVs] [-F fingerprint] [-c cert_file] [-f ca_file] " 44 | "[-p ca_path] program [args...]\n"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | int 49 | main(int argc, char *argv[]) 50 | { 51 | struct tls *tls = NULL; 52 | int ch; 53 | 54 | /* pipes to communicate with the front end */ 55 | int in = -1; 56 | int out = -1; 57 | 58 | bool show_cert_info = false; 59 | bool no_name_verification = false; 60 | bool no_cert_verification = false; 61 | bool no_time_verification = false; 62 | char *host = getenv("TCPREMOTEHOST"); 63 | char *fingerprint = getenv("TLSC_FINGERPRINT"); 64 | struct tls_config *tls_config; 65 | 66 | #ifdef __OpenBSD__ 67 | if (pledge("stdio rpath proc exec", NULL) == -1) 68 | err(EXIT_FAILURE, "pledge"); 69 | #endif 70 | 71 | if (getenv("TLSC_NO_VERIFICATION") != NULL) { 72 | no_name_verification = true; 73 | no_cert_verification = true; 74 | no_time_verification = true; 75 | } 76 | 77 | if (getenv("TLSC_NO_HOST_VERIFICATION") != NULL) 78 | no_name_verification = true; 79 | 80 | if (getenv("TLSC_NO_CERT_VERIFICATION") != NULL) 81 | no_cert_verification = true; 82 | 83 | if (getenv("TLSC_NO_TIME_VERIFICATION") != NULL) 84 | no_time_verification = true; 85 | 86 | if (tls_init() != 0) 87 | err(EXIT_FAILURE, "tls_init"); 88 | 89 | if ((tls_config = tls_config_new()) == NULL) 90 | err(EXIT_FAILURE, "tls_config_new"); 91 | 92 | char *str = NULL; 93 | if ((str = getenv("TLSC_CERT_FILE")) != NULL) 94 | if (tls_config_set_cert_file(tls_config, str) == -1) 95 | err(EXIT_FAILURE, "tls_config_set_cert_file"); 96 | 97 | if ((str = getenv("TLSC_KEY_FILE")) != NULL) 98 | if (tls_config_set_key_file(tls_config, str) == -1) 99 | err(EXIT_FAILURE, "tls_config_set_key_file"); 100 | 101 | if ((str = getenv("TLSC_CA_FILE")) != NULL) 102 | if (tls_config_set_ca_file(tls_config, str) == -1) 103 | err(EXIT_FAILURE, "tls_config_set_ca_file"); 104 | 105 | if ((str = getenv("TLSC_CA_PATH")) != NULL) 106 | if (tls_config_set_ca_path(tls_config, str) == -1) 107 | err(EXIT_FAILURE, "tls_config_set_ca_path"); 108 | 109 | while ((ch = getopt(argc, argv, "c:k:f:p:n:sF:HCTVh")) != -1) { 110 | switch (ch) { 111 | case 'c': 112 | if (tls_config_set_cert_file(tls_config, optarg) == -1) 113 | err(EXIT_FAILURE, "tls_config_set_cert_file"); 114 | break; 115 | case 'k': 116 | if (tls_config_set_key_file(tls_config, optarg) == -1) 117 | err(EXIT_FAILURE, "tls_config_set_key_file"); 118 | break; 119 | case 'f': 120 | if (tls_config_set_ca_file(tls_config, optarg) == -1) 121 | err(EXIT_FAILURE, "tls_config_set_ca_file"); 122 | break; 123 | case 'p': 124 | if (tls_config_set_ca_path(tls_config, optarg) == -1) 125 | err(EXIT_FAILURE, "tls_config_set_ca_path"); 126 | break; 127 | case 'n': 128 | if ((host = strdup(optarg)) == NULL) 129 | err(EXIT_FAILURE, "strdup"); 130 | break; 131 | case 's': 132 | show_cert_info = true; 133 | no_name_verification = true; 134 | no_cert_verification = true; 135 | no_time_verification = true; 136 | break; 137 | case 'F': 138 | if ((fingerprint = strdup(optarg)) == NULL) 139 | err(EXIT_FAILURE, "strdup"); 140 | break; 141 | case 'H': 142 | no_name_verification = true; 143 | break; 144 | case 'C': 145 | no_cert_verification = true; 146 | break; 147 | case 'T': 148 | no_time_verification = true; 149 | break; 150 | case 'V': 151 | no_name_verification = true; 152 | no_cert_verification = true; 153 | no_time_verification = true; 154 | break; 155 | case 'h': 156 | default: 157 | usage(); 158 | /* NOTREACHED */ 159 | } 160 | } 161 | argc -= optind; 162 | argv += optind; 163 | 164 | if (show_cert_info == false && argc < 1) 165 | usage(); 166 | 167 | /* verification settings */ 168 | if (no_cert_verification) 169 | tls_config_insecure_noverifycert(tls_config); 170 | 171 | if (no_name_verification) 172 | tls_config_insecure_noverifyname(tls_config); 173 | 174 | if (no_time_verification) 175 | tls_config_insecure_noverifytime(tls_config); 176 | 177 | /* libtls setup */ 178 | if ((tls = tls_client()) == NULL) 179 | err(EXIT_FAILURE, "tls_client"); 180 | 181 | if (tls_configure(tls, tls_config) != 0) 182 | errx(EXIT_FAILURE, "tls_configure: %s", tls_error(tls)); 183 | 184 | if (tls_connect_fds(tls, READ_FD, WRITE_FD, host) == -1) 185 | errx(EXIT_FAILURE, "tls_connect_fds: %s", tls_error(tls)); 186 | 187 | if (tls_handshake(tls) == -1) 188 | errx(EXIT_FAILURE, "tls_handshake: %s", tls_error(tls)); 189 | 190 | if (show_cert_info) { 191 | time_t notbefore = tls_peer_cert_notbefore(tls); 192 | time_t notafter = tls_peer_cert_notafter(tls); 193 | char notbefore_str[26]; 194 | char notafter_str[26]; 195 | 196 | printf("issuer: %s\n" 197 | "subject: %s\n" 198 | "start date: %s" 199 | "end date: %s" 200 | "fingerprint: %s\n", 201 | tls_peer_cert_issuer(tls), 202 | tls_peer_cert_subject(tls), 203 | ctime_r(¬before, notbefore_str), 204 | ctime_r(¬after, notafter_str), 205 | tls_peer_cert_hash(tls)); 206 | return EXIT_SUCCESS; 207 | } 208 | 209 | if (fingerprint != NULL && 210 | strcmp(tls_peer_cert_hash(tls), fingerprint) != 0) 211 | err(EXIT_FAILURE, "certificate hash has changed from %s to %s", 212 | fingerprint, tls_peer_cert_hash(tls)); 213 | 214 | /* overide PROTO to signal the application layer that the communication 215 | * channel is save. */ 216 | if (setenv("PROTO", "SSL", 1) == -1) 217 | err(EXIT_FAILURE, "setenv"); 218 | 219 | /* fork front end program */ 220 | char *prog = argv[0]; 221 | # define PIPE_READ 0 222 | # define PIPE_WRITE 1 223 | int pi[2]; /* input pipe */ 224 | int po[2]; /* output pipe */ 225 | if (pipe(pi) == -1) err(EXIT_FAILURE, "pipe"); 226 | if (pipe(po) == -1) err(EXIT_FAILURE, "pipe"); 227 | 228 | switch (fork()) { 229 | case -1: 230 | err(EXIT_FAILURE, "fork"); 231 | case 0: /* client program */ 232 | 233 | /* close non-using ends of pipes */ 234 | if (close(pi[PIPE_READ]) == -1) err(EXIT_FAILURE, "close"); 235 | if (close(po[PIPE_WRITE]) == -1) err(EXIT_FAILURE, "close"); 236 | 237 | /* 238 | * We have to move one descriptor cause po[] may 239 | * overlaps with descriptor 6 and 7. 240 | */ 241 | int po_read = 0; 242 | if ((po_read = dup(po[PIPE_READ])) == -1) 243 | err(EXIT_FAILURE, "dup"); 244 | if (close(po[PIPE_READ]) < 0) err(EXIT_FAILURE, "close"); 245 | 246 | if (dup2(pi[PIPE_WRITE], WRITE_FD) < 0) 247 | err(EXIT_FAILURE, "dup2"); 248 | if (dup2(po_read, READ_FD) < 0) err(EXIT_FAILURE, "dup2"); 249 | 250 | if (close(pi[PIPE_WRITE]) < 0) err(EXIT_FAILURE, "close"); 251 | if (close(po_read) < 0) err(EXIT_FAILURE, "close"); 252 | execvp(prog, argv); 253 | err(EXIT_FAILURE, "execvp: %s", prog); 254 | default: break; /* parent */ 255 | } 256 | 257 | #ifdef __OpenBSD__ 258 | if (pledge("stdio", NULL) == -1) 259 | err(EXIT_FAILURE, "pledge"); 260 | #endif 261 | 262 | /* close non-using ends of pipes */ 263 | if (close(pi[PIPE_WRITE]) == -1) err(EXIT_FAILURE, "close"); 264 | if (close(po[PIPE_READ]) == -1) err(EXIT_FAILURE, "close"); 265 | 266 | in = pi[PIPE_READ]; 267 | out = po[PIPE_WRITE]; 268 | 269 | /* communication loop */ 270 | for (;;) { 271 | int ret; 272 | char buf[BUFSIZ]; 273 | ssize_t sn = 0; 274 | fd_set readfds; 275 | FD_ZERO(&readfds); 276 | FD_SET(in, &readfds); 277 | FD_SET(READ_FD, &readfds); 278 | int max_fd = MAX(in, READ_FD); 279 | 280 | ret = select(max_fd+1, &readfds, NULL, NULL, NULL); 281 | if (ret == -1) 282 | err(EXIT_FAILURE, "select"); 283 | 284 | if (FD_ISSET(READ_FD, &readfds)) { 285 | do { 286 | again: 287 | sn = tls_read(tls, buf, sizeof buf); 288 | if (sn == TLS_WANT_POLLIN || 289 | sn == TLS_WANT_POLLOUT) 290 | goto again; 291 | if (sn == -1) 292 | errx(EXIT_FAILURE, "tls_read: %s", 293 | tls_error(tls)); 294 | if (sn == 0) 295 | return EXIT_SUCCESS; 296 | 297 | if (write(out, buf, sn) == -1) 298 | err(EXIT_FAILURE, "write()"); 299 | } while (sn == sizeof buf); 300 | } else if (FD_ISSET(in, &readfds)) { 301 | if ((sn = read(in, buf, sizeof buf)) == -1) 302 | err(EXIT_FAILURE, "read()"); 303 | if (sn == 0) 304 | goto out; 305 | if ((sn = tls_write(tls, buf, sn)) == -1) 306 | goto out; 307 | } 308 | } 309 | out: 310 | tls_close(tls); 311 | return EXIT_SUCCESS; 312 | } 313 | --------------------------------------------------------------------------------