├── README.md
├── bin
├── cable-id
├── cable-info
├── cable-ping
├── cable-send
├── gen-cable-username
├── gen-i2p-hostname
└── gen-tor-hostname
├── cable
├── cabled
├── cms
├── comm
├── crypto
├── fetch
├── loop
├── send
└── validate
├── conf
├── cabled
├── extensions.cnf
├── profile
└── rfc3526-modp-18.pem
├── doc
└── cable.txt
├── makefile
├── pkg
└── cables-x.y.ebuild
├── share
└── cable-info.desktop
├── src
├── daemon.c
├── daemon.h
├── hex2base32.c
├── mhdrop.c
├── process.c
├── process.h
├── server.c
├── server.h
├── service.c
├── service.h
├── su
│ └── dee
│ │ └── i2p
│ │ └── EepPriv.java
├── util.c
└── util.h
└── test
├── curl
├── logger
├── oakley-group-2.pem
└── simulate
/README.md:
--------------------------------------------------------------------------------
1 | # Secure Cables Communication
2 |
3 | Secure and anonymous communication using email-like addresses, pioneered in [Liberté Linux](http://dee.su/liberte).
4 | Cables communication is Liberté's pivotal component for enabling anyone to communicate safely and covertly in [hostile environments](http://dee.su/liberte-motivation).
5 |
6 | See the [project site](http://dee.su/cables) for further details, and read the [Wiki](https://github.com/mkdesu/cables/wiki) for implementation and [deployment](https://github.com/mkdesu/cables/wiki/deployment) details.
7 |
8 | License: [GPLv2](http://www.gnu.org/licenses/gpl-2.0.html).
9 |
--------------------------------------------------------------------------------
/bin/cable-id:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # Setup environment with needed environment vars
4 | . /etc/cable/profile
5 |
6 |
7 | error() {
8 | echo "cable-id: $@" 1>&2
9 | exit 1
10 | }
11 |
12 | torhost=${CABLE_TOR}/hidden_service/hostname
13 | i2phost=${CABLE_I2P}/eepsite/hostname
14 | username=${CABLE_CERTS}/certs/username
15 |
16 | undefined="undefined"
17 |
18 |
19 | case "$1" in
20 | user)
21 | if [ ! -e ${username} ]; then
22 | echo "${undefined}"
23 | else
24 | username=`cat ${username} | tr -cd a-z2-7`
25 | [ ${#username} = 32 ] || error "bad username"
26 | echo "${username}"
27 | fi
28 | ;;
29 |
30 | tor)
31 | if [ ! -e ${torhost} ]; then
32 | echo "${undefined}"
33 | else
34 | torhost=`cat ${torhost} | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
35 | [ ${#torhost} != 0 ] || error "bad Tor hostname"
36 | echo "${torhost}"
37 | fi
38 | ;;
39 |
40 | i2p)
41 | if [ ! -e ${i2phost} ]; then
42 | echo "${undefined}"
43 | else
44 | i2phost=`cat ${i2phost} | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
45 | [ ${#i2phost} != 0 ] || error "bad I2P hostname"
46 | echo "${i2phost}"
47 | fi
48 | ;;
49 |
50 | *)
51 | error "param: user|tor|i2p"
52 | ;;
53 | esac
54 |
--------------------------------------------------------------------------------
/bin/cable-info:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | cableid=cable-id
4 | undefined="undefined"
5 | title="Cables Communication Identity"
6 |
7 | username=`${cableid} user`
8 | torhost=`${cableid} tor | sed 's/\.onion$//'`
9 | i2phost=`${cableid} i2p | sed 's/\.b32\.i2p$//'`
10 |
11 |
12 | if [ "${username}" = "${undefined}" -o "${torhost}${i2phost}" = "${undefined}${undefined}" ]; then
13 | message="${title}
14 |
15 | Cables communication addresses have not been configured.
16 |
17 | When using cables in Liberté Linux, this is typically a result of disabled persistence: booting from an ISO image in a virtual machine, booting from an actual CD, or write-protecting the boot media.
18 |
19 | In order to enable persistence, install Liberté Linux to a writable media, such as a USB stick or an SD card."
20 |
21 | exec zenity --error --title="${title}" --text="${message}"
22 | fi
23 |
24 |
25 | splitre='s@\([[:alnum:]]\{4\}\)\([[:alnum:]]\{4\}\)\?@\1\2@g'
26 |
27 | username=`echo "${username}" | sed "${splitre}"`
28 | addrs=
29 |
30 | if [ "${torhost}" != "${undefined}" ]; then
31 | torhost=`echo "${torhost}" | sed "${splitre}"`.onion
32 | addrs="${addrs}
${username}@${torhost}"
33 | fi
34 | if [ "${i2phost}" != "${undefined}" ]; then
35 | i2phost=`echo "${i2phost}" | sed "${splitre}"`.b32.i2p
36 | addrs="${addrs}
${username}@${i2phost}"
37 | fi
38 |
39 |
40 | message="${title}
41 |
42 | You can use the following addresses for cables communication via Claws-Mail:
43 | ${addrs}
44 |
45 | Always check the username of incoming messages — its authenticity is guaranteed by the cables communication protocol. When manually reading addresses, keep in mind that only digits 2–7 are used, the rest are letters.
46 |
47 | You can set either address in Claws-Mail account settings. Upon startup, Claws-Mail will reset the account to Tor-based address if the configured address is not one of the above."
48 |
49 | exec zenity --info --title="${title}" --text="${message}"
50 |
--------------------------------------------------------------------------------
/bin/cable-ping:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # Setup environment with needed environment vars
4 | . /etc/cable/profile
5 |
6 |
7 | # Command-line parameters
8 | if [ $# != 1 ]; then
9 | echo "Format: $0 user@host"
10 | exit 1
11 | fi
12 |
13 |
14 | error() {
15 | echo "cable-ping: $@" 1>&2
16 | exit 1
17 | }
18 |
19 |
20 | emailregex="${CABLE_REGEX}"
21 | cableregex="LIBERTE CABLE [[:alnum:]._-]+"
22 | maxresp=128
23 | addr="$1"
24 |
25 | if ! echo x "${addr}" | egrep -q "^x ${emailregex}$"; then
26 | error "unsupported address"
27 | fi
28 |
29 |
30 | user=`echo "${addr}" | cut -d@ -f1`
31 | host=`echo "${addr}" | cut -d@ -f2`
32 | url=http://"${host}"/"${user}"/request/ver
33 |
34 |
35 | # Pipe eats curl's error status, if any
36 | resp=`curl -sSfg "${url}" 2>&1 | head -c ${maxresp} | tr -cd '[:alnum:][:blank:]:()/._-'`
37 |
38 | if echo x "${resp}" | grep -q "^x curl:"; then
39 | error "communication error: ${resp}"
40 | elif echo x "${resp}" | egrep -q "^x ${cableregex}$"; then
41 | echo "${resp}"
42 | else
43 | error "unexpected output: ${resp}"
44 | fi
45 |
--------------------------------------------------------------------------------
/bin/cable-send:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # /etc/sudoers should contain something like
4 | # anon ALL = (cable) NOPASSWD: /usr/libexec/cable/send ""
5 |
6 | . /etc/cable/profile
7 | exec sudo -n -u cable ${CABLE_HOME}/send "$@"
8 |
--------------------------------------------------------------------------------
/bin/gen-cable-username:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # This command can be run via "sudo -u anon"
4 | . /etc/cable/profile
5 |
6 |
7 | # NOTE: PSS signatures support in certificates
8 | # (but not in CMS) is available in OpenSSL 1.0.1
9 |
10 | sslconf=${CABLE_CONF}/extensions.cnf
11 | base32=${CABLE_HOME}/hex2base32
12 |
13 | certdir=${CABLE_CERTS}/certs
14 | keysdir=${CABLE_CERTS}/private
15 |
16 | v1certdir=${CABLE_CERTS}/../ssl/certs
17 | v1keysdir=${CABLE_CERTS}/../ssl/private
18 |
19 | certdirtmp=${certdir}.tmp
20 | keysdirtmp=${keysdir}.tmp
21 |
22 | # reqsubj not affected by locale
23 | rsabits=8192
24 | shabits=512
25 | reqsubj='/O=LIBERTE CABLE/CN=Anonymous'
26 | crtdays=18300
27 |
28 |
29 | # Fail if CA dir already exists
30 | if [ -e ${certdir} -a -e ${keysdir} ]; then
31 | echo ${certdir} and ${keysdir} already exist
32 | exit 1
33 | fi
34 |
35 |
36 | # Create temporary directories (erase previous ones, if exist)
37 | rm -rf ${certdirtmp} ${keysdirtmp} ${certdir} ${keysdir}
38 | mkdir ${certdirtmp} ${keysdirtmp}
39 |
40 |
41 | # Migrate v1 directories, if present
42 | if [ ! -e ${v1certdir}/username ]; then
43 |
44 | # Generate RSA key + X.509 self-signed root CA certificate
45 | openssl req -batch -new -utf8 -subj "${reqsubj}" \
46 | -newkey rsa:${rsabits} -nodes -keyout ${keysdirtmp}/root.pem \
47 | -x509 -days ${crtdays} -sha${shabits} -out ${certdirtmp}/ca.pem \
48 | -config "${sslconf}" -extensions root
49 |
50 |
51 | # Save 32-character Base32 username (root CA SHA-1 hash)
52 | fingerprint=`openssl x509 -in ${certdirtmp}/ca.pem -outform der | sha1sum | head -c 40`
53 | [ ${#fingerprint} = 40 ]
54 | ${base32} ${fingerprint} > ${certdirtmp}/username
55 |
56 |
57 | # Use the same key for ephemeral DH peer keys authentication
58 | ln -s root.pem ${keysdirtmp}/sign.pem
59 |
60 |
61 | # Generate X.509 verification certificate
62 | openssl req -batch -new -utf8 -subj "${reqsubj}" \
63 | -key ${keysdirtmp}/sign.pem | \
64 | openssl x509 -req -days ${crtdays} -sha${shabits} -out ${certdirtmp}/verify.pem \
65 | -CA ${certdirtmp}/ca.pem -CAkey ${keysdirtmp}/root.pem \
66 | -CAcreateserial -CAserial ${certdirtmp}/certs.srl \
67 | -extfile "${sslconf}" -extensions verify 2>/dev/null
68 |
69 | else
70 | echo "Migrating v1.0 certificates"
71 |
72 | # NTFS-3G denies 'getfattr -h' on symlinks for non-root user
73 | rsync -aHA ${v1certdir}/ ${certdirtmp}
74 | rsync -aHA ${v1keysdir}/ ${keysdirtmp}
75 |
76 | rm ${certdirtmp}/encrypt.pem ${keysdirtmp}/decrypt.pem
77 | fi
78 |
79 |
80 | # Sanity checks
81 | checks=`
82 | openssl verify -x509_strict -check_ss_sig -policy_check -purpose crlsign \
83 | -CAfile ${certdirtmp}/ca.pem -CApath /dev/null ${certdirtmp}/ca.pem
84 | openssl verify -x509_strict -check_ss_sig -policy_check -purpose smimesign \
85 | -CAfile ${certdirtmp}/ca.pem -CApath /dev/null ${certdirtmp}/verify.pem
86 | `
87 |
88 | test "${checks}" = "${certdirtmp}/ca.pem: OK
89 | ${certdirtmp}/verify.pem: OK"
90 |
91 |
92 | # Commit new directories
93 | chmod 710 ${certdirtmp} ${keysdirtmp}
94 | chmod 640 ${certdirtmp}/* ${keysdirtmp}/*
95 |
96 | mv -T ${keysdirtmp} ${keysdir}
97 | mv -T ${certdirtmp} ${certdir}
98 |
--------------------------------------------------------------------------------
/bin/gen-i2p-hostname:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # This command can be run via "sudo -u anon"
4 | . /etc/cable/profile
5 |
6 |
7 | i2pdir=${CABLE_I2P}/eepsite
8 | eeppriv=${CABLE_HOME}/eeppriv.jar
9 |
10 | i2pdirtmp=${i2pdir}.tmp
11 |
12 |
13 | # Fail if dir already exists
14 | if [ -e ${i2pdir} ]; then
15 | echo ${i2pdir} already exists
16 | exit 1
17 | fi
18 |
19 |
20 | # Create temporary directory (erase previous one, if exists)
21 | rm -rf ${i2pdirtmp}
22 | mkdir ${i2pdirtmp}
23 |
24 |
25 | # Generate eepPriv.dat and b32/b64 hostnames the same way that I2P does
26 | # JamVM's SecureRandom uses /dev/urandom
27 | java -jar ${eeppriv} ${i2pdirtmp}
28 |
29 |
30 | # Sanity checks
31 | if [ ! -s ${i2pdirtmp}/eepPriv.dat -o ! -s ${i2pdirtmp}/hostname -o ! -s ${i2pdirtmp}/hostname.b64 ]; then
32 | echo Failed to create eepsite key
33 | exit 1
34 | fi
35 |
36 |
37 | # Commit new directory
38 | mv -T ${i2pdirtmp} ${i2pdir}
39 |
--------------------------------------------------------------------------------
/bin/gen-tor-hostname:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # This command can be run via "sudo -u anon"
4 | . /etc/cable/profile
5 |
6 |
7 | # https://gitweb.torproject.org/tor.git/blob/HEAD:/doc/spec/tor-spec.txt
8 | # https://trac.torproject.org/projects/tor/wiki/TheOnionRouter/HiddenServiceNames
9 |
10 | tordir=${CABLE_TOR}/hidden_service
11 | base32=${CABLE_HOME}/hex2base32
12 |
13 | tordirtmp=${tordir}.tmp
14 |
15 |
16 | # Fail if dir already exists
17 | if [ -e ${tordir} ]; then
18 | echo ${tordir} already exists
19 | exit 1
20 | fi
21 |
22 |
23 | # Create temporary directory (erase previous one, if exists)
24 | rm -rf ${tordirtmp}
25 | mkdir ${tordirtmp}
26 |
27 |
28 | # Generate RSA-1024 with exponent 65537, as per spec
29 | # Use genrsa instead of genpkey, for result similarity
30 | openssl genrsa -f4 -out ${tordirtmp}/private_key 1024 2>/dev/null
31 |
32 |
33 | # Tor hashes ASN.1 RSAPublicKey instead of SubjectPublicKeyInfo,
34 | # which contains RSAPublicKey at offset 22; it then converts the
35 | # first half of SHA-1 hash to Base32
36 | hex=`openssl pkey -in ${tordirtmp}/private_key -pubout -outform der 2>/dev/null \
37 | | tail -c +23 | sha1sum | head -c 20`
38 | [ ${#hex} = 20 ]
39 | b32=`${base32} ${hex}`
40 |
41 | echo ${b32}.onion > ${tordirtmp}/hostname
42 |
43 |
44 | # Commit new directory
45 | mv -T ${tordirtmp} ${tordir}
46 |
--------------------------------------------------------------------------------
/cable/cabled:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # Setup restricted environment
4 | # (rely on init via sudo to set up TMPDIR)
5 | . /etc/cable/profile
6 |
7 |
8 | # Variables
9 | daemon=${CABLE_HOME}/daemon
10 | queue=${CABLE_QUEUES}/queue
11 | rqueue=${CABLE_QUEUES}/rqueue
12 |
13 | mktempre='tmp\.[A-Za-z0-9]{10}'
14 | newmsgidre='[0-9a-f]{40}\.new'
15 |
16 |
17 | # Remove stale temporary directories with old timestamps
18 | find ${queue} -mindepth 1 -maxdepth 1 -regextype posix-egrep \
19 | -regex "${queue}/${mktempre}" -mtime +1 -exec rm -rf {} \;
20 | find ${rqueue} -mindepth 1 -maxdepth 1 -regextype posix-egrep \
21 | -regex "${rqueue}/${newmsgidre}" -mtime +1 -exec rm -rf {} \;
22 |
23 |
24 | # Let fuse-vfs inotify emulation stabilize
25 | sleep 30
26 |
27 |
28 | # Launch the inotify-based daemon
29 | exec "${daemon}"
30 |
--------------------------------------------------------------------------------
/cable/cms:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # Encryption, decryption and verification of messages
4 | # NOTE: the "-e" switch above is important (script failure on error)!
5 |
6 | # WARNING: keys are provided as parameters to OpenSSL.
7 | # This script should be rewritten as a C program.
8 |
9 | # Variables
10 | cmd="$1"
11 | ssldir="$2"
12 | msgdir="$3"
13 |
14 | modp18=${CABLE_CONF}/rfc3526-modp-18.pem
15 | base32=${CABLE_HOME}/hex2base32
16 |
17 | sigalg=sha512
18 | encalg=aes256
19 | enchash=sha256
20 |
21 |
22 | # Command-line parameters
23 | if [ \( "${cmd}" != send -a "${cmd}" != peer -a "${cmd}" != recv \) \
24 | -o ! -d "${ssldir}" -o ! -d "${msgdir}" ]; then
25 | echo "Format: $0 send|peer|recv "
26 | exit 1
27 | fi
28 |
29 |
30 | trap '[ $? = 0 ] || error failed' 0
31 | error() {
32 | logger -t cms -p mail.err "$@ (${msgdir##*/})"
33 | trap - 0
34 | exit 1
35 | }
36 |
37 |
38 | # Verifying the ca/verify/encrypt certificates triple
39 | # * parses and extracts the first certificate from each file
40 | # * verifies the certificates chain
41 | # * generates username from ca.pem and checks it against the given one
42 | verify_certs() {
43 | local cvedir="$1" name count fingerprint username
44 |
45 | for name in ca verify; do
46 | openssl x509 -in "${cvedir}"/${name}.pem \
47 | -out "${cvedir}"/${name}.norm.pem
48 | mv -- "${cvedir}"/${name}.norm.pem "${cvedir}"/${name}.pem
49 |
50 | # Sanity check - OpenSSL should output exactly 1 certificate
51 | count=`grep -c -- '^-----BEGIN ' "${cvedir}"/${name}.pem`
52 | [ "${count}" = 1 ]
53 | done
54 |
55 | # certificates chain verification is also implicitly done later,
56 | # so not strictly necessary
57 | openssl verify -x509_strict -policy_check -purpose crlsign -check_ss_sig \
58 | -CApath /dev/null -CAfile "${cvedir}"/ca.pem "${cvedir}"/ca.pem \
59 | > "${cvedir}"/certs.vfy 2>&1
60 | openssl verify -x509_strict -policy_check -purpose smimesign \
61 | -CApath /dev/null -CAfile "${cvedir}"/ca.pem "${cvedir}"/verify.pem \
62 | >> "${cvedir}"/certs.vfy 2>&1
63 |
64 | if ! cmp -s -- "${cvedir}"/certs.vfy - <
100 | #
101 | # out: derive.pem, rpeer.sig[atomic]
102 | #
103 | # <--- rpeer.sig
104 | #
105 | #
106 | # in: message, username, {ca,verify}.pem, rpeer.sig
107 | # out: speer.sig[atomic], message.enc[atomic], {send,recv,ack}.mac
108 | #
109 | # ---> speer.sig, message.enc, send.mac
110 | #
111 | #
112 | # in: message.enc, username, send.mac, {ca,verify,derive}.pem, speer.sig
113 | # out: message, {recv,ack}.mac
114 | #
115 | # <--- recv.mac
116 | #
117 | #
118 | #
119 | # ---> ack.mac
120 | case ${cmd} in
121 | peer)
122 | rm -f -- "${msgdir}"/derive.pem "${msgdir}"/rpeer.der "${msgdir}"/rpeer.sig \
123 | "${msgdir}"/rpeer.sig.tmp
124 |
125 | # generate ephemeral peer key
126 | openssl genpkey -paramfile "${modp18}" \
127 | -out "${msgdir}"/derive.pem
128 |
129 | # extract and sign ephemeral public peer key
130 | openssl pkey -pubout -outform der \
131 | -in "${msgdir}"/derive.pem \
132 | -out "${msgdir}"/rpeer.der
133 |
134 | openssl cms -sign -noattr -binary -md ${sigalg} -nodetach -nocerts -outform pem \
135 | -signer "${certdir}"/verify.pem \
136 | -inkey "${keysdir}"/sign.pem \
137 | -in "${msgdir}"/rpeer.der \
138 | -out "${msgdir}"/rpeer.sig.tmp
139 | mv -- "${msgdir}"/rpeer.sig.tmp "${msgdir}"/rpeer.sig
140 |
141 | rm -- "${msgdir}"/rpeer.der
142 | ;;
143 |
144 |
145 | send)
146 | rm -f -- "${msgdir}"/derive.pem "${msgdir}"/speer.der "${msgdir}"/rpeer.der \
147 | "${msgdir}"/speer.sig "${msgdir}"/shared.key "${msgdir}"/message.enc \
148 | "${msgdir}"/send.mac "${msgdir}"/recv.mac "${msgdir}"/ack.mac \
149 | "${msgdir}"/speer.sig.tmp "${msgdir}"/message.enc.tmp
150 |
151 | # verify certificates chain
152 | verify_certs "${msgdir}"
153 |
154 | # verify and extract signed recipient's ephemeral public peer key
155 | openssl cms -verify \
156 | -x509_strict -policy_check -purpose smimesign -check_ss_sig -inform pem \
157 | -CAfile "${msgdir}"/ca.pem -CApath /dev/null \
158 | -certfile "${msgdir}"/verify.pem \
159 | -in "${msgdir}"/rpeer.sig \
160 | -out "${msgdir}"/rpeer.der
161 |
162 | # generate ephemeral peer key
163 | openssl genpkey -paramfile "${modp18}" \
164 | -out "${msgdir}"/derive.pem
165 |
166 | # derive (large) shared secret
167 | openssl pkeyutl -derive -peerform der \
168 | -inkey "${msgdir}"/derive.pem \
169 | -peerkey "${msgdir}"/rpeer.der \
170 | -out "${msgdir}"/shared.key
171 |
172 | # extract and sign ephemeral public peer key
173 | openssl pkey -pubout -outform der \
174 | -in "${msgdir}"/derive.pem \
175 | -out "${msgdir}"/speer.der
176 |
177 | openssl cms -sign -noattr -binary -md ${sigalg} -nodetach -nocerts -outform pem \
178 | -signer "${certdir}"/verify.pem \
179 | -inkey "${keysdir}"/sign.pem \
180 | -in "${msgdir}"/speer.der \
181 | -out "${msgdir}"/speer.sig.tmp
182 | mv -- "${msgdir}"/speer.sig.tmp "${msgdir}"/speer.sig
183 |
184 |
185 | # deterministically derive encryption and MAC keys from shared secret
186 | enckey=`openssl dgst -mac hmac -${enchash} -macopt key:encrypt "${msgdir}"/shared.key | cut -d' ' -f2`
187 | sendkey=`openssl dgst -mac hmac -${sigalg} -macopt key:send "${msgdir}"/shared.key | cut -d' ' -f2`
188 | recvkey=`openssl dgst -mac hmac -${sigalg} -macopt key:recv "${msgdir}"/shared.key | cut -d' ' -f2`
189 | [ ${#enckey} = 64 -a ${#sendkey} = 128 -a ${#recvkey} = 128 ]
190 |
191 | # compute message send/recv/ack MACs using derived MAC keys
192 | openssl dgst -mac hmac -${sigalg} \
193 | -macopt hexkey:${sendkey} \
194 | -out "${msgdir}"/send.mac \
195 | "${msgdir}"/message
196 |
197 | openssl dgst -mac hmac -${sigalg} \
198 | -macopt hexkey:${recvkey} \
199 | -out "${msgdir}"/recv.mac \
200 | "${msgdir}"/message
201 |
202 | openssl dgst -mac hmac -${sigalg} -macopt key:ack \
203 | -out "${msgdir}"/ack.mac \
204 | "${msgdir}"/shared.key
205 |
206 | sed -i 's/^.* //' -- "${msgdir}"/send.mac "${msgdir}"/recv.mac "${msgdir}"/ack.mac
207 |
208 | # encrypt message using encryption key
209 | openssl cms -EncryptedData_encrypt -binary -${encalg} -outform pem \
210 | -secretkey ${enckey} \
211 | -in "${msgdir}"/message \
212 | -out "${msgdir}"/message.enc.tmp
213 | mv -- "${msgdir}"/message.enc.tmp "${msgdir}"/message.enc
214 |
215 | rm -- "${msgdir}"/derive.pem "${msgdir}"/speer.der "${msgdir}"/rpeer.der \
216 | "${msgdir}"/shared.key
217 |
218 | ;;
219 |
220 |
221 | recv)
222 | rm -f -- "${msgdir}"/speer.der "${msgdir}"/shared.key "${msgdir}"/message \
223 | "${msgdir}"/send.cmp "${msgdir}"/recv.mac "${msgdir}"/ack.mac
224 |
225 | # verify certificates chain
226 | verify_certs "${msgdir}"
227 |
228 | # verify and extract signed sender's ephemeral public peer key
229 | openssl cms -verify \
230 | -x509_strict -policy_check -purpose smimesign -check_ss_sig -inform pem \
231 | -CAfile "${msgdir}"/ca.pem -CApath /dev/null \
232 | -certfile "${msgdir}"/verify.pem \
233 | -in "${msgdir}"/speer.sig \
234 | -out "${msgdir}"/speer.der
235 |
236 | # derive (large) shared secret
237 | openssl pkeyutl -derive -peerform der \
238 | -inkey "${msgdir}"/derive.pem \
239 | -peerkey "${msgdir}"/speer.der \
240 | -out "${msgdir}"/shared.key
241 |
242 | # deterministically derive encryption and MAC keys from shared secret
243 | enckey=`openssl dgst -mac hmac -${enchash} -macopt key:encrypt "${msgdir}"/shared.key | cut -d' ' -f2`
244 | sendkey=`openssl dgst -mac hmac -${sigalg} -macopt key:send "${msgdir}"/shared.key | cut -d' ' -f2`
245 | recvkey=`openssl dgst -mac hmac -${sigalg} -macopt key:recv "${msgdir}"/shared.key | cut -d' ' -f2`
246 | [ ${#enckey} = 64 -a ${#sendkey} = 128 -a ${#recvkey} = 128 ]
247 |
248 | # decrypt message using encryption key
249 | openssl cms -EncryptedData_decrypt -inform pem \
250 | -secretkey ${enckey} \
251 | -in "${msgdir}"/message.enc \
252 | -out "${msgdir}"/message
253 |
254 | # compute message send/recv/ack MACs using derived MAC keys
255 | openssl dgst -mac hmac -${sigalg} \
256 | -macopt hexkey:${sendkey} \
257 | -out "${msgdir}"/send.cmp \
258 | "${msgdir}"/message
259 |
260 | openssl dgst -mac hmac -${sigalg} \
261 | -macopt hexkey:${recvkey} \
262 | -out "${msgdir}"/recv.mac \
263 | "${msgdir}"/message
264 |
265 | openssl dgst -mac hmac -${sigalg} -macopt key:ack \
266 | -out "${msgdir}"/ack.mac \
267 | "${msgdir}"/shared.key
268 |
269 | sed -i 's/^.* //' -- "${msgdir}"/send.cmp "${msgdir}"/recv.mac "${msgdir}"/ack.mac
270 |
271 | # verify message MAC
272 | if ! cmp -s -- "${msgdir}"/send.mac "${msgdir}"/send.cmp; then
273 | error "MAC verification failed"
274 | fi
275 |
276 | rm -- "${msgdir}"/speer.der "${msgdir}"/shared.key "${msgdir}"/send.cmp
277 |
278 | ;;
279 |
280 |
281 | *)
282 | error "unknown command"
283 | ;;
284 | esac
285 |
--------------------------------------------------------------------------------
/cable/comm:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | if [ $# != 2 ]; then
4 | echo "Format: $0 send|recv|ack|fin "
5 | exit 1
6 | fi
7 |
8 |
9 | # Directories and files
10 | username=`cat ${CABLE_CERTS}/certs/username | tr -cd a-z2-7`
11 | queue=${CABLE_QUEUES}/queue
12 | rqueue=${CABLE_QUEUES}/rqueue
13 |
14 | # Parameters
15 | cmd="$1"
16 | msgid="$2"
17 |
18 |
19 | trap '[ $? = 0 ] || error failed' 0
20 | error() {
21 | logger -t comm -p mail.err "$@ (${msgid})"
22 | trap - 0
23 | exit 1
24 | }
25 |
26 |
27 | urlprefix() {
28 | local username=`cat $1/"${msgid}"/username | tr -cd a-z2-7`
29 | local hostname=`cat $1/"${msgid}"/hostname | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
30 | check_userhost "${username}" "${hostname}"
31 |
32 | echo http://"${hostname}"/"${username}"/request
33 | }
34 |
35 |
36 | # MAC key extractor
37 | getmac() {
38 | local src="$1" mac=
39 |
40 | mac=`cat "${src}" | tr -cd '[:xdigit:]' | tr A-F a-f`
41 | [ ${#mac} = 128 ] || error "malformed or non-existing MAC in `basename "${src}"`"
42 |
43 | echo "${mac}"
44 | }
45 |
46 |
47 | # Sanity checks
48 | [ ${#msgid} = 40 ] || error "bad msgid"
49 | [ ${#username} = 32 ] || error "bad own username"
50 |
51 | check_userhost() {
52 | [ ${#1} = 32 ] || error "bad username"
53 | [ ${#2} != 0 ] || error "bad hostname"
54 | }
55 |
56 |
57 | case "${cmd}" in
58 | send)
59 | # [comm loop]
60 | if [ -e ${queue}/"${msgid}"/${cmd}.ok -a ! -e ${queue}/"${msgid}"/ack.ok ]; then
61 | prefix=`urlprefix ${queue}`
62 | sendmac=`getmac ${queue}/"${msgid}"/send.mac`
63 | curl -sSfg "${prefix}"/snd/"${msgid}"/"${sendmac}"
64 | else
65 | error "${cmd}.ok (without ack.ok) not found"
66 | fi
67 | ;;
68 |
69 | recv)
70 | # NOTE: dir can be renamed at any moment by [service]
71 | # [comm loop]
72 | if [ -e ${rqueue}/"${msgid}"/${cmd}.ok ]; then
73 | prefix=`urlprefix ${rqueue}`
74 | recvmac=`getmac ${rqueue}/"${msgid}"/recv.mac`
75 | curl -sSfg "${prefix}"/rcp/"${msgid}"/"${recvmac}"
76 | else
77 | error "${cmd}.ok not found"
78 | fi
79 | ;;
80 |
81 | ack)
82 | # [comm loop]
83 | if [ -e ${queue}/"${msgid}"/${cmd}.ok ]; then
84 | prefix=`urlprefix ${queue}`
85 | ackmac=`getmac ${queue}/"${msgid}"/ack.mac`
86 | curl -sSfg "${prefix}"/ack/"${msgid}"/"${ackmac}"
87 |
88 | mv -T ${queue}/"${msgid}" ${queue}/"${msgid}".del
89 |
90 | # try to run 2nd ack variant immediately
91 | # rm -r --one-file-system ${queue}/"${msgid}".del
92 | elif [ -e ${queue}/"${msgid}".del ]; then
93 | rm -r --one-file-system ${queue}/"${msgid}".del
94 | else
95 | error "${cmd}.ok or .del directory not found"
96 | fi
97 | ;;
98 |
99 | fin)
100 | # [comm loop]
101 | if [ -e ${rqueue}/"${msgid}".del ]; then
102 | rm -r --one-file-system ${rqueue}/"${msgid}".del
103 | else
104 | error ".del directory not found"
105 | fi
106 | ;;
107 |
108 | *)
109 | error "unknown command"
110 | ;;
111 | esac
112 |
--------------------------------------------------------------------------------
/cable/crypto:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | if [ $# != 2 ]; then
4 | echo "Format: $0 send|peer|recv|ack "
5 | exit 1
6 | fi
7 |
8 |
9 | # Helpers
10 | cms=${CABLE_HOME}/cms
11 | mhdrop=${CABLE_HOME}/mhdrop
12 |
13 |
14 | # Directories
15 | inbox=${CABLE_INBOX}
16 | ssldir=${CABLE_CERTS}
17 | queue=${CABLE_QUEUES}/queue
18 | rqueue=${CABLE_QUEUES}/rqueue
19 |
20 | # Parameters
21 | cmd="$1"
22 | msgid="$2"
23 |
24 |
25 | trap '[ $? = 0 ] || error failed' 0
26 | error() {
27 | logger -t crypto -p mail.err "$@ (${msgid})"
28 | trap - 0
29 | exit 1
30 | }
31 |
32 |
33 | getuserhost() {
34 | username=`cat $1/"${msgid}"/username | tr -cd a-z2-7`
35 | hostname=`cat $1/"${msgid}"/hostname | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
36 | check_userhost "${username}" "${hostname}"
37 | }
38 |
39 |
40 | deliver() {
41 | local dir="$1" msg="$2"
42 | local grp=`stat -c %g "${dir}"`
43 |
44 | chgrp "${grp}" "${msg}"
45 | chmod 660 "${msg}"
46 | "${mhdrop}" "${dir}" "${msg}"
47 | }
48 |
49 |
50 | # Sanity checks
51 | [ ${#msgid} = 40 ] || error "bad msgid"
52 |
53 | check_userhost() {
54 | [ ${#1} = 32 ] || error "bad username"
55 | [ ${#2} != 0 ] || error "bad hostname"
56 | }
57 |
58 |
59 | case "${cmd}" in
60 | send)
61 | # [crypto loop]
62 | if [ -e ${queue}/"${msgid}"/${cmd}.rdy ]; then
63 | if "${cms}" ${cmd} ${ssldir} ${queue}/"${msgid}" 2>/dev/null; then
64 | mv ${queue}/"${msgid}"/${cmd}.rdy ${queue}/"${msgid}"/${cmd}.ok
65 | rm ${queue}/"${msgid}"/message ${queue}/"${msgid}"/ca.pem \
66 | ${queue}/"${msgid}"/verify.pem ${queue}/"${msgid}"/rpeer.sig
67 | else
68 | mv ${queue}/"${msgid}"/${cmd}.rdy ${queue}/"${msgid}"/${cmd}.req
69 | error "${cmd} failed"
70 | fi
71 | else
72 | error "${cmd}.rdy not found"
73 | fi
74 | ;;
75 |
76 | peer)
77 | # [crypto loop]
78 | if [ -e ${rqueue}/"${msgid}"/${cmd}.req ]; then
79 | if "${cms}" ${cmd} ${ssldir} ${rqueue}/"${msgid}" 2>/dev/null; then
80 | mv ${rqueue}/"${msgid}"/${cmd}.req ${rqueue}/"${msgid}"/${cmd}.ok
81 | else
82 | error "${cmd} failed"
83 | fi
84 | else
85 | error "${cmd}.req not found"
86 | fi
87 | ;;
88 |
89 | recv)
90 | # [crypto loop]
91 | if [ -e ${rqueue}/"${msgid}"/${cmd}.rdy ]; then
92 | if "${cms}" ${cmd} ${ssldir} ${rqueue}/"${msgid}" 2>/dev/null; then
93 | getuserhost ${rqueue}
94 | date=`date -uR`
95 |
96 | if ! /bin/gzip -cdt ${rqueue}/"${msgid}"/message 2>/dev/null; then
97 | error "${cmd}: non-gzipped message"
98 | fi
99 | /bin/gzip -cd ${rqueue}/"${msgid}"/message \
100 | | formail -z -I 'From ' \
101 | -i "From: <${username}@${hostname}>" \
102 | -I "X-Received-Date: ${date}" \
103 | > ${rqueue}/"${msgid}"/message.ibx
104 |
105 | # Prepare headers for local MUA failure message
106 | formail -f -X From: -X To: -X Cc: -X Bcc: -X Subject: -X Date: \
107 | -X Message-ID: -X In-Reply-To: -X References: -a 'Subject: ' \
108 | < ${rqueue}/"${msgid}"/message.ibx | sed 's/^Subject: /&[fail] /i' \
109 | > ${rqueue}/"${msgid}"/message.hdr
110 |
111 | deliver ${inbox} ${rqueue}/"${msgid}"/message.ibx
112 |
113 | mv ${rqueue}/"${msgid}"/${cmd}.rdy ${rqueue}/"${msgid}"/${cmd}.ok
114 | rm ${rqueue}/"${msgid}"/message ${rqueue}/"${msgid}"/message.enc \
115 | ${rqueue}/"${msgid}"/ca.pem ${rqueue}/"${msgid}"/verify.pem \
116 | ${rqueue}/"${msgid}"/derive.pem ${rqueue}/"${msgid}"/rpeer.sig \
117 | ${rqueue}/"${msgid}"/speer.sig ${rqueue}/"${msgid}"/send.mac
118 | else
119 | rm ${rqueue}/"${msgid}"/send.mac
120 | mv ${rqueue}/"${msgid}"/${cmd}.rdy ${rqueue}/"${msgid}"/${cmd}.req
121 | error "${cmd} failed"
122 | fi
123 | else
124 | error "${cmd}.rdy not found"
125 | fi
126 | ;;
127 |
128 | ack)
129 | # [crypto loop]
130 | if [ -e ${queue}/"${msgid}"/${cmd}.req -a ! -e ${queue}/"${msgid}"/${cmd}.ok ]; then
131 | getuserhost ${queue}
132 | date=`date -uR`
133 |
134 | (formail -f -i "Date: ${date}" < ${queue}/"${msgid}"/message.hdr; \
135 | echo "Verified message delivery to ${username}@${hostname}") \
136 | > ${queue}/"${msgid}"/message.ack
137 |
138 | deliver ${inbox} ${queue}/"${msgid}"/message.ack
139 |
140 | mv ${queue}/"${msgid}"/${cmd}.req ${queue}/"${msgid}"/${cmd}.ok
141 | rm ${queue}/"${msgid}"/message.enc ${queue}/"${msgid}"/speer.sig \
142 | ${queue}/"${msgid}"/send.mac ${queue}/"${msgid}"/recv.mac
143 | else
144 | error "${cmd}.req (without ${cmd}.ok) not found"
145 | fi
146 | ;;
147 |
148 | *)
149 | error "unknown command"
150 | ;;
151 | esac
152 |
--------------------------------------------------------------------------------
/cable/fetch:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | if [ $# != 2 ]; then
4 | echo "Format: $0 send|recv "
5 | exit 1
6 | fi
7 |
8 |
9 | # Directories
10 | queue=${CABLE_QUEUES}/queue
11 | rqueue=${CABLE_QUEUES}/rqueue
12 |
13 | # Parameters
14 | cmd="$1"
15 | msgid="$2"
16 |
17 |
18 | trap '[ $? = 0 ] || error failed' 0
19 | error() {
20 | logger -t fetch -p mail.err "$@ (${msgid})"
21 | trap - 0
22 | exit 1
23 | }
24 |
25 |
26 | urlprefix() {
27 | local username=`cat $1/"${msgid}"/username | tr -cd a-z2-7`
28 | local hostname=`cat $1/"${msgid}"/hostname | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
29 | check_userhost "${username}" "${hostname}"
30 |
31 | echo http://"${hostname}"/"${username}"
32 | }
33 |
34 |
35 | # Sanity checks
36 | [ ${#msgid} = 40 ] || error "bad msgid"
37 |
38 | check_userhost() {
39 | [ ${#1} = 32 ] || error "bad username"
40 | [ ${#2} != 0 ] || error "bad hostname"
41 | }
42 |
43 |
44 | # Retry curl request for 400+ status codes
45 | retrycurl() {
46 | local status= delay=
47 |
48 | for delay in 0 5 10 15; do
49 | sleep ${delay}
50 | status=0; curl "$@" || status=$?
51 |
52 | if [ ${status} != 22 ]; then
53 | break
54 | fi
55 | done
56 |
57 | return ${status}
58 | }
59 |
60 |
61 | case "${cmd}" in
62 | send)
63 | # [fetch loop]
64 | if [ -e ${queue}/"${msgid}"/${cmd}.req ]; then
65 | prefix=`urlprefix ${queue}`
66 |
67 | susername=`cat ${queue}/"${msgid}"/susername | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
68 | shostname=`cat ${queue}/"${msgid}"/shostname | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
69 | check_userhost "${susername}" "${shostname}"
70 |
71 | curl -sSfg "${prefix}"/request/msg/"${msgid}"/"${shostname}"/"${susername}"
72 |
73 | # TODO: with precomputed rpeers, a delay will be much less likely
74 | retrycurl -sSfg -o ${queue}/"${msgid}"/rpeer.sig "${prefix}"/rqueue/"${msgid}".key
75 |
76 | # A multi-URI curl command doesn't fail on a bad early fetch
77 | curl -sSfg -o ${queue}/"${msgid}"/ca.pem "${prefix}"/certs/ca.pem
78 | curl -sSfg -o ${queue}/"${msgid}"/verify.pem "${prefix}"/certs/verify.pem
79 |
80 | mv ${queue}/"${msgid}"/${cmd}.req ${queue}/"${msgid}"/${cmd}.rdy
81 | else
82 | error "${cmd}.req not found"
83 | fi
84 | ;;
85 |
86 | recv)
87 | # [fetch loop]
88 | if [ -e ${rqueue}/"${msgid}"/${cmd}.req -a -e ${rqueue}/"${msgid}"/send.mac -a \
89 | ! -e ${rqueue}/"${msgid}"/${cmd}.rdy -a ! -e ${rqueue}/"${msgid}"/${cmd}.ok ]; then
90 | prefix=`urlprefix ${rqueue}`
91 |
92 | # A multi-URI curl command deosn't fail on a bad early fetch
93 | curl -sSfg -o ${rqueue}/"${msgid}"/message.enc "${prefix}"/queue/"${msgid}"
94 | curl -sSfg -o ${rqueue}/"${msgid}"/speer.sig "${prefix}"/queue/"${msgid}".key
95 | curl -sSfg -o ${rqueue}/"${msgid}"/ca.pem "${prefix}"/certs/ca.pem
96 | curl -sSfg -o ${rqueue}/"${msgid}"/verify.pem "${prefix}"/certs/verify.pem
97 |
98 | mv ${rqueue}/"${msgid}"/${cmd}.req ${rqueue}/"${msgid}"/${cmd}.rdy
99 | else
100 | error "${cmd}.req (without .rdy/.ok) or send.mac not found"
101 | fi
102 | ;;
103 |
104 | *)
105 | error "unknown command"
106 | ;;
107 | esac
108 |
--------------------------------------------------------------------------------
/cable/loop:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # This script ensures at most one running instance for a
4 |
5 | if [ $# -lt 2 -o \( queue != "$1" -a rqueue != "$1" \) ]; then
6 | echo "Format: $0 queue|rqueue [.del]"
7 | exit 1
8 | fi
9 |
10 |
11 | # Helpers
12 | validate=${CABLE_HOME}/validate
13 | fetch=${CABLE_HOME}/fetch
14 | crypto=${CABLE_HOME}/crypto
15 | comm=${CABLE_HOME}/comm
16 |
17 | # Parameters
18 | qtype=$1
19 | msgid="${2%.del}"
20 | dirid="${2}"
21 |
22 | lockmagick="$3"
23 | locktmout=2
24 |
25 | # Directories
26 | msgdir=${CABLE_QUEUES}/${qtype}/"${dirid}"
27 |
28 |
29 | trap '[ $? = 0 ] || error failed' 0
30 | error() {
31 | logger -t loop -p mail.err "$@ (${msgid})"
32 | trap - 0
33 | exit 1
34 | }
35 |
36 |
37 | # Sanity checks
38 | [ ${#msgid} = 40 ] || error "bad msgid"
39 | [ -r "${msgdir}" ] || error "cannot access"
40 |
41 |
42 | # .del actions: blocking lock (to let the renaming action finish)
43 | if [ "${dirid}" != "${msgid}" ]; then
44 |
45 | if [ ${qtype} = queue ]; then
46 |
47 | if [ -e "${msgdir}" ]; then
48 | exec flock -w ${locktmout} "${msgdir}"/ "${comm}" ack "${msgid}"
49 | else
50 | error ".del directory not found"
51 | fi
52 |
53 | else
54 |
55 | if [ -e "${msgdir}" ]; then
56 | exec flock -w ${locktmout} "${msgdir}"/ "${comm}" fin "${msgid}"
57 | else
58 | error ".del directory not found"
59 | fi
60 |
61 | fi
62 |
63 | else
64 |
65 | if [ ${qtype} = queue ]; then
66 |
67 | # lock combined operations
68 | if [ lockmagick != "${lockmagick}" ]; then
69 | exec flock -w ${locktmout} -n "${msgdir}"/ "$0" ${qtype} "${msgid}" lockmagick
70 | fi
71 |
72 | "${validate}" ${qtype} "${msgid}"
73 | set +e
74 |
75 | # handle ack first, to retry send if failed
76 | if [ -e "${msgdir}"/ack.ok ]; then
77 | "${comm}" ack "${msgid}"
78 | elif [ -e "${msgdir}"/ack.req ]; then
79 | "${crypto}" ack "${msgid}" && \
80 | "${comm}" ack "${msgid}"
81 | fi
82 |
83 | # if ack succeeds, ${msgdir} is renamed
84 | if [ -e "${msgdir}" ]; then
85 | if [ -e "${msgdir}"/send.req ]; then
86 | "${fetch}" send "${msgid}" && \
87 | "${crypto}" send "${msgid}" && \
88 | exec "${comm}" send "${msgid}"
89 | elif [ -e "${msgdir}"/send.rdy ]; then
90 | "${crypto}" send "${msgid}" && \
91 | exec "${comm}" send "${msgid}"
92 | elif [ -e "${msgdir}"/send.ok ]; then
93 | if [ ! -e "${msgdir}"/ack.ok ]; then
94 | exec "${comm}" send "${msgid}"
95 | fi
96 | else
97 | error "send.req/rdy/ok not found"
98 | fi
99 | fi
100 |
101 | else
102 |
103 | # lock combined operations
104 | if [ lockmagick != "${lockmagick}" ]; then
105 | exec flock -w ${locktmout} -n "${msgdir}"/ "$0" ${qtype} "${msgid}" lockmagick
106 | fi
107 |
108 | "${validate}" ${qtype} "${msgid}"
109 | set +e
110 |
111 | if [ -e "${msgdir}"/recv.rdy ]; then
112 | "${crypto}" recv "${msgid}" && \
113 | "${comm}" recv "${msgid}"
114 | elif [ -e "${msgdir}"/recv.ok ]; then
115 | "${comm}" recv "${msgid}"
116 | elif [ -e "${msgdir}"/recv.req ]; then
117 | "${fetch}" recv "${msgid}" && \
118 | "${crypto}" recv "${msgid}" && \
119 | "${comm}" recv "${msgid}"
120 | fi
121 |
122 | if [ -e "${msgdir}"/peer.req ]; then
123 | exec "${crypto}" peer "${msgid}"
124 | elif [ ! -e "${msgdir}"/peer.ok ]; then
125 | error "peer.req/ok not found"
126 | fi
127 |
128 | fi
129 |
130 | fi
131 |
--------------------------------------------------------------------------------
/cable/send:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | # Setup environment with needed environment vars
4 | . /etc/cable/profile
5 |
6 |
7 | # Creates ${queuedir}/{username,hostname,message,send.req}
8 | queuedir=${CABLE_QUEUES}/queue
9 |
10 | msgidbytes=20
11 | emailregex="${CABLE_REGEX}"
12 |
13 | # Temporary directory on same filesystem
14 | tmpdir=`mktemp -d --tmpdir=${queuedir}`
15 |
16 | cleanup() {
17 | rm -r ${tmpdir}
18 | }
19 |
20 | trap 'st=$?; set +e; cleanup; exit ${st}' 0
21 | trap : INT QUIT TERM SEGV
22 |
23 |
24 | error() {
25 | # don't log via syslog, since invocation is interactive
26 | echo "send: $@" 1>&2
27 | exit 1
28 | }
29 |
30 |
31 | # Read the message on stdin, and remove X-*: headers
32 | formail -fz -I X- > ${tmpdir}/message
33 |
34 |
35 | # Prepare headers for local MUA confirmation message
36 | formail -f -X From: -X To: -X Cc: -X Bcc: -X Subject: -X Date: \
37 | -X Message-ID: -X In-Reply-To: -X References: -a 'Subject: ' \
38 | < ${tmpdir}/message | sed 's/^Subject: /&[vfy] /i' \
39 | > ${tmpdir}/message.hdr
40 |
41 |
42 | # Extract bare x@y addresses
43 | addresses=`formail -fcz -x To: -x Cc: -x Bcc: < ${tmpdir}/message \
44 | | tr , '\n' \
45 | | sed 's/^.*<\([^>]*\)>/\1/' \
46 | | sed 's/^[[:blank:]]\+//; s/[[:blank:]]\+$//' \
47 | | tr '[:upper:]' '[:lower:]'`
48 | addresses=`echo ${addresses} | tr '[:blank:]' '\n' | sort -u`
49 |
50 | # Extract the bare From: address
51 | fromaddr=`formail -fcz -x From: < ${tmpdir}/message \
52 | | sed 's/^.*<\([^>]*\)>/\1/' \
53 | | tr '[:upper:]' '[:lower:]'`
54 |
55 |
56 | # Check address validity to prevent accidental information leaks
57 | for addr in "${fromaddr}" ${addresses}; do
58 | if ! echo x "${addr}" | egrep -q "^x ${emailregex}$"; then
59 | error "unsupported address: ${addr}"
60 | fi
61 | done
62 |
63 |
64 | # Extract the from-user and from-host
65 | echo ${fromaddr%@*} > ${tmpdir}/susername
66 | echo ${fromaddr#*@} > ${tmpdir}/shostname
67 |
68 |
69 | # Remove Bcc: header and replace Date: with a UTC one:
70 | date=`date -uR`
71 | formail -f -I Bcc: -I "Date: ${date}" < ${tmpdir}/message \
72 | | gzip -c9n > ${tmpdir}/message.gz
73 | mv ${tmpdir}/message.gz ${tmpdir}/message
74 |
75 |
76 | # Create actual directories, one per addressee
77 | for addr in ${addresses}; do
78 | # generate and create as subdirectory
79 | msgid=`openssl rand -hex ${msgidbytes}`
80 | [ ${#msgid} = $((msgidbytes * 2)) ] || error "could not generate msgid"
81 |
82 | mkdir ${tmpdir}/${msgid}
83 |
84 | # extract username and hostname from address, and generate respective files
85 | echo ${addr%@*} > ${tmpdir}/${msgid}/username
86 | echo ${addr#*@} > ${tmpdir}/${msgid}/hostname
87 |
88 | # copy the sanitized message (assume the files will not be modified later)
89 | ln ${tmpdir}/message ${tmpdir}/message.hdr \
90 | ${tmpdir}/susername ${tmpdir}/shostname \
91 | ${tmpdir}/${msgid}/
92 |
93 | # atomically move directory to queue dir, and create send.req indicator
94 | touch ${tmpdir}/${msgid}/send.req
95 | mv -T ${tmpdir}/${msgid} ${queuedir}/${msgid}
96 | done
97 |
--------------------------------------------------------------------------------
/cable/validate:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | if [ $# != 2 -o \( queue != "$1" -a rqueue != "$1" \) ]; then
4 | echo "Format: $0 queue|rqueue "
5 | exit 1
6 | fi
7 |
8 |
9 | # Helpers
10 | mhdrop=${CABLE_HOME}/mhdrop
11 |
12 |
13 | # Parameters
14 | qtype=$1
15 | msgid="${2}"
16 |
17 | sectmout=${CABLE_TMOUT}
18 | subjrep='s/^\(Subject: \)\(\[vfy\] \)\?/\1[fail] /i'
19 |
20 | # Directories
21 | inbox=${CABLE_INBOX}
22 | msgdir=${CABLE_QUEUES}/${qtype}/"${msgid}"
23 | tsfile="${msgdir}"/username
24 |
25 |
26 | trap '[ $? = 0 ] || error failed' 0
27 | error() {
28 | logger -t validate -p mail.err "$@ (${msgid})"
29 | trap - 0
30 | exit 1
31 | }
32 |
33 |
34 | deliver() {
35 | local dir="$1" msg="$2"
36 | local grp=`stat -c %g "${dir}"`
37 |
38 | chgrp "${grp}" "${msg}"
39 | chmod 660 "${msg}"
40 | "${mhdrop}" "${dir}" "${msg}"
41 | }
42 |
43 |
44 | # Sanity checks
45 | [ ${#msgid} = 40 ] || error "bad msgid"
46 | [ -s "${tsfile}" ] || error "bad username file"
47 |
48 | check_userhost() {
49 | [ ${#1} = 32 ] || error "bad username"
50 | [ ${#2} != 0 ] || error "bad hostname"
51 | }
52 |
53 |
54 | # Determine if the message has timed out
55 | secstart=`stat -c %Y "${tsfile}"`
56 | secend=`date -u +%s`
57 | secdiff=$((secend - secstart))
58 |
59 | if [ ${sectmout} -gt ${secdiff} ]; then
60 | exit
61 | fi
62 |
63 |
64 | # Variables
65 | date=`date -uR`
66 | username=`cat "${msgdir}"/username | tr -cd a-z2-7`
67 | hostname=`cat "${msgdir}"/hostname | tr -cd '[:alnum:].-' | tr '[:upper:]' '[:lower:]'`
68 | check_userhost "${username}" "${hostname}"
69 |
70 |
71 | # Classify message state and create an appropriate MUA notification
72 | if [ ${qtype} = queue ]; then
73 | # message.hdr is available, but [vfy] must be replaced with [fail]
74 | formail -f -i "Date: ${date}" < "${msgdir}"/message.hdr | sed "${subjrep}"
75 |
76 | if [ -e "${msgdir}"/ack.ok ]; then
77 | echo "Failed to acknowledge receipt received from ${username}@${hostname}"
78 | elif [ -e "${msgdir}"/send.ok ]; then
79 | echo "Failed to send message and receive receipt from ${username}@${hostname}"
80 | else
81 | echo "Failed to peer and encrypt message for ${username}@${hostname}"
82 | fi
83 | else
84 | if [ -e "${msgdir}"/recv.ok ]; then
85 | # message.hdr is available
86 | formail -f -i "Date: ${date}" < "${msgdir}"/message.hdr
87 | echo "Failed to send receipt and receive acknowledgment from ${username}@${hostname}"
88 | else
89 | # no message.hdr is available at this point
90 | msgdate=$(date -d "$(stat -c %y "${tsfile}")" -uR)
91 | formail -f -I "From: <${username}@${hostname}>" \
92 | -I "Old-Date: ${msgdate}" -I "Date: ${date}" \
93 | -I "Subject: [fail]" <&-
94 |
95 | if [ -e "${msgdir}"/recv.req -o -e "${msgdir}"/recv.rdy ]; then
96 | echo "Failed to fetch and decrypt message from ${username}@${hostname}"
97 | else
98 | echo "Failed to generate peer key for message from ${username}@${hostname}"
99 | fi
100 | fi
101 | fi > "${msgdir}"/message.rej
102 |
103 |
104 | # Deliver the reject message
105 | deliver ${inbox} "${msgdir}"/message.rej
106 |
107 | # Schedule message directory for removal
108 | mv -T "${msgdir}" "${msgdir}".del
109 |
110 | # Indicate unsuccessful status to caller
111 | exit 42
112 |
--------------------------------------------------------------------------------
/conf/cabled:
--------------------------------------------------------------------------------
1 | #!/sbin/runscript
2 |
3 | description="Cables communication daemon"
4 |
5 | command=/usr/libexec/cable/cabled
6 | command_background="true"
7 | start_stop_daemon_args="-u cable"
8 | pidfile=/var/run/cabled.pid
9 |
--------------------------------------------------------------------------------
/conf/extensions.cnf:
--------------------------------------------------------------------------------
1 | # Distinguished name is directly supplied to ssl-req
2 |
3 | [ req ]
4 | distinguished_name = req_empty
5 |
6 | [ req_empty ]
7 |
8 |
9 | # X.509 extensions
10 | # http://www.openssl.org/docs/apps/x509v3_config.html
11 |
12 | [ root ]
13 |
14 | subjectKeyIdentifier = hash
15 | authorityKeyIdentifier = keyid:always,issuer:always
16 |
17 | authorityInfoAccess = caIssuers;URI:http://dee.su/cables
18 |
19 | basicConstraints = critical, CA:true, pathlen:0
20 | keyUsage = critical, keyCertSign, cRLSign
21 |
22 |
23 | [ verify ]
24 |
25 | subjectKeyIdentifier = hash
26 | authorityKeyIdentifier = keyid:always,issuer:always
27 |
28 | keyUsage = critical, digitalSignature
29 | extendedKeyUsage = critical, emailProtection
30 |
31 |
32 | [ encrypt ]
33 |
34 | subjectKeyIdentifier = hash
35 | authorityKeyIdentifier = keyid:always,issuer:always
36 |
37 | keyUsage = critical, keyEncipherment
38 | extendedKeyUsage = critical, emailProtection
39 |
--------------------------------------------------------------------------------
/conf/profile:
--------------------------------------------------------------------------------
1 | # Essential environment variables, to be sourced by
2 | # cables executables, possibly after "sudo -u "
3 | # Guaranteed environment: HOME, USER
4 |
5 | # rwX------
6 | umask 077
7 | export LC_ALL=C
8 |
9 |
10 | # Base directories
11 | export CABLE_CONF=/etc/cable
12 | export CABLE_HOME=/usr/libexec/cable
13 |
14 |
15 | # Optional common root (not used outside this script)
16 | export CABLE_MOUNT=/home/anon/persist
17 |
18 | # Certificates and private keys directories
19 | export CABLE_CERTS=${CABLE_MOUNT}/security/cable
20 | export CABLE_TOR=${CABLE_MOUNT}/security/tor
21 | export CABLE_I2P=${CABLE_MOUNT}/security/i2p
22 |
23 | # CABLE_QUEUES/(r)queue directories, must be writable by uid 'cable'
24 | export CABLE_QUEUES=${CABLE_MOUNT}/cables
25 |
26 | # Mail delivery directory, must be writable by uid 'cable'
27 | export CABLE_INBOX=${CABLE_MOUNT}/mail/inbox
28 |
29 |
30 | # Message or receipt timeout in seconds (e.g., 7 days)
31 | export CABLE_TMOUT=$((7 * 24 * 60 * 60))
32 |
33 |
34 | # Host and port on which cables daemon listens to HTTP connections
35 | # (symbolic names can be used; leave host empty for wildcard bind)
36 | export CABLE_HOST=127.0.0.1
37 | export CABLE_PORT=9080
38 |
39 |
40 | # Supported email-like IDs (do not modify!)
41 | export CABLE_REGEX='[a-z2-7]{32}@([a-z2-7]{16}\.onion|[a-z2-7]{52}\.b32\.i2p)'
42 |
43 |
44 | # Proxy settings (used by curl)
45 | export http_proxy=http://127.0.0.1:8118/
46 | export https_proxy=${http_proxy}
47 |
48 |
49 | # Support pam_mktemp
50 | export TMPDIR=${TMPDIR:-/tmp/.private/${USER}}
51 | if [ ! -e "${TMPDIR}" ]; then
52 | TMPDIR=/tmp/.${USER}
53 | mkdir -pm 700 ${TMPDIR}
54 | fi
55 |
56 |
57 | # OpenSSL random seed
58 | export RANDFILE="${TMPDIR}"/openssl.rnd
59 |
--------------------------------------------------------------------------------
/conf/rfc3526-modp-18.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIIECAKCBAEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxOb
3 | IlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjft
4 | awv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT
5 | mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhgh
6 | fDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq
7 | 5RXSJhiY+gUQFXKOWoqqxC2tMxcNBFB6M6hVIavfHLpk7PuFBFjb7wqK6nFXXQYM
8 | fbOXD4Wm4eTHq/WujNsJM9cejJTgSiVhnc7j0iYa0u5r8S/6BtmKCGTYdgJzPshq
9 | ZFIfKxgXeyAMu+EXV3phXWx3CYjAutlG4gjiT6B05asxQ9tb/OD9EI5LgtEgqSEI
10 | ARpyPBKnh+bXiHGaEL26WyaZwycYavTiPBqUaDS2FQvaJYPpyirUTOjbu8LbBN6O
11 | +S6O/BQfvsqmKHxZR05rwF2ZspZPoJDDoiM7oYZRW+ftH2EpcM7i16+4G912IXBI
12 | HNAGkSfVsFqpk7TqmI2P3cGG/7fckKbAj030Nck0AoSSNsP6tNJ8cCbB1NyyYCZG
13 | 3sl1HnY9uje9+P+UBq2eUw7l2zgvQTABrrBqU+2QJ9gxF5cnsIZaiRjaPtvrz5sU
14 | 7UTObLrO1Lsb238UR+bMJUszIFFRK9evQm+49AE3jNK/WYPKAcZLkuzwMuoV0XId
15 | A/SC185udP721V5wL0aYDIK1qEAxkAscnlnnyX++x+jzI6l6fjbMiL4PHUW3/1ha
16 | xUvUB7IrQVSqzI9tfr9I4dgUzF7SD4A34KeXFe7ym+MoBqHVi7fF2nb1UKo9ih+/
17 | 8OsZzLGjE9Vc2lbJ7C7yljI4f+jXbjwEaAQ+j2Y/SGDuEr8tWwt0dNbmlPkebb4R
18 | WXSjkm8S/uXkOHd8tqky34zYvsTQc7kxujvIMraNndMAdB+nv4r8R+0ldvaTa6Qk
19 | ZjqrY5xa5PVoNCO0dCvxyXgjjxbL451lLeP9uL78hIrZIiIuBKQDfAcT61eoGiPw
20 | xzRz/GRs6jBrS8vIhi+Dhd36nUt/osCH6HloMwPtW906Bis89bOieKZtKhP4P0T4
21 | Ld8xDuB0q2o2RZfomaAlXcFk8xzFCEaFHfmrSBld7X6hsdUQvX7nTXP682vDHs+i
22 | aDWQRvTrh5+SQAlDi0gcbNeImgAu1e44K8kZDab8Am5HlVjkR1Z36aqeMFDidlaU
23 | 38gfVuiAuW5xYMmA3Zjt09///////////wIBAg==
24 | -----END DH PARAMETERS-----
25 |
--------------------------------------------------------------------------------
/doc/cable.txt:
--------------------------------------------------------------------------------
1 | Message exchange with PFS and repudiability
2 | ===========================================
3 |
4 | Authentication and encryption
5 | -----------------------------
6 |
7 | Permanent keys:
8 | + public X.509 [ca.pem] (root CA) + username (hash of [ca.pem])
9 | + public X.509 [verify.pem] (issued by root CA)
10 | + private key [sign.pem] (corresponding to X.509 [verify.pem])
11 |
12 | Ephemeral keys:
13 | + public DH key [speer.sig or rpeer.sig] (signed, verifiable with [verify.pem])
14 | + private DH key [derive.pem] (corresponding to DH [s/rpeer.pem])
15 | + shared [shared.key]
16 |
17 | Key derivation:
18 | + sender's ephemeral DH key [speer.der] or private [derive.pem]
19 | + recipient's ephemeral DH key [rpeer.der] or private [derive.pem]
20 | + MAC_{send,recv,ack} keys are different hash functions derived from [shared.key]
21 | (ephemeral-ephemeral key agreement: http://tools.ietf.org/html/rfc2785)
22 |
23 | (sender)
24 | + [message] <-
25 | + [peer request] ->
26 |
27 | (recipient)
28 | + [rpeer.der] -> signed with recipient's [sign.pem] -> [rpeer.sig]
29 | + [rpeer.sig] ->
30 |
31 | (contd., sender)
32 | + [rpeer.sig] <-
33 | + [rpeer.sig] -> verified with recipient's [verify.pem] -> [rpeer.der]
34 | + [derive.pem] + [rpeer.der] -> DH key derivation -> [shared.key]
35 | + [speer.der] -> signed with sender's [sign.pem] -> [speer.sig]
36 | + [message] -> encrypted with shared [shared.key] -> [message.enc]
37 | + [message.enc] + [speer.sig] + MAC_send([message]) ->
38 |
39 | (recipient)
40 | + [message.enc] + [speer.sig] + MAC_send(message) <-
41 | + [speer.sig] -> verified with sender's [verify.pem] -> [speer.der]
42 | + [derive.pem] + [speer.der] -> DH key derivation -> [shared.key]
43 | + [message.enc] -> decrypted with shared [shared.key] -> [message]
44 | + [message] -> verified with shared MAC_send
45 | + MAC_recv([message]) ->
46 |
47 | (sender)
48 | + MAC_recv([message]) <-
49 | + [message] -> verified with shared MAC_recv
50 | + MAC_ack ->
51 |
52 | (recipient)
53 | + MAC_ack <-
54 | + MAC_ack -> compared with shared MAC_ack
55 |
56 |
57 | Protocol
58 | --------
59 | + username is explicitly verified against public key fingerprint
60 | + hostname is implicitly verified when fetching files
61 | + perfect forward secrecy and repudiability
62 | + resistant against MITM injections (except first MSG substitution with same )
63 | + resistant against temporary MITM resources substitution
64 | + resistant to request replay attacks intended to cause large number of disk writes
65 |
66 | + resistant against fingerprinting if username is unknown
67 | + vulnerable to DoS (e.g., many MSG requests) if username is known
68 |
69 | + each loop type is mutually exclusive for a given (r)queue/
70 | + all code blocks are restartable (e.g., after crash)
71 | + messages and confirmations are never lost if /cables filesystem is transactional
72 |
73 | + /cables/ private directory
74 | /queue// outgoing message work dir
75 | /rqueue// incoming message work dir
76 |
77 | + [send] (MUA-invoked script) writes to /cables/queue
78 | + [service] (fast and secure web service) writes to /cables/(r)queue
79 | + [crypto loop] writes to /cables/(r)queue, MUA inbox directory;
80 | reads from certs (public, private) directories
81 | + [fetch loop] writes to /cables/(r)queue, network
82 | + [comm loop] writes to network;
83 | reads username from certs directory
84 | + [webserver] writes to network;
85 | reads from certs public directory, /cables/(r)queue
86 |
87 |
88 | [webserver]
89 | + / common URL prefix
90 | + /certs/{ca,verify}.pem serve public certificates
91 | + /queue/ serve /cables/queue//message.enc
92 | + /queue/.key serve /cables/queue//speer.sig
93 | + /rqueue/.key serve /cables/rqueue//rpeer.sig
94 | + /request/... invoke service[...] and serve answer
95 |
96 |
97 | (sender)
98 | [send]
99 | + generate random 160-bit (40 hex digits)
100 | + prepare /cables/queue//{message{,.hdr},{,s}{user,host}name}
101 | + create /cables/queue//send.req
102 | * (atomic via rename from /cables/queue/tmp.//)
103 |
104 | [fetch loop]
105 | + check /cables/queue//send.req
106 | + request //request/msg///
107 | + fetch //rqueue/.key -> /cables/queue//rpeer.sig
108 | + fetch //certs/{ca,verify}.pem -> /cables/queue//
109 | + rename /cables/queue//send.req -> send.rdy
110 |
111 | [crypto loop]
112 | + check /cables/queue//send.rdy
113 | + prepare /cables/queue//{speer.sig[atomic],message.enc[atomic],{send,recv,ack}.mac}
114 | + rename /cables/queue//send.rdy -> send.ok (success)
115 | + -> send.req (crypto fail)
116 | + remove /cables/queue//{message,{ca,verify}.pem,rpeer.sig} (if success)
117 |
118 | [comm loop]
119 | + check /cables/queue//send.ok
120 | + checkno /cables/queue//ack.ok
121 | + read /cables/queue//send.mac (128 hex digits)
122 | + request //request/snd//
123 |
124 |
125 | (recipient)
126 | [service]
127 | + upon msg///
128 | + checkno /cables/rqueue/ (ok and skip if exists)
129 | + create /cables/rqueue/.new/ (ok if exists)
130 | + write /cables/rqueue/.new/{username,hostname}
131 | + create /cables/rqueue/.new/peer.req (ok if exists)
132 | + rename /cables/rqueue/.new ->
133 |
134 | [crypto loop]
135 | + check /cables/rqueue//peer.req
136 | + prepare /cables/rqueue//{derive.pem,rpeer.sig[atomic]}
137 | + rename /cables/rqueue//peer.req -> peer.ok (success)
138 |
139 |
140 | (recipient)
141 | [service]
142 | + upon snd//
143 | + check /cables/rqueue//peer.ok
144 | + write /cables/rqueue//send.mac (skip if exists)
145 | + create /cables/rqueue//recv.req (atomic, ok if exists)
146 | + touch /cables/rqueue// (if recv.req did not exist)
147 |
148 | [fetch loop]
149 | + check /cables/rqueue//recv.req
150 | + check /cables/rqueue//send.mac
151 | + checkno /cables/rqueue//recv.{rdy,ok} (in this order)
152 | + fetch //queue/ -> /cables/rqueue//message.enc
153 | + fetch //queue/.key -> /cables/rqueue//speer.sig
154 | + fetch //certs/{ca,verify}.pem -> /cables/rqueue//
155 | + rename /cables/rqueue//recv.req -> recv.rdy
156 |
157 | [crypto loop]
158 | + check /cables/rqueue//recv.rdy
159 | + prepare /cables/rqueue//{message{,.hdr},{recv,ack}.mac}
160 | + verify /cables/rqueue//send.mac (remove if fail)
161 | + create <- /cables/rqueue//message
162 | + rename /cables/rqueue//recv.rdy -> recv.ok (success)
163 | + -> recv.req (crypto fail)
164 | + remove /cables/rqueue//{message{,.enc},{ca,verify,derive}.pem,{r,s}peer.sig,send.mac} (if success)
165 |
166 | [comm loop]
167 | + check /cables/rqueue//recv.ok
168 | + read /cables/rqueue//recv.mac (128 hex digits)
169 | + request //request/rcp//
170 |
171 |
172 | (sender)
173 | [service]
174 | + upon rcp//
175 | + check /cables/queue//send.ok
176 | + compare /cables/queue//recv.mac <->
177 | + create /cables/queue//ack.req (atomic, ok if exists)
178 | + touch /cables/queue// (if ack.req did not exist)
179 |
180 | [crypto loop]
181 | + check /cables/queue//ack.req
182 | + checkno /cables/queue//ack.ok
183 | + create
184 | + rename /cables/queue//ack.req -> ack.ok
185 | + remove /cables/queue//{message.enc,speer.sig,{send,recv}.mac} (if success)
186 |
187 | [comm loop]
188 | + check /cables/queue//ack.ok
189 | + read /cables/queue//ack.mac (128 hex digits)
190 | + request //request/ack// (wait for good response)
191 | + rename /cables/queue/ -> .del
192 | (if ack is not handled or lost due to MITM attack, recipient will keep requesting rcp//)
193 |
194 | -and/or-
195 |
196 | + check /cables/queue/.del/
197 | + remove /cables/queue/.del/
198 |
199 |
200 | (recipient)
201 | [service]
202 | + upon ack//
203 | + check /cables/rqueue//recv.ok
204 | + compare /cables/rqueue//ack.mac <->
205 | + rename /cables/rqueue/ -> .del
206 |
207 | [comm loop]
208 | + check /cables/rqueue/.del/
209 | + remove /cables/rqueue/.del/
210 |
211 |
212 | Loop scheduler
213 | --------------
214 |
215 | Initialization (for s of 40 hex digits):
216 | + remove /cables/rqueue/.new/ (before [service] startup)
217 |
218 | Watch list (for s of 40 hex digits):
219 | + /cables/queue/ , .del (inotify: moved_to, attrib)
220 | + /cables/rqueue/ , .del (inotify: moved_to, attrib)
221 |
222 | + [service]: non-blocking lock attempt
223 | + [loop]: blocking lock (to let renaming actions complete, with short timeout)
224 |
225 | Retry policies:
226 | + retry every X min. (+ random component)
227 |
228 | Validation: upon reaching max age (from /username timestamp):
229 | (mutually exclusive with all loop types)
230 |
231 | + (queue) create
232 | [if] ack.ok: failed to acknowledge receipt
233 | [elif] send.ok: failed to send message and receive receipt
234 | [else] failed to peer and encrypt message
235 | + (queue) rename /cables/queue/ -> .del
236 |
237 | + (rqueue) create
238 | [if] recv.ok: failed to send receipt and receive acknowledgment
239 | [elif] recv.req/rdy: failed to fetch and decrypt message
240 | [else] failed to generate peer key
241 | + (rqueue) rename /cables/rqueue/ -> .del
242 |
243 |
244 | Message format
245 | --------------
246 |
247 | (send)
248 | + extract all unique To:, Cc:, Bcc: addresses
249 | + check that all addresses (+ From:) are recognized (e.g., *.onion)
250 | + remove Bcc: and all X-*: headers
251 | + reformat Date: as UTC
252 | + compress with gzip
253 |
254 |
255 | (recv)
256 | + uncompress with classic (single-threaded) gzip
257 | + replace From: header with the verified address (rename old header)
258 | + add X-Received-Date: header
259 |
260 |
261 | (ack)
262 | + extract original From:, To:, Cc:, Bcc:, Subject:, Date:, Message-ID:,
263 | In-Reply-To:, References: fields
264 | + replace Date: header with current date (rename old header)
265 | + prepend [vfy] to Subject: field contents
266 | + append body with verification message, including current timestamp
267 | and verified delivery address
268 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | # Single-source file programs to build
2 | progs = cable/daemon cable/mhdrop cable/hex2base32 \
3 | $(if $(NOI2P),,cable/eeppriv.jar)
4 | objextra_daemon = obj/server.o obj/service.o obj/process.o obj/util.o
5 | ldextra_daemon = -lrt -lmicrohttpd
6 | cpextra_EepPriv = /opt/i2p/lib/i2p.jar
7 |
8 | title := $(shell grep -o 'LIBERTE CABLE [[:alnum:]._-]\+' src/daemon.h)
9 |
10 |
11 | # Installation directories (override DESTDIR and/or PREFIX)
12 | # (DESTDIR is temporary, (ETC)PREFIX is hard-coded into scripts)
13 | DESTDIR=
14 | PREFIX=/usr
15 | ETCPREFIX=$(patsubst %/usr/etc,%/etc,$(PREFIX)/etc)
16 |
17 | instdir=$(DESTDIR)$(PREFIX)
18 | etcdir=$(DESTDIR)$(ETCPREFIX)
19 |
20 |
21 | # Default compilers
22 | CC = gcc
23 | JAVAC = javac
24 |
25 | # Disable I2P eepSite keypair generation functionality? (non-empty to disable)
26 | NOI2P =
27 |
28 | # Modifications to compiler flags
29 | CFLAGS := -std=c99 -Wall -pedantic -MMD -D_FILE_OFFSET_BITS=64 -D_POSIX_C_SOURCE=200809L -D_BSD_SOURCE -DNDEBUG $(CFLAGS)
30 | JFLAGS := -target 1.5 -deprecation -Werror -g:none $(JFLAGS)
31 | JLIBS = $(subst : ,:,$(addsuffix :,$(wildcard lib/*.jar)))
32 |
33 |
34 | # Build rules
35 | .PHONY: all clean install
36 | .SUFFIXES: .o
37 | .SECONDARY:
38 |
39 | all: $(progs)
40 |
41 | clean:
42 | $(RM) -r $(progs) obj/*
43 |
44 | cable/%: obj/%.o
45 | $(CC) -o $@ $(CFLAGS) $< $(objextra_$*) $(LDFLAGS) $(ldextra_$*)
46 |
47 | obj/%.o: src/%.c
48 | $(CC) -c -o $@ $(CFLAGS) $<
49 |
50 | cable/eeppriv.jar: obj/su/dee/i2p/EepPriv.class
51 | echo "Manifest-Version: 1.0" > $(> $(> $(> $(&$(PREFIX)/libexec/cable&g' \
67 | $(addprefix $(etcdir)/cable/,profile cabled) \
68 | $(instdir)/bin/cable-send
69 | sed -i 's&/etc/cable\>&$(ETCPREFIX)/cable&g' \
70 | $(etcdir)/cable/profile \
71 | $(addprefix $(instdir)/libexec/cable/,cabled send) \
72 | $(addprefix $(instdir)/bin/,cable-id cable-ping cable-send gen-cable-username gen-tor-hostname gen-i2p-hostname)
73 | ifeq ($(strip $(NOI2P)),)
74 | chmod a-x $(instdir)/libexec/cable/eeppriv.jar
75 | else
76 | rm $(instdir)/bin/gen-i2p-hostname
77 | endif
78 |
79 |
80 | # File-specific dependencies
81 | cable/daemon: $(objextra_daemon)
82 | -include $(wildcard obj/*.d)
83 |
--------------------------------------------------------------------------------
/pkg/cables-x.y.ebuild:
--------------------------------------------------------------------------------
1 | # Copyright 1999-2012 Gentoo Foundation
2 | # Distributed under the terms of the GNU General Public License v2
3 | # $Header: $
4 |
5 | EAPI="4"
6 |
7 | # no mime types, so no need to inherit fdo-mime
8 | inherit eutils user
9 |
10 | DESCRIPTION="Secure and anonymous serverless email-like communication."
11 | HOMEPAGE="http://dee.su/cables"
12 | LICENSE="GPL-2"
13 |
14 | # I2P version is not critical, and need not be updated
15 | MY_P_PF=mkdesu-cables
16 | I2P_PV=0.8.8
17 | I2P_MY_P=i2pupdate_${I2P_PV}
18 |
19 | # GitHub URI can refer to a tagged download or the master branch
20 | SRC_URI="https://github.com/mkdesu/cables/tarball/v${PV} -> ${P}.tar.gz
21 | i2p? (
22 | http://mirror.i2p2.de/${I2P_MY_P}.zip
23 | http://launchpad.net/i2p/trunk/${I2P_PV}/+download/${I2P_MY_P}.zip
24 | )"
25 |
26 | SLOT="0"
27 | KEYWORDS="x86 amd64"
28 | IUSE="i2p"
29 |
30 | DEPEND="app-arch/unzip
31 | i2p? ( >=virtual/jdk-1.5 )"
32 | RDEPEND="net-libs/libmicrohttpd
33 | mail-filter/procmail
34 | net-misc/curl
35 | dev-libs/openssl
36 | i2p? ( >=virtual/jre-1.5 )
37 | gnome-extra/zenity"
38 |
39 | src_unpack() {
40 | unpack ${P}.tar.gz
41 | mv ${MY_P_PF}-* ${P} || die "failed to recognize archive top directory"
42 |
43 | if use i2p; then
44 | unzip -j -d ${P}/lib ${DISTDIR}/${I2P_MY_P}.zip lib/i2p.jar || die "failed to extract i2p.jar"
45 | fi
46 | }
47 |
48 | src_prepare() {
49 | if ! use i2p; then
50 | export MAKEOPTS+=" NOI2P=1"
51 | fi
52 |
53 | default
54 | }
55 |
56 | src_install() {
57 | default
58 |
59 | doinitd "${D}"/etc/cable/cabled
60 | rm "${D}"/etc/cable/cabled || die
61 | }
62 |
63 | pkg_preinst() {
64 | enewgroup cable
65 | enewuser cable -1 -1 -1 cable
66 | }
67 |
68 | pkg_postinst() {
69 | elog "Remember to add 'cabled' to the default runlevel."
70 | elog "You need to adjust the user-specific paths in /etc/cable/profile."
71 | elog "Generate cables certificates and Tor/I2P keypairs for the user:"
72 | elog " gen-cable-username"
73 | elog " gen-tor-hostname"
74 | elog " copy CABLE_TOR/hidden_service to /var/lib/tor (readable only by 'tor')"
75 | if use i2p; then
76 | elog " gen-i2p-hostname"
77 | elog " copy CABLE_I2P/eepsite to /var/lib/i2p (readable only by 'i2p')"
78 | fi
79 | elog "Configure Tor to forward HTTP connections to cables daemon:"
80 | elog " /etc/tor/torrc"
81 | elog " HiddenServiceDir /var/lib/tor/hidden_service/"
82 | elog " HiddenServicePort 80 127.0.0.1:9080"
83 | if use i2p; then
84 | elog "Configure I2P similarly:"
85 | elog " /var/lib/i2p/i2ptunnel.config"
86 | elog " tunnel.X.privKeyFile=eepsite/eepPriv.dat"
87 | elog " tunnel.X.targetHost=127.0.0.1"
88 | elog " tunnel.X.targetPort=9080"
89 | fi
90 | elog "Finally, the user should configure the email client to run cable-send"
91 | elog "as a pipe for sending messages from addresses shown by cable-info."
92 | elog "See comments in /usr/bin/cable-send for suggested /etc/sudoers entry."
93 | }
94 |
--------------------------------------------------------------------------------
/share/cable-info.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Cables Identity
3 | Comment=Show user identity for secure cables communication
4 | Exec=cable-info
5 | Terminal=false
6 | Type=Application
7 | Categories=GTK;Network;Email;
8 | Icon=emblem-mail
9 |
--------------------------------------------------------------------------------
/src/daemon.c:
--------------------------------------------------------------------------------
1 | /*
2 | The following environment variables are used (from /etc/cable/profile):
3 | CABLE_HOME, CABLE_QUEUES, CABLE_CERTS, CABLE_HOST, CABLE_PORT
4 |
5 | Testing environment:
6 | CABLE_NOLOOP, CABLE_NOWATCH
7 | */
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include "daemon.h"
23 | #include "server.h"
24 | #include "process.h"
25 | #include "util.h"
26 |
27 |
28 | /* environment variables */
29 | #define CABLE_HOME "CABLE_HOME"
30 | #define CABLE_QUEUES "CABLE_QUEUES"
31 | #define CABLE_CERTS "CABLE_CERTS"
32 | #define CABLE_HOST "CABLE_HOST"
33 | #define CABLE_PORT "CABLE_PORT"
34 |
35 | /* executables and subdirectories */
36 | #define LOOP_NAME "loop"
37 | #define QUEUE_NAME "queue"
38 | #define RQUEUE_NAME "rqueue"
39 | #define CERTS_NAME "certs"
40 |
41 |
42 | /* waiting strategy for inotify setup retries (e.g., after fs unmount) */
43 | #define WAIT_INIT 2
44 | #define WAIT_MULT 1.5
45 | #define WAIT_MAX 60
46 |
47 | /*
48 | retry and limits strategies
49 | wait time for too many processes can be long, since SIGCHLD interrupts sleep
50 | */
51 | #ifndef TESTING
52 | #define RETRY_TMOUT 150
53 | #define MAX_PROC 100
54 | #define WAIT_PROC 300
55 | #else
56 | #define RETRY_TMOUT 5
57 | #define MAX_PROC 5
58 | #define WAIT_PROC 5
59 | #endif
60 |
61 | /* inotify mask for for (r)queue directories */
62 | #define INOTIFY_MASK (IN_ATTRIB | IN_MOVED_TO | IN_MOVE_SELF | IN_DONT_FOLLOW | IN_ONLYDIR)
63 |
64 |
65 | /* inotify file descriptor and (r)queue directories watch descriptors */
66 | static int inotfd = -1, inotqwd = -1, inotrqwd = -1;
67 |
68 |
69 | /* unregister inotify watches and dispose of allocated file descriptor */
70 | static void unreg_watches() {
71 | if (inotfd != -1) {
72 | /* ignore errors due to automatically removed watches (IN_IGNORED) */
73 | if (inotqwd != -1) {
74 | if (inotify_rm_watch(inotfd, inotqwd) && errno != EINVAL)
75 | warning("failed to remove inotify watch");
76 | inotqwd = -1;
77 | }
78 |
79 | /* ignore errors due to automatically removed watches (IN_IGNORED) */
80 | if (inotrqwd != -1) {
81 | if (inotify_rm_watch(inotfd, inotrqwd) && errno != EINVAL)
82 | warning("failed to remove inotify watch");
83 | inotrqwd = -1;
84 | }
85 |
86 | /*
87 | closing/reopening an inotify fd is an expensive operation, but must be done
88 | because otherwise fd provides infinite stream of IN_IGNORED events
89 | */
90 | if (close(inotfd))
91 | warning("could not close inotify fd");
92 | inotfd = -1;
93 | }
94 | }
95 |
96 |
97 | /* register (r)queue-specific inotify watches, returning 1 if successful */
98 | static int reg_watches(const char *qpath, const char *rqpath) {
99 | /* don't block on read(), since select() is used for polling */
100 | if (inotfd == -1 && (inotfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC)) == -1) {
101 | warning("failed to initialize inotify instance");
102 | return 0;
103 | }
104 |
105 | #ifdef TESTING
106 | if (getenv("CABLE_NOWATCH"))
107 | return 1;
108 | #endif
109 |
110 | /* existing watch is ok */
111 | if ((inotqwd = inotify_add_watch(inotfd, qpath, INOTIFY_MASK)) == -1) {
112 | warning("could not add inotify watch");
113 | return 0;
114 | }
115 |
116 | /* existing watch is ok */
117 | if ((inotrqwd = inotify_add_watch(inotfd, rqpath, INOTIFY_MASK)) == -1) {
118 | warning("could not add inotify watch");
119 | return 0;
120 | }
121 |
122 | return 1;
123 | }
124 |
125 |
126 | /*
127 | try to register inotify watches, unregistering them if not compeltely successful
128 | hold an open fd during the attempt, to prevent unmount during the process
129 | */
130 | static int try_reg_watches(const char *qpath, const char *rqpath) {
131 | int mpfd, ret = 0;
132 | struct stat st;
133 |
134 | /* unregister existing inotify watches */
135 | unreg_watches();
136 |
137 | /* try to quickly open a fd (expect read access on qpath) */
138 | if ((mpfd = open(qpath, O_RDONLY | O_NONBLOCK | O_CLOEXEC)) != -1) {
139 | if (lstat(qpath, &st) == -1 || !S_ISDIR(st.st_mode))
140 | flog(LOG_NOTICE, "%s is not a directory, waiting...", qpath);
141 | else if (lstat(rqpath, &st) == -1 || !S_ISDIR(st.st_mode))
142 | flog(LOG_NOTICE, "%s is not a directory, waiting...", rqpath);
143 |
144 | /* if registering inotify watches is unsuccessful, immediately unregister */
145 | else if (reg_watches(qpath, rqpath))
146 | ret = 1;
147 | else
148 | unreg_watches();
149 |
150 |
151 | /* free the pin fd */
152 | if (close(mpfd))
153 | warning("could not close pin directory");
154 | }
155 | else
156 | flog(LOG_NOTICE, "failed to pin %s, waiting...", qpath);
157 |
158 | return ret;
159 | }
160 |
161 |
162 | /* retry registering inotify watches, using the retry strategy parameters */
163 | static void wait_reg_watches(const char *qpath, const char *rqpath) {
164 | double slp = WAIT_INIT;
165 |
166 | while (!stop_requested()) {
167 | if (try_reg_watches(qpath, rqpath)) {
168 | flog(LOG_DEBUG, "registered watches");
169 | break;
170 | }
171 | else {
172 | sleepsec(slp);
173 |
174 | slp = (slp * WAIT_MULT);
175 | if (slp > WAIT_MAX)
176 | slp = WAIT_MAX;
177 | }
178 | }
179 | }
180 |
181 |
182 | /* lower-case hexadecimal of correct length, possibly ending with ".del" */
183 | static int is_msgdir(char *s) {
184 | size_t len = strlen(s);
185 | int res = 0;
186 |
187 | if (len == MSGID_LENGTH)
188 | res = vfyhex(MSGID_LENGTH, s);
189 |
190 | else if (len == MSGID_LENGTH+4 && strcmp(".del", s + MSGID_LENGTH) == 0) {
191 | s[MSGID_LENGTH] = '\0';
192 | res = vfyhex(MSGID_LENGTH, s);
193 | s[MSGID_LENGTH] = '.';
194 | }
195 |
196 | return res;
197 | }
198 |
199 |
200 | /* run loop for given queue type and msgid[.del]; msgid is a volatile string */
201 | static void run_loop(const char *qtype, const char *msgid, const char *looppath) {
202 | const char *args[] = { looppath, qtype, msgid, NULL };
203 |
204 | if (run_process(MAX_PROC, WAIT_PROC, args))
205 | flog(LOG_INFO, "processing: %s %s", qtype, msgid);
206 | else
207 | flog(LOG_WARNING, "failed to launch: %s %s", qtype, msgid);
208 | }
209 |
210 |
211 | /* return whether an event is ready on the inotify fd, with timeout */
212 | static int wait_read(int fd, double sec) {
213 | struct timeval tv;
214 | fd_set rfds;
215 | int ret;
216 |
217 | /* support negative arguments */
218 | if (sec < 0)
219 | sec = 0;
220 |
221 | tv.tv_sec = (time_t) sec;
222 | tv.tv_usec = (suseconds_t) ((sec - tv.tv_sec) * 1e6);
223 |
224 | FD_ZERO(&rfds);
225 | FD_SET(fd, &rfds);
226 |
227 | ret = select(fd+1, &rfds, NULL, NULL, &tv);
228 |
229 | if (ret == -1 && errno != EINTR)
230 | warning("waiting on inotify queue failed");
231 | else if (ret > 0 && !(ret == 1 && FD_ISSET(fd, &rfds))) {
232 | flog(LOG_WARNING, "unexpected fd while waiting on inotify queue");
233 | ret = 0;
234 | }
235 |
236 | return ret > 0;
237 | }
238 |
239 |
240 | /*
241 | exec run_loop for all correct entries in (r)queue directory
242 | NOT thread-safe, since readdir_r is unreliable with filenames > NAME_MAX
243 | (e.g., NTFS + 255 unicode chars get truncated to 256 chars w/o terminating NUL)
244 | */
245 | static void retry_dir(const char *qtype, const char *qpath, const char *looppath) {
246 | /* [offsetof(struct dirent, d_name) + fpathconf(fd, _PC_NAME_MAX) + 1] */
247 | struct dirent *de;
248 | struct stat st;
249 | DIR *qdir;
250 | int fd, run;
251 |
252 | flog(LOG_DEBUG, "retrying %s directories", qtype);
253 |
254 | /* open directory (O_CLOEXEC is implied) */
255 | if ((qdir = opendir(qpath))) {
256 | /* get corresponding file descriptor for stat */
257 | if ((fd = dirfd(qdir)) != -1) {
258 | for (errno = 0; !stop_requested() && ((de = readdir(qdir))); ) {
259 | run = 0;
260 |
261 | /* some filesystems don't support d_type, need to stat entry */
262 | if (de->d_type == DT_UNKNOWN && is_msgdir(de->d_name)) {
263 | if (!fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW))
264 | run = S_ISDIR(st.st_mode);
265 | else
266 | warning("fstat failed");
267 | }
268 | else
269 | run = (de->d_type == DT_DIR && is_msgdir(de->d_name));
270 |
271 | if (run)
272 | run_loop(qtype, de->d_name, looppath);
273 | }
274 |
275 | if (errno && errno != EINTR)
276 | warning("reading directory failed");
277 | }
278 | else
279 | warning("dirfd failed");
280 |
281 | /* close directory */
282 | if (closedir(qdir))
283 | warning("could not close directory");
284 | }
285 | else
286 | warning("could not open directory");
287 | }
288 |
289 |
290 | int main() {
291 | /* using NAME_MAX prevents EINVAL on read() (twice for UTF-16 on NTFS) */
292 | char buf[sizeof(struct inotify_event) + NAME_MAX*2 + 1];
293 | char *crtpath, *qpath, *rqpath, *looppath, *lsthost, *lstport;
294 | int sz, offset, rereg, evqok, retryid;
295 | struct inotify_event *iev;
296 | double retrytmout, lastclock;
297 |
298 |
299 | /* init logging */
300 | syslog_init();
301 |
302 |
303 | /* extract environment */
304 | crtpath = alloc_env(CABLE_CERTS, "/" CERTS_NAME);
305 | qpath = alloc_env(CABLE_QUEUES, "/" QUEUE_NAME);
306 | rqpath = alloc_env(CABLE_QUEUES, "/" RQUEUE_NAME);
307 | looppath = alloc_env(CABLE_HOME, "/" LOOP_NAME);
308 | lsthost = alloc_env(CABLE_HOST, "");
309 | lstport = alloc_env(CABLE_PORT, "");
310 |
311 |
312 | /* initialize rng */
313 | if (!rand_init())
314 | warning("failed to initialize RNG");
315 |
316 |
317 | /* initialize process accounting */
318 | if (!init_process_acc())
319 | warning("failed to initialize process accounting");
320 |
321 |
322 | /* initialize webserver */
323 | if (!init_server(crtpath, qpath, rqpath, lsthost, lstport)) {
324 | flog(LOG_ERR, "failed to initialize webserver");
325 | return EXIT_FAILURE;
326 | }
327 |
328 |
329 | /* try to reregister watches as long as no signal caught */
330 | for (lastclock = getmontime(), retryid = 0; !stop_requested(); ) {
331 | /* support empty CABLE_NOLOOP when testing, to act as pure server */
332 | #ifdef TESTING
333 | if (getenv("CABLE_NOLOOP")) {
334 | sleepsec(RETRY_TMOUT);
335 | continue;
336 | }
337 | #endif
338 |
339 | wait_reg_watches(qpath, rqpath);
340 |
341 | /* read events as long as no signal caught and no unmount / move_self / etc. events read */
342 | for (rereg = evqok = 0; !stop_requested() && !rereg; ) {
343 | /* wait for an event, or timeout (later blocking read() results in error) */
344 | retrytmout = RETRY_TMOUT + RETRY_TMOUT * (rand_shift() / 2);
345 |
346 | if (wait_read(inotfd, retrytmout - (getmontime() - lastclock))) {
347 | /* read events (non-blocking), taking care to handle interrupts due to signals */
348 | if ((sz = read(inotfd, buf, sizeof(buf))) == -1 && errno != EINTR) {
349 | /* happens buffer is too small (e.g., NTFS + 255 unicode chars) */
350 | warning("error while reading from inotify queue");
351 | rereg = 1;
352 | }
353 |
354 | /* process all events in buffer, sz = -1 and 0 are automatically ignored */
355 | for (offset = 0; offset < sz && !stop_requested() && !rereg; evqok = 1) {
356 | /* get handler to next event in read buffer, and update offset */
357 | iev = (struct inotify_event*) (buf + offset);
358 | offset += sizeof(struct inotify_event) + iev->len;
359 |
360 | /*
361 | IN_IGNORED is triggered by watched directory removal / fs unmount
362 | IN_MOVE_SELF is only triggered by move of actual watched directory
363 | (i.e., not its parent)
364 | */
365 | if ((iev->mask & (IN_IGNORED | IN_UNMOUNT | IN_Q_OVERFLOW | IN_MOVE_SELF)))
366 | rereg = 1;
367 |
368 | /* ignore non-subdirectory events, and events with incorrect name */
369 | else if (iev->len > 0 && (iev->mask & IN_ISDIR) && is_msgdir(iev->name)) {
370 | assert(iev->wd == inotqwd || iev->wd == inotrqwd);
371 | if (iev->wd == inotqwd || iev->wd == inotrqwd) {
372 | /* stop can be indicated here (while waiting for less processes) */
373 | const char *qtype = (iev->wd == inotqwd) ? QUEUE_NAME : RQUEUE_NAME;
374 | run_loop(qtype, iev->name, looppath);
375 | }
376 | else
377 | flog(LOG_WARNING, "unknown watch descriptor");
378 | }
379 | }
380 | }
381 |
382 | /*
383 | if sufficient time passed since last retries, retry again
384 | */
385 | if (!stop_requested() && getmontime() - lastclock >= retrytmout) {
386 | /* alternate between queue dirs to prevent lock starvation on self-send */
387 | if ((retryid ^= 1))
388 | retry_dir(QUEUE_NAME, qpath, looppath);
389 | else
390 | retry_dir(RQUEUE_NAME, rqpath, looppath);
391 |
392 | lastclock = getmontime();
393 |
394 | /* inotify is apparently unreliable on fuse, so reregister when no events */
395 | if (!evqok)
396 | rereg = 1;
397 | evqok = 0;
398 | }
399 | }
400 | }
401 |
402 |
403 | unreg_watches();
404 |
405 | if (!shutdown_server())
406 | flog(LOG_WARNING, "failed to shutdown webserver");
407 |
408 | dealloc_env(lstport);
409 | dealloc_env(lsthost);
410 | dealloc_env(looppath);
411 | dealloc_env(rqpath);
412 | dealloc_env(qpath);
413 | dealloc_env(crtpath);
414 |
415 | flog(LOG_INFO, "exiting");
416 | closelog();
417 |
418 | return EXIT_SUCCESS;
419 | }
420 |
--------------------------------------------------------------------------------
/src/daemon.h:
--------------------------------------------------------------------------------
1 | #ifndef DAEMON_H
2 | #define DAEMON_H
3 |
4 | /* common constants */
5 | #define VERSION "LIBERTE CABLE 3.0"
6 |
7 | #define MSGID_LENGTH 40
8 | #define USERNAME_LENGTH 32
9 |
10 | #endif
11 |
--------------------------------------------------------------------------------
/src/hex2base32.c:
--------------------------------------------------------------------------------
1 | /*
2 | http://tools.ietf.org/html/rfc3548
3 | http://en.wikipedia.org/wiki/Base32
4 | */
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | int main(int argc, char *argv[]) {
11 | char *hex, *b32;
12 | int len, i, j, digit, sum;
13 |
14 | if (argc != 2) {
15 | printf("%s \n", argv[0]);
16 | return 1;
17 | }
18 |
19 | hex = argv[1];
20 | len = strlen(hex);
21 |
22 | if (len == 0 || len % 5 != 0) {
23 | printf("Argument length must be a positive multiple of 5\n");
24 | return 1;
25 | }
26 |
27 | b32 = (char*) malloc(len/5 * 4 + 1);
28 | if (!b32) {
29 | printf("Memory allocation error\n");
30 | return 1;
31 | }
32 | b32[len/5 * 4] = '\0';
33 |
34 | for (i = 0; i < len; ) {
35 | for (j = sum = 0; j < 5; ++i, ++j) {
36 | if (hex[i] >= '0' && hex[i] <= '9')
37 | digit = hex[i] - '0';
38 | else if (hex[i] >= 'a' && hex[i] <= 'f')
39 | digit = hex[i] - 'a' + 10;
40 | else if (hex[i] >= 'A' && hex[i] <= 'F')
41 | digit = hex[i] - 'A' + 10;
42 | else {
43 | printf("Non-hexadecimal character encountered\n");
44 | return 1;
45 | }
46 |
47 | sum = sum * 16 + digit;
48 | }
49 |
50 | for (j = 1; j <= 4; ++j) {
51 | digit = sum % 32;
52 | sum /= 32;
53 |
54 | if (digit < 26)
55 | digit += 'a';
56 | else
57 | digit = digit - 26 + '2';
58 |
59 | b32[i/5 * 4 - j] = digit;
60 | }
61 | }
62 |
63 | puts(b32);
64 |
65 | free(b32);
66 | return 0;
67 | }
68 |
--------------------------------------------------------------------------------
/src/mhdrop.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 |
17 | /* flock timeout */
18 | #define LOCK_TMOUT 300
19 |
20 | #define NOT_NUM ULLONG_MAX
21 | #define NUM_LEN 20
22 | typedef unsigned long long num_t;
23 |
24 |
25 | static volatile int alrm = 0;
26 |
27 |
28 | /* logging init */
29 | static void syslog_init() {
30 | openlog("mhdrop", LOG_PID, LOG_MAIL);
31 | }
32 |
33 | /* logging */
34 | static void flog(int priority, const char *format, ...) {
35 | va_list ap;
36 |
37 | va_start(ap, format);
38 |
39 | #ifndef TESTING
40 | vsyslog(priority, format, ap);
41 | #else
42 | fprintf(stderr, "[%d] cable: ", priority);
43 | vfprintf(stderr, format, ap);
44 | fprintf(stderr, "\n");
45 | #endif
46 |
47 | va_end(ap);
48 | }
49 |
50 | static void flogexit(int priority, const char *format, ...) {
51 | va_list ap;
52 |
53 | va_start(ap, format);
54 |
55 | #ifndef TESTING
56 | vsyslog(priority, format, ap);
57 | #else
58 | fprintf(stderr, "[%d] cable: ", priority);
59 | vfprintf(stderr, format, ap);
60 | fprintf(stderr, "\n");
61 | #endif
62 |
63 | va_end(ap);
64 |
65 | exit(EXIT_FAILURE);
66 | }
67 |
68 |
69 | /* support for flock timeout */
70 | static void alrm_handler(int signum) {
71 | if (signum == SIGALRM)
72 | alrm = 1;
73 | }
74 |
75 |
76 | /* fatal errors which shouldn't happen in a correct program */
77 | static void error() {
78 | flogexit(LOG_ERR, "%m");
79 | }
80 |
81 |
82 | /* set signal handlers */
83 | static void set_signals() {
84 | struct sigaction sa;
85 |
86 | sa.sa_handler = alrm_handler;
87 | sa.sa_flags = 0;
88 | sa.sa_restorer = NULL;
89 | sigemptyset(&sa.sa_mask);
90 |
91 | if (sigaction(SIGALRM, &sa, NULL) == -1)
92 | error();
93 | }
94 |
95 |
96 | /*
97 | convert file name to a number, if possible
98 | if not possible, NOT_NUM is returned
99 | */
100 | static num_t getidx(const char *name) {
101 | char *end;
102 | size_t len, i;
103 | num_t num;
104 |
105 | len = strlen(name);
106 |
107 | /* check [1-9][0-9]* format */
108 | if (!*name || name[0] == '0')
109 | return NOT_NUM;
110 |
111 | for (i = 0; i < len; ++i)
112 | if (!(name[i] >= '0' && name[i] <= '9'))
113 | return NOT_NUM;
114 |
115 | /* convert to number, checking overflow / would-be overflow */
116 | errno = 0;
117 | num = strtoull(name, &end, 10);
118 | if (*end || errno || num == ULLONG_MAX)
119 | return NOT_NUM;
120 |
121 | return num;
122 | }
123 |
124 |
125 | /*
126 | locks are automatically released on program exit,
127 | so don't bother with unlocking on exceptions
128 | */
129 | int main(int argc, char *argv[]) {
130 | const char *mhdir;
131 | struct dirent *de = NULL;
132 | DIR *dir;
133 | int dfd, i, spret;
134 | num_t maxidx = 0, curidx;
135 | char numname[NUM_LEN+1];
136 |
137 | if (argc < 3) {
138 | fprintf(stderr, "%s ...\n", argv[0]);
139 | return 1;
140 | }
141 |
142 | mhdir = argv[1];
143 |
144 | /* init logging */
145 | syslog_init();
146 |
147 | /* signals */
148 | set_signals();
149 |
150 | /* open directory */
151 | if ((dir = opendir(mhdir)) == NULL)
152 | error();
153 |
154 | /* get corresponding file descriptor for lock / access */
155 | if ((dfd = dirfd(dir)) == -1)
156 | error();
157 |
158 | /* lock directory with timeout */
159 | alarm(LOCK_TMOUT);
160 | if (flock(dfd, LOCK_EX) == -1) {
161 | if (errno == EINTR && alrm)
162 | flogexit(LOG_ERR, "timed out while locking %s", mhdir);
163 | else
164 | error();
165 | }
166 | alarm(0);
167 |
168 | /* find max entry, don't bother with file types */
169 | for (errno = 0; (de = readdir(dir)) != NULL; )
170 | if ((curidx = getidx(de->d_name)) != NOT_NUM && curidx > maxidx)
171 | maxidx = curidx;
172 |
173 | if (de == NULL && errno)
174 | error();
175 |
176 |
177 | /* deliver messages */
178 | for (i = 2, ++maxidx; i < argc; ++i, ++maxidx) {
179 | if (maxidx == NOT_NUM || maxidx == 0)
180 | flogexit(LOG_ERR, "indexes exhausted, %llu not legal", maxidx);
181 |
182 | /*
183 | convert to string, but also check back due to possible locale-related problems
184 | (unlikely if setlocale() is not explicitly invoked
185 | */
186 | spret = snprintf(numname, sizeof(numname), "%llu", maxidx);
187 | if (spret < 0 || spret >= sizeof(numname) || getidx(numname) != maxidx)
188 | flogexit(LOG_ERR, "could not convert %llu to file name", maxidx);
189 |
190 | /* rename() may replace an externally created file, so use link/unlink */
191 | if (linkat(AT_FDCWD, argv[i], dfd, numname, 0) == -1)
192 | error();
193 | if (unlink(argv[i]) == -1)
194 | error();
195 |
196 | flog(LOG_INFO, "delivered %s/%s", mhdir, numname);
197 | }
198 |
199 |
200 | /* unlock (explicitly) */
201 | if (flock(dfd, LOCK_UN) == -1)
202 | error();
203 |
204 | /* close directory */
205 | if (closedir(dir) == -1)
206 | error();
207 |
208 | closelog();
209 | return 0;
210 | }
211 |
--------------------------------------------------------------------------------
/src/process.c:
--------------------------------------------------------------------------------
1 | /*
2 | NOT thread-safe!
3 | */
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "process.h"
14 | #include "util.h"
15 |
16 |
17 | /* fast shutdown indicator */
18 | static volatile int stop;
19 |
20 |
21 | /*
22 | process counters (not used if initok is 0)
23 | pstarted: incremented in run_process()
24 | pfinished: incremented in chld_handler()
25 | */
26 | static volatile long pstarted = 0, pfinished = 0;
27 | static int initok;
28 |
29 |
30 | int stop_requested() {
31 | return stop;
32 | }
33 |
34 |
35 | static void stop_handler(int signum) {
36 | assert(signum == SIGINT || signum == SIGTERM);
37 | if (signum == SIGINT || signum == SIGTERM) {
38 | if (!stop) {
39 | stop = 1;
40 |
41 | #ifndef TESTING
42 | /* kill pgroup; also sends signal to self, but stop=1 prevents recursion */
43 | kill(0, SIGTERM);
44 | #endif
45 | }
46 | }
47 | }
48 |
49 |
50 | static void chld_handler(int signum) {
51 | pid_t pid;
52 | int status;
53 |
54 | assert(signum == SIGCHLD);
55 | if (signum == SIGCHLD) {
56 | /*
57 | multiple instances of pending signals are compressed, so
58 | handle all completed processes as soon as possible
59 | */
60 | while ((pid = waitpid(0, &status, WNOHANG)) > 0)
61 | ++pfinished;
62 |
63 | /* -1/ECHILD is returned for no pending completed processes */
64 | assert(pid != -1 || errno == ECHILD);
65 | }
66 | }
67 |
68 |
69 | int init_process_acc() {
70 | struct sigaction sa;
71 | memset(&sa, 0, sizeof(sa));
72 |
73 | stop = 0;
74 |
75 | /* restart system calls interrupted by SIGCHLD */
76 | sa.sa_handler = chld_handler;
77 | sa.sa_flags = SA_RESTART;
78 |
79 | /* SIGCHLD is automatically added to the mask */
80 | initok = !sigemptyset(&sa.sa_mask)
81 | && !sigaction(SIGCHLD, &sa, NULL);
82 |
83 | sa.sa_handler = stop_handler;
84 | sa.sa_flags = 0;
85 |
86 | initok = initok
87 | && !sigaction(SIGINT, &sa, NULL)
88 | && !sigaction(SIGTERM, &sa, NULL);
89 |
90 | return initok;
91 | }
92 |
93 |
94 | int run_process(long maxproc, double waitsec, const char *const argv[]) {
95 | int res = 0;
96 | long pcount;
97 | pid_t pid;
98 |
99 | /*
100 | wait if too many processes have been launched
101 | SIGCHLD from terminated processes also interrupts sleep
102 | */
103 | while (initok && !stop_requested() && (pcount = pstarted - pfinished) >= maxproc) {
104 | flog(LOG_NOTICE, "too many processes (%ld), waiting...", pcount);
105 | sleepsec(waitsec);
106 | }
107 |
108 | if (!stop_requested()) {
109 | if ((pid = fork()) == -1)
110 | warning("fork failed");
111 | else if (pid == 0) {
112 | /* modifiable strings signature seems to be historic */
113 | execvp(argv[0], (char *const *) argv);
114 |
115 | /* exits just the fork */
116 | error("loop execution failed");
117 | }
118 | else {
119 | ++pstarted;
120 | res = 1;
121 | }
122 | }
123 |
124 | return res;
125 | }
126 |
--------------------------------------------------------------------------------
/src/process.h:
--------------------------------------------------------------------------------
1 | #ifndef PROCESS_H
2 | #define PROCESS_H
3 |
4 | int init_process_acc();
5 | int run_process(long maxproc, double waitsec, const char *const argv[]);
6 |
7 | int stop_requested();
8 |
9 | #endif
10 |
--------------------------------------------------------------------------------
/src/server.c:
--------------------------------------------------------------------------------
1 | /*
2 | + / common URL prefix: CABLE_CERTS/certs/username
3 | + /certs/{ca,verify}.pem serve CABLE_CERTS/certs/{ca,verify}.pem
4 | + /queue/ serve CABLE_QUEUES/queue//message.enc
5 | + /queue/.key serve CABLE_QUEUES/queue//speer.sig
6 | + /rqueue/.key serve CABLE_QUEUES/rqueue//rpeer.sig
7 | + /request/... invoke service(...), and return answer
8 | */
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 |
17 | #define MHD_PLATFORM_H
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #ifndef EAI_ADDRFAMILY
28 | #define EAI_ADDRFAMILY -9
29 | #endif
30 |
31 | #include "server.h"
32 | #include "daemon.h"
33 | #include "service.h"
34 | #include "util.h"
35 |
36 |
37 | /* retry and limit strategies */
38 | #ifndef TESTING
39 | #define WAIT_CONN 100
40 | #define MAX_THREAD 4
41 | #else
42 | #define WAIT_CONN 10
43 | #define MAX_THREAD 2
44 | #endif
45 |
46 | /* path and url suffixes */
47 | #define USERNAME_SFX "username"
48 | #define CA_SFX "ca.pem"
49 | #define VERIFY_SFX "verify.pem"
50 | #define MESSAGE_SFX "message.enc"
51 | #define SPEER_SFX "speer.sig"
52 | #define RPEER_SFX "rpeer.sig"
53 | #define KEY_SFX ".key"
54 |
55 | /* url prefixes */
56 | #define CERTS_PFX "/certs/"
57 | #define QUEUE_PFX "/queue/"
58 | #define RQUEUE_PFX "/rqueue/"
59 | #define REQUEST_PFX "/request/"
60 |
61 | /* service responses */
62 | #define SVC_RESP_OK VERSION "\n"
63 | #define SVC_RESP_ERR VERSION ": ERROR\n"
64 |
65 |
66 | /* read-only values after server startup */
67 | static struct MHD_Daemon *mhd_daemon;
68 | static struct MHD_Response *mhd_empty, *mhd_svc_ok, *mhd_svc_err;
69 | static const char *crt_path, *cq_path, *crq_path;
70 | static char username[USERNAME_LENGTH+2];
71 |
72 |
73 | static int advance_pfx(const char **url, const char *pfx) {
74 | size_t len = strlen(pfx);
75 | int ret = 0;
76 |
77 | if (!strncmp(*url, pfx, len)) {
78 | *url += len;
79 | ret = 1;
80 | }
81 |
82 | return ret;
83 | }
84 |
85 |
86 | /*
87 | dir + [ / subdir ] + sfx
88 | */
89 | static int queue_fd(struct MHD_Connection *connection,
90 | const char *dir, const char *subdir, const char *sfx) {
91 | char path[strlen(dir) + (subdir ? strlen(subdir) + 1 : 0) + strlen(sfx) + 1];
92 | struct MHD_Response *resp;
93 | struct stat st;
94 | int ret, fd;
95 |
96 | /* construct full path */
97 | strcpy(path, dir);
98 | if (subdir) {
99 | strcat(path, "/");
100 | strcat(path, subdir);
101 | }
102 | strcat(path, sfx);
103 |
104 | if ((fd = open(path, O_RDONLY | O_CLOEXEC)) != -1) {
105 | if (!fstat(fd, &st) && ((resp = MHD_create_response_from_fd(st.st_size, fd)))) {
106 | ret = MHD_queue_response(connection, MHD_HTTP_OK, resp);
107 | MHD_destroy_response(resp);
108 | }
109 | else {
110 | close(fd);
111 | ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, mhd_empty);
112 | }
113 | }
114 | else
115 | ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, mhd_empty);
116 |
117 | return ret;
118 | }
119 |
120 |
121 | static int handle_connection(void *cls, struct MHD_Connection *connection,
122 | const char *url, const char *method, const char *version,
123 | const char *upload_data, size_t *upload_data_size,
124 | void **con_cls) {
125 | enum SVC_Status svc_status;
126 | char msgid[MSGID_LENGTH + sizeof(KEY_SFX)];
127 | int ret;
128 |
129 | /* support GET only, close connection otherwise */
130 | if ((strcmp(method, MHD_HTTP_METHOD_GET) && strcmp(method, MHD_HTTP_METHOD_HEAD))
131 | || *upload_data_size)
132 | ret = MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, mhd_empty);
133 |
134 | /* do not queue response on first call (enabled pipelining) */
135 | else if (!*con_cls) {
136 | *con_cls = "";
137 | ret = MHD_YES;
138 | }
139 |
140 | /* check / prefix, close connection if no match */
141 | else if (!(*(url++) == '/' && advance_pfx(&url, username)))
142 | ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty);
143 |
144 | /* handle username-authenticated queries */
145 | else {
146 | /* serve /certs/ files */
147 | if ( !strcmp(url, CERTS_PFX CA_SFX))
148 | ret = queue_fd(connection, crt_path, NULL, "/" CA_SFX);
149 | else if (!strcmp(url, CERTS_PFX VERIFY_SFX))
150 | ret = queue_fd(connection, crt_path, NULL, "/" VERIFY_SFX);
151 |
152 | /* serve /queue/{,.key} and /rqueue/.key */
153 | else if (advance_pfx(&url, QUEUE_PFX)) {
154 | strncpy(msgid, url, sizeof(msgid));
155 |
156 | if (!msgid[MSGID_LENGTH] && vfyhex(MSGID_LENGTH, msgid))
157 | ret = queue_fd(connection, cq_path, msgid, "/" MESSAGE_SFX);
158 | else if (!strncmp(msgid + MSGID_LENGTH, KEY_SFX, sizeof(KEY_SFX))
159 | && (msgid[MSGID_LENGTH] = '\0', vfyhex(MSGID_LENGTH, msgid)))
160 | ret = queue_fd(connection, cq_path, msgid, "/" SPEER_SFX);
161 | else
162 | ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty);
163 | }
164 | else if (advance_pfx(&url, RQUEUE_PFX)) {
165 | strncpy(msgid, url, sizeof(msgid));
166 |
167 | if (!strncmp(msgid + MSGID_LENGTH, KEY_SFX, sizeof(KEY_SFX))
168 | && (msgid[MSGID_LENGTH] = '\0', vfyhex(MSGID_LENGTH, msgid)))
169 | ret = queue_fd(connection, crq_path, msgid, "/" RPEER_SFX);
170 | else
171 | ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty);
172 | }
173 |
174 | /* handle /request/ interface */
175 | else if (advance_pfx(&url, REQUEST_PFX)) {
176 | svc_status = handle_request(url, cq_path, crq_path);
177 |
178 | switch (svc_status) {
179 | case SVC_OK:
180 | ret = MHD_queue_response(connection, MHD_HTTP_OK, mhd_svc_ok);
181 | break;
182 | case SVC_ERR:
183 | ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, mhd_svc_err);
184 | break;
185 | case SVC_BADFMT:
186 | ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, mhd_svc_err);
187 | break;
188 | default:
189 | ret = MHD_NO;
190 | }
191 | }
192 | else
193 | ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty);
194 | }
195 |
196 | return ret;
197 | }
198 |
199 |
200 | static int read_username(const char *certs) {
201 | int res = 0, len;
202 | FILE *file;
203 | char path[strlen(certs) + sizeof("/" USERNAME_SFX)];
204 |
205 | strcpy(path, certs);
206 | strcat(path, "/" USERNAME_SFX);
207 |
208 | if ((file = fopen(path, "re"))) {
209 | if(fgets(username, sizeof(username), file) && fgetc(file) == EOF) {
210 | len = strlen(username);
211 | if (username[len-1] == '\n')
212 | username[len-1] = '\0';
213 |
214 | if (vfybase32(USERNAME_LENGTH, username))
215 | res = 1;
216 | }
217 |
218 | if (fclose(file))
219 | res = 0;
220 | }
221 |
222 | return res;
223 | }
224 |
225 |
226 | static int ignore_sigpipe() {
227 | struct sigaction sa;
228 | memset(&sa, 0, sizeof(sa));
229 |
230 | /* restart system calls interrupted by SIGPIPE (if not using SIG_IGN) */
231 | sa.sa_handler = SIG_IGN;
232 | sa.sa_flags = SA_RESTART;
233 |
234 | /* SIGPIPE is automatically added to the mask */
235 | return !sigemptyset(&sa.sa_mask)
236 | && !sigaction(SIGPIPE, &sa, NULL);
237 | }
238 |
239 |
240 | int init_server(const char *certs, const char *qpath, const char *rqpath,
241 | const char *host, const char *port) {
242 | #ifdef TESTING
243 | const enum MHD_FLAG extra_flags = MHD_USE_DEBUG;
244 | #else
245 | const enum MHD_FLAG extra_flags = 0;
246 | #endif
247 | struct addrinfo addr_hints, *address;
248 | int addr_res;
249 |
250 |
251 | /* save paths */
252 | crt_path = certs;
253 | cq_path = qpath;
254 | crq_path = rqpath;
255 |
256 |
257 | /* ignore SIGPIPE, as recommended by libmicrohttpd */
258 | if (!ignore_sigpipe())
259 | warning("failed to ignore PIPE signal");
260 |
261 |
262 | if (!read_username(certs)) {
263 | flog(LOG_ERR, "could not read %s/username", certs);
264 | return 0;
265 | }
266 |
267 |
268 | /* create immutable responses */
269 | if ( !(mhd_empty = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT))
270 | || !(mhd_svc_ok = MHD_create_response_from_buffer(sizeof(SVC_RESP_OK)-1, SVC_RESP_OK, MHD_RESPMEM_PERSISTENT))
271 | || !(mhd_svc_err = MHD_create_response_from_buffer(sizeof(SVC_RESP_ERR)-1, SVC_RESP_ERR, MHD_RESPMEM_PERSISTENT))
272 | || MHD_NO == MHD_add_response_header(mhd_svc_ok, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain")
273 | || MHD_NO == MHD_add_response_header(mhd_svc_ok, MHD_HTTP_HEADER_CACHE_CONTROL, "no-cache")
274 | || MHD_NO == MHD_add_response_header(mhd_svc_err, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain"))
275 | return 0;
276 |
277 |
278 | /* translate host address */
279 | memset(&addr_hints, 0, sizeof(addr_hints));
280 | addr_hints.ai_family = AF_UNSPEC /* AF_INET causes EAI_NONAME when network is down */;
281 | addr_hints.ai_socktype = SOCK_STREAM;
282 | addr_hints.ai_protocol = IPPROTO_TCP;
283 | addr_hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_PASSIVE;
284 |
285 | if (((addr_res = getaddrinfo((*host ? host : NULL), port, &addr_hints, &address)))
286 | || (address->ai_family != AF_INET && ((addr_res = EAI_ADDRFAMILY)))) {
287 | flog(LOG_ERR, "%s", gai_strerror(addr_res));
288 | return 0;
289 | }
290 |
291 |
292 | if (!(mhd_daemon = MHD_start_daemon(
293 | MHD_USE_SELECT_INTERNALLY | MHD_USE_PEDANTIC_CHECKS | MHD_SUPPRESS_DATE_NO_CLOCK | extra_flags,
294 | /* port, ignored if sock_addr is specified, but must be non-0 */
295 | -1,
296 | /* MHD_AcceptPolicyCallback */
297 | NULL, NULL,
298 | /* MHD_AccessHandlerCallback */
299 | handle_connection, NULL,
300 | /* options section */
301 | MHD_OPTION_CONNECTION_LIMIT, (unsigned) WAIT_CONN,
302 | MHD_OPTION_THREAD_POOL_SIZE, (unsigned) MAX_THREAD,
303 | MHD_OPTION_SOCK_ADDR, address->ai_addr,
304 | MHD_OPTION_END)))
305 | return 0;
306 |
307 |
308 | freeaddrinfo(address);
309 |
310 | return 1;
311 | }
312 |
313 | int shutdown_server() {
314 | MHD_stop_daemon(mhd_daemon);
315 |
316 | MHD_destroy_response(mhd_svc_err);
317 | MHD_destroy_response(mhd_svc_ok);
318 | MHD_destroy_response(mhd_empty);
319 |
320 | return 1;
321 | }
322 |
--------------------------------------------------------------------------------
/src/server.h:
--------------------------------------------------------------------------------
1 | #ifndef SERVER_H
2 | #define SERVER_H
3 |
4 | int init_server(const char *certs, const char *qpath, const char *rqpath, const char *host, const char *port);
5 | int shutdown_server();
6 |
7 | #endif
8 |
--------------------------------------------------------------------------------
/src/service.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include