diff --git a/cable/cms b/cable/cms index f621398..6bb9b8f 100755 --- a/cable/cms +++ b/cable/cms @@ -98,13 +98,13 @@ keysdir="${ssldir}"/private # # # -# out: derive.pem, rpeer.sig +# out: derive.pem, rpeer.sig[atomic] # # <--- rpeer.sig # # # in: message, username, {ca,verify}.pem, rpeer.sig -# out: speer.sig, message.enc, {send,recv,ack}.mac +# out: speer.sig[atomic], message.enc[atomic], {send,recv,ack}.mac # # ---> speer.sig, message.enc, send.mac # @@ -119,7 +119,8 @@ keysdir="${ssldir}"/private # ---> ack.mac case ${cmd} in peer) - rm -f -- "${msgdir}"/derive.pem "${msgdir}"/rpeer.der "${msgdir}"/rpeer.sig + rm -f -- "${msgdir}"/derive.pem "${msgdir}"/rpeer.der "${msgdir}"/rpeer.sig \ + "${msgdir}"/rpeer.sig.tmp # generate ephemeral peer key openssl genpkey -paramfile "${modp18}" \ @@ -134,7 +135,8 @@ peer) -signer "${certdir}"/verify.pem \ -inkey "${keysdir}"/sign.pem \ -in "${msgdir}"/rpeer.der \ - -out "${msgdir}"/rpeer.sig + -out "${msgdir}"/rpeer.sig.tmp + mv -- "${msgdir}"/rpeer.sig.tmp "${msgdir}"/rpeer.sig rm -- "${msgdir}"/rpeer.der ;; @@ -143,7 +145,8 @@ peer) send) rm -f -- "${msgdir}"/derive.pem "${msgdir}"/speer.der "${msgdir}"/rpeer.der \ "${msgdir}"/speer.sig "${msgdir}"/shared.key "${msgdir}"/message.enc \ - "${msgdir}"/send.mac "${msgdir}"/recv.mac "${msgdir}"/ack.mac + "${msgdir}"/send.mac "${msgdir}"/recv.mac "${msgdir}"/ack.mac \ + "${msgdir}"/speer.sig.tmp "${msgdir}"/message.enc.tmp # verify certificates chain verify_certs "${msgdir}" @@ -175,7 +178,9 @@ send) -signer "${certdir}"/verify.pem \ -inkey "${keysdir}"/sign.pem \ -in "${msgdir}"/speer.der \ - -out "${msgdir}"/speer.sig + -out "${msgdir}"/speer.sig.tmp + mv -- "${msgdir}"/speer.sig.tmp "${msgdir}"/speer.sig + # deterministically derive encryption and MAC keys from shared secret enckey=`openssl dgst -mac hmac -${enchash} -macopt key:encrypt "${msgdir}"/shared.key | cut -d' ' -f2` @@ -204,7 +209,8 @@ send) openssl cms -EncryptedData_encrypt -binary -${encalg} -outform pem \ -secretkey ${enckey} \ -in "${msgdir}"/message \ - -out "${msgdir}"/message.enc + -out "${msgdir}"/message.enc.tmp + mv -- "${msgdir}"/message.enc.tmp "${msgdir}"/message.enc rm -- "${msgdir}"/derive.pem "${msgdir}"/speer.der "${msgdir}"/rpeer.der \ "${msgdir}"/shared.key diff --git a/cable/comm b/cable/comm index 312d959..eb61f69 100755 --- a/cable/comm +++ b/cable/comm @@ -1,7 +1,7 @@ #!/bin/sh -e if [ $# != 2 ]; then - echo "Format: $0 send|peer|recv|ack|fin " + echo "Format: $0 send|recv|ack|fin " exit 1 fi @@ -10,8 +10,6 @@ fi username=`cat ${CABLE_CERTS}/certs/username | tr -cd a-z2-7` queue=${CABLE_QUEUES}/queue rqueue=${CABLE_QUEUES}/rqueue -pubqueue=${CABLE_PUB}/"${username}"/queue -pubrqueue=${CABLE_PUB}/"${username}"/rqueue # Parameters cmd="$1" @@ -35,23 +33,6 @@ urlprefix() { } -# Atomic no-clobber copy -atomicnccopy() { - local src="$1" dst="$2" - - if [ ! -e "${dst}" ]; then - # prevent race condition in possible implementations of max retry-num - if cp -T "${src}" "${dst}".new && [ -e "${src}" ]; then - chmod 640 "${dst}".new - mv -T "${dst}".new "${dst}" - else - rm -f "${dst}".new - error "failed to copy ${src}" - fi - fi -} - - # MAC key extractor getmac() { local src="$1" mac= @@ -66,8 +47,6 @@ getmac() { # Sanity checks [ ${#msgid} = 40 ] || error "bad msgid" [ ${#username} = 32 ] || error "bad own username" -[ -e "${pubqueue}" ] || error "public queue does not exist" -[ -e "${pubrqueue}" ] || error "public rqueue does not exist" check_userhost() { [ ${#1} = 32 ] || error "bad username" @@ -80,10 +59,6 @@ send) # [comm loop] if [ -e ${queue}/"${msgid}"/${cmd}.ok -a ! -e ${queue}/"${msgid}"/ack.ok ]; then prefix=`urlprefix ${queue}` - - atomicnccopy ${queue}/"${msgid}"/speer.sig "${pubqueue}"/"${msgid}".key - atomicnccopy ${queue}/"${msgid}"/message.enc "${pubqueue}"/"${msgid}" - sendmac=`getmac ${queue}/"${msgid}"/send.mac` curl -sSfg "${prefix}"/snd/"${msgid}"/"${sendmac}" else @@ -91,20 +66,11 @@ send) fi ;; -peer) - # [comm loop] - if [ -e ${rqueue}/"${msgid}"/${cmd}.ok -a ! -e ${rqueue}/"${msgid}"/recv.ok ]; then - atomicnccopy ${rqueue}/"${msgid}"/rpeer.sig "${pubrqueue}"/"${msgid}".key - else - error "${cmd}.ok not found" - fi - ;; - recv) + # NOTE: dir can be renamed at any moment by [service] # [comm loop] if [ -e ${rqueue}/"${msgid}"/${cmd}.ok ]; then prefix=`urlprefix ${rqueue}` - recvmac=`getmac ${rqueue}/"${msgid}"/recv.mac` curl -sSfg "${prefix}"/rcp/"${msgid}"/"${recvmac}" else @@ -116,19 +82,14 @@ ack) # [comm loop] if [ -e ${queue}/"${msgid}"/${cmd}.ok ]; then prefix=`urlprefix ${queue}` - - rm -f "${pubqueue}"/"${msgid}" "${pubqueue}"/"${msgid}".key - ackmac=`getmac ${queue}/"${msgid}"/ack.mac` curl -sSfg "${prefix}"/ack/"${msgid}"/"${ackmac}" mv -T ${queue}/"${msgid}" ${queue}/"${msgid}".del # try to run 2nd ack variant immediately - # rm -f "${pubqueue}"/"${msgid}" # rm -r --one-file-system ${queue}/"${msgid}".del elif [ -e ${queue}/"${msgid}".del ]; then - rm -f "${pubqueue}"/"${msgid}" "${pubqueue}"/"${msgid}".key rm -r --one-file-system ${queue}/"${msgid}".del else error "${cmd}.ok or .del directory not found" @@ -138,7 +99,6 @@ ack) fin) # [comm loop] if [ -e ${rqueue}/"${msgid}".del ]; then - rm -f "${pubrqueue}"/"${msgid}".key rm -r --one-file-system ${rqueue}/"${msgid}".del else error ".del directory not found" diff --git a/cable/loop b/cable/loop index 3831821..0eec379 100755 --- a/cable/loop +++ b/cable/loop @@ -123,14 +123,9 @@ else "${comm}" recv "${msgid}" fi - if [ -e "${msgdir}"/peer.req ]; then - "${crypto}" peer "${msgid}" && \ - exec "${comm}" peer "${msgid}" - elif [ -e "${msgdir}"/peer.ok ]; then - if [ ! -e "${msgdir}"/recv.ok ]; then - exec "${comm}" peer "${msgid}" - fi - else + if [ -e "${msgdir}"/peer.req ]; then + exec "${crypto}" peer "${msgid}" + elif [ ! -e "${msgdir}"/peer.ok ]; then error "peer.req/ok not found" fi diff --git a/conf/nginx.conf b/conf/nginx.conf deleted file mode 100644 index 4d4fd06..0000000 --- a/conf/nginx.conf +++ /dev/null @@ -1,97 +0,0 @@ -# Upon boot: -# _CABLE_ must be replaced with the cables username -# "allow" lines must be uncommented -# Without the above, nginx will deny all requests - -user nginx nginx; -worker_processes 1; - -error_log /var/log/nginx/error_log crit; - -events { - worker_connections 1024; - use epoll; -} - -http { - include mime.types; - default_type application/octet-stream; - index index.html; - - client_header_timeout 10m; - client_body_timeout 10m; - send_timeout 10m; - - # NOTE: nginx doesn't support range requests for compressed data - gzip on; - gzip_comp_level 9; - gzip_proxied any; - gzip_types text/plain text/css text/xml application/x-javascript - application/xhtml+xml application/rss+xml - application/atom+xml image/svg+xml - application/x-x509-ca-cert application/octet-stream; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - - keepalive_timeout 20; - ignore_invalid_headers on; - - log_format main - '$remote_addr - $remote_user [$time_iso8601] ' - '"$request" $status $bytes_sent ' - '"$http_referer" "$http_user_agent" ' - '"$gzip_ratio"'; - # access_log /var/log/nginx/access_log main; - access_log off; - - server_tokens off; - server_name_in_redirect off; - - server { - listen 127.0.0.1 default; - server_name localhost; - - # Disable all access when substitutions are not done during boot - ## allow 127.0.0.1; - deny all; - - root /srv/www; - - location / { - deny all; - } - - # Provide access to published files (certs/, queue/, rqueue/) - location /CABLE/ { - limit_except GET { - deny all; - } - } - - # Provide access to FastCGI service - location /CABLE/request/ { - # Disable FastCGI if substitution fails during boot - if (-e $document_root/CABLE) { - fastcgi_pass unix:/var/run/fastcgi.sock-1; - } - - fastcgi_split_path_info ^(/CABLE/request/)(.*)$; - - fastcgi_param SCRIPT_FILENAME /usr/libexec/cable/service; - fastcgi_param PATH_INFO $fastcgi_path_info; - - limit_except GET { - deny all; - } - } - - # location = /nginx-status { - # stub_status on; - # access_log off; - # allow 127.0.0.1; - # deny all; - # } - } -} diff --git a/conf/profile b/conf/profile index fd015bf..d6e6f18 100644 --- a/conf/profile +++ b/conf/profile @@ -21,24 +21,26 @@ export CABLE_TOR=${CABLE_MOUNT}/security/tor export CABLE_I2P=${CABLE_MOUNT}/security/i2p # CABLE_QUEUES/(r)queue directories, must be writable by uid 'cable' -# Value must be mirrored in /etc/conf.d/spawn-fcgi.cable export CABLE_QUEUES=${CABLE_MOUNT}/cables # Mail delivery directory, must be writable by uid 'cable' export CABLE_INBOX=${CABLE_MOUNT}/mail/inbox -# CABLE_PUB//(r)queue directories, must be writable by uid 'cable' -# CABLE_PUB//{certs,(r)queue} must be readable by nginx -# Value must be mirrored in /etc/nginx/nginx.conf: "root" directive -export CABLE_PUB=/srv/www - -# Supported email-like IDs -export CABLE_REGEX='[a-z2-7]{32}@([a-z2-7]{16}\.onion|[a-z2-7]{52}\.b32\.i2p)' # Message or receipt timeout in seconds (e.g., 7 days) export CABLE_TMOUT=$((7 * 24 * 60 * 60)) +# Host and port on which cables daemon listens to HTTP connections +# (symbolic names can be used; leave host empty for wildcard bind) +export CABLE_HOST=127.0.0.1 +export CABLE_PORT=9080 + + +# Supported email-like IDs (do not modify!) +export CABLE_REGEX='[a-z2-7]{32}@([a-z2-7]{16}\.onion|[a-z2-7]{52}\.b32\.i2p)' + + # OpenSSL random seed export RANDFILE=${TMPDIR}/openssl.rnd diff --git a/conf/spawn-fcgi.cable b/conf/spawn-fcgi.cable deleted file mode 100644 index 460c9e9..0000000 --- a/conf/spawn-fcgi.cable +++ /dev/null @@ -1,25 +0,0 @@ -# Need to start before nginx -rc_before="nginx" - -# The filename is suffixed with -1, -2, ... for each child process -FCGI_SOCKET=/var/run/fastcgi.sock - -# 0.0.0.0 binds to all addresses -# Child processes use consecutive ports -FCGI_ADDRESS=127.0.0.1 -FCGI_PORT= - -FCGI_PROGRAM=/usr/sbin/fcgiwrap -FCGI_CHILDREN=1 - -FCGI_CHROOT= -FCGI_CHDIR= - -FCGI_USER=cable -FCGI_GROUP=cable - -FCGI_EXTRA_OPTIONS="-U nginx -G nginx -M 0600" - -# Additional environment variables -ALLOWED_ENV="PATH CABLE_QUEUES" -CABLE_QUEUES=/home/anon/persist/cables diff --git a/doc/cable.txt b/doc/cable.txt index 30d52a7..44ecaf0 100644 --- a/doc/cable.txt +++ b/doc/cable.txt @@ -70,13 +70,6 @@ Protocol + all code blocks are restartable (e.g., after crash) + messages and confirmations are never lost if /cables filesystem is transactional - + // transient public directory w/o list permission - /certs/{ca.pem,verify.pem} public certificates - /queue/ outgoing message - /queue/.key outgoing message 's ephemeral peer key - /rqueue/.key incoming message 's ephemeral peer key - /request/... service interface - + /cables/ private directory /queue// outgoing message work dir /rqueue// incoming message work dir @@ -86,8 +79,19 @@ Protocol + [crypto loop] writes to /cables/(r)queue, MUA inbox directory; reads from certs (public, private) directories + [fetch loop] writes to /cables/(r)queue, network - + [comm loop] writes to /{,cables}/(r)queue, network; + + [comm loop] writes to network; reads username from certs directory + + [webserver] writes to network; + reads from certs public directory, /cables/(r)queue + + +[webserver] + + / common URL prefix + + /certs/{ca,verify}.pem serve public certificates + + /queue/ serve /cables/queue//message.enc + + /queue/.key serve /cables/queue//speer.sig + + /rqueue/.key serve /cables/rqueue//rpeer.sig + + /request/... invoke service[...] and serve answer (sender) @@ -106,7 +110,7 @@ Protocol [crypto loop] + check /cables/queue//send.rdy - + prepare /cables/queue//{speer.sig,message.enc,{send,recv,ack}.mac} + + prepare /cables/queue//{speer.sig[atomic],message.enc[atomic],{send,recv,ack}.mac} + rename /cables/queue//send.rdy -> send.ok (success) + -> send.req (crypto fail) + remove /cables/queue//{message,{ca,verify}.pem,rpeer.sig} (if success) @@ -114,8 +118,6 @@ Protocol [comm loop] + check /cables/queue//send.ok + checkno /cables/queue//ack.ok - + copy /cables/queue//speer.sig -> ///queue/.key (if not exists) - + copy /cables/queue//message.enc -> ///queue/ (atomic, if not exists) + read /cables/queue//send.mac (128 hex digits) + request //request/snd// @@ -131,14 +133,9 @@ Protocol [crypto loop] + check /cables/rqueue//peer.req - + prepare /cables/rqueue//{derive.pem,rpeer.sig} + + prepare /cables/rqueue//{derive.pem,rpeer.sig[atomic]} + rename /cables/rqueue//peer.req -> peer.ok (success) - [comm loop] - + check /cables/rqueue//peer.ok - + checkno /cables/rqueue//recv.ok - + copy /cables/rqueue//rpeer.sig -> ///rqueue/.key (atomic, if not exists) - (recipient) [service] @@ -170,6 +167,7 @@ Protocol + check /cables/rqueue//recv.ok + read /cables/rqueue//recv.mac (128 hex digits) + request //request/rcp// + (dir can be renamed at any moment by [service]) (sender) @@ -189,7 +187,6 @@ Protocol [comm loop] + check /cables/queue//ack.ok - + remove ///queue/{,.key} (if exists) + read /cables/queue//ack.mac (128 hex digits) + request //request/ack// (wait for response) + rename /cables/queue/ -> .del @@ -198,20 +195,18 @@ Protocol -and/or- + check /cables/queue/.del/ - + remove ///queue/{,.key} (if exists) + remove /cables/queue/.del/ (recipient) [service] + upon ack// - + check /cables/rqueue//recv.ok + + check /cables/rqueue//recv.ok (ok if dir locked) + compare /cables/rqueue//ack.mac <-> + rename /cables/rqueue/ -> .del [comm loop] + check /cables/rqueue/.del/ - + remove ///rqueue/.key (if exists) + remove /cables/rqueue/.del/ diff --git a/makefile b/makefile index 9413097..5437880 100644 --- a/makefile +++ b/makefile @@ -1,10 +1,11 @@ # Single-source file programs to build -progs = cable/daemon cable/service cable/mhdrop cable/hex2base32 \ +progs = cable/daemon cable/mhdrop cable/hex2base32 \ cable/eeppriv.jar +objextra_daemon = obj/server.o obj/service.o +ldextra_daemon = -lrt -lmicrohttpd cpextra_EepPriv = /opt/i2p/lib/i2p.jar -ldextra_daemon = -lrt -title := $(shell grep -o 'LIBERTE CABLE [[:alnum:]._-]\+' src/service.c) +title := $(shell grep -o 'LIBERTE CABLE [[:alnum:]._-]\+' src/daemon.h) # Installation directories (override DESTDIR and/or PREFIX) @@ -22,9 +23,9 @@ CC = gcc JAVAC = javac # Modifications to compiler flags -CFLAGS += -std=c99 -Wall -pedantic -JFLAGS += -target 1.5 -deprecation -Werror -g:none -JLIBS := $(subst : ,:,$(patsubst %,%:,$(wildcard lib/*.jar))) +CFLAGS := -std=c99 -Wall -pedantic -MMD -D_FILE_OFFSET_BITS=64 -D_POSIX_C_SOURCE=200809L -D_BSD_SOURCE -DNDEBUG $(CFLAGS) +JFLAGS := -target 1.5 -deprecation -Werror -g:none $(JFLAGS) +JLIBS = $(subst : ,:,$(addsuffix :,$(wildcard lib/*.jar))) # Build rules @@ -35,10 +36,10 @@ JLIBS := $(subst : ,:,$(patsubst %,%:,$(wildcard lib/*.jar))) all: $(progs) clean: - $(RM) -r $(progs) obj/* stage + $(RM) -r $(progs) obj/* -bin/% cable/%: obj/%.o - $(CC) -o $@ $(CFLAGS) $< $(LDFLAGS) $(ldextra_$*) +cable/%: obj/%.o + $(CC) -o $@ $(CFLAGS) $< $(objextra_$*) $(LDFLAGS) $(ldextra_$*) obj/%.o: src/%.c $(CC) -c -o $@ $(CFLAGS) $< @@ -60,10 +61,15 @@ install: all install -t $(instdir)/libexec/cable cable/* install -m 644 -t $(instdir)/share/applications $(wildcard share/*.desktop) -chmod a-x $(instdir)/libexec/cable/eeppriv.jar - sed -i 's&/usr/libexec/cable\>&$(PREFIX)/libexec/cable&g' \ - $(patsubst %,$(etcdir)/cable/%,profile cabled nginx.conf) \ + sed -i 's&/usr/libexec/cable\>&$(PREFIX)/libexec/cable&g' \ + $(addprefix $(etcdir)/cable/,profile cabled) \ $(instdir)/bin/cable-send - sed -i 's&/etc/cable\>&$(ETCPREFIX)/cable&g' \ - $(etcdir)/cable/profile \ - $(patsubst %,$(instdir)/libexec/cable/%,cabled send) \ - $(patsubst %,$(instdir)/bin/%,cable-id cable-ping cable-send gen-cable-username gen-tor-hostname gen-i2p-hostname) + sed -i 's&/etc/cable\>&$(ETCPREFIX)/cable&g' \ + $(etcdir)/cable/profile \ + $(addprefix $(instdir)/libexec/cable/,cabled send) \ + $(addprefix $(instdir)/bin/,cable-id cable-ping cable-send gen-cable-username gen-tor-hostname gen-i2p-hostname) + + +# File-specific dependencies +cable/daemon: $(objextra_daemon) +-include $(wildcard obj/*.d) diff --git a/obj/.gitignore b/obj/.gitignore index 9018022..856faca 100644 --- a/obj/.gitignore +++ b/obj/.gitignore @@ -1,3 +1,4 @@ *.o +*.d *.class manifest.mf diff --git a/pkg/cables-x.y.ebuild b/pkg/cables-x.y.ebuild index 6c01631..dfc6997 100644 --- a/pkg/cables-x.y.ebuild +++ b/pkg/cables-x.y.ebuild @@ -27,9 +27,7 @@ KEYWORDS="x86 amd64" IUSE="" DEPEND="app-arch/unzip >=virtual/jdk-1.5" -RDEPEND="www-servers/nginx[http,pcre,nginx_modules_http_access,nginx_modules_http_fastcgi,nginx_modules_http_gzip,nginx_modules_http_rewrite] - www-servers/spawn-fcgi - www-misc/fcgiwrap +RDEPEND="net-libs/libmicrohttpd mail-filter/procmail net-misc/curl dev-libs/openssl @@ -52,46 +50,26 @@ src_install() { default doinitd "${D}"/etc/cable/cabled - doconfd "${D}"/etc/cable/spawn-fcgi.cable - rm "${D}"/etc/cable/{cabled,spawn-fcgi.cable} || die - dosym spawn-fcgi /etc/init.d/spawn-fcgi.cable - fperms 600 /etc/cable/nginx.conf - - # /srv/www(/cable) drwx--x--x root root - # /srv/www/cable/certs d-wx--s--T root nginx - # /srv/www/cable/(r)queue d-wx--s--T cable nginx - keepdir /srv/www/cable/{certs,{,r}queue} - fperms 3310 /srv/www/cable/{certs,{,r}queue} - fperms 711 /srv/www{,/cable} - fowners :nginx /srv/www/cable/certs - fowners cable:nginx /srv/www/cable/{,r}queue + rm "${D}"/etc/cable/cabled || die } pkg_postinst() { - elog "Remember to add cabled, nginx, and spawn-fcgi.cable to the default runlevel." - elog "You need to adjust the user-specific paths in /etc/cable/profile and set the" - elog "nginx configuration: ln -sf /etc/cable/nginx.conf /etc/nginx/nginx.conf" + elog "Remember to add 'cabled' to the default runlevel." + elog "You need to adjust the user-specific paths in /etc/cable/profile." elog "Generate cables certificates and Tor/I2P keypairs for the user:" elog " gen-cable-username" - elog " copy CABLE_CERTS/certs/*.pem to CABLE_PUB/cable/certs (group-readable)" elog " gen-tor-hostname" elog " copy CABLE_TOR/hidden_service to /var/lib/tor (readable only by 'tor')" elog " gen-i2p-hostname" elog " copy CABLE_I2P/eepsite to /var/lib/i2p (readable only by 'i2p')" - elog "Once a cables username has been generated for the user:" - elog " rename CABLE_PUB/cable to CABLE_PUB/" - elog " is located in CABLE_CERTS/certs/username" - elog " /etc/cable/nginx.conf" - elog " replace each occurrence of CABLE with " - elog " uncomment the 'allow' line" - elog "Configure Tor and I2P to forward HTTP connections to nginx:" + elog "Configure Tor and I2P to forward HTTP connections to cables daemon:" elog " /etc/tor/torrc" elog " HiddenServiceDir /var/lib/tor/hidden_service/" - elog " HiddenServicePort 80 127.0.0.1:80" + elog " HiddenServicePort 80 127.0.0.1:9080" elog " /var/lib/i2p/i2ptunnel.config" elog " tunnel.X.privKeyFile=eepsite/eepPriv.dat" elog " tunnel.X.targetHost=127.0.0.1" - elog " tunnel.X.targetPort=80" + elog " tunnel.X.targetPort=9080" elog "Finally, the user should configure the email client to run cable-send" elog "as a pipe for sending messages from addresses shown by cable-info." elog "See comments in /usr/bin/cable-send for suggested /etc/sudoers entry." diff --git a/src/daemon.c b/src/daemon.c index d1ee708..fc44b1d 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -1,23 +1,8 @@ /* The following environment variables are used (from /etc/cable/profile): - CABLE_HOME, CABLE_QUEUES + CABLE_HOME, CABLE_QUEUES, CABLE_CERTS, CABLE_HOST, CABLE_PORT */ -/* Alternative: _POSIX_C_SOURCE 200809L */ -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 -#endif - -/* DT_DIR, DT_UNKNOWN, dirfd() */ -#ifndef _BSD_SOURCE -#define _BSD_SOURCE -#endif - -/* O_DIRECTORY, O_NOFOLLOW */ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - #include #include #include @@ -38,8 +23,22 @@ #include #include +#include "daemon.h" + + +/* environment variables */ +#define CABLE_HOME "CABLE_HOME" +#define CABLE_QUEUES "CABLE_QUEUES" +#define CABLE_CERTS "CABLE_CERTS" +#define CABLE_HOST "CABLE_HOST" +#define CABLE_PORT "CABLE_PORT" + +/* executables and subdirectories */ +#define LOOP_NAME "loop" +#define QUEUE_NAME "queue" +#define RQUEUE_NAME "rqueue" +#define CERTS_NAME "certs" -#define MSGID_LENGTH 40 /* waiting strategy for inotify setup retries (e.g., after fs unmount) */ #define WAIT_INIT 2 @@ -60,15 +59,6 @@ #define WAIT_PROC 5 #endif -/* environment variables */ -#define CABLE_QUEUES "CABLE_QUEUES" -#define CABLE_HOME "CABLE_HOME" - -/* loop executable */ -#define LOOP_NAME "loop" -#define QUEUE_NAME "queue" -#define RQUEUE_NAME "rqueue" - /* inotify file descriptor and (r)queue directories watch descriptors */ static int inotfd = -1, inotqwd = -1, inotrqwd = -1; @@ -86,7 +76,7 @@ static void syslog_init() { } /* logging */ -static void flog(int priority, const char *format, ...) { +void flog(int priority, const char *format, ...) { va_list ap; va_start(ap, format); @@ -158,7 +148,7 @@ static void chld_handler(int signum) { static void unreg_watches() { if (inotfd != -1) { /* ignore errors due to automatically removed watches (IN_IGNORED) */ - if (inotqwd != -1 && inotify_rm_watch(inotfd, inotqwd) == -1 && errno != EINVAL) + if (inotqwd != -1 && inotify_rm_watch(inotfd, inotqwd) == -1 && errno != EINVAL) error(); else inotqwd = -1; @@ -216,7 +206,7 @@ static int try_reg_watches(const char *qpath, const char *rqpath) { unreg_watches(); /* try to quickly open a fd (expect read access on qpath) */ - if ((mpfd = open(qpath, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_NONBLOCK)) == -1) + if ((mpfd = open(qpath, O_RDONLY | O_NONBLOCK)) == -1) flog(LOG_NOTICE, "failed to pin %s, waiting...", qpath); else if (lstat(qpath, &st) == -1 || !S_ISDIR(st.st_mode)) @@ -303,11 +293,20 @@ static void set_signals() { if (sigaction(SIGINT, &sa, NULL) == -1 || sigaction(SIGTERM, &sa, NULL) == -1) error(); + sa.sa_handler = chld_handler; sa.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &sa, NULL) == -1) error(); + + + /* ignore SIGPIPE, as recommended for libmicrohttpd */ + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &sa, NULL) == -1) + error(); } @@ -456,7 +455,7 @@ static void retry_dir(const char *qtype, const char *qpath, const char *looppath /* allocate buffer for environment variable + suffix */ -char* alloc_env(const char *var, const char *suffix) { +static char* alloc_env(const char *var, const char *suffix) { const char *value; char *buf; size_t varlen; @@ -481,7 +480,7 @@ char* alloc_env(const char *var, const char *suffix) { int main() { /* using FILENAME_MAX prevents EINVAL on read() */ char buf[sizeof(struct inotify_event) + FILENAME_MAX+1]; - char *qpath, *rqpath, *looppath; + char *crtpath, *qpath, *rqpath, *looppath, *lsthost, *lstport; int sz, offset, rereg, evqok = 0; struct inotify_event *iev; double retrytmout, lastclock; @@ -492,7 +491,7 @@ int main() { /* init logging */ syslog_init(); - /* set INT/TERM handler */ + /* install INT/TERM/CHLD handlers and ignore PIPE */ set_signals(); /* become one's own process group (EPERM if session leader) */ @@ -501,19 +500,37 @@ int main() { error(); */ - /* initialize rng */ - rand_init(); - - /* extract environment */ + crtpath = alloc_env(CABLE_CERTS, "/" CERTS_NAME); qpath = alloc_env(CABLE_QUEUES, "/" QUEUE_NAME); rqpath = alloc_env(CABLE_QUEUES, "/" RQUEUE_NAME); looppath = alloc_env(CABLE_HOME, "/" LOOP_NAME); + lsthost = alloc_env(CABLE_HOST, ""); + lstport = alloc_env(CABLE_PORT, ""); + + + /* initialize rng */ + rand_init(); + + + /* initialize webserver */ + if (!init_server(crtpath, qpath, rqpath, lsthost, lstport)) { + flog(LOG_ERR, "failed to initialize webserver"); + exit(EXIT_FAILURE); + } /* try to reregister watches as long as no signal caught */ lastclock = getmontime(); while (!stop) { + /* support empty CABLE_NOLOOP when testing, to act as pure server */ +#ifdef TESTING + if (getenv("CABLE_NOLOOP")) { + sleepsec(RETRY_TMOUT); + continue; + } +#endif + wait_reg_watches(qpath, rqpath); /* read events as long as no signal caught and no unmount / move_self / etc. events read */ @@ -576,9 +593,17 @@ int main() { unreg_watches(); + /* initialize webserver */ + if (!shutdown_server()) + flog(LOG_WARNING, "failed to shutdown webserver"); + + + free(lstport); + free(lsthost); free(looppath); free(rqpath); free(qpath); + free(crtpath); flog(LOG_INFO, "exiting"); closelog(); diff --git a/src/daemon.h b/src/daemon.h new file mode 100644 index 0000000..b3acfee --- /dev/null +++ b/src/daemon.h @@ -0,0 +1,31 @@ +#ifndef DAEMON_H +#define DAEMON_H + +/* common constants */ +#define VERSION "LIBERTE CABLE 3.0" + +#define MSGID_LENGTH 40 +#define USERNAME_LENGTH 32 + +enum SVC_Status { + SVC_BADFMT = -1, + SVC_ERR = 0, + SVC_OK = 1 +}; + + +/* common functions: daemon */ +void flog(int priority, const char *format, ...); + + +/* common functions: server */ +int init_server(const char *certs, const char *qpath, const char *rqpath, const char *host, const char *port); +int shutdown_server(); + + +/* common functions: service */ +enum SVC_Status handle_request(const char *request, const char *queues, const char *rqueues); +int vfyhex(int sz, const char *s); +int vfybase32(int sz, const char *s); + +#endif diff --git a/src/mhdrop.c b/src/mhdrop.c index 1b06a4f..f5d0a0d 100644 --- a/src/mhdrop.c +++ b/src/mhdrop.c @@ -1,13 +1,3 @@ -/* Alternative: _POSIX_C_SOURCE 200809L */ -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 -#endif - -/* DT_DIR, DT_UNKNOWN, dirfd() */ -#ifndef _BSD_SOURCE -#define _BSD_SOURCE -#endif - #include #include #include diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..7ce2c7c --- /dev/null +++ b/src/server.c @@ -0,0 +1,294 @@ +/* + + / common URL prefix: CABLE_CERTS/certs/username + + /certs/{ca,verify}.pem serve CABLE_CERTS/certs/{ca,verify}.pem + + /queue/ serve CABLE_QUEUES/queue//message.enc + + /queue/.key serve CABLE_QUEUES/queue//speer.sig + + /rqueue/.key serve CABLE_QUEUES/rqueue//rpeer.sig + + /request/... invoke service(...), and return answer + */ + +#include +#include +#include +#include +#include +#include + +#define MHD_PLATFORM_H +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "daemon.h" + + +/* retry and limit strategies */ +#ifndef TESTING +#define WAIT_CONN 100 +#define MAX_THREAD 4 +#else +#define WAIT_CONN 10 +#define MAX_THREAD 2 +#endif + +/* path and url suffixes */ +#define USERNAME_SFX "username" +#define CA_SFX "ca.pem" +#define VERIFY_SFX "verify.pem" +#define MESSAGE_SFX "message.enc" +#define SPEER_SFX "speer.sig" +#define RPEER_SFX "rpeer.sig" +#define KEY_SFX ".key" + +/* url prefixes */ +#define CERTS_PFX "/certs/" +#define QUEUE_PFX "/queue/" +#define RQUEUE_PFX "/rqueue/" +#define REQUEST_PFX "/request/" + +/* service responses */ +#define SVC_RESP_OK VERSION "\n" +#define SVC_RESP_ERR VERSION ": ERROR\n" + + +/* read-only values after server startup */ +static struct MHD_Daemon *mhd_daemon; +static struct MHD_Response *mhd_empty, *mhd_svc_ok, *mhd_svc_err; +static const char *crt_path, *cq_path, *crq_path; +static char username[USERNAME_LENGTH+2]; + + +static int advance_pfx(const char **url, const char *pfx) { + size_t len = strlen(pfx); + int ret = 0; + + if (!strncmp(*url, pfx, len)) { + *url += len; + ret = 1; + } + + return ret; +} + + +/* + dir + [ / subdir ] + sfx +*/ +static int queue_fd(struct MHD_Connection *connection, + const char *dir, const char *subdir, const char *sfx) { + char path[strlen(dir) + (subdir ? strlen(subdir) + 1 : 0) + strlen(sfx) + 1]; + struct MHD_Response *resp; + struct stat st; + int ret, fd; + + /* construct full path */ + strcpy(path, dir); + if (subdir) { + strcat(path, "/"); + strcat(path, subdir); + } + strcat(path, sfx); + + if ((fd = open(path, O_RDONLY)) != -1) { + if (!fstat(fd, &st) && ((resp = MHD_create_response_from_fd(st.st_size, fd)))) { + ret = MHD_queue_response(connection, MHD_HTTP_OK, resp); + MHD_destroy_response(resp); + } + else { + close(fd); + ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, mhd_empty); + } + } + else + ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, mhd_empty); + + return ret; +} + + +static int handle_connection(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, size_t *upload_data_size, + void **con_cls) { + enum SVC_Status svc_status; + char msgid[MSGID_LENGTH + sizeof(KEY_SFX)]; + int ret; + + /* support GET only, close connection otherwise */ + if ((strcmp(method, MHD_HTTP_METHOD_GET) && strcmp(method, MHD_HTTP_METHOD_HEAD)) + || *upload_data_size) + ret = MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, mhd_empty); + + /* do not queue response on first call (enabled pipelining) */ + else if (!*con_cls) { + *con_cls = ""; + ret = MHD_YES; + } + + /* check / prefix, close connection if no match */ + else if (!(*(url++) == '/' && advance_pfx(&url, username))) + ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty); + + /* handle username-authenticated queries */ + else { + /* serve /certs/ files */ + if ( !strcmp(url, CERTS_PFX CA_SFX)) + ret = queue_fd(connection, crt_path, NULL, "/" CA_SFX); + else if (!strcmp(url, CERTS_PFX VERIFY_SFX)) + ret = queue_fd(connection, crt_path, NULL, "/" VERIFY_SFX); + + /* serve /queue/{,.key} and /rqueue/.key */ + else if (advance_pfx(&url, QUEUE_PFX)) { + strncpy(msgid, url, sizeof(msgid)); + + if (!msgid[MSGID_LENGTH] && vfyhex(MSGID_LENGTH, msgid)) + ret = queue_fd(connection, cq_path, msgid, "/" MESSAGE_SFX); + else if (!strncmp(msgid + MSGID_LENGTH, KEY_SFX, sizeof(KEY_SFX)) + && (msgid[MSGID_LENGTH] = '\0', vfyhex(MSGID_LENGTH, msgid))) + ret = queue_fd(connection, cq_path, msgid, "/" SPEER_SFX); + else + ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty); + } + else if (advance_pfx(&url, RQUEUE_PFX)) { + strncpy(msgid, url, sizeof(msgid)); + + if (!strncmp(msgid + MSGID_LENGTH, KEY_SFX, sizeof(KEY_SFX)) + && (msgid[MSGID_LENGTH] = '\0', vfyhex(MSGID_LENGTH, msgid))) + ret = queue_fd(connection, crq_path, msgid, "/" RPEER_SFX); + else + ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty); + } + + /* handle /request/ interface */ + else if (advance_pfx(&url, REQUEST_PFX)) { + svc_status = handle_request(url, cq_path, crq_path); + + switch (svc_status) { + case SVC_OK: + ret = MHD_queue_response(connection, MHD_HTTP_OK, mhd_svc_ok); + break; + case SVC_ERR: + ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, mhd_svc_err); + break; + case SVC_BADFMT: + ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, mhd_svc_err); + break; + default: + ret = MHD_NO; + } + } + else + ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, mhd_empty); + } + + return ret; +} + + +static int read_username(const char *certs) { + int res = 0, len; + FILE *file; + char path[strlen(certs) + sizeof("/" USERNAME_SFX)]; + + strcpy(path, certs); + strcat(path, "/" USERNAME_SFX); + + if ((file = fopen(path, "r"))) { + if(fgets(username, sizeof(username), file) && fgetc(file) == EOF) { + len = strlen(username); + if (username[len-1] == '\n') + username[len-1] = '\0'; + + if (vfybase32(USERNAME_LENGTH, username)) + res = 1; + } + + if (fclose(file)) + res = 0; + } + + return res; +} + + +int init_server(const char *certs, const char *qpath, const char *rqpath, + const char *host, const char *port) { +#ifdef TESTING + const enum MHD_FLAG extra_flags = MHD_USE_DEBUG; +#else + const enum MHD_FLAG extra_flags = 0; +#endif + struct addrinfo addr_hints, *address; + int addr_res; + + + /* save paths */ + crt_path = certs; + cq_path = qpath; + crq_path = rqpath; + + if (!read_username(certs)) { + flog(LOG_ERR, "could not read %s/username", certs); + return 0; + } + + + /* create immutable responses */ + if ( !(mhd_empty = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT)) + || !(mhd_svc_ok = MHD_create_response_from_buffer(sizeof(SVC_RESP_OK)-1, SVC_RESP_OK, MHD_RESPMEM_PERSISTENT)) + || !(mhd_svc_err = MHD_create_response_from_buffer(sizeof(SVC_RESP_ERR)-1, SVC_RESP_ERR, MHD_RESPMEM_PERSISTENT)) + || MHD_NO == MHD_add_response_header(mhd_svc_ok, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain") + || MHD_NO == MHD_add_response_header(mhd_svc_ok, MHD_HTTP_HEADER_CACHE_CONTROL, "no-cache") + || MHD_NO == MHD_add_response_header(mhd_svc_err, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain")) + return 0; + + + /* translate host address */ + memset(&addr_hints, 0, sizeof(addr_hints)); + addr_hints.ai_family = AF_INET; + addr_hints.ai_socktype = SOCK_STREAM; + addr_hints.ai_protocol = IPPROTO_TCP; + addr_hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_PASSIVE; + + if ((addr_res = getaddrinfo((*host ? host : NULL), port, &addr_hints, &address))) { + flog(LOG_ERR, "%s", gai_strerror(addr_res)); + return 0; + } + + + if (!(mhd_daemon = MHD_start_daemon( + MHD_USE_SELECT_INTERNALLY | MHD_USE_PEDANTIC_CHECKS | MHD_SUPPRESS_DATE_NO_CLOCK | extra_flags, + /* port, ignored if sock_addr is specified, but must be non-0 */ + -1, + /* MHD_AcceptPolicyCallback */ + NULL, NULL, + /* MHD_AccessHandlerCallback */ + handle_connection, NULL, + /* options section */ + MHD_OPTION_CONNECTION_LIMIT, (unsigned) WAIT_CONN, + MHD_OPTION_THREAD_POOL_SIZE, (unsigned) MAX_THREAD, + MHD_OPTION_SOCK_ADDR, address->ai_addr, + MHD_OPTION_END))) + return 0; + + + freeaddrinfo(address); + + return 1; +} + +int shutdown_server() { + MHD_stop_daemon(mhd_daemon); + + MHD_destroy_response(mhd_svc_err); + MHD_destroy_response(mhd_svc_ok); + MHD_destroy_response(mhd_empty); + + return 1; +} diff --git a/src/service.c b/src/service.c index ec2aff4..8200152 100644 --- a/src/service.c +++ b/src/service.c @@ -1,12 +1,3 @@ -/* - Returned status: OK, BADREQ, BADFMT, BADCFG, - */ - -/* Alternative: _POSIX_C_SOURCE 200809L */ -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 -#endif - #include #include #include @@ -19,48 +10,21 @@ #include #include +#include "daemon.h" -#define VERSION "LIBERTE CABLE 3.0" -#define REQVAR "PATH_INFO" -#define MAX_REQ_LENGTH 512 -/* caller shouldn't be able to differentiate OK/ERROR */ -#define OK VERSION -#define BADREQ "BADREQ" -#define BADFMT "BADFMT" -#define BADCFG "BADCFG" -#ifndef TESTING -#define ERROR OK -#else -#define ERROR "ERROR" -#endif +#define MAX_REQUEST_LENGTH 255 -#define MSGID_LENGTH 40 #define TOR_HOSTNAME_LENGTH 16 #define I2P_HOSTNAME_LENGTH 52 -#define USERNAME_LENGTH 32 #define MAC_LENGTH 128 #define DCREAT_MODE (S_IRWXU | S_IRWXG | S_IRWXO) #define FCREAT_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) -#define CABLE_QUEUES "CABLE_QUEUES" -#define QUEUE_SUBDIR "queue" -#define RQUEUE_SUBDIR "rqueue" - - -static void retstatus(const char *status) { -#ifdef TESTING - if (!strcmp(status, ERROR) && errno) - perror("Error"); -#endif - puts(status); - exit(0); -} - /* lowercase hexadecimal */ -static int vfyhex(int sz, const char *s) { +int vfyhex(int sz, const char *s) { if (strlen(s) != sz) return 0; @@ -73,7 +37,7 @@ static int vfyhex(int sz, const char *s) { /* lowercase Base-32 encoding (a-z, 2-7) */ -static int vfybase32(int sz, const char *s) { +int vfybase32(int sz, const char *s) { if (strlen(s) != sz) return 0; @@ -108,371 +72,332 @@ static int vfyhost(char *s) { } -static void write_line(int dir, const char *path, const char *s) { - int fd; +static int write_line(int dir, const char *path, const char *s) { + int res = 0, fd; FILE *file; - if ((fd = openat(dir, path, O_CREAT | O_WRONLY | O_TRUNC, FCREAT_MODE)) == -1) - retstatus(ERROR); - - if (!(file = fdopen(fd, "w"))) { - close(fd); - retstatus(ERROR); - } - - if (s) { - if(fputs(s, file) < 0 || fputc('\n', file) != '\n') { - fclose(file); - retstatus(ERROR); + if ((fd = openat(dir, path, O_CREAT | O_WRONLY | O_TRUNC, FCREAT_MODE)) != -1) { + if ((file = fdopen(fd, "w"))) { + if (s) { + if (fputs(s, file) >= 0 && fputc('\n', file) == '\n') + res = 1; + } + else + res = 1; + + if (fclose(file)) + res = 0; } + else + close(fd); } - if (fclose(file)) - retstatus(ERROR); + return res; } -static void create_file(int dir, const char *path) { - if (faccessat(dir, path, F_OK, 0)) - write_line(dir, path, NULL); -} - - -static void read_line(int dir, const char *path, char *s, int sz) { - int fd; +static int read_line(int dir, const char *path, char *s, int sz) { + int res = 0, fd; FILE *file; - if ((fd = openat(dir, path, O_RDONLY)) == -1) - retstatus(ERROR); + if ((fd = openat(dir, path, O_RDONLY)) != -1) { + if ((file = fdopen(fd, "r"))) { + if(fgets(s, sz, file) && fgetc(file) == EOF) { + sz = strlen(s); + if (s[sz-1] == '\n') + s[sz-1] = '\0'; - if (!(file = fdopen(fd, "r"))) - retstatus(ERROR); + res = 1; + } - if (s) { - if(!fgets(s, sz, file) || fgetc(file) != EOF) - retstatus(ERROR); - - sz = strlen(s); - if (s[sz-1] == '\n') - s[sz-1] = '\0'; + if (fclose(file)) + res = 0; + } + else + close(fd); } - if (fclose(file)) - retstatus(ERROR); + return res; } -static void check_file(int dir, const char *path) { - if (faccessat(dir, path, F_OK, 0)) - retstatus(ERROR); +static int check_file(int dir, const char *path) { + return !faccessat(dir, path, F_OK, 0); } -/* attemts lock; closes fd is lock would block */ -static int try_lock(int fd) { - if (!flock(fd, LOCK_EX | LOCK_NB)) - return 1; +static int create_file(int dir, const char *path) { + return check_file(dir, path) || write_line(dir, path, NULL); +} - if (errno == EWOULDBLOCK) { - if (close(fd)) - retstatus(ERROR); - } - else - retstatus(ERROR); - return 0; +/* attemts non-blocking lock (note: errno == EWOULDBLOCK) */ +static int try_lock(int fd) { + return !flock(fd, LOCK_EX | LOCK_NB); } -static void handle_msg(const char *msgid, const char *hostname, +static int handle_msg(const char *msgid, const char *hostname, const char *username, int cqdir) { + int res = 0, dorename = 0, msgdir; char msgidnew[MSGID_LENGTH+4+1]; - int msgdir; /* checkno /cables/rqueue/ (ok and skip if exists) */ - if (!faccessat(cqdir, msgid, F_OK, 0)) - return; - - /* temp base: .../cables/rqueue/.new */ - strncpy(msgidnew, msgid, MSGID_LENGTH); - strcpy(msgidnew + MSGID_LENGTH, ".new"); - - /* create directory (ok if exists) */ - if (mkdirat(cqdir, msgidnew, DCREAT_MODE) && errno != EEXIST) - retstatus(ERROR); - - if ((msgdir = openat(cqdir, msgidnew, O_RDONLY)) == -1) - retstatus(ERROR); - - /* lock temp base (ok if locked) */ - if (!try_lock(msgdir)) - return; - - /* write hostname */ - write_line(msgdir, "hostname", hostname); - - /* write username */ - write_line(msgdir, "username", username); - - /* create peer.req */ - create_file(msgdir, "peer.req"); - - /* unlock and close temp base (rename triggers loop's lock) */ - if (close(msgdir)) - retstatus(ERROR); + if (check_file(cqdir, msgid)) + res = 1; + + else if (errno == ENOENT) { + /* temp base: .../cables/rqueue/.new */ + strncpy(msgidnew, msgid, MSGID_LENGTH); + strcpy(msgidnew + MSGID_LENGTH, ".new"); + + /* create directory (ok if exists) */ + if (!mkdirat(cqdir, msgidnew, DCREAT_MODE) || errno == EEXIST) { + if ((msgdir = openat(cqdir, msgidnew, O_RDONLY)) != -1) { + /* lock temp base (skip if locked) */ + if (!try_lock(msgdir)) + res = (errno == EWOULDBLOCK); + + else + /* write hostname, username, and create peer.req */ + res = dorename = + write_line(msgdir, "hostname", hostname) + && write_line(msgdir, "username", username) + && create_file(msgdir, "peer.req"); + + /* unlock and close temp base (rename triggers loop's lock) */ + if (close(msgdir)) + res = 0; + + /* rename .../cables/rqueue/.new -> */ + if (res && dorename) + if (renameat(cqdir, msgidnew, cqdir, msgid)) + res = 0; + } + } + } - /* rename .../cables/rqueue/.new -> */ - if (renameat(cqdir, msgidnew, cqdir, msgid)) - retstatus(ERROR); + return res; } -static void handle_snd(const char *msgid, const char *mac, int cqdir) { - int msgdir; +static int handle_snd(const char *msgid, const char *mac, int cqdir) { + int res = 0, msgdir; /* base: .../cables/rqueue/ */ - if ((msgdir = openat(cqdir, msgid, O_RDONLY)) == -1) - retstatus(ERROR); - - /* lock base (ok if locked) */ - if (!try_lock(msgdir)) - return; - - /* check peer.ok */ - check_file(msgdir, "peer.ok"); - - /* write send.mac (skip if exists) */ - if (faccessat(msgdir, "send.mac", F_OK, 0)) - write_line(msgdir, "send.mac", mac); - - /* create recv.req (atomic, ok if exists) */ - if (! linkat(msgdir, "peer.ok", msgdir, "recv.req", 0)) { - /* unlock base (touch triggers loop's lock) */ - if (flock(msgdir, LOCK_UN)) - retstatus(ERROR); - - /* touch /cables/rqueue// (if recv.req didn't exist) */ - /* euid owns msgdir, so O_RDWR is not needed */ - if (futimens(msgdir, NULL)) - retstatus(ERROR); + if ((msgdir = openat(cqdir, msgid, O_RDONLY)) != -1) { + /* lock base (skip if locked) */ + if (!try_lock(msgdir)) + res = (errno == EWOULDBLOCK); + + /* check peer.ok */ + else if (check_file(msgdir, "peer.ok")) { + /* write send.mac (skip if exists) */ + if (check_file(msgdir, "send.mac") + || (errno == ENOENT && write_line(msgdir, "send.mac", mac))) { + /* create recv.req (atomic, ok if exists) */ + if (linkat(msgdir, "peer.ok", msgdir, "recv.req", 0)) + res = (errno == EEXIST); + else + res = + /* unlock base (touch triggers loop's lock) */ + !flock(msgdir, LOCK_UN) + /* touch /cables/rqueue// (if recv.req didn't exist) */ + /* euid owns msgdir, so O_RDWR is not needed */ + && !futimens(msgdir, NULL); + } + } + + /* close base (and unlock if locked) */ + if (close(msgdir)) + res = 0; } - else if (errno != EEXIST) - retstatus(ERROR); - if (close(msgdir)) - retstatus(ERROR); + return res; } -static void handle_rcp(const char *msgid, const char *mac, int cqdir) { +static int handle_rcp(const char *msgid, const char *mac, int cqdir) { + int res = 0, msgdir; char exmac[MAC_LENGTH+2]; - int msgdir; /* base: .../cables/queue/ */ - if ((msgdir = openat(cqdir, msgid, O_RDONLY)) == -1) - retstatus(ERROR); - - /* lock base (ok if locked) */ - if (!try_lock(msgdir)) - return; - - /* check send.ok */ - check_file(msgdir, "send.ok"); - - /* read recv.mac */ - read_line(msgdir, "recv.mac", exmac, sizeof(exmac)); - - /* compare <-> recv.mac */ - if (strcmp(mac, exmac)) - retstatus(ERROR); - - /* create ack.req (atomic, ok if exists) */ - if (! linkat(msgdir, "send.ok", msgdir, "ack.req", 0)) { - /* unlock base (touch triggers loop's lock) */ - if (flock(msgdir, LOCK_UN)) - retstatus(ERROR); + if ((msgdir = openat(cqdir, msgid, O_RDONLY)) != -1) { + /* lock base (skip if locked) */ + if (!try_lock(msgdir)) + res = (errno == EWOULDBLOCK); + + /* check send.ok */ + /* read recv.mac */ + /* compare <-> recv.mac */ + else if (check_file(msgdir, "send.ok") + && read_line(msgdir, "recv.mac", exmac, sizeof(exmac)) + && !strcmp(mac, exmac)) { + /* create ack.req (atomic, ok if exists) */ + if (linkat(msgdir, "send.ok", msgdir, "ack.req", 0)) + res = (errno == EEXIST); + else + res = + /* unlock base (touch triggers loop's lock) */ + !flock(msgdir, LOCK_UN) + /* touch /cables/queue// (if ack.req didn't exist) */ + /* euid owns msgdir, so O_RDWR is not needed */ + && !futimens(msgdir, NULL); + } - /* touch /cables/queue// (if ack.req didn't exist) */ - /* euid owns msgdir, so O_RDWR is not needed */ - if (futimens(msgdir, NULL)) - retstatus(ERROR); + /* close base (and unlock if locked) */ + if (close(msgdir)) + res = 0; } - else if (errno != EEXIST) - retstatus(ERROR); - if (close(msgdir)) - retstatus(ERROR); + return res; } -static void handle_ack(const char *msgid, const char *mac, int cqdir) { +static int handle_ack(const char *msgid, const char *mac, int cqdir) { + int res = 0, msgdir; char msgiddel[MSGID_LENGTH+4+1], exmac[MAC_LENGTH+2]; - int msgdir; /* base: .../cables/rqueue/ */ - if ((msgdir = openat(cqdir, msgid, O_RDONLY)) == -1) - retstatus(ERROR); - - /* check recv.ok */ - check_file(msgdir, "recv.ok"); - - /* read ack.mac */ - read_line(msgdir, "ack.mac", exmac, sizeof(exmac)); - - /* close base */ - if (close(msgdir)) - retstatus(ERROR); - - /* compare <-> ack.mac */ - if (strcmp(mac, exmac)) - retstatus(ERROR); - - /* rename .../cables/rqueue/ -> .del */ - strncpy(msgiddel, msgid, MSGID_LENGTH); - strcpy(msgiddel + MSGID_LENGTH, ".del"); - - if (renameat(cqdir, msgid, cqdir, msgiddel)) - retstatus(ERROR); -} - - -int open_cqdir(const char *subdir) { - const char *cqenv; - char *buf; - size_t varlen; - int cqdir; - - /* get queues prefix from environment */ - if (!(cqenv = getenv(CABLE_QUEUES))) - retstatus(BADCFG); - - /* allocate buffer: cqenv + '/' + subdir + '\0' */ - varlen = strlen(cqenv); - if (!(buf = (char*) malloc(varlen + strlen(subdir) + 2))) - retstatus(ERROR); - - /* fill buffer */ - strncpy(buf, cqenv, varlen); - cqenv = NULL; - buf[varlen] = '/'; - strcpy(buf + varlen + 1, subdir); - - /* get file descriptor */ - if ((cqdir = open(buf, O_RDONLY)) == -1) - retstatus(ERROR); - - free(buf); - return cqdir; -} - - -int main() { - char buf[MAX_REQ_LENGTH+1]; - const char *pathinfo, *delim = "/"; - char *cmd, *msgid, *arg1, *arg2; - int cqdir = -1; - - umask(0077); - setlocale(LC_ALL, "C"); - - - /* HTTP headers */ - printf("Content-Type: text/plain\n" - "Cache-Control: no-cache\n\n"); - - - /* Check request availability and length */ - pathinfo = getenv(REQVAR); - if (!pathinfo || strlen(pathinfo) >= sizeof(buf)) - retstatus(BADREQ); - - /* Copy request to writeable buffer */ - strcpy(buf, pathinfo); - pathinfo = NULL; - - - /* Tokenize the request */ - cmd = strtok(buf, delim); - msgid = strtok(NULL, delim); - arg1 = strtok(NULL, delim); - arg2 = strtok(NULL, delim); - - if (strtok(NULL, delim) || !cmd) - retstatus(BADFMT); - - - /* Handle commands - - ver - msg/// - snd// - rcp// - ack// - - msgid: MSGID_LENGTH lowercase xdigits - mac: MAC_LENGTH lowercase xdigits - hostname: TOR_HOSTNAME_LENGTH lowercase base-32 chars + ".onion" - I2P_HOSTNAME_LENGTH lowercase base-32 chars + ".b32.i2p" - username: USERNAME_LENGTH lowercase base-32 chars - */ - if (!strcmp("ver", cmd)) { - if (msgid) - retstatus(BADFMT); - - retstatus(VERSION); - } - else if (!strcmp("msg", cmd)) { - if (!arg2) - retstatus(BADFMT); - - if ( !vfyhex(MSGID_LENGTH, msgid) - || !vfyhost(arg1) - || !vfybase32(USERNAME_LENGTH, arg2)) - retstatus(BADFMT); - - cqdir = open_cqdir(RQUEUE_SUBDIR); - handle_msg(msgid, arg1, arg2, cqdir); + if ((msgdir = openat(cqdir, msgid, O_RDONLY)) != -1) { + /* lock base (ok if locked) */ + if (try_lock(msgdir) || (errno == EWOULDBLOCK)) + res = + /* check recv.ok */ + check_file(msgdir, "recv.ok") + /* read ack.mac */ + && read_line(msgdir, "ack.mac", exmac, sizeof(exmac)) + /* compare <-> ack.mac */ + && !strcmp(mac, exmac); + + /* close base (and unlock if locked) */ + if (close(msgdir)) + res = 0; } - else if (!strcmp("snd", cmd)) { - if (!arg1 || arg2) - retstatus(BADFMT); - if ( !vfyhex(MSGID_LENGTH, msgid) - || !vfyhex(MAC_LENGTH, arg1)) - retstatus(BADFMT); + /* rename .../cables/rqueue/ -> .del */ + if (res) { + strncpy(msgiddel, msgid, MSGID_LENGTH); + strcpy(msgiddel + MSGID_LENGTH, ".del"); - cqdir = open_cqdir(RQUEUE_SUBDIR); - handle_snd(msgid, arg1, cqdir); + if (renameat(cqdir, msgid, cqdir, msgiddel)) + res = 0; } - else if (!strcmp("rcp", cmd)) { - if (!arg1 || arg2) - retstatus(BADFMT); - - if ( !vfyhex(MSGID_LENGTH, msgid) - || !vfyhex(MAC_LENGTH, arg1)) - retstatus(BADFMT); - cqdir = open_cqdir(QUEUE_SUBDIR); - handle_rcp(msgid, arg1, cqdir); - } - else if (!strcmp("ack", cmd)) { - if (!arg1 || arg2) - retstatus(BADFMT); + return res; +} - if ( !vfyhex(MSGID_LENGTH, msgid) - || !vfyhex(MAC_LENGTH, arg1)) - retstatus(BADFMT); - cqdir = open_cqdir(RQUEUE_SUBDIR); - handle_ack(msgid, arg1, cqdir); +/* + returns memory-persistent response (including trailing newline) + thread-safe + does not leak memory / file descriptors + */ +enum SVC_Status handle_request(const char *request, const char *queues, const char *rqueues) { + enum SVC_Status status = SVC_BADFMT; + char buf[MAX_REQUEST_LENGTH+1], *saveptr, *cmd, *msgid, *arg1, *arg2; + int cqdir; + size_t reqlen; + + + /* Copy request to modifiable buffer, check for length and bad delimiters */ + reqlen = strlen(request); + if (reqlen < sizeof(buf) && reqlen > 0 + && !strstr(request, "//") + && request[0] != '/' && request[reqlen-1] != '/') { + strcpy(buf, request); + + /* Tokenize the request */ + cmd = strtok_r(buf, "/", &saveptr); + msgid = strtok_r(NULL, "/", &saveptr); + arg1 = strtok_r(NULL, "/", &saveptr); + arg2 = strtok_r(NULL, "/", &saveptr); + + if (cmd && !strtok_r(NULL, "/", &saveptr)) { + /* + ver + msg/// + snd// + rcp// + ack// + + msgid: MSGID_LENGTH lowercase xdigits + mac: MAC_LENGTH lowercase xdigits + hostname: TOR_HOSTNAME_LENGTH lowercase base-32 chars + ".onion" + I2P_HOSTNAME_LENGTH lowercase base-32 chars + ".b32.i2p" + username: USERNAME_LENGTH lowercase base-32 chars + */ + if (!strcmp("ver", cmd)) { + if (!msgid) + status = SVC_OK; + } + else if (!strcmp("msg", cmd)) { + if (arg2 + && vfyhex(MSGID_LENGTH, msgid) + && vfyhost(arg1) + && vfybase32(USERNAME_LENGTH, arg2)) { + + status = SVC_ERR; + + if ((cqdir = open(rqueues, O_RDONLY)) != -1) { + if (handle_msg(msgid, arg1, arg2, cqdir)) + status = SVC_OK; + + if (close(cqdir)) + status = SVC_ERR; + } + } + } + else if (!strcmp("snd", cmd)) { + if (arg1 && !arg2 + && vfyhex(MSGID_LENGTH, msgid) + && vfyhex(MAC_LENGTH, arg1)) { + + status = SVC_ERR; + + if ((cqdir = open(rqueues, O_RDONLY)) != -1) { + if (handle_snd(msgid, arg1, cqdir)) + status = SVC_OK; + + if (close(cqdir)) + status = SVC_ERR; + } + } + } + else if (!strcmp("rcp", cmd)) { + if (arg1 && !arg2 + && vfyhex(MSGID_LENGTH, msgid) + && vfyhex(MAC_LENGTH, arg1)) { + + status = SVC_ERR; + + if ((cqdir = open(queues, O_RDONLY)) != -1) { + if (handle_rcp(msgid, arg1, cqdir)) + status = SVC_OK; + + if (close(cqdir)) + status = SVC_ERR; + } + } + } + else if (!strcmp("ack", cmd)) { + if (arg1 && !arg2 + && vfyhex(MSGID_LENGTH, msgid) + && vfyhex(MAC_LENGTH, arg1)) { + + status = SVC_ERR; + + if ((cqdir = open(rqueues, O_RDONLY)) != -1) { + if (handle_ack(msgid, arg1, cqdir)) + status = SVC_OK; + + if (close(cqdir)) + status = SVC_ERR; + } + } + } + } } - else - retstatus(BADFMT); - - - if (close(cqdir)) - retstatus(ERROR); - retstatus(OK); - return 0; + return status; } diff --git a/test/curl b/test/curl index daeb606..bb88351 100755 --- a/test/curl +++ b/test/curl @@ -14,100 +14,22 @@ cd ${scriptdir:-./}.. root=${PWD} -# parse supported curl arguments -args=`getopt -o sSfgo: --long compressed -- "$@"` -eval set -- "${args}" -out= -url= +# Hostnames and their replacement +u1tor=http://`cat ${root}/user1/tor/hidden_service/hostname` +u1i2p=http://`cat ${root}/user1/i2p/eepsite/hostname` +u1rep=http://localhost:9081 -while :; do - case "$1" in - -o) - out="$2" - shift 2 - ;; +u2tor=http://`cat ${root}/user2/tor/hidden_service/hostname` +u2i2p=http://`cat ${root}/user2/i2p/eepsite/hostname` +u2rep=http://localhost:9082 - --) - shift - break - ;; - *) - shift - ;; - esac -done - -if [ $# != 1 ]; then - error "need single url" -fi -url="$1" - - -# parse url and determine url/request staging directory -# http://hostname/username/{certs,queue,rqueue}/ -# http://hostname/username/request/ -comps=`echo "${url}" | sed -r 's@^http://([^/]*)/([^/]*)/([^/]*)/(.*)@\1 \2 \3 \4@'` -eval set -- "${comps}" - -urlhost="$1" -urluser="$2" -urldir="$3" -urlpath="$4" - -u1tor=`cat ${root}/user1/tor/hidden_service/hostname` -u1i2p=`cat ${root}/user1/i2p/eepsite/hostname` -u1user=`cat ${root}/user1/cable/certs/username` -u2tor=`cat ${root}/user2/tor/hidden_service/hostname` -u2i2p=`cat ${root}/user2/i2p/eepsite/hostname` -u2user=`cat ${root}/user2/cable/certs/username` - -if [ "${urlhost}" = "${u1tor}" -o "${urlhost}" = "${u1i2p}" ]; then - if [ "${urluser}" != "${u1user}" ]; then - error "hostname and username do not match" - fi - urlbase=${root}/user1 - stagebase=${root}/stage1 -elif [ "${urlhost}" = "${u2tor}" -o "${urlhost}" = "${u2i2p}" ]; then - if [ "${urluser}" != "${u2user}" ]; then - error "hostname and username do not match" - fi - urlbase=${root}/user2 - stagebase=${root}/stage2 -else - error "unknown hostname" -fi - - -# set "server" environment -. ${stagebase}/etc/cable/profile - -if [ ! -e "${CABLE_PUB}/${urluser}" ]; then - error "www basedir not found" +params=`echo x "$@" | sed -r "s/^x //; s@(${u1tor}|${u1i2p})@${u1rep}@g; s@(${u2tor}|${u2i2p})@${u2rep}@g"` +if [ "x ${params}" = "$(echo x "$@")" ]; then + error "502: unknown hostname" + exit 22 fi -# handle file or request -if [ ${urldir} != request ]; then - file="${CABLE_PUB}/${urluser}/${urldir}/${urlpath}" - - if [ ! -e "${file}" ]; then - error "404: ${urldir}/${urlpath}" || : - exit 22 - fi - - if [ -n "${out}" ]; then - exec 1>"${out}" - fi - - if ! cat "${file}" 2>/dev/null; then - error "ultra-404: ${urldir}/${urlpath}" || : - exit 22 - fi -else - if [ -n "${out}" ]; then - exec 1>"${out}" - fi - - PATH_INFO="${urlpath}" ${stagebase}/libexec/cable/service | tail -n +4 -fi +eval set -- "${params}" +exec /usr/bin/curl "$@" diff --git a/test/simulate b/test/simulate index 7c27292..816c9d9 100755 --- a/test/simulate +++ b/test/simulate @@ -4,20 +4,24 @@ sinfo() { echo -e "\033[1;33;41m$@\033[0m" } -version="LIBERTE CABLE 3.0" +trap '[ $? = 0 ] || kill ${killpid1} ${killpid2} >& /dev/null' 0 +killpid1= killpid2= + scriptdir="${0%"${0##*/}"}" cd ${scriptdir:-./}.. +version="LIBERTE CABLE 3.0" -root=${PWD}/stage + +root=${TMPDIR}/stage sinfo "Root dir: ${root}" rm -rf ${root} sinfo "Building" -CFLAGS="-O2 -march=core2 -mfpmath=sse -fomit-frame-pointer -pipe -DTESTING" \ -LDFLAGS="-Wl,-O1,--as-needed,-z,combreloc" make -s +CFLAGS="-O2 -march=core2 -mfpmath=sse -fomit-frame-pointer -pipe -g -UNDEBUG -DTESTING" \ +LDFLAGS="-Wl,-O1,--as-needed,-z,combreloc" make sinfo "Installing stage1" @@ -27,12 +31,12 @@ make -s PREFIX=${root}/stage1 install sinfo "Setting stage1 paths for user1" cat >> ${root}/stage1/etc/cable/profile <> ${root}/stage2/etc/cable/profile < -# csexec -csexec() { local lock= if [ "$1" = lock ]; then - lock="flock $2/" + lock="flock $2" shift 2 fi + ${lock} ${root}/mockup/curl -sSfg "$@" http://${host}"${path}" 2>&1 \ + | sed "s/\<${version}\>/VEROK/; s/.*: //" +} + +csexec() { + local user=`cat ${root}/user1/cable/certs/username` local req="$1" - CABLE_QUEUES=${root}/user1/queues PATH_INFO="${req}" ${lock} ${root}/stage1/libexec/cable/service 2>&1 | sed '/^$\|:/d' + shift + + direxec /${user}"${req}" "$@" +} + +wsgrep() { + grep -qw "^${1}.\?\$" } +# authentication and basic requests +direxec / -d x | wsgrep 405 +direxec / | wsgrep 403 +direxec /`cat ${root}/user2/cable/certs/username` | wsgrep 403 +csexec "" | wsgrep 403 + +csexec /certs/ | wsgrep 403 +csexec /certs/xx | wsgrep 403 +csexec /request | wsgrep 403 +csexec /request/ | wsgrep 400 +csexec /request/ver | wsgrep VEROK +csexec /request/ver -I | wsgrep 'HTTP/1.1 200 OK' +csexec /request/ver/ | wsgrep 400 + + +# certs fetching +csexec /certs/ca.pem -o ${root}/ca.pem.tmp +csexec /certs/verify.pem -o ${root}/verify.pem.tmp +cmp ${root}/user1/cable/certs/ca.pem ${root}/ca.pem.tmp +cmp ${root}/user1/cable/certs/verify.pem ${root}/verify.pem.tmp +rm ${root}/{ca,verify}.pem.tmp + +chmod u-r ${root}/user1/cable/certs/ca.pem +csexec /certs/ca.pem | wsgrep 404 +chmod u+r ${root}/user1/cable/certs/ca.pem + + +# message and key fetching mid1=1111111111aaaaaaaaaa9999999999ffffffffff mid2=0000000000bbbbbbbbbb7777777777eeeeeeeeee + +csexec /queue/${mid1} | wsgrep 404 +csexec /queue/${mid1}.key | wsgrep 404 +csexec /rqueue/${mid2} | wsgrep 403 +csexec /rqueue/${mid2}.key | wsgrep 404 +csexec //queue/${mid1} | wsgrep 403 +csexec /rqueue//${mid2}.key | wsgrep 403 +csexec /queue/${mid1}0 | wsgrep 403 +csexec /queue/${mid1/a/A} | wsgrep 403 +csexec /qqueue/${mid1}.keyx | wsgrep 403 +csexec /rqueue/${mid2}.keyx | wsgrep 403 + +mkdir ${root}/user1/queues/queue/${mid1} ${root}/user1/queues/rqueue/${mid2} +echo test1 > ${root}/user1/queues/queue/${mid1}/message.enc +echo test2 > ${root}/user1/queues/queue/${mid1}/speer.sig +echo test3 > ${root}/user1/queues/rqueue/${mid2}/rpeer.sig + +csexec /queue/${mid1} -o ${root}/message.enc.tmp +csexec /queue/${mid1}.key -o ${root}/speer.sig.tmp +csexec /rqueue/${mid2}.key -o ${root}/rpeer.sig.tmp +cmp ${root}/user1/queues/queue/${mid1}/message.enc ${root}/message.enc.tmp +cmp ${root}/user1/queues/queue/${mid1}/speer.sig ${root}/speer.sig.tmp +cmp ${root}/user1/queues/rqueue/${mid2}/rpeer.sig ${root}/rpeer.sig.tmp + +chmod u-r ${root}/user1/queues/queue/${mid1}/message.enc +csexec /queue/${mid1} | wsgrep 404 + +rm -r ${root}/{message.enc,{s,r}peer.sig}.tmp \ + ${root}/user1/queues/queue/${mid1} ${root}/user1/queues/rqueue/${mid2} + + +# web service host1=o7te4msv3iexije6.onion host2=rorhxd3mqkngsj4m4y53jv42tfp2fd4bl4w7jbdeitnxw65wweaa.b32.i2p user1=25ebhnuidr6sbporsuhm43tig6oj2moo mac1=13c3fc33754e4266df492a43f8aa72a82e2ef55eb0f20051da98c8c96dfd0cef576e895b9a61211a5ee1b3057999e56db1b6ff39d5502963c0266095e4c62612 mac2=6a799bb1b80087cc23f5955551f2e56c08f69287f87fb59fdb21251d912b8d6be8791f7528f82fe7ab453f432b04ac9859d8524d01740810d87c1c6a19781e97 + # oversize long request -[ "`csexec ${mac1}${mac1}${mac1}${mac1}/`" = BADREQ ] +csexec /request/${mac1}${mac1}${mac1}${mac1} | wsgrep 400 # incorrect request formats -for req in "" \ - msg/${mid1//f/F}/${host1}/${user1} \ - msg/${mid1}/${host1//3/1}/${user1} \ - msg/${mid1}/${host2//3/8}/${user1} \ - msg/${mid1}/${host2}/${user1//o/O} \ - msg/${mid1}/${host1}/${user1}/extra \ - snd/${mid1//f/g}/${mac1} \ - rcp/${mid1}/${mac1//9/A} \ - ack/${mid1}/${mac1}0 \ - snd/${mid1}/${mac1}/extra \ - ver/extra \ - ${mac1}${mac1}${mac1}${mac1} - do [ "`csexec ${req}`" = BADFMT ] -done +csexec /request/msg/${mid1//f/F}/${host1}/${user1} | wsgrep 400 +csexec /request/msg/${mid1}/${host1//3/1}/${user1} | wsgrep 400 +csexec /request/msg/${mid1}/${host2//3/8}/${user1} | wsgrep 400 +csexec /request/msg/${mid1}/${host2}/${user1//o/O} | wsgrep 400 +csexec /request/msg/${mid1}/${host1}/${user1}/extra | wsgrep 400 +csexec /request/snd/${mid1//f/g}/${mac1} | wsgrep 400 +csexec /request/rcp/${mid1}/${mac1//9/A} | wsgrep 400 +csexec /request/ack/${mid1}/${mac1}0 | wsgrep 400 +csexec /request/snd/${mid1}/${mac1}/extra | wsgrep 400 +csexec /request/ver/extra | wsgrep 400 +csexec /request//ver | wsgrep 400 +csexec /request/ver/ | wsgrep 400 +csexec /request/snd/${mid1}//${mac1} | wsgrep 400 + # msg request (mid1, host1, user1) -[ "`csexec msg/${mid1}/${host1}/${user1}`" = "${version}" ] +csexec /request/msg/${mid1}/${host1}/${user1} | wsgrep VEROK [ -e ${root}/user1/queues/rqueue/${mid1}/peer.req ] # repeated msg request (mid1, host1, user1) [ok and skip if exists] -[ "`csexec msg/${mid1}/${host1}/${user1}`" = "${version}" ] +csexec /request/msg/${mid1}/${host1}/${user1} | wsgrep VEROK # simultaneous msg request (mid1, host2, user1) [skip if locked] mv ${root}/user1/queues/rqueue/${mid1}{,.new} -[ "`csexec lock ${root}/user1/queues/rqueue/${mid1}.new msg/${mid1}/${host2}/${user1}`" = "${version}" ] +csexec /request/msg/${mid1}/${host2}/${user1} lock ${root}/user1/queues/rqueue/${mid1}.new/ | wsgrep VEROK [ "`cat ${root}/user1/queues/rqueue/${mid1}.new/hostname`" = ${host1} ] # repeated msg request after failed msg (mid1, host2, user1) -[ "`csexec msg/${mid1}/${host2}/${user1}`" = "${version}" ] +csexec /request/msg/${mid1}/${host2}/${user1} | wsgrep VEROK [ "`cat ${root}/user1/queues/rqueue/${mid1}/hostname`" = ${host2} ] # too early snd request (mid1, mac1) [check peer.ok] -[ "`csexec snd/${mid1}/${mac1}`" = ERROR ] +csexec /request/snd/${mid1}/${mac1} | wsgrep 500 [ ! -e ${root}/user1/queues/rqueue/${mid1}/send.mac ] # simultaneous snd request (mid1, mac1) [skip if locked] touch ${root}/user1/queues/rqueue/${mid1}/peer.ok -[ "`csexec lock ${root}/user1/queues/rqueue/${mid1} snd/${mid1}/${mac1}`" = "${version}" ] +csexec /request/snd/${mid1}/${mac1} lock ${root}/user1/queues/rqueue/${mid1}/ | wsgrep VEROK [ ! -e ${root}/user1/queues/rqueue/${mid1}/send.mac ] [ ! -e ${root}/user1/queues/rqueue/${mid1}/recv.req ] # snd request (mid1, mac1) -[ "`csexec snd/${mid1}/${mac1}`" = "${version}" ] +csexec /request/snd/${mid1}/${mac1} | wsgrep VEROK [ "`cat ${root}/user1/queues/rqueue/${mid1}/send.mac`" = ${mac1} ] [ ${root}/user1/queues/rqueue/${mid1}/recv.req -ef ${root}/user1/queues/rqueue/${mid1}/peer.ok ] # repeated snd request (mid1, mac2) [write send.mac: skip if exists] -[ "`csexec snd/${mid1}/${mac2}`" = "${version}" ] +csexec /request/snd/${mid1}/${mac2} | wsgrep VEROK [ "`cat ${root}/user1/queues/rqueue/${mid1}/send.mac`" = ${mac1} ] # too early rcp request (mid2, mac1) [check send.ok] mkdir ${root}/user1/queues/queue/${mid2} echo ${mac1} > ${root}/user1/queues/queue/${mid2}/recv.mac -[ "`csexec rcp/${mid2}/${mac1}`" = ERROR ] +csexec /request/rcp/${mid2}/${mac1} | wsgrep 500 [ ! -e ${root}/user1/queues/queue/${mid2}/ack.req ] # simultaneous rcp request (mid2, mac1) [skip if locked] touch ${root}/user1/queues/queue/${mid2}/send.ok -[ "`csexec lock ${root}/user1/queues/queue/${mid2} rcp/${mid2}/${mac1}`" = "${version}" ] +csexec /request/rcp/${mid2}/${mac1} lock ${root}/user1/queues/queue/${mid2}/ | wsgrep VEROK [ ! -e ${root}/user1/queues/queue/${mid2}/ack.req ] # rcp request (mid2, mac1) -[ "`csexec rcp/${mid2}/${mac1}`" = "${version}" ] +csexec /request/rcp/${mid2}/${mac1} | wsgrep VEROK [ ${root}/user1/queues/queue/${mid2}/ack.req -ef ${root}/user1/queues/queue/${mid2}/send.ok ] # incorrect rcp request (mid2, mac2) [compare recv.mac] rm ${root}/user1/queues/queue/${mid2}/ack.req -[ "`csexec rcp/${mid2}/${mac2}`" = ERROR ] +csexec /request/rcp/${mid2}/${mac2} | wsgrep 500 [ ! -e ${root}/user1/queues/queue/${mid2}/ack.req ] # too early ack request (mid1, mac1) [check recv.ok] rm ${root}/user1/queues/rqueue/${mid1}/* echo ${mac1} > ${root}/user1/queues/rqueue/${mid1}/ack.mac -[ "`csexec ack/${mid1}/${mac1}`" = ERROR ] +csexec /request/ack/${mid1}/${mac1} | wsgrep 500 [ -e ${root}/user1/queues/rqueue/${mid1} ] # ack request (mid1, mac1) touch ${root}/user1/queues/rqueue/${mid1}/recv.ok -[ "`csexec ack/${mid1}/${mac1}`" = "${version}" ] +csexec /request/ack/${mid1}/${mac1} | wsgrep VEROK +[ -e ${root}/user1/queues/rqueue/${mid1}.del ] + +# simultaneous ack request (mid1, mac1) [ok if locked] +mv -T ${root}/user1/queues/rqueue/${mid1}{.del,} +csexec /request/ack/${mid1}/${mac1} lock ${root}/user1/queues/rqueue/${mid1}/ | wsgrep VEROK [ -e ${root}/user1/queues/rqueue/${mid1}.del ] # incorrect ack request (mid1, mac2) [compare ack.mac] mv -T ${root}/user1/queues/rqueue/${mid1}{.del,} -[ "`csexec ack/${mid1}/${mac2}`" = ERROR ] +csexec /request/ack/${mid1}/${mac2} | wsgrep 500 [ -e ${root}/user1/queues/rqueue/${mid1} ] rm -r ${root}/user1/queues/rqueue/${mid1} ${root}/user1/queues/queue/${mid2} @@ -262,7 +343,7 @@ ccloop() { ) } -sed -i 's/\/exit 222/' ${root}/mockup/curl +sed -i 's/\<22\>/222/' ${root}/stage{1,2}/libexec/cable/fetch mids=`find ${root}/user1/queues/queue -mindepth 1 -maxdepth 1 -printf '%P\n'` for mid in ${mids}; do ccloop 1 ${mid} || : @@ -273,6 +354,11 @@ for mid in ${mids}; do ccloop 1 ${mid}.del ccloop 2 ${mid}.del done +sed -i 's/\<222\>/22/' ${root}/stage{1,2}/libexec/cable/fetch + +kill ${killpid1} ${killpid2} +wait ${killpid1} ${killpid2} || : +killpid1= killpid2= sinfo "Testing daemon operation" @@ -291,8 +377,8 @@ ccdaemon() { kill ${pid1} ${pid2} wait ${pid1} ${pid2} || : - if find ${root} -path '*/www/*queue/*' | grep -q .; then - echo "www leftovers" + if find ${root} -path '*queue/*' | grep -q .; then + echo "queue leftovers" return 1 fi } @@ -308,7 +394,6 @@ fakemid=${root}/user1/queues/rqueue/012345abcd012345abcd012345abcd012345abcd.new cp -r ${faketmp} ${fakemid} touch --date="2 days ago" ${faketmp} ${fakemid} -sed -i 's/\/exit 22/' ${root}/mockup/curl ccdaemon if grep -rq Failed ${root}/user{1,2}/inbox; then @@ -331,4 +416,14 @@ if ! grep -rq Failed ${root}/user{1,2}/inbox; then fi +sinfo "Verifying valgrind statistics" +if grep 'in use at exit:' ${root}/valgrind.* | grep -v ' 0 bytes in 0 blocks'; then + false +fi + +if grep 'FILE DESCRIPTORS:' ${root}/valgrind.* | grep -v ' 4 open at exit'; then + false +fi + + sinfo "Success"