diff --git a/.gitignore b/.gitignore index 272c4eb..91d07b3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ # tuntox related, not needed in repo tuntox +tuntox_nostatic +debian/debhelper-build-stamp +debian/files +debian/tuntox.substvars +gitversion.h +*.deb diff --git a/Makefile b/Makefile index 808d57c..c4d4f97 100644 --- a/Makefile +++ b/Makefile @@ -1,51 +1,80 @@ -SOURCES = $(wildcard *.c) -DEPS=toxcore -CC=gcc -CFLAGS=-g -Wall #-std=c99 +SOURCES = client.c gitversion.c log.c mach.c main.c util.c +OBJECTS = $(SOURCES:.c=.o) +SCRIPTS = scripts/tokssh scripts/toxish +EXECUTABLES = tuntox tuntox_nostatic +DEB_VERSION = 0.0.9-1 +DEB_ARCH = amd64 +DEBS = ../tuntox_$(DEB_VERSION)_$(DEB_ARCH).deb ../tuntox-dbgsym_$(DEB_VERSION)_$(DEB_ARCH).deb +INCLUDES = client.h gitversion.h log.h mach.h main.h tox_bootstrap.h utarray.h uthash.h util.h utlist.h utstring.h +DEPS = toxcore +CC = gcc +CFLAGS = -g -W -Wall -O2 -fcompare-debug-second CFLAGS += $(shell pkg-config --cflags $(DEPS)) -LDFLAGS=-g -pthread -lm -static -lrt +LDFLAGS = -g -pthread -lm -static -lrt LDFLAGS += $(shell pkg-config --static --libs $(DEPS)) -DSO_LDFLAGS=-g -pthread -lm -lrt +DSO_LDFLAGS = -g -pthread -lm -lrt DSO_LDFLAGS += $(shell pkg-config --libs $(DEPS)) -OBJECTS=$(SOURCES:.c=.o) -INCLUDES = $(wildcard *.h) PYTHON = /usr/bin/env python3 INSTALL = install -C INSTALL_MKDIR = $(INSTALL) -d -m 755 -prefix ?= /usr -bindir ?= $(prefix)/bin +prefix = /usr +bindir = $(prefix)/bin +etcdir = /etc # Targets -all: tuntox tuntox_nostatic +.PHONY: all clean +all: $(EXECUTABLES) -gitversion.h: FORCE - @if [ -f .git/HEAD ] ; then echo " GEN $@"; echo "#define GITVERSION \"$(shell git rev-parse HEAD)\"" > $@; fi +gitversion != printf %s $$(git rev-parse HEAD) && (git diff --quiet || printf %s -dirty) +gitversion_on_disk != 2>/dev/null read _ _ v < gitversion.h && echo $$v || true +ifneq ("$(gitversion)", $(gitversion_on_disk)) +.PHONY: gitversion.h +endif -FORCE: +gitversion.h: + @echo " GEN $@" + @echo '#define GITVERSION "$(gitversion)"' > $@ -tox_bootstrap.h: - $(PYTHON) generate_tox_bootstrap.py +tox_bootstrap.h: + $(PYTHON) generate_tox_bootstrap.py %.o: %.c $(INCLUDES) gitversion.h tox_bootstrap.h @echo " CC $@" @$(CC) -c $(CFLAGS) $< -o $@ tuntox: $(OBJECTS) $(INCLUDES) - $(CC) -o $@ $(OBJECTS) -lpthread $(LDFLAGS) + $(CC) -o $@ $(OBJECTS) -lpthread $(LDFLAGS) tuntox_nostatic: $(OBJECTS) $(INCLUDES) - $(CC) -o $@ $(OBJECTS) -lpthread $(DSO_LDFLAGS) + $(CC) -o $@ $(OBJECTS) -lpthread $(DSO_LDFLAGS) cscope.out: @echo " GEN $@" @cscope -bv ./*.[ch] &> /dev/null clean: - rm -f *.o tuntox cscope.out gitversion.h tox_bootstrap.h + rm -f $(OBJECTS) $(EXECUTABLES) cscope.out gitversion.h +ifneq ($(shell id -u),0) +install: + sudo MAKEFLAGS=$(MAKEFLAGS) -- $(MAKE) install +else install: tuntox_nostatic - $(INSTALL_MKDIR) -d $(DESTDIR)$(bindir) - cp tuntox_nostatic $(DESTDIR)$(bindir)/tuntox + install -d -m755 $(DESTDIR)$(bindir) $(DESTDIR)$(etcdir) + install -d -m700 $(DESTDIR)$(etcdir)/tuntox + install -D -T tuntox_nostatic $(DESTDIR)$(bindir)/tuntox + install -D -t $(DESTDIR)$(bindir) $(SCRIPTS) + install -m0644 -D -t $(DESTDIR)$(etcdir)/systemd/system scripts/tuntox.service +ifeq ($(SKIP_SYSTEMCTL),) + systemctl daemon-reload + systemctl restart tuntox + systemctl status tuntox +endif +endif -.PHONY: all clean tuntox +.PHONY: install-debs debs +install-debs: debs + $(shell [ "$$(id -u)" = 0 ] || echo sudo) dpkg -i $(DEBS) +$(DEBS) debs: + fakeroot -- sh -c 'SKIP_SYSTEMCTL=y ./debian/rules binary' diff --git a/client.c b/client.c index 3073e5d..61d1fec 100644 --- a/client.c +++ b/client.c @@ -9,8 +9,10 @@ #include "main.h" #include "client.h" +const bool attempt_reconnect = true; + /* The state machine */ -int state = CLIENT_STATE_INITIAL; +int state = CLIENT_STATE_AWAIT_FRIENDSHIP; /* Used in ping mode */ struct timespec ping_sent_time; @@ -35,9 +37,10 @@ int handle_pong_frame() log_printf(L_INFO, "GOT PONG! Time = %.3fs\n", secs2-secs1); - if(ping_mode) + if(program_mode == Mode_Client_Ping) { - state = CLIENT_STATE_SEND_PING; + log_printf(L_INFO, "Ping mode successful. Exiting."); + exit(0); } return 0; } @@ -76,8 +79,7 @@ int local_bind() setsockopt_status = setsockopt(bind_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); if(setsockopt_status < 0) { - log_printf(L_ERROR, "Could not set socket options: %s\n", - strerror(errno)); + log_printf(L_ERROR, "Could not set socket options: %s\n", strerror(errno)); freeaddrinfo(res); exit(1); } @@ -121,38 +123,34 @@ int handle_acktunnel_frame(protocol_frame *rcvd_frame) { tunnel *tun; - if(!client_mode) - { - log_printf(L_WARNING, "Got ACK tunnel frame when not in client mode!?\n"); + switch (program_mode) { + case Mode_Client_Local_Port_Forward: + case Mode_Client_Pipe: + break; + default: + log_printf(L_WARNING, "Got ACKTUNNEL frame in unexpected mode (%d); ignoring", program_mode); return -1; } - tun = tunnel_create( - client_tunnel.sockfd, - rcvd_frame->connid, - rcvd_frame->friendnumber - ); - - /* Mark that we can accept() another connection */ - client_tunnel.sockfd = -1; - -// printf("New tunnel ID: %d\n", tun->connid); - - if(client_local_port_mode || client_pipe_mode) + if(state != CLIENT_STATE_AWAIT_TUNNEL) { - update_select_nfds(tun->sockfd); - FD_SET(tun->sockfd, &client_master_fdset); - if(client_local_port_mode) - { - log_printf(L_INFO, "Accepted a new connection on port %d\n", local_port); - } + log_printf(L_WARNING, "Got ACKTUNNEL frame in unexpected state (%d); ignoring", state); + return -1; } - else + + tun = tunnel_create(client_tunnel.sockfd, rcvd_frame->connid, rcvd_frame->friendnumber); + if (!tun) { - log_printf(L_ERROR, "This tunnel mode is not supported yet\n"); + log_printf(L_ERROR, "Got ACKTUNNEL frame but tunnel_create() failed to create new tunnel object"); exit(1); } + /* Mark that we can accept() another connection */ + client_tunnel.sockfd = -1; + + update_select_nfds(tun->sockfd); + FD_SET(tun->sockfd, &client_master_fdset); + state = CLIENT_STATE_CONNECTED; return 0; } @@ -175,25 +173,18 @@ int handle_server_tcp_frame(protocol_frame *rcvd_frame) { int sent_bytes; - if(client_pipe_mode) - { - sent_bytes = write( - 1, /* STDOUT */ - rcvd_frame->data + offset, - rcvd_frame->data_length - offset - ); - } - else - { - sent_bytes = send( - tun->sockfd, - rcvd_frame->data + offset, - rcvd_frame->data_length - offset, - MSG_NOSIGNAL - ); + switch (program_mode) { + case Mode_Client_Pipe: + sent_bytes = write(1, rcvd_frame->data + offset, rcvd_frame->data_length - offset); + break; + case Mode_Client_Local_Port_Forward: + sent_bytes = send(tun->sockfd, rcvd_frame->data + offset, rcvd_frame->data_length - offset, MSG_NOSIGNAL); + break; + default: + log_printf(L_ERROR, "BUG: Impossible client mode at %s:%s", __FILE__, __LINE__); + return -1; } - if(sent_bytes < 0) { uint8_t data[PROTOCOL_BUFFER_OFFSET]; @@ -226,7 +217,7 @@ int handle_server_tcp_frame(protocol_frame *rcvd_frame) } /* Delete tunnel and clear client-side fdset */ -void client_close_tunnel(tunnel *tun) +void client_close_tunnel(tunnel *tun) { if(tun->sockfd) { @@ -273,20 +264,122 @@ void client_close_all_connections() } } +void client_connected_loop_iteration(uint32_t friendnumber, struct tox_timer *t) +{ + static unsigned char tox_packet_buf[PROTOCOL_MAX_PACKET_SIZE]; + static fd_set fds; + + int select_rv = 0; + tunnel *tmp = NULL; + tunnel *tun = NULL; + + fds = client_master_fdset; + + /* Handle accepting new connections */ + if(client_tunnel.sockfd <= 0) /* Don't accept if we're already waiting to establish a tunnel */ + { + log_printf(L_DEBUG4, "calling accept(%d)", bind_sockfd); + int accept_fd = accept(bind_sockfd, NULL, NULL); + if(accept_fd != -1) + { + log_printf(L_INFO, "Accepting a new connection - requesting tunnel...\n"); + + /* Open a new tunnel for this FD */ + client_tunnel.sockfd = accept_fd; + send_tunnel_request_packet(remote_host, remote_port, friendnumber); + } + } + + /* Handle reading from sockets */ + select_rv = select(select_nfds, &fds, NULL, NULL, &(t->tv)); + if(select_rv == -1 || select_rv == 0) + { + if(select_rv == -1) + { + log_printf(L_DEBUG, "Reading from local socket failed: code=%d (%s)\n", errno, strerror(errno)); + } + else + { + log_printf(L_DEBUG4, "Nothing to read..."); + } + } + else + { + HASH_ITER(hh, by_id, tun, tmp) + { + if(FD_ISSET(tun->sockfd, &fds)) + { + int nbytes; + if(program_mode == Mode_Client_Local_Port_Forward) + { + nbytes = recv(tun->sockfd, + tox_packet_buf + PROTOCOL_BUFFER_OFFSET, + READ_BUFFER_SIZE, 0); + } + else + { + nbytes = read(tun->sockfd, + tox_packet_buf + PROTOCOL_BUFFER_OFFSET, + READ_BUFFER_SIZE + ); + } + + /* Check if connection closed */ + if(nbytes == 0) + { + uint8_t data[PROTOCOL_BUFFER_OFFSET]; + protocol_frame frame_st, *frame; + + log_printf(L_INFO, "Connection closed\n"); + + frame = &frame_st; + memset(frame, 0, sizeof(protocol_frame)); + frame->friendnumber = tun->friendnumber; + frame->packet_type = PACKET_TYPE_TCP_FIN; + frame->connid = tun->connid; + frame->data_length = 0; + send_frame(frame, data); + if(tun->sockfd) + { + FD_CLR(tun->sockfd, &client_master_fdset); + } + tunnel_delete(tun); + } + else + { + protocol_frame frame_st, *frame; + + frame = &frame_st; + memset(frame, 0, sizeof(protocol_frame)); + frame->friendnumber = tun->friendnumber; + frame->packet_type = PACKET_TYPE_TCP; + frame->connid = tun->connid; + frame->data_length = nbytes; + send_frame(frame, tox_packet_buf); + +// printf("Wrote %d bytes from sock %d to tunnel %d\n", nbytes, tun->sockfd, tun->connid); + } + } + } + } + + fds = client_master_fdset; + + /* Check friend connection status changes */ + if(friend_connection_status == TOX_CONNECTION_NONE) + { + log_printf(L_ERROR, "Lost connection to server. Exiting."); + exit(1); + } +} + /* Main loop for the client */ int do_client_loop(uint8_t *tox_id_str) { - unsigned char tox_packet_buf[PROTOCOL_MAX_PACKET_SIZE]; unsigned char tox_id[TOX_ADDRESS_SIZE]; uint32_t friendnumber = 0; - TOX_CONNECTION last_friend_connection_status = TOX_CONNECTION_NONE; - time_t last_friend_connection_status_received = 0; - time_t connection_lost_timestamp = 0; - struct timeval tv; - fd_set fds; static time_t invitation_sent_time = 0; uint32_t invitations_sent = 0; - TOX_ERR_FRIEND_QUERY friend_query_error; TOX_ERR_FRIEND_CUSTOM_PACKET custom_packet_error; client_tunnel.sockfd = 0; @@ -300,35 +393,27 @@ int do_client_loop(uint8_t *tox_id_str) exit(1); } - if(!ping_mode && !client_pipe_mode) + if(program_mode == Mode_Client_Local_Port_Forward) { local_bind(); signal(SIGPIPE, SIG_IGN); } - log_printf(L_INFO, "Connecting to Tox...\n"); - while(1) { - /* Let tox do its stuff */ - tox_iterate(tox, NULL); + /* Let tox do its stuff */ + tox_iterate(tox, NULL); + struct tox_timer t = init_tox_timer(tox); switch(state) { - /* + /* * Send friend request */ - case CLIENT_STATE_INITIAL: - if(connection_status != TOX_CONNECTION_NONE) - { - state = CLIENT_STATE_CONNECTED; - } - break; - case CLIENT_STATE_CONNECTED: + case CLIENT_STATE_AWAIT_FRIENDSHIP: { uint8_t* data = (uint8_t *)"Hi, fellow tuntox instance!"; uint16_t length = sizeof(data); - /* https://github.com/TokTok/c-toxcore/blob/acb6b2d8543c8f2ea0c2e60dc046767cf5cc0de8/toxcore/tox.h#L1168 */ TOX_ERR_FRIEND_ADD add_error; if(use_shared_secret) @@ -339,13 +424,13 @@ int do_client_loop(uint8_t *tox_id_str) log_printf(L_DEBUG, "Sent shared secret of length %u\n", length); } - if(invitations_sent == 0) + if(invitations_sent > 0) { - log_printf(L_INFO, "Connected. Sending friend request.\n"); + log_printf(L_INFO, "Sending friend request #%d.", invitations_sent+1); } else { - log_printf(L_INFO, "Sending another friend request.\n"); + log_printf(L_INFO, "Sending friend request."); } friendnumber = tox_friend_add( @@ -366,307 +451,80 @@ int do_client_loop(uint8_t *tox_id_str) invitation_sent_time = time(NULL); invitations_sent++; - state = CLIENT_STATE_SENTREQUEST; - log_printf(L_INFO, "Waiting for friend to accept us...\n"); + state = CLIENT_STATE_AWAIT_FRIEND_CONNECTED; + log_printf(L_INFO, "Waiting for server to accept friend request...\n"); } break; - case CLIENT_STATE_SENTREQUEST: + case CLIENT_STATE_AWAIT_FRIEND_CONNECTED: + if(friend_connection_status != TOX_CONNECTION_NONE) { - TOX_CONNECTION friend_connection_status; - friend_connection_status = tox_friend_get_connection_status(tox, friendnumber, &friend_query_error); - if(friend_query_error != TOX_ERR_FRIEND_QUERY_OK) - { - log_printf(L_DEBUG, "tox_friend_get_connection_status: error %u", friend_query_error); - } - else - { - last_friend_connection_status_received = time(NULL); - last_friend_connection_status = friend_connection_status; + const char* status = readable_connection_status(friend_connection_status); + log_printf(L_INFO, "Friend request accepted (%s)!\n", status); - if(friend_connection_status != TOX_CONNECTION_NONE) + switch (program_mode) { + case Mode_Client_Ping: + /* Send the ping packet */ + { + uint8_t data[] = { + 0xa2, 0x6a, 0x01, 0x08, 0x00, 0x00, 0x00, 0x05, + 0x48, 0x65, 0x6c, 0x6c, 0x6f + }; + clock_gettime(CLOCK_MONOTONIC, &ping_sent_time); + tox_friend_send_lossless_packet(tox, friendnumber, data, sizeof(data), &custom_packet_error); + } + if(custom_packet_error != TOX_ERR_FRIEND_CUSTOM_PACKET_OK) { - const char* status = readable_connection_status(friend_connection_status); - log_printf(L_INFO, "Friend request accepted (%s)!\n", status); - state = CLIENT_STATE_REQUEST_ACCEPTED; + log_printf(L_WARNING, "When sending ping packet: %u", custom_packet_error); + exit(1); } - else + state = CLIENT_STATE_AWAIT_PONG; + break; + case Mode_Client_Local_Port_Forward: + if(bind_sockfd < 0) { - if(1 && (time(NULL) - invitation_sent_time > 45)) - { - TOX_ERR_FRIEND_DELETE error = 0; - - log_printf(L_INFO, "Sending another friend request..."); - tox_friend_delete( - tox, - friendnumber, - &error); - if(error != TOX_ERR_FRIEND_DELETE_OK) - { - log_printf(L_ERROR, "Error %u deleting friend before reconnection\n", error); - exit(-1); - } - - state = CLIENT_STATE_CONNECTED; - } + log_printf(L_ERROR, "Shutting down - could not bind to listening port\n"); + exit(1); } + client_tunnel.sockfd = 0; + /* fall through... */ + case Mode_Client_Pipe: + send_tunnel_request_packet(remote_host, remote_port, friendnumber); + state = CLIENT_STATE_AWAIT_TUNNEL; + break; + default: + log_printf(L_ERROR, "BUG: Impossible client mode at %s:%s", __FILE__, __LINE__); + exit(1); } - break; - } - case CLIENT_STATE_REQUEST_ACCEPTED: - if(ping_mode) - { - state = CLIENT_STATE_SEND_PING; - } - else if(client_pipe_mode) - { - state = CLIENT_STATE_SETUP_PIPE; - } - else - { - state = CLIENT_STATE_BIND_PORT; - } - break; - case CLIENT_STATE_SEND_PING: - /* Send the ping packet */ - { - uint8_t data[] = { - 0xa2, 0x6a, 0x01, 0x08, 0x00, 0x00, 0x00, 0x05, - 0x48, 0x65, 0x6c, 0x6c, 0x6f - }; - - clock_gettime(CLOCK_MONOTONIC, &ping_sent_time); - tox_friend_send_lossless_packet( - tox, - friendnumber, - data, - sizeof(data), - &custom_packet_error - ); - } - if(custom_packet_error == TOX_ERR_FRIEND_CUSTOM_PACKET_OK) - { - state = CLIENT_STATE_PING_SENT; } else { - log_printf(L_WARNING, "When sending ping packet: %u", custom_packet_error); - } - break; - case CLIENT_STATE_PING_SENT: - /* Just sit there and wait for pong */ - break; - - case CLIENT_STATE_BIND_PORT: - if(bind_sockfd < 0) - { - log_printf(L_ERROR, "Shutting down - could not bind to listening port\n"); - state = CLIENT_STATE_SHUTDOWN; - } - else - { - state = CLIENT_STATE_FORWARDING; - } - break; - case CLIENT_STATE_SETUP_PIPE: - send_tunnel_request_packet( - remote_host, - remote_port, - friendnumber - ); - state = CLIENT_STATE_FORWARDING; - break; - case CLIENT_STATE_REQUEST_TUNNEL: - send_tunnel_request_packet( - remote_host, - remote_port, - friendnumber - ); - state = CLIENT_STATE_WAIT_FOR_ACKTUNNEL; - break; - case CLIENT_STATE_WAIT_FOR_ACKTUNNEL: - client_tunnel.sockfd = 0; - send_tunnel_request_packet( - remote_host, - remote_port, - friendnumber - ); - break; - case CLIENT_STATE_FORWARDING: - { - int accept_fd = 0; - int select_rv = 0; - tunnel *tmp = NULL; - tunnel *tun = NULL; - - tv.tv_sec = 0; - tv.tv_usec = 20000; - fds = client_master_fdset; - - /* Handle accepting new connections */ - if(!client_pipe_mode && - client_tunnel.sockfd <= 0) /* Don't accept if we're already waiting to establish a tunnel */ + const int INVITATION_SEND_INTERVAL = 90; + if (time(NULL) - invitation_sent_time > INVITATION_SEND_INTERVAL) { - accept_fd = accept(bind_sockfd, NULL, NULL); - if(accept_fd != -1) - { - log_printf(L_INFO, "Accepting a new connection - requesting tunnel...\n"); - - /* Open a new tunnel for this FD */ - client_tunnel.sockfd = accept_fd; - send_tunnel_request_packet( - remote_host, - remote_port, - friendnumber - ); - } - } + TOX_ERR_FRIEND_DELETE error = 0; - /* Handle reading from sockets */ - select_rv = select(select_nfds, &fds, NULL, NULL, &tv); - if(select_rv == -1 || select_rv == 0) - { - if(select_rv == -1) - { - log_printf(L_DEBUG, "Reading from local socket failed: code=%d (%s)\n", - errno, strerror(errno)); - } - else - { - log_printf(L_DEBUG2, "Nothing to read..."); - } - } - else - { - HASH_ITER(hh, by_id, tun, tmp) + log_printf(L_INFO, "Friend request timed out. Sending another..."); + tox_friend_delete(tox, friendnumber, &error); + if(error != TOX_ERR_FRIEND_DELETE_OK) { - if(FD_ISSET(tun->sockfd, &fds)) - { - int nbytes; - if(client_local_port_mode) - { - nbytes = recv(tun->sockfd, - tox_packet_buf + PROTOCOL_BUFFER_OFFSET, - READ_BUFFER_SIZE, 0); - } - else - { - nbytes = read(tun->sockfd, - tox_packet_buf + PROTOCOL_BUFFER_OFFSET, - READ_BUFFER_SIZE - ); - } - - /* Check if connection closed */ - if(nbytes == 0) - { - uint8_t data[PROTOCOL_BUFFER_OFFSET]; - protocol_frame frame_st, *frame; - - log_printf(L_INFO, "Connection closed\n"); - - frame = &frame_st; - memset(frame, 0, sizeof(protocol_frame)); - frame->friendnumber = tun->friendnumber; - frame->packet_type = PACKET_TYPE_TCP_FIN; - frame->connid = tun->connid; - frame->data_length = 0; - send_frame(frame, data); - if(tun->sockfd) - { - FD_CLR(tun->sockfd, &client_master_fdset); - } - tunnel_delete(tun); - } - else - { - protocol_frame frame_st, *frame; - - frame = &frame_st; - memset(frame, 0, sizeof(protocol_frame)); - frame->friendnumber = tun->friendnumber; - frame->packet_type = PACKET_TYPE_TCP; - frame->connid = tun->connid; - frame->data_length = nbytes; - send_frame(frame, tox_packet_buf); - - // printf("Wrote %d bytes from sock %d to tunnel %d\n", nbytes, tun->sockfd, tun->connid); - } - } + log_printf(L_ERROR, "Error %u deleting friend before reconnection\n", error); + exit(-1); } - } - - fds = client_master_fdset; - /* Check friend connection status changes */ - /* TODO: learned about tox_friend_connection_status_cb after writing this... */ - /* TODO: also check friend status tox_callback_friend_status */ - if(time(NULL) - last_friend_connection_status_received > 15) - { - TOX_CONNECTION friend_connection_status; - friend_connection_status = tox_friend_get_connection_status(tox, friendnumber, &friend_query_error); - if(friend_query_error != TOX_ERR_FRIEND_QUERY_OK) - { - log_printf(L_DEBUG, "tox_friend_get_connection_status: error %u\n", friend_query_error); - } - else - { - if(friend_connection_status != last_friend_connection_status) - { - const char* status = readable_connection_status(friend_connection_status); - log_printf(L_INFO, "Friend connection status changed to: %s (%d)\n", status, friend_connection_status); - - if(friend_connection_status == TOX_CONNECTION_NONE) - { - state = CLIENT_STATE_CONNECTION_LOST; - connection_lost_timestamp = time(NULL); - } - } - - last_friend_connection_status_received = time(NULL); - last_friend_connection_status = friend_connection_status; - } + state = CLIENT_STATE_AWAIT_FRIENDSHIP; } } break; - case CLIENT_STATE_CONNECTION_LOST: - { - TOX_CONNECTION friend_connection_status; - friend_connection_status = tox_friend_get_connection_status(tox, friendnumber, &friend_query_error); - if(friend_query_error != TOX_ERR_FRIEND_QUERY_OK) - { - log_printf(L_DEBUG, "tox_friend_get_connection_status: error %u\n", friend_query_error); - } - else - { - if(friend_connection_status == TOX_CONNECTION_NONE) - { - /* https://github.com/TokTok/c-toxcore/blob/acb6b2d8543c8f2ea0c2e60dc046767cf5cc0de8/toxcore/tox.h#L1267 */ - TOX_ERR_FRIEND_DELETE tox_delete_error; - - log_printf(L_WARNING, "Lost connection to server, closing all tunnels and re-adding friend\n"); - client_close_all_connections(); - tox_friend_delete(tox, friendnumber, &tox_delete_error); - if(tox_delete_error) - { - log_printf(L_ERROR, "Error when deleting server from friend list: %d\n", tox_delete_error); - } - state = CLIENT_STATE_INITIAL; - } - else - { - state = CLIENT_STATE_FORWARDING; - } - } - } - break; - case 0xffffffff: - log_printf(L_ERROR, "You forgot a break statement\n"); - case CLIENT_STATE_SHUTDOWN: - exit(0); + case CLIENT_STATE_AWAIT_TUNNEL: + break; + case CLIENT_STATE_AWAIT_PONG: + break; + case CLIENT_STATE_CONNECTED: + client_connected_loop_iteration(friendnumber, &t); break; } - usleep(tox_iteration_interval(tox) * 1000); + run_tox_timer(tox, t); } } diff --git a/client.h b/client.h index be68f25..c951044 100644 --- a/client.h +++ b/client.h @@ -1,19 +1,12 @@ #include "main.h" -#define CLIENT_STATE_INITIAL 1 -#define CLIENT_STATE_SENTREQUEST 2 -#define CLIENT_STATE_REQUEST_ACCEPTED 3 -#define CLIENT_STATE_PING_SENT 4 -#define CLIENT_STATE_CONNECTED 5 -#define CLIENT_STATE_PONG_RECEIVED 6 -#define CLIENT_STATE_SEND_PING 7 -#define CLIENT_STATE_REQUEST_TUNNEL 8 -#define CLIENT_STATE_WAIT_FOR_ACKTUNNEL 9 -#define CLIENT_STATE_FORWARDING 10 -#define CLIENT_STATE_SHUTDOWN 11 -#define CLIENT_STATE_BIND_PORT 12 -#define CLIENT_STATE_SETUP_PIPE 13 -#define CLIENT_STATE_CONNECTION_LOST 14 +enum CLIENT_STATE { + CLIENT_STATE_AWAIT_FRIENDSHIP, + CLIENT_STATE_AWAIT_FRIEND_CONNECTED, + CLIENT_STATE_AWAIT_PONG, + CLIENT_STATE_AWAIT_TUNNEL, + CLIENT_STATE_CONNECTED +}; int handle_pong_frame(); int handle_acktunnel_frame(protocol_frame *rcvd_frame); diff --git a/gitversion.h b/gitversion.h deleted file mode 100644 index fdb8a74..0000000 --- a/gitversion.h +++ /dev/null @@ -1 +0,0 @@ -#define GITVERSION "0a8b0399cfac7873d350e4b2a90ec5203178922c" diff --git a/log.c b/log.c index 3124434..1be6ffa 100644 --- a/log.c +++ b/log.c @@ -41,6 +41,12 @@ char *log_printable_level(int level) return "DEBUG"; case L_DEBUG2: return "DEBUG2"; + case L_DEBUG3: + return "DEBUG3"; + case L_DEBUG4: + return "DEBUG4"; + case L_DEBUG5: + return "DEBUG5"; } return "UNKNOWN"; } @@ -145,13 +151,14 @@ static const char *tox_log_level_name(TOX_LOG_LEVEL level) void on_tox_log(Tox *tox, TOX_LOG_LEVEL level, const char *path, uint32_t line, const char *func, const char *message, void *user_data) { + tox = tox; uint32_t index = user_data ? *(uint32_t *)user_data : 0; const char *file = strrchr(path, '/'); - if(level == TOX_LOG_LEVEL_TRACE && !log_tox_trace) - { - return; - } + if(level == TOX_LOG_LEVEL_TRACE && !log_tox_trace) + { + return; + } file = file ? file + 1 : path; log_printf(L_DEBUG2, "[#%d] %s %s:%d\t%s:\t%s\n", index, tox_log_level_name(level), file, line, func, message); diff --git a/log.h b/log.h index bcd4c9b..a05af64 100644 --- a/log.h +++ b/log.h @@ -6,6 +6,9 @@ #define L_INFO 6 #define L_DEBUG 7 #define L_DEBUG2 8 +#define L_DEBUG3 9 +#define L_DEBUG4 10 +#define L_DEBUG5 11 #define L_UNSET 0x29a diff --git a/main.c b/main.c index ed2020e..c316c32 100644 --- a/main.c +++ b/main.c @@ -3,6 +3,7 @@ #include "tox_bootstrap.h" #include "log.h" + #ifdef __MACH__ #include "mach.h" #endif @@ -11,19 +12,15 @@ static struct Tox_Options tox_options; Tox *tox; int client_socket = 0; TOX_CONNECTION connection_status = TOX_CONNECTION_NONE; - +TOX_CONNECTION friend_connection_status = TOX_CONNECTION_NONE; /** CONFIGURATION OPTIONS **/ /* Whether we're a client */ -int client_mode = 0; -/* Just send a ping and exit */ -int ping_mode = 0; -/* Open a local port and forward it */ -int client_local_port_mode = 0; +/* Don't bootstrap nodes */ +int skip_bootstrap = 0; -/* Forward stdin/stdout to remote machine - SSH ProxyCommand mode */ -int client_pipe_mode = 0; +enum Mode program_mode = Mode_Unspecified; /* Remote Tox ID in client mode */ uint8_t *remote_tox_id = NULL; @@ -36,13 +33,14 @@ long int udp_start_port = 0; long int udp_end_port = 0; /* Directory with config and tox save */ -char config_path[500] = "/etc/tuntox/"; +char *config_path = "/etc/tuntox"; /* Limit hostname and port in server */ -int nrules = 0; -char rules_file[500] = "/etc/tuntox/rules"; -enum rules_policy_enum rules_policy = NONE; -rule *rules = NULL; +int tunnel_target_whitelist_size = 0; +char *tunnel_target_whitelist_file; +bool tunnel_target_whitelist_enforced = false; +rule *tunnel_target_whitelist_rules = NULL; +time_t tunnel_target_whitelist_mtime = 0; /* Ports and hostname for port forwarding */ int remote_port = 0; @@ -64,7 +62,7 @@ char shared_secret[TOX_MAX_FRIEND_REQUEST_LENGTH]; int server_whitelist_mode = 0; allowed_toxid *allowed_toxids = NULL; -int load_saved_toxid_in_client_mode = 0; +bool config_path_modified = false; fd_set master_server_fds; @@ -105,14 +103,13 @@ int allowed_toxid_cmp(allowed_toxid *a, allowed_toxid *b) return memcmp(a->toxid, b->toxid, TOX_PUBLIC_KEY_SIZE); } -/* Comparison function for rule objects */ -int rule_cmp(rule *a, rule *b) +/* Match rule r against candidate host, port. Returns 0 for match. */ +int rule_match(rule *r, rule *candidate) { - //log_printf(L_INFO, "Comparison result: %d %d\n", strcmp(a->host, b->host), (a->port == b->port)); - if ((strcmp(a->host, b->host)==0) && (a->port == b->port)) - return 0; - else - return -1; + bool host_match = !strcmp(r->host, "*") || !strcmp(r->host, candidate->host); + bool port_match = r->port == 0 || r->port == candidate->port; + + return port_match && host_match ? 0 : -1; } void update_select_nfds(int fd) @@ -161,7 +158,7 @@ void tunnel_delete(tunnel *t) free(t); } -int tunnel_in_delete_queue(tunnel *t) +int tunnel_in_delete_queue(tunnel *t) { tunnel_list *element; @@ -232,7 +229,7 @@ void set_tox_username(Tox *tox) /* Get sockaddr, IPv4 or IPv6 */ void *get_in_addr(struct sockaddr *sa) { - if (sa->sa_family == AF_INET) + if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } @@ -254,7 +251,7 @@ int get_client_socket(char *hostname, int port) hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; - if ((rv = getaddrinfo(hostname, port_str, &hints, &servinfo)) != 0) + if ((rv = getaddrinfo(hostname, port_str, &hints, &servinfo)) != 0) { /* Add a special case for "localhost" when name resolution is broken */ if(!strncmp("localhost", hostname, 256)) @@ -273,7 +270,7 @@ int get_client_socket(char *hostname, int port) } // loop through all the results and connect to the first we can - for(p = servinfo; p != NULL; p = p->ai_next) + for(p = servinfo; p != NULL; p = p->ai_next) { if (p->ai_family != AF_INET && p->ai_family != AF_INET6) continue; @@ -311,8 +308,8 @@ int get_client_socket(char *hostname, int port) /* Proto - our protocol handling */ -/* - * send_frame: (almost) zero-copy. Overwrites first PROTOCOL_BUFFER_OFFSET bytes of data +/* + * send_frame: (almost) zero-copy. Overwrites first PROTOCOL_BUFFER_OFFSET bytes of data * so actual data should start at position PROTOCOL_BUFFER_OFFSET */ int send_frame(protocol_frame *frame, uint8_t *data) @@ -414,12 +411,33 @@ int handle_ping_frame(protocol_frame *rcvd_frame) frame->friendnumber = rcvd_frame->friendnumber; frame->packet_type = PACKET_TYPE_PONG; frame->data_length = rcvd_frame->data_length; - + send_frame(frame, data); return 0; } +void tunnel_target_whitelist_load(); +bool check_requested_tunnel_against_rules(char *hostname, in_port_t port) +{ + if (!tunnel_target_whitelist_enforced) return true; + + tunnel_target_whitelist_load(); + + if (tunnel_target_whitelist_size <= 0) + { + log_printf(L_WARNING, "Whitelist enforced, but no whitelisted entries. All requests will be dropped.\n"); + return false; + } + + rule candidate, *found = NULL; + candidate.host = hostname; + candidate.port = port; + + LL_SEARCH(tunnel_target_whitelist_rules, found, &candidate, rule_match); + return found; +} + int handle_request_tunnel_frame(protocol_frame *rcvd_frame) { char *hostname = NULL; @@ -428,12 +446,12 @@ int handle_request_tunnel_frame(protocol_frame *rcvd_frame) int sockfd = 0; uint16_t tunnel_id; - if(client_mode) + if(program_mode != Mode_Server) { - log_printf(L_WARNING, "Got tunnel request frame from friend #%d when in client mode\n", rcvd_frame->friendnumber); + log_printf(L_WARNING, "Got tunnel request frame from friend #%d but not in server mode\n", rcvd_frame->friendnumber); return -1; } - + port = rcvd_frame->connid; hostname = calloc(1, rcvd_frame->data_length + 1); if(!hostname) @@ -446,35 +464,15 @@ int handle_request_tunnel_frame(protocol_frame *rcvd_frame) hostname[rcvd_frame->data_length] = '\0'; log_printf(L_INFO, "Got a request to forward data from %s:%d\n", hostname, port); - - // check rules - if (rules_policy == VALIDATE && nrules > 0 ) { - - rule temp_rule, *found = NULL; - temp_rule.host = hostname; - temp_rule.port = port; - - LL_SEARCH(rules, found, &temp_rule, rule_cmp); - if(!found) - { - log_printf(L_WARNING, "Rejected, request not in rules\n"); - if(hostname) - { - free(hostname); - } - return -1; - } - } else if (rules_policy != NONE) { - log_printf(L_WARNING, "Filter option active but no allowed host/port. All requests will be dropped.\n"); - if(hostname) - { - free(hostname); - } + + if (!check_requested_tunnel_against_rules(hostname, port)) + { + log_printf(L_WARNING, "Rejected tunnel request from #%d to non-whitelisted target host:port (%s:%d)", + rcvd_frame->friendnumber, hostname, port); + free(hostname); return -1; } - - tunnel_id = get_random_tunnel_id(); log_printf(L_DEBUG, "Tunnel ID: %d\n", tunnel_id); @@ -532,7 +530,7 @@ int handle_client_tcp_frame(protocol_frame *rcvd_frame) int sent_bytes; sent_bytes = send( - tun->sockfd, + tun->sockfd, rcvd_frame->data + offset, rcvd_frame->data_length - offset, MSG_NOSIGNAL @@ -569,7 +567,7 @@ int handle_client_tcp_fin_frame(protocol_frame *rcvd_frame) log_printf(L_WARNING, "Friend #%d tried to close tunnel which belongs to #%d\n", rcvd_frame->friendnumber, tun->friendnumber); return -1; } - + log_printf(L_DEBUG2, "Deleting tunnel #%d (%p) in handle_client_tcp_fin_frame(), socket %d", rcvd_frame->connid, tun, tun->sockfd); tunnel_queue_delete(tun); @@ -588,15 +586,9 @@ int handle_frame(protocol_frame *frame) return handle_pong_frame(frame); break; case PACKET_TYPE_TCP: - if(client_mode) - { - return handle_server_tcp_frame(frame); - } - else - { - return handle_client_tcp_frame(frame); - } - break; + return program_mode != Mode_Server + ? handle_server_tcp_frame(frame) + : handle_client_tcp_frame(frame); case PACKET_TYPE_REQUESTTUNNEL: handle_request_tunnel_frame(frame); break; @@ -604,17 +596,11 @@ int handle_frame(protocol_frame *frame) handle_acktunnel_frame(frame); break; case PACKET_TYPE_TCP_FIN: - if(client_mode) - { - return handle_server_tcp_fin_frame(frame); - } - else - { - return handle_client_tcp_fin_frame(frame); - } - break; + return program_mode != Mode_Server + ? handle_server_tcp_fin_frame(frame) + : handle_client_tcp_fin_frame(frame); default: - log_printf(L_DEBUG, "Got unknown packet type 0x%x from friend %d\n", + log_printf(L_DEBUG, "Got unknown packet type 0x%x from friend %d\n", frame->packet_type, frame->friendnumber ); @@ -623,13 +609,15 @@ int handle_frame(protocol_frame *frame) return 0; } -/* +/* * This is a callback which gets a packet from Tox core. * It checks for basic inconsistiencies and allocates the * protocol_frame structure. */ void parse_lossless_packet(Tox *tox, uint32_t friendnumber, const uint8_t *data, size_t len, void *tmp) { + tox = tox; + tmp = tmp; protocol_frame *frame = NULL; if(len < PROTOCOL_BUFFER_OFFSET) @@ -664,18 +652,20 @@ void parse_lossless_packet(Tox *tox, uint32_t friendnumber, const uint8_t *data, frame->data_length = INT16_AT(data, 6); frame->data = (uint8_t *)(data + PROTOCOL_BUFFER_OFFSET); frame->friendnumber = friendnumber; - log_printf(L_DEBUG, "Got protocol frame magic 0x%x type 0x%x from friend %d\n", frame->magic, frame->packet_type, frame->friendnumber); + log_printf(L_DEBUG4, "Got protocol frame magic 0x%x type 0x%x from friend %d\n", frame->magic, frame->packet_type, frame->friendnumber); if(len < (size_t)frame->data_length + PROTOCOL_BUFFER_OFFSET) { - log_printf(L_WARNING, "Received frame too small (attempted buffer overflow?): %d bytes, excepted at least %d bytes\n", len, frame->data_length + PROTOCOL_BUFFER_OFFSET); + log_printf(L_WARNING, "Received frame too small (attempted buffer overflow?): %d bytes, excepted at least %d bytes\n", + len, frame->data_length + PROTOCOL_BUFFER_OFFSET); free(frame); return; } if(frame->data_length > (TOX_MAX_CUSTOM_PACKET_SIZE - PROTOCOL_BUFFER_OFFSET)) { - log_printf(L_WARNING, "Declared data length too big (attempted buffer overflow?): %d bytes, excepted at most %d bytes\n", frame->data_length, (TOX_MAX_CUSTOM_PACKET_SIZE - PROTOCOL_BUFFER_OFFSET)); + log_printf(L_WARNING, "Declared data length too big (attempted buffer overflow?): %d bytes, excepted at most %d bytes\n", + frame->data_length, (TOX_MAX_CUSTOM_PACKET_SIZE - PROTOCOL_BUFFER_OFFSET)); free(frame); return; } @@ -715,38 +705,42 @@ int send_tunnel_request_packet(char *remote_host, int remote_port, int friend_nu /* End proto */ +void path_append(char *target, char *prefix, char *suffix) +{ + size_t prefix_len = strlen(prefix); + size_t suffix_len = strlen(suffix); + if (prefix_len + suffix_len + 1 > PATH_MAX) return; + memcpy(target, prefix, prefix_len); + memcpy(target + prefix_len, suffix, suffix_len); + *(target + prefix_len + suffix_len) = 0; +} + /* Save tox identity to a file */ static void write_save(Tox *tox) { void *data; uint32_t size; - uint8_t path_tmp[512], path_real[512], *p; + char path_tmp[PATH_MAX], path_real[PATH_MAX]; FILE *file; size = tox_get_savedata_size(tox); data = malloc(size); tox_get_savedata(tox, data); - strncpy((char *)path_real, config_path, sizeof(path_real)); - - p = path_real + strlen((char *)path_real); - memcpy(p, "tox_save", sizeof("tox_save")); - - unsigned int path_len = (p - path_real) + sizeof("tox_save"); - memcpy(path_tmp, path_real, path_len); - memcpy(path_tmp + (path_len - 1), ".tmp", sizeof(".tmp")); + path_append(path_real, config_path, "/tox_save"); + path_append(path_tmp, path_real, ".tmp"); - file = fopen((char*)path_tmp, "wb"); - if(file) { + file = fopen(path_tmp, "wb"); + if (file) { fwrite(data, size, 1, file); fflush(file); fclose(file); - if (rename((char*)path_tmp, (char*)path_real) != 0) { + if (rename(path_tmp, path_real) != 0) { log_printf(L_WARNING, "Failed to rename file. %s to %s deleting and trying again\n", path_tmp, path_real); - if(remove((const char *)path_real) < 0) { + if (remove(path_real) < 0) { log_printf(L_WARNING, "Failed to remove old save file %s\n", path_real); } - if (rename((char*)path_tmp, (char*)path_real) != 0) { + if (rename(path_tmp, path_real) != 0) { log_printf(L_WARNING, "Saving Failed\n"); } else { log_printf(L_DEBUG, "Saved data\n"); @@ -768,14 +762,10 @@ static size_t load_save(uint8_t **out_data) { void *data; uint32_t size; - uint8_t path_real[512], *p; + char f[PATH_MAX]; - strncpy((char *)path_real, config_path, sizeof(path_real)); - - p = path_real + strlen((char *)path_real); - memcpy(p, "tox_save", sizeof("tox_save")); - - data = file_raw((char *)path_real, &size); + path_append(f, config_path, "/tox_save"); + data = file_raw(f, &size); if(data) { @@ -789,75 +779,94 @@ static size_t load_save(uint8_t **out_data) } } +void tunnel_target_whitelist_clear(); /* Loads a list of allowed hostnames and ports from file. Format is hostname:port*/ -void load_rules() +void tunnel_target_whitelist_load() { char *ahost=NULL; int aport=0; - char line[100 + 1] = ""; + char line[1024]; FILE *file = NULL; rule *rule_obj = NULL; - int valid_rules = 0; - file = fopen(rules_file, "r"); - - if (file == NULL) { - log_printf(L_WARNING, "Could not open rules file (%s)\n", rules_file); - return; - } - - while (fgets(line, sizeof(line), file)) { - /* allow comments & white lines */ - if (line[0]=='#'||line[0]=='\n') { - continue; + if (!tunnel_target_whitelist_enforced) return; + + /* If we have existing rules, check to see if we need to continue. */ + if(tunnel_target_whitelist_rules) + { + struct stat buf; + if (stat(tunnel_target_whitelist_file, &buf) < 0) + { + /* File removed? Better clear the whitelist. */ + tunnel_target_whitelist_mtime = 0; + tunnel_target_whitelist_clear(); + return; } - if (parse_pipe_port_forward(line, &ahost, &aport) >= 0) { - if (aport > 0 && aport < 65535) { + if (buf.st_mtime == tunnel_target_whitelist_mtime) return; - rule_obj = (rule *)calloc(sizeof(rule), 1); - if(!rule_obj) - { - log_printf(L_ERROR, "Could not allocate memory for rule"); - exit(1); - } + tunnel_target_whitelist_mtime = buf.st_mtime; + tunnel_target_whitelist_clear(); + } - rule_obj->port = aport; - rule_obj->host = strdup(ahost); + file = fopen(tunnel_target_whitelist_file, "r"); - LL_APPEND(rules, rule_obj); - valid_rules++; - } else { - log_printf(L_WARNING, "Invalid port in line: %s\n", line); + if(file == NULL) { + log_printf(L_WARNING, "Could not open rules file (%s)\n", tunnel_target_whitelist_file); + return; + } + + while(fgets(line, sizeof(line), file)) + { + strtok(line, "#\n"); /* Chop line at first hash */ + char *orig_line = strdup(line); /* Not quite the original line; keeps + newline and comments out of logs */ + if(parse_pipe_port_forward(line, &ahost, &aport)) + { + rule_obj = (rule *)calloc(sizeof(rule), 1); + if(!rule_obj) + { + log_printf(L_ERROR, "Could not allocate memory for rule"); + exit(1); } - } else { - log_printf(L_WARNING, "Could not parse line: %s\n", line); + + rule_obj->port = aport; + rule_obj->host = strdup(ahost); + + LL_APPEND(tunnel_target_whitelist_rules, rule_obj); + tunnel_target_whitelist_size++; + } + else + { + log_printf(L_WARNING, "Could not parse line: %s\n", orig_line); } + free(orig_line); } fclose(file); - - /* save valid rules in global variable */ - nrules = valid_rules; - - log_printf(L_INFO, "Loaded %d rules\n", nrules); - if (nrules==0 && rules_policy != NONE){ + + log_printf(L_INFO, "Loaded %d rule%s\n", tunnel_target_whitelist_size, tunnel_target_whitelist_size == 1 ? "" : "s"); + if (tunnel_target_whitelist_size == 0 && tunnel_target_whitelist_enforced){ log_printf(L_WARNING, "No rules loaded! NO CONNECTIONS WILL BE ALLOWED!\n"); } } /* Clear rules loaded into memory */ -void clear_rules() +void tunnel_target_whitelist_clear() { rule * elt, *tmp; /* delete each elemen using the safe iterator */ - LL_FOREACH_SAFE(rules,elt,tmp) { - LL_DELETE(rules,elt); + LL_FOREACH_SAFE(tunnel_target_whitelist_rules,elt,tmp) { + LL_DELETE(tunnel_target_whitelist_rules,elt); free(elt->host); free(elt); } + free(tunnel_target_whitelist_rules); + tunnel_target_whitelist_rules = NULL; + tunnel_target_whitelist_size = 0; } void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *userdata) { + userdata = userdata; unsigned char tox_printable_id[TOX_ADDRESS_SIZE * 2 + 1]; uint32_t friendnumber; TOX_ERR_FRIEND_ADD friend_add_error; @@ -884,7 +893,7 @@ void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *m return; } } - + memset(tox_printable_id, '\0', sizeof(tox_printable_id)); id_to_string(tox_printable_id, public_key); @@ -908,16 +917,54 @@ void accept_friend_request(Tox *tox, const uint8_t *public_key, const uint8_t *m return; } - log_printf(L_INFO, "Accepted friend request from %s as %d\n", tox_printable_id, friendnumber); + log_printf(L_INFO, "Accepted friend request from %s as #%d\n", tox_printable_id, friendnumber); } /* Callback for tox_callback_self_connection_status() */ -void handle_connection_status_change(Tox *tox, TOX_CONNECTION p_connection_status, void *user_data) +void handle_connection_status_change(Tox *tox, TOX_CONNECTION new_connection_status, void *user_data) { + tox = tox; + user_data = user_data; + + connection_status = new_connection_status; + if (connection_status) + { + log_printf(L_INFO, "Connected to Tox network: %s\n", readable_connection_status(connection_status)); + } + else + { + log_printf(L_INFO, "Disconnected from Tox network\n"); + } +} + +#ifdef LOG_IP_ADDRESS +void log_friend_ip_address(Tox *tox, uint32_t friendnumber) +{ + IP_Port ip_port = tox_friend_get_connection_info(tox, friendnumber); + if (net_family_is_unspec(ip_port.ip.family)) return; + char ip_str[IP_NTOA_LEN]; + log_printf(L_INFO, "Friend #%d: IP: %s Port: %u\n", + friendnumber, + ip_ntoa(&ip_port.ip, ip_str, sizeof(ip_str)), + net_ntohs(ip_port.port)); +} +#endif + +void handle_friend_connection_status(Tox *tox, uint32_t friend_number, TOX_CONNECTION connection_status, void *user_data) +{ + tox = tox; + user_data = user_data; const char *status = NULL; - connection_status = p_connection_status; status = readable_connection_status(connection_status); - log_printf(L_INFO, "Connection status changed: %s", status); + log_printf(L_INFO, "Friend #%d connection status changed: %s", friend_number, status); +#ifdef LOG_IP_ADDRESS + if(connection_status == TOX_CONNECTION_UDP) + log_friend_ip_address(tox, friend_number); +#endif + if(program_mode != Mode_Server) + { + friend_connection_status = connection_status; + } } void cleanup() @@ -927,77 +974,72 @@ void cleanup() tox_kill(tox); if(client_socket) { - close(client_socket); + close(client_socket); } log_close(); } +struct tox_timer init_tox_timer(Tox *tox) +{ + struct tox_timer t; + t.tox_iteration_interval_ms = tox_iteration_interval(tox); + log_printf(L_DEBUG5, "Iteration interval: %dms\n", t.tox_iteration_interval_ms); + + gettimeofday(&t.tv_start, NULL); + + t.tv.tv_usec = (t.tox_iteration_interval_ms % 1000) * 1000; + t.tv.tv_sec = t.tox_iteration_interval_ms / 1000; + return t; +} + +void run_tox_timer(Tox *tox, struct tox_timer t) +{ + tox = tox; + struct timeval tv_end; + gettimeofday(&tv_end, NULL); + unsigned long long ms_elapsed = tv_end.tv_sec + tv_end.tv_usec/1000 - t.tv_start.tv_sec - t.tv_start.tv_usec/1000; + + if(ms_elapsed < t.tox_iteration_interval_ms) + { + log_printf(L_DEBUG5, "Sleeping for %d ms extra to prevent high CPU usage\n", t.tox_iteration_interval_ms - ms_elapsed); + usleep((t.tox_iteration_interval_ms - ms_elapsed) * 1000); + } +} int do_server_loop() { - struct timeval tv, tv_start, tv_end; - unsigned long long ms_start, ms_end; fd_set fds; unsigned char tox_packet_buf[PROTOCOL_MAX_PACKET_SIZE]; tunnel *tun = NULL; tunnel *tmp = NULL; - TOX_CONNECTION connected = 0; - int sent_data = 0; tox_callback_friend_lossless_packet(tox, parse_lossless_packet); - tv.tv_sec = 0; - tv.tv_usec = 20000; - FD_ZERO(&master_server_fds); while(1) { - TOX_CONNECTION tmp_isconnected = 0; - uint32_t tox_do_interval_ms; + bool tox_timer_do_sleep = false; int select_rv = 0; - sent_data = 0; /* Let tox do its stuff */ tox_iterate(tox, NULL); /* Get the desired sleep time, used in select() later */ - tox_do_interval_ms = tox_iteration_interval(tox); - tv.tv_usec = (tox_do_interval_ms % 1000) * 1000; - tv.tv_sec = tox_do_interval_ms / 1000; - log_printf(L_DEBUG2, "Iteration interval: %dms\n", tox_do_interval_ms); - gettimeofday(&tv_start, NULL); - - /* Check change in connection state */ - tmp_isconnected = connection_status; - if(tmp_isconnected != connected) - { - connected = tmp_isconnected; - if(connected) - { - log_printf(L_DEBUG, "Connected to Tox network\n"); - } - else - { - log_printf(L_DEBUG, "Disconnected from Tox network\n"); - } - } + struct tox_timer t = init_tox_timer(tox); fds = master_server_fds; /* Poll for data from our client connection */ - select_rv = select(select_nfds, &fds, NULL, NULL, &tv); - if(select_rv == -1 || select_rv == 0) + select_rv = select(select_nfds, &fds, NULL, NULL, &t.tv); + if(select_rv == -1) { - if(select_rv == -1) - { - log_printf(L_DEBUG, "Reading from local socket failed: code=%d (%s)\n", - errno, strerror(errno)); - } - else - { - log_printf(L_DEBUG2, "Nothing to read..."); - } + log_printf(L_DEBUG, "Reading from local socket failed: code=%d (%s)\n", + errno, strerror(errno)); + } + else if (select_rv == 0) + { + log_printf(L_DEBUG5, "Nothing to read..."); } else { @@ -1005,14 +1047,15 @@ int do_server_loop() tmp = NULL; tun = NULL; - log_printf(L_DEBUG, "Starting tunnel iteration..."); + log_printf(L_DEBUG3, "Starting tunnel iteration..."); HASH_ITER(hh, by_id, tun, tmp) { - log_printf(L_DEBUG, "Current tunnel: %p", tun); + log_printf(L_DEBUG3, "Current tunnel: %p", tun); if(FD_ISSET(tun->sockfd, &fds)) { - int nbytes = recv(tun->sockfd, - tox_packet_buf+PROTOCOL_BUFFER_OFFSET, + tox_timer_do_sleep = true; + int nbytes = recv(tun->sockfd, + tox_packet_buf+PROTOCOL_BUFFER_OFFSET, READ_BUFFER_SIZE, 0); /* Check if connection closed */ @@ -1038,10 +1081,8 @@ int do_server_loop() frame->connid = tun->connid; frame->data_length = 0; send_frame(frame, data); - sent_data = 1; - tunnel_queue_delete(tun); - + continue; } else @@ -1055,11 +1096,10 @@ int do_server_loop() frame->connid = tun->connid; frame->data_length = nbytes; send_frame(frame, tox_packet_buf); - sent_data = 1; } } } - log_printf(L_DEBUG, "Tunnel iteration done"); + log_printf(L_DEBUG3, "Tunnel iteration done"); LL_FOREACH_SAFE(tunnels_to_delete, tunnel_list_entry, list_tmp) { @@ -1069,14 +1109,9 @@ int do_server_loop() } } - gettimeofday(&tv_end, NULL); - ms_start = 1000 * tv_start.tv_sec + tv_start.tv_usec/1000; - ms_end = 1000 * tv_end.tv_sec + tv_end.tv_usec/1000; - - if(!sent_data && (ms_end - ms_start < tox_do_interval_ms)) + if(tox_timer_do_sleep) { - /*log_printf(L_DEBUG, "Sleeping for %d ms extra to prevent high CPU usage\n", (tox_do_interval_ms - (ms_end - ms_start)));*/ - usleep((tox_do_interval_ms - (ms_end - ms_start)) * 1000); + run_tox_timer(tox, t); } } } @@ -1091,7 +1126,7 @@ static void child_handler(int signum) } } -/* +/* * Daemonize the process if -D is set * Optionally drop privileges and create a lock file */ @@ -1101,17 +1136,17 @@ void do_daemonize() FILE *pidf = NULL; /* already a daemon */ - if (getppid() == 1) + if (getppid() == 1) { return; } /* Drop user if there is one, and we were run as root */ - if (daemon_username && (getuid() == 0 || geteuid() == 0)) + if (daemon_username && (getuid() == 0 || geteuid() == 0)) { struct passwd *pw = getpwnam(daemon_username); - if(pw) + if(pw) { log_printf(L_DEBUG, "Setuid to user %s", daemon_username); setuid(pw->pw_uid); @@ -1141,14 +1176,14 @@ void do_daemonize() /* Fork off the parent process */ pid = fork(); - if (pid < 0) + if (pid < 0) { log_printf(L_ERROR, "Unable to fork daemon, code=%d (%s)", errno, strerror(errno)); exit(1); } /* If we got a good PID, then we can exit the parent process. */ - if (pid > 0) + if (pid > 0) { /* Wait for confirmation from the child via SIGTERM or SIGCHLD, or for two seconds to elapse (SIGALRM). pause() should not return. */ @@ -1177,7 +1212,7 @@ void do_daemonize() /* Create a new SID for the child process */ sid = setsid(); - if (sid < 0) + if (sid < 0) { log_printf(L_ERROR, "unable to create a new session, code %d (%s)", errno, strerror(errno)); @@ -1186,7 +1221,7 @@ void do_daemonize() /* Change the current working directory. This prevents the current directory from being locked; hence not being able to remove it. */ - if ((chdir("/")) < 0) + if ((chdir("/")) < 0) { log_printf(L_ERROR, "Unable to change directory to %s, code %d (%s)", "/", errno, strerror(errno) ); @@ -1199,10 +1234,10 @@ void do_daemonize() freopen( "/dev/null", "w", stderr); /* Create the pid file as the new user */ - if (pidfile && pidfile[0]) + if (pidfile && pidfile[0]) { pidf = fopen(pidfile, "w"); - if (!pidf) + if (!pidf) { log_printf(L_ERROR, "Unable to create PID file %s, code=%d (%s)", pidfile, errno, strerror(errno)); @@ -1214,7 +1249,7 @@ void do_daemonize() /* Tell the parent process that we are A-okay */ - kill( parent, SIGUSR1 ); + kill( parent, SIGUSR1 ); } void help() @@ -1241,8 +1276,8 @@ void help() fprintf(stderr, " mode\n"); fprintf(stderr, " -s - shared secret used for connection authentication (max\n"); fprintf(stderr, " %u characters)\n", TOX_MAX_FRIEND_REQUEST_LENGTH-1); - fprintf(stderr, " -t - set TCP relay port (0 disables TCP relaying)\n"); - fprintf(stderr, " -u : - set Tox UDP port range\n"); + fprintf(stderr, " -t - set TCP relay port (0 disables TCP relaying)\n"); + fprintf(stderr, " -u : - set Tox UDP port range\n"); fprintf(stderr, " -d - debug mode (use twice to display toxcore log too)\n"); fprintf(stderr, " -q - quiet mode\n"); fprintf(stderr, " -S - send output to syslog instead of stderr\n"); @@ -1262,61 +1297,44 @@ int main(int argc, char *argv[]) size_t save_size = 0; uint8_t *save_data = NULL; allowed_toxid *allowed_toxid_obj = NULL; - - srand(time(NULL)); - tcp_relay_port = 1024 + (rand() % 64511); - udp_start_port = 1024 + (rand() % 64500); - udp_end_port = udp_start_port + 10; + int verbosity = 0; + + srand(time(NULL)); + tcp_relay_port = 1024 + (rand() % 64511); + udp_start_port = 1024 + (rand() % 64500); + udp_end_port = udp_start_port + 10; log_init(); - while ((oc = getopt(argc, argv, "L:pi:C:s:f:W:dqhSF:DU:t:u:")) != -1) + while ((oc = getopt(argc, argv, "L:pi:C:s:f:W:vdqhSF:DU:t:u:")) != -1) { switch(oc) { case 'L': /* Local port forwarding */ - client_mode = 1; - client_local_port_mode = 1; - if(parse_local_port_forward(optarg, &local_port, &remote_host, &remote_port) < 0) + program_mode = Mode_Client_Local_Port_Forward; + if(!parse_local_port_forward(optarg, &local_port, &remote_host, &remote_port)) { log_printf(L_ERROR, "Invalid value for -L option - use something like -L 22:127.0.0.1:22\n"); exit(1); } - if(min_log_level == L_UNSET) - { - min_log_level = L_INFO; - } - log_printf(L_DEBUG, "Forwarding remote port %d to local port %d\n", remote_port, local_port); break; case 'W': /* Pipe forwarding */ - client_mode = 1; - client_pipe_mode = 1; - if(parse_pipe_port_forward(optarg, &remote_host, &remote_port) < 0) + program_mode = Mode_Client_Pipe; + if(!parse_pipe_port_forward(optarg, &remote_host, &remote_port) || remote_port == 0) { log_printf(L_ERROR, "Invalid value for -W option - use something like -W 127.0.0.1:22\n"); exit(1); } - if(min_log_level == L_UNSET) - { - min_log_level = L_ERROR; - } - log_printf(L_INFO, "Forwarding remote port %d to stdin/out\n", remote_port); break; case 'p': /* Ping */ - client_mode = 1; - ping_mode = 1; - if(min_log_level == L_UNSET) - { - min_log_level = L_INFO; - } + program_mode = Mode_Client_Ping; break; case 'i': /* Tox ID */ server_whitelist_mode = 1; - log_printf(L_DEBUG, "Server whitelist mode enabled"); allowed_toxid_obj = (allowed_toxid *)calloc(sizeof(allowed_toxid), 1); if(!allowed_toxid_obj) { @@ -1333,20 +1351,12 @@ int main(int argc, char *argv[]) break; case 'C': /* Config directory */ - strncpy(config_path, optarg, sizeof(config_path) - 1); - if(optarg[strlen(optarg) - 1] != '/') - { - int optarg_len = strlen(optarg); - - config_path[optarg_len] = '/'; - config_path[optarg_len + 1] = '\0'; - } - load_saved_toxid_in_client_mode = 1; + config_path = strdup(optarg); + config_path_modified = 1; break; case 'f': - strncpy(rules_file, optarg, sizeof(rules_file) - 1); - rules_policy = VALIDATE; - log_printf(L_INFO, "Filter policy set to VALIDATE\n"); + tunnel_target_whitelist_file = strdup(optarg); + tunnel_target_whitelist_enforced = true; break; case 's': /* Shared secret */ @@ -1354,20 +1364,27 @@ int main(int argc, char *argv[]) memset(shared_secret, 0, TOX_MAX_FRIEND_REQUEST_LENGTH); strncpy(shared_secret, optarg, TOX_MAX_FRIEND_REQUEST_LENGTH-1); break; + case 'v': case 'd': - if(min_log_level == L_DEBUG2) - { - log_tox_trace = 1; - } - if(min_log_level != L_DEBUG && min_log_level != L_DEBUG2) - { - min_log_level = L_DEBUG; - } - else - { - min_log_level = L_DEBUG2; - } - + switch(++verbosity) + { + case 1: + min_log_level = L_DEBUG; + break; + case 2: + min_log_level = L_DEBUG2; + break; + case 3: + min_log_level = L_DEBUG3; + log_tox_trace = 1; + break; + case 4: + min_log_level = L_DEBUG4; + break; + case 5: + default: + min_log_level = L_DEBUG5; + } break; case 'q': min_log_level = L_ERROR; @@ -1385,42 +1402,42 @@ int main(int argc, char *argv[]) case 'U': daemon_username = optarg; break; - case 't': - errno = 0; - tcp_relay_port = strtol(optarg, NULL, 10); - if(errno != 0 || tcp_relay_port < 0 || tcp_relay_port > 65535) - { - tcp_relay_port = 1024 + (rand() % 64511); - log_printf(L_WARNING, "Ignored -t %s: TCP port number needs to be a number between 0 and 65535."); - } - break; - case 'u': - { /* TODO make a function in util.h */ - char *sport; - char *eport; - - sport = strtok(optarg, ":"); - eport = strtok(NULL, ":"); - if(!sport || !eport) - { - log_printf(L_WARNING, "Ignored -u %s: wrong format"); - } - else - { - errno = 0; - udp_start_port = strtol(sport, NULL, 10); - udp_end_port = strtol(eport, NULL, 10); - if(errno != 0 || udp_start_port < 1 || udp_start_port > 65535 || \ - udp_end_port < 1 || udp_end_port > 65535) - { - log_printf(L_WARNING, "Ignored -u %s: ports need to be integers between 1 and 65535"); - udp_start_port = 1024 + (rand() % 64500); - udp_end_port = udp_start_port + 10; - } - - } - } - break; + case 't': + errno = 0; + tcp_relay_port = strtol(optarg, NULL, 10); + if(errno != 0 || tcp_relay_port < 0 || tcp_relay_port > 65535) + { + tcp_relay_port = 1024 + (rand() % 64511); + log_printf(L_WARNING, "Ignored -t %s: TCP port number needs to be a number between 0 and 65535."); + } + break; + case 'u': + { /* TODO make a function in util.h */ + char *sport; + char *eport; + + sport = strtok(optarg, ":"); + eport = strtok(NULL, ":"); + if(!sport || !eport) + { + log_printf(L_WARNING, "Ignored -u %s: wrong format"); + } + else + { + errno = 0; + udp_start_port = strtol(sport, NULL, 10); + udp_end_port = strtol(eport, NULL, 10); + if(errno != 0 || udp_start_port < 1 || udp_start_port > 65535 || \ + udp_end_port < 1 || udp_end_port > 65535) + { + log_printf(L_WARNING, "Ignored -u %s: ports need to be integers between 1 and 65535"); + udp_start_port = 1024 + (rand() % 64500); + udp_end_port = udp_start_port + 10; + } + + } + } + break; case '?': case 'h': default: @@ -1430,19 +1447,56 @@ int main(int argc, char *argv[]) } } - if(!client_mode && min_log_level == L_UNSET) - { - min_log_level = L_INFO; + switch (program_mode) { + case Mode_Client_Local_Port_Forward: + case Mode_Client_Pipe: + case Mode_Client_Ping: + if(!remote_tox_id) + { + log_printf(L_ERROR, "Tox id is required in client mode. Use -i 58435984ABCDEF475...\n"); + exit(1); + } + break; + case Mode_Unspecified: + program_mode = Mode_Server; + case Mode_Server: + break; } - if(!client_mode && server_whitelist_mode) - { - log_printf(L_INFO, "Server in ToxID whitelisting mode - only clients listed with -i can connect"); - } - - if((!client_mode) && (rules_policy != NONE)) - { - load_rules(); + switch (program_mode) { + case Mode_Client_Local_Port_Forward: + log_printf(L_DEBUG, "Forwarding remote port %d to local port %d\n", remote_port, local_port); + break; + case Mode_Client_Pipe: + if(min_log_level == L_UNSET) + { + min_log_level = L_ERROR; + } + log_printf(L_INFO, "Forwarding remote port %d to stdin/out\n", remote_port); + break; + case Mode_Client_Ping: + if(min_log_level == L_UNSET) + { + min_log_level = L_INFO; + } + break; + case Mode_Server: + if(min_log_level == L_UNSET) + { + min_log_level = L_INFO; + } + if(tunnel_target_whitelist_enforced) + { + log_printf(L_INFO, "Whitelist enforced on outgoing connections: only matched hosts/ports will be allowed.\n"); + tunnel_target_whitelist_load(); + } + if(server_whitelist_mode) + { + log_printf(L_INFO, "Server in ToxID whitelisting mode - only clients listed with -i can connect"); + } + break; + case Mode_Unspecified: + exit(1); } /* If shared secret has not been provided via -s, read from TUNTOX_SHARED_SECRET env variable */ @@ -1467,24 +1521,24 @@ int main(int argc, char *argv[]) /* Bootstrap tox */ tox_options_default(&tox_options); - if(min_log_level >= L_DEBUG2) - { - tox_options.log_callback = on_tox_log; - } - tox_options.udp_enabled = 1; - tox_options.local_discovery_enabled = 1; - tox_options.tcp_port = tcp_relay_port; - tox_options.start_port = udp_start_port; - tox_options.end_port = udp_end_port; - tox_options.hole_punching_enabled = 1; - - log_printf(L_INFO, "Using %d for TCP relay port and %d-%d for UDP", - tox_options.tcp_port, - tox_options.start_port, - tox_options.end_port - ); - - if((!client_mode) || load_saved_toxid_in_client_mode) + if(min_log_level >= L_DEBUG2) + { + tox_options.log_callback = on_tox_log; + } + tox_options.udp_enabled = 1; + tox_options.local_discovery_enabled = 1; + tox_options.tcp_port = tcp_relay_port; + tox_options.start_port = udp_start_port; + tox_options.end_port = udp_end_port; + tox_options.hole_punching_enabled = 1; + + log_printf(L_INFO, "Using %d for TCP relay port and %d-%d for UDP", + tox_options.tcp_port, + tox_options.start_port, + tox_options.end_port + ); + + if(program_mode == Mode_Server || config_path_modified) { save_size = load_save(&save_data); if(save_data && save_size) @@ -1521,37 +1575,20 @@ int main(int argc, char *argv[]) set_tox_username(tox); tox_callback_self_connection_status(tox, handle_connection_status_change); + tox_callback_friend_connection_status(tox, handle_friend_connection_status); - do_bootstrap(tox); + if(!skip_bootstrap) + do_bootstrap(tox); - if(client_mode) + if(program_mode == Mode_Server || config_path_modified) { - uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; - char_t readable_dht_key[2 * TOX_PUBLIC_KEY_SIZE + 1]; - - tox_self_get_address(tox, tox_id); - id_to_string(tox_printable_id, tox_id); - tox_printable_id[TOX_ADDRESS_SIZE * 2] = '\0'; - log_printf(L_DEBUG, "Generated Tox ID: %s\n", tox_printable_id); - - tox_self_get_dht_id(tox, dht_key); - to_hex(readable_dht_key, dht_key, TOX_PUBLIC_KEY_SIZE); - log_printf(L_DEBUG, "DHT key: %s\n", readable_dht_key); - - if(!remote_tox_id) - { - log_printf(L_ERROR, "Tox id is required in client mode. Use -i 58435984ABCDEF475...\n"); - exit(1); - } - do_client_loop(remote_tox_id); + write_save(tox); } - else + { uint8_t dht_key[TOX_PUBLIC_KEY_SIZE]; char_t readable_dht_key[2 * TOX_PUBLIC_KEY_SIZE + 1]; - write_save(tox); - if(!use_shared_secret) { log_printf(L_WARNING, "Shared secret authentication is not used - skilled attackers may connect to your tuntox server"); @@ -1567,10 +1604,14 @@ int main(int argc, char *argv[]) to_hex(readable_dht_key, dht_key, TOX_PUBLIC_KEY_SIZE); log_printf(L_DEBUG, "DHT key: %s\n", readable_dht_key); - tox_callback_friend_request(tox, accept_friend_request); - do_server_loop(); - clear_rules(); + if (program_mode == Mode_Server) + { + tox_callback_friend_request(tox, accept_friend_request); + return do_server_loop(); + } + else + { + return do_client_loop(remote_tox_id); + } } - - return 0; } diff --git a/main.h b/main.h index 17fb9b9..8411cc6 100644 --- a/main.h +++ b/main.h @@ -80,26 +80,35 @@ typedef struct protocol_frame_t { } protocol_frame; /* Rules policy */ -enum rules_policy_enum { VALIDATE, NONE }; typedef struct rule { uint16_t port; char * host; struct rule *next; } rule; +enum Mode { + Mode_Unspecified, + Mode_Server, + Mode_Client_Local_Port_Forward, + Mode_Client_Pipe, + Mode_Client_Ping +}; + +struct tox_timer { + uint32_t tox_iteration_interval_ms; + struct timeval tv, tv_start; +}; +struct tox_timer init_tox_timer(Tox *tox); +void run_tox_timer(Tox *tox, struct tox_timer t); + /**** GLOBAL VARIABLES ****/ extern Tox *tox; -/* Whether we're a client */ -extern int client_mode; -/* Just send a ping and exit */ -extern int ping_mode; + +/* Whether we're a server, client, etc. */ +extern enum Mode program_mode; /* TOX_CONNECTION global variable */ extern TOX_CONNECTION connection_status; /* Open a local port and forward it */ -extern int client_local_port_mode; -/* Forward stdin/stdout to remote machine - SSH ProxyCommand mode */ -extern int client_pipe_mode; -/* Remote Tox ID in client mode */ extern uint8_t *remote_tox_id; /* Ports and hostname for port forwarding */ extern int remote_port; @@ -112,6 +121,8 @@ extern char shared_secret[TOX_MAX_FRIEND_REQUEST_LENGTH]; extern int select_nfds; extern tunnel *by_id; +extern TOX_CONNECTION friend_connection_status; + void parse_lossless_packet(Tox *tox, uint32_t friendnumber, const uint8_t *data, size_t len, void *tmp); tunnel *tunnel_create(int sockfd, int connid, uint32_t friendnumber); void tunnel_delete(tunnel *t); diff --git a/scripts/tokssh b/scripts/tokssh index eeddb21..1e13eca 100755 --- a/scripts/tokssh +++ b/scripts/tokssh @@ -2,86 +2,92 @@ set -e function help { cat < Usage: - tokssh [ssh options] [user@]address [-s secret] + TUNTOX_DESTINATION=[user@]
tokssh where - ssh options: options to pass to ssh process - user: login on remote host - address: either a ToxID or a hostname. ~/.tuntox/hosts is read to map - hostname to ToxID. hostname MUST resolve to 127.0.0.1 + ssh options: options to pass to ssh process + user: login username on remote host (you could also use "-l user") + address: a ToxID + + To specify a tuntox secret (password), set the environment variable + TUNTOX_SECRET. + + Specifying passwords on the command line is insecure, since the arguments of + programs are considered public data. - -s optional secret to use to connect to tuntox server + For that reason, you can also specify the remote address and username with + the environment variable TUNTOX_DESTINATION. This hides your desintation + from other users on the system. In this case, all options will be passed to + SSH. examples: + + TUNTOX_SECRET=sOmEPassWOrd tokssh 5A40C3443ABD6E1DDEE682E83F84A4D556C24C22D2230DCC141A4723C123473C171A4D9C4054 tokssh user@5A40C3443ABD6E1DDEE682E83F84A4D556C24C22D2230DCC141A4723C123473C171A4D9C4054 tokssh 5A40C3443ABD6E1DDEE682E83F84A4D556C24C22D2230DCC141A4723C123473C171A4D9C4054 - tokssh -p 2222 -o ForwardAgent=yes user@5A40C3443ABD6E1DDEE682E83F84A4D556C24C22D2230DCC141A4723C123473C171A4D9C4054 - tokssh user@5A40C3443ABD6E1DDEE682E83F84A4D556C24C22D2230DCC141A4723C123473C171A4D9C4054 -s TuNToXSeCreT + tokssh -p 2222 -o ForwardAgent=yes -l user 5A40C3443ABD6E1DDEE682E83F84A4D556C24C22D2230DCC141A4723C123473C171A4D9C4054 + TUNTOX_DESTINATION=5A40C3443ABD6E1DDEE682E83F84A4D556C24C22D2230DCC141A4723C123473C171A4D9C4054 tokssh -p 2222 files: - ~/.tuntox/hosts maps hostname to ToxID and optional secret. - format is - hostname ToxID secret(optional) -EOF -} - -strargs="'$*'" -if [ -z "${strargs##*-h*}" ] || [ -z "${strargs##*--help*}" ] ;then - help - exit -fi + ~/.tuntox/persist/ -array=( $@ ) -len=${#array[@]} - -if [ $len -lt 1 ]; then - help - exit -fi + If directory exists, then read & store a persistent secret key/TOXID within. +EOF +} -# look for secret and remvove it from args -if [ $len -gt 2 ] && [ "${array[$len-2]}" == "-s" ] +if [ "$TUNTOX_DESTINATION" ] then - secret="${array[@]:$len-2:$len-1}" - len=$[len-2] + set -- "$@" "$TUNTOX_DESTINATION" + unset TUNTOX_DESTINATION fi -userhost=${array[$len-1]} -args=${array[@]:0:$len-1} - -# check for user@id -arruserhost=(${userhost//@/ }) -arruserhostlen=${#arruserhost[@]} - -if [ $arruserhostlen -gt 1 ] -then - # last argument is user@toxid - user="${arruserhost[0]}@" - toxid=${arruserhost[1]} - hostname=localhost +ssh_options=() +while [ $# -gt 0 ] +do + case "$1" in + -h|--help) + help + exit ;; + *) + if [ $# -eq 1 ] + then + break + else + ssh_options += "$1" + fi ;; + esac + shift +done +[ $# = 1 ] || { help; exit 1; } + +if [ -d ~/.tuntox/persist ]; then + persist='-C ~/.tuntox/persist' else - # last argument is just toxid - user="" - toxid=$userhost - hostname=localhost -fi - -#search toxid in ~/.tuntox/hosts and map it to toxid -if [ -f ~/.tuntox/hosts ]; then - while read c_hostname c_toxid c_secret; do - if [ "${c_hostname:0:1}" != "#" ] && [ "$c_hostname" == "$toxid" ]; then - toxid="$c_toxid" - if [ "$secret" == "" ]; then - secret="-s $c_secret" - fi - break - fi - done < ~/.tuntox/hosts + persist= fi -ssh -o ProxyCommand="tuntox -i $toxid -W 127.0.0.1:%p $secret" $args ${user}${hostname} +# Explicitly set the default values for CanonicalizeHostname and UpdateHostKeys, +# just to be safe. + +# We use StrictHostKeyChecking=accept because Tox validates the Tox identity and +# the Tox key is the identity. We have already performed initial public key +# exchange. + +ssh \ + -o ProxyCommand="tuntox $persist -i %h -W localhost:%p '$TUNTOX_SECRET'" \ + -o StrictHostKeyChecking=accept-new \ + -o CanonicalizeHostname=no \ + -o UpdateHostKeys=yes \ + "${ssh_options[@]}" \ + -- \ + "$1" diff --git a/scripts/toxish b/scripts/toxish new file mode 100755 index 0000000..d55e8fd --- /dev/null +++ b/scripts/toxish @@ -0,0 +1,128 @@ +#!/bin/bash +set -e +function help +{ + PROGNAME=${0##*/} + cat < +Usage: $PROGNAME connect + + +For example, first do this: + + $PROGNAME add billy 4BC18209278C9B2AA1BF9B9B27E671FC47D3DE3B15D175A63CC2C6E01B532A4CAE3D4BE083C8 + +Then you can connect to billy's SSH server through Tox with this command: + + ssh billy + + + +It will use Tox for connectivity but provide all the features of ssh (such as +git, rsync, tunneling with -w, etc). + + + +It is also possible to use this command: + + $PROGNAME connect + $PROGNAME connect billy 4BC18209278C9B2AA1BF9B9B27E671FC47D3DE3B15D175A63CC2C6E01B532A4CAE3D4BE083C8 + +...which will first add the entry, if necessary, and then connect by running the +command 'ssh billy'. + + +EOF +} + +function main +{ + if [ $# = 0 ] + then + help + exit + fi + case "$1" in + add) + shift + tokssh_add "$@" + ;; + connect) + shift + exists_ok=y + tokssh_add "$@" + exec ssh "$name" + ;; + *) + help + exit 1 + ;; + esac + exit +} + + +function ssh_config_fragment +{ + cat <&2 + exit 1 + else + name=$1 + toxid=$2 + fi + mkdir -p ~/.ssh/config.d + mkdir -p ~/.tuntox/persist + line='Include config.d/*.conf' + grep -qF "$LINE" ~/.ssh/config || sed -i -e "1i $LINE" ~/.ssh/config + if grep -q "^Host $name" ~/.ssh/config + then + if [ "$exists_ok" ] + then + return + else + echo "$0: Error: name exists in your .ssh/config. Refused to edit. name=$name" >&2 + exit 1 + fi + fi + out=~/.ssh/config.d/$name.conf + if [ -e "$out" ] + then + if [ "$exists_ok" ] + then + return + else + echo "$0: Error: file exists. Refused to edit. file=$out" >&2 + exit 1 + fi + else + ssh_config_fragment > "$out" + fi +} + +main "$@" diff --git a/uthash.h b/uthash.h index 7205c67..d6b54e9 100644 --- a/uthash.h +++ b/uthash.h @@ -449,15 +449,25 @@ do { hashv += keylen; \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \ + [[fallthrough]]; \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \ + [[fallthrough]]; \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \ + [[fallthrough]]; \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \ + [[fallthrough]]; \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \ + [[fallthrough]]; \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \ + [[fallthrough]]; \ case 5: _hj_j += _hj_key[4]; \ + [[fallthrough]]; \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \ + [[fallthrough]]; \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \ + [[fallthrough]]; \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \ + [[fallthrough]]; \ case 1: _hj_i += _hj_key[0]; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ diff --git a/util.c b/util.c index 3e6a99c..03cf30f 100644 --- a/util.c +++ b/util.c @@ -83,64 +83,62 @@ int string_to_id(char_t *w, char_t *a) } /* Parse the -L parameter */ -/* 0 = success */ -int parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port) +/* true = success */ +bool parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port) { char *lport; - char *host; - char *rport; - /* First replace all @ with :, ':' is forbidden in some environments */ - char *p = string; - while(*p) - { - if(*p == '@') *p = ':'; - p++; - } - - lport = strtok(string, ":"); - host = strtok(NULL, ":"); - rport = strtok(NULL, ":"); + /* Alternative delimiter '@', as ':' is forbidden in some environments */ + lport = strtok(string, ":@"); - if(!lport || !host || !rport) + if(!(*local_port = atoi(lport))) { - return -1; + return false; } - *local_port = atoi(lport); - *hostname = host; - *remote_port = atoi(rport); - - return 0; + if(parse_pipe_port_forward(lport + strlen(lport), hostname, remote_port)) + { + return *remote_port; + } + return false; } /* Parse the -W parameter */ -/* 0 = success */ -int parse_pipe_port_forward(char *string, char **hostname, int *remote_port) +/* true = success */ +bool parse_pipe_port_forward(char *string, char **hostname, int *remote_port) { char *host; char *rport; - /* First replace all @ with :, ':' is forbidden in some environments */ - char *p = string; - while(*p) - { - if(*p == '@') *p = ':'; - p++; - } - - host = strtok(string, ":"); - rport = strtok(NULL, ":"); + /* Alternative delimiter '@', as ':' is forbidden in some environments */ + host = strtok(string, ":@"); + rport = strtok(NULL, ""); if(!host || !rport) { - return -1; + return false; } *hostname = host; *remote_port = atoi(rport); - return 0; + if(*remote_port > 0 && *remote_port < 65535) + { + /* This is tolerant of nonsense tokens after the port. */ + return true; + } + else + { + /* Port 0 is not allowed in the input. Only a literal '*' can produce a + * port 0 in the output, which will be treated as a wildcard if this is + * a rule. */ + if (rport[0] != '*') + { + return false; + } + /* Return an error if an extra token follows, but tolerate whitespace. */ + return !strtok(rport+1, "\n\t "); + } } void* file_raw(char *path, uint32_t *size) diff --git a/util.h b/util.h index 7dced6b..e97119b 100644 --- a/util.h +++ b/util.h @@ -13,7 +13,7 @@ void id_to_string(char_t *dest, const char_t *src); int string_to_id(char_t *w, char_t *a); void* file_raw(char *path, uint32_t *size); const char *readable_connection_status(TOX_CONNECTION status); -int parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port); -int parse_pipe_port_forward(char *string, char **hostname, int *remote_port); +bool parse_local_port_forward(char *string, int *local_port, char **hostname, int *remote_port); +bool parse_pipe_port_forward(char *string, char **hostname, int *remote_port); #endif