From 7a8d4d0934cb51c8ad7f7af84669bb0197c7b89e Mon Sep 17 00:00:00 2001
From: Tom Smeding <tom.smeding@gmail.com>
Date: Fri, 19 Jun 2020 18:03:08 +0200
Subject: Zero incoming passwords in memory using libsodium

---
 Makefile  |   6 ++-
 command.c | 167 ++++++++++++++++++++++++++++++++++----------------------------
 main.c    |   5 ++
 3 files changed, 101 insertions(+), 77 deletions(-)

diff --git a/Makefile b/Makefile
index 03ad55b..0ed32b2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,8 @@
 CC = gcc
-CFLAGS = -Wall -Wextra -std=c11 -g -fwrapv -D_DEFAULT_SOURCE
-LDFLAGS = -lsqlite3 -ldl
+CFLAGS = -Wall -Wextra -std=c11 -g -O2 -fwrapv -D_DEFAULT_SOURCE
+LDFLAGS = -ldl
+CFLAGS += $(shell pkg-config --cflags sqlite3 libsodium)
+LDFLAGS += $(shell pkg-config --libs sqlite3 libsodium)
 
 TARGETS = tomsg_server
 
diff --git a/command.c b/command.c
index e4d75d3..04e9fe0 100644
--- a/command.c
+++ b/command.c
@@ -5,6 +5,7 @@
 #include <errno.h>
 #include <sys/time.h>
 #include <sys/socket.h>
+#include <sodium.h>
 #include "broadcast.h"
 #include "command.h"
 #include "db.h"
@@ -15,17 +16,31 @@
 #include "util.h"
 
 
-static bool cmd_register(struct conn_data *data,const char *tag,const char **args){
+struct cmd_retval{
+	bool socket_close;
+	bool memzero;
+};
+#define RET_OK ((struct cmd_retval){.socket_close=false,.memzero=false})
+#define RET_CLOSE(close_) ((struct cmd_retval){.socket_close=(close_),.memzero=false})
+#define RET_MEMZERO ((struct cmd_retval){.socket_close=false,.memzero=true})
+#define RET_MEMZERO_CLOSE(close_) ((struct cmd_retval){.socket_close=(close_),.memzero=true})
+
+static struct cmd_retval cmd_register(struct conn_data *data,const char *tag,const char **args){
 	i64 userid=db_find_user(args[0]);
 	if(userid!=-1){
 		net_send_error(data->fd,tag,"Username already exists");
-		return false;
+		return RET_OK;
 	}
 	db_create_user(args[0],args[1]);
-	return net_send_ok(data->fd,tag);
+	return RET_MEMZERO_CLOSE(net_send_ok(data->fd,tag));
 }
 
-static bool cmd_login(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_login(struct conn_data *data,const char *tag,const char **args){
+	// TODO: use sodium_mlock correctly for the password
+
+	// Note: this function has exactly ONE return point, so that it is easier
+	// to see that we indeed return MEMZERO.
+
 	i64 userid=db_find_user(args[0]);
 	if(userid==-1){
 		net_send_error(data->fd,tag,"User not found");
@@ -34,28 +49,28 @@ static bool cmd_login(struct conn_data *data,const char *tag,const char **args){
 			broadcast_online_change(data->userid);
 			data->userid=-1;
 		}
-		return false;
-	}
-	char *pass=db_get_pass(userid);
-	bool success=strcmp(args[1],pass)==0;
-	free(pass);
-	if(data->userid!=-1){
-		userdata_unregister(data->userid,data->fd);
-		broadcast_online_change(data->userid);
-	}
-	if(success){
-		data->userid=userid;
-		userdata_register(userid,data->fd);
-		net_send_ok(data->fd,tag);
-		broadcast_online_change(userid);
 	} else {
-		data->userid=-1;
-		net_send_error(data->fd,tag,"Incorrect password");
+		char *pass=db_get_pass(userid);
+		bool success=strcmp(args[1],pass)==0;
+		free(pass);
+		if(data->userid!=-1){
+			userdata_unregister(data->userid,data->fd);
+			broadcast_online_change(data->userid);
+		}
+		if(success){
+			data->userid=userid;
+			userdata_register(userid,data->fd);
+			net_send_ok(data->fd,tag);
+			broadcast_online_change(userid);
+		} else {
+			data->userid=-1;
+			net_send_error(data->fd,tag,"Incorrect password");
+		}
 	}
-	return false;
+	return RET_MEMZERO;
 }
 
-static bool cmd_logout(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_logout(struct conn_data *data,const char *tag,const char **args){
 	(void)args;
 	if(data->userid!=-1){
 		userdata_unregister(data->userid,data->fd);
@@ -63,19 +78,19 @@ static bool cmd_logout(struct conn_data *data,const char *tag,const char **args)
 		data->userid=-1;
 	}
 	net_send_ok(data->fd,tag);
-	return false;
+	return RET_OK;
 }
 
-static bool cmd_list_rooms(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_list_rooms(struct conn_data *data,const char *tag,const char **args){
 	(void)args;
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	struct db_room_list rl=db_list_rooms(data->userid);
 	if(rl.count<=0){
 		db_nullify_room_list(rl);
-		return net_send_list(data->fd,tag,0,NULL);
+		return RET_CLOSE(net_send_list(data->fd,tag,0,NULL));
 	}
 	const char *names[rl.count];
 	for(i64 i=0;i<rl.count;i++){
@@ -83,28 +98,28 @@ static bool cmd_list_rooms(struct conn_data *data,const char *tag,const char **a
 	}
 	bool closed=net_send_list(data->fd,tag,rl.count,names);
 	db_nullify_room_list(rl);
-	return closed;
+	return RET_CLOSE(closed);
 }
 
-static bool cmd_list_members(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_list_members(struct conn_data *data,const char *tag,const char **args){
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	i64 roomid=db_find_room(args[0]);
 	if(roomid==-1){
 		net_send_error(data->fd,tag,"Room not found");
-		return false;
+		return RET_OK;
 	}
 	if(!db_is_member(roomid,data->userid)){
 		net_send_error(data->fd,tag,"Not in that room");
-		return false;
+		return RET_OK;
 	}
 
 	struct db_user_list ul=db_list_members(roomid);
 	if(ul.count<=0){
 		db_nullify_user_list(ul);
-		return net_send_list(data->fd,tag,0,NULL);
+		return RET_CLOSE(net_send_list(data->fd,tag,0,NULL));
 	}
 	const char *names[ul.count];
 	for(i64 i=0;i<ul.count;i++){
@@ -112,49 +127,49 @@ static bool cmd_list_members(struct conn_data *data,const char *tag,const char *
 	}
 	bool closed=net_send_list(data->fd,tag,ul.count,names);
 	db_nullify_user_list(ul);
-	return closed;
+	return RET_CLOSE(closed);
 }
 
-static bool cmd_create_room(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_create_room(struct conn_data *data,const char *tag,const char **args){
 	(void)args;
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	userdata_mark_active(data->userid,data->fd,true);
 	struct db_name_id room=db_create_room();
 	db_add_member(room.id,data->userid);
 	bool closed=net_send_name(data->fd,tag,room.name);
 	db_nullify_name_id(room);
-	return closed;
+	return RET_CLOSE(closed);
 }
 
-static bool cmd_invite(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_invite(struct conn_data *data,const char *tag,const char **args){
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	userdata_mark_active(data->userid,data->fd,true);
 	const char *roomname=args[0];
 	i64 roomid=db_find_room(roomname);
 	if(roomid==-1){
 		net_send_error(data->fd,tag,"Room not found");
-		return false;
+		return RET_OK;
 	}
 
 	i64 user2=db_find_user(args[1]);
 	if(user2==-1){
 		net_send_error(data->fd,tag,"User not found");
-		return false;
+		return RET_OK;
 	}
 
 	if(!db_is_member(roomid,data->userid)){
 		net_send_error(data->fd,tag,"Not in that room");
-		return false;
+		return RET_OK;
 	}
 	if(db_is_member(roomid,user2)){
 		net_send_error(data->fd,tag,"User already in that room");
-		return false;
+		return RET_OK;
 	}
 
 	db_add_member(roomid,user2);
@@ -187,13 +202,13 @@ static bool cmd_invite(struct conn_data *data,const char *tag,const char **args)
 	free(joinbuf);
 	free(invitebuf);
 
-	return net_send_ok(data->fd,tag);
+	return RET_CLOSE(net_send_ok(data->fd,tag));
 }
 
-static bool cmd_send(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_send(struct conn_data *data,const char *tag,const char **args){
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	userdata_mark_active(data->userid,data->fd,true);
 	const char *roomname=args[0];
@@ -201,11 +216,11 @@ static bool cmd_send(struct conn_data *data,const char *tag,const char **args){
 	i64 roomid=db_find_room(roomname);
 	if(roomid==-1){
 		net_send_error(data->fd,tag,"Room not found");
-		return false;
+		return RET_OK;
 	}
 	if(!db_is_member(roomid,data->userid)){
 		net_send_error(data->fd,tag,"Not in that room");
-		return false;
+		return RET_OK;
 	}
 
 	i64 timestamp=make_timestamp();
@@ -236,10 +251,10 @@ static bool cmd_send(struct conn_data *data,const char *tag,const char **args){
 	db_nullify_user_list(members);
 	free(pushbuf);
 
-	return closed;
+	return RET_CLOSE(closed);
 }
 
-static bool history_cmd_helper(
+static struct cmd_retval history_cmd_helper(
 		struct conn_data *data,const char *tag,const char **args,
 		const char *cmdname,i64 beforeid){
 	char *endp;
@@ -247,22 +262,22 @@ static bool history_cmd_helper(
 	if(args[1][0]=='\0'||*endp!='\0'||nrequested<0){
 		debug("Connection fd=%d sent an invalid number for '%s': '%s'",
 				data->fd,cmdname,args[1]);
-		return true;
+		return RET_CLOSE(true);
 	}
 
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	const char *roomname=args[0];
 	i64 roomid=db_find_room(roomname);
 	if(roomid==-1){
 		net_send_error(data->fd,tag,"Room not found");
-		return false;
+		return RET_OK;
 	}
 	if(!db_is_member(roomid,data->userid)){
 		net_send_error(data->fd,tag,"Not in that room");
-		return false;
+		return RET_OK;
 	}
 
 	struct db_message_list ml=db_get_messages_before(roomid,nrequested,beforeid);
@@ -273,7 +288,7 @@ static bool history_cmd_helper(
 
 	if(closed){
 		db_nullify_message_list(ml);
-		return true;
+		return RET_CLOSE(true);
 	}
 
 	for(i64 i=ml.count-1;i>=0;i--){
@@ -287,73 +302,73 @@ static bool history_cmd_helper(
 	}
 
 	db_nullify_message_list(ml);
-	return closed;
+	return RET_CLOSE(closed);
 }
 
-static bool cmd_history(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_history(struct conn_data *data,const char *tag,const char **args){
 	return history_cmd_helper(data,tag,args,"history",-1);
 }
 
-static bool cmd_history_before(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_history_before(struct conn_data *data,const char *tag,const char **args){
 	char *endp;
 	i64 beforeid=strtoll(args[2],&endp,10);
 	if(args[2][0]=='\0'||*endp!='\0'){
 		debug("Connection fd=%d sent an invalid id for 'history_before': '%s'",
 				data->fd,args[2]);
-		return true;
+		return RET_CLOSE(true);
 	}
 	if(beforeid<0)beforeid=INT64_MAX;
 
 	return history_cmd_helper(data,tag,args,"history_before",beforeid);
 }
 
-static bool cmd_ping(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_ping(struct conn_data *data,const char *tag,const char **args){
 	(void)args;
-	return net_send_pong(data->fd,tag);
+	return RET_CLOSE(net_send_pong(data->fd,tag));
 }
 
-static bool cmd_is_online(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_is_online(struct conn_data *data,const char *tag,const char **args){
 	i64 userid=db_find_user(args[0]);
 	if(userid==-1){
 		net_send_error(data->fd,tag,"User not found");
-		return false;
+		return RET_OK;
 	}
 	i64 nfds;
 	(void)userdata_online(userid,&nfds);
-	return net_send_number(data->fd,tag,nfds);
+	return RET_CLOSE(net_send_number(data->fd,tag,nfds));
 }
 
-static bool cmd_firebase_token(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_firebase_token(struct conn_data *data,const char *tag,const char **args){
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	db_add_token(data->userid,args[0]);
-	return net_send_ok(data->fd,tag);
+	return RET_CLOSE(net_send_ok(data->fd,tag));
 }
 
-static bool cmd_delete_firebase_token(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_delete_firebase_token(struct conn_data *data,const char *tag,const char **args){
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	db_delete_token(data->userid,args[0]);
-	return net_send_ok(data->fd,tag);
+	return RET_CLOSE(net_send_ok(data->fd,tag));
 }
 
-static bool cmd_user_active(struct conn_data *data,const char *tag,const char **args){
+static struct cmd_retval cmd_user_active(struct conn_data *data,const char *tag,const char **args){
 	if(data->userid==-1){
 		net_send_error(data->fd,tag,"Not logged in");
-		return false;
+		return RET_OK;
 	}
 	char *endp;
 	i64 active=strtoll(args[0],&endp,10);
 	if(args[0][0]=='\0'||*endp!='\0'||active<0){
 		debug("Connection fd=%d sent an invalid number for 'user_active': '%s'",data->fd,args[0]);
-		return true;
+		return RET_CLOSE(true);
 	}
 	userdata_mark_active(data->userid,data->fd,active>0);
-	return net_send_ok(data->fd,tag);
+	return RET_CLOSE(net_send_ok(data->fd,tag));
 }
 
 
@@ -361,7 +376,7 @@ struct cmd_info{
 	const char *cmdname;
 	int nargs;
 	bool longlast;  // whether the last argument should span the rest of the input line
-	bool (*handler)(struct conn_data *data,const char *tag,const char **args);
+	struct cmd_retval (*handler)(struct conn_data *data,const char *tag,const char **args);
 };
 
 static const struct cmd_info commands[]={
@@ -437,5 +452,7 @@ bool handle_input_line(struct conn_data *data,char *line,size_t linelen){
 		return true;
 	}
 
-	return commands[cmdi].handler(data,tag,(const char**)args);
+	struct cmd_retval retval=commands[cmdi].handler(data,tag,(const char**)args);
+	if(retval.memzero)sodium_memzero(line,linelen);
+	return retval.socket_close;
 }
diff --git a/main.c b/main.c
index 6388fe2..497f66e 100644
--- a/main.c
+++ b/main.c
@@ -8,6 +8,7 @@
 #include <netinet/in.h>
 #include <errno.h>
 #include <assert.h>
+#include <sodium.h>
 #include "broadcast.h"
 #include "command.h"
 #include "conn_data.h"
@@ -157,6 +158,10 @@ void signal_handler(int sig){
 
 int main(int argc,char **argv){
 	srandomdev();
+	if(sodium_init()<0){
+		fprintf(stderr,"Could not initialise libsodium!\n");
+		return 1;
+	}
 
 	signal(SIGPIPE,signal_handler);
 
-- 
cgit v1.2.3-70-g09d2