← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/net-uuid into lp:widelands

 

Notabilis has proposed merging lp:~widelands-dev/widelands/net-uuid into lp:widelands.

Commit message:
Improved the relogin code by adding semi-constant user ids.
Also improved doubled logins with registered accounts.

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/net-uuid/+merge/332264

Improved the relogin code by adding semi-constant user ids.

Reconnecting to the same name on the metaserver should now work for most cases, even when the complete client computer crashes. The id is used to recognize the user and assign his old name to him instead of giving him the requested name. When the user does a clean logout the reservation is removed.

If logging in with a registered account a second time, a new name will be assigned to the new client automatically instead of displaying an error message.

This branch should be merged at the same time as deploying the related branch[1] of the metaserver, since compatibility with the current metaserver version breaks.

[1] https://github.com/widelands/widelands_metaserver/pull/13
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/net-uuid into lp:widelands.
=== modified file 'src/network/CMakeLists.txt'
--- src/network/CMakeLists.txt	2017-06-26 11:35:50 +0000
+++ src/network/CMakeLists.txt	2017-10-13 19:50:29 +0000
@@ -1,6 +1,8 @@
 wl_library(network
   SRCS
     constants.h
+    crypto.h
+    crypto.cc
     internet_gaming.cc
     internet_gaming.h
     internet_gaming_messages.cc
@@ -45,6 +47,7 @@
     profile
     scripting_lua_interface
     scripting_lua_table
+    third_party_crypto
     ui_basic
     ui_fsmenu
     widelands_ball_of_mud

=== added file 'src/network/crypto.cc'
--- src/network/crypto.cc	1970-01-01 00:00:00 +0000
+++ src/network/crypto.cc	2017-10-13 19:50:29 +0000
@@ -0,0 +1,28 @@
+#include "network/crypto.h"
+
+#include "third_party/crypto/sha3.h"
+
+namespace crypto {
+
+std::string hash(const std::string& input) {
+
+	// Hash the input
+	sha3_context c;
+	sha3_Init256(&c);
+	sha3_Update(&c, reinterpret_cast<const uint8_t*>(input.c_str()), input.length());
+	// 'hash' points to a buffer inside 'c'
+	// with the value of SHA3-256
+	const uint8_t* hash = sha3_Finalize(&c);
+
+	// Format returned byte array to hex string
+	static constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+									  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+	std::string output(32 * 2, ' ');
+	for (int i = 0; i < 32; ++i) {
+		output[2 * i]     = hexmap[(hash[i] & 0xF0) >> 4];
+		output[2 * i + 1] = hexmap[hash[i] & 0x0F];
+	}
+	return output;
+}
+
+}

=== added file 'src/network/crypto.h'
--- src/network/crypto.h	1970-01-01 00:00:00 +0000
+++ src/network/crypto.h	2017-10-13 19:50:29 +0000
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2006-2017 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef WL_NETWORK_CRYPTO_H
+#define WL_NETWORK_CRYPTO_H
+
+#include <string>
+
+namespace crypto {
+
+/**
+ Hashes the given input string and returns the hash.
+ @param input A string to calculate the hash of.
+ @return The hash as hex-string.
+ */
+std::string hash(const std::string& input);
+
+}
+
+#endif  // end of include guard: WL_NETWORK_CRYPTO_H

=== modified file 'src/network/internet_gaming.cc'
--- src/network/internet_gaming.cc	2017-08-16 04:31:56 +0000
+++ src/network/internet_gaming.cc	2017-10-13 19:50:29 +0000
@@ -32,6 +32,7 @@
 #include "io/fileread.h"
 #include "io/filesystem/layered_filesystem.h"
 #include "network/constants.h"
+#include "network/crypto.h"
 #include "network/internet_gaming_messages.h"
 
 /// Private constructor by purpose: NEVER call directly. Always call InternetGaming::ref(), this
@@ -57,6 +58,16 @@
 	// Set connection tracking variables to 0
 	lastbrokensocket_[0] = 0;
 	lastbrokensocket_[1] = 0;
+
+	// Create a temporary uuid for the case that we have no password and shall not send the real uuid
+	static const std::string random_chars = "0123456789ABCDEF";
+	tmp_uuid_ = "";
+	std::random_device rd;
+	std::mt19937 gen(rd());
+	std::uniform_int_distribution<> dist(0, random_chars.length() - 1);
+	while (tmp_uuid_.length() < 16) {
+		tmp_uuid_.push_back(random_chars[dist(gen)]);
+	}
 }
 
 /// resets all stored variables without the chat messages for a clean new login (not relogin)
@@ -122,29 +133,33 @@
 bool InternetGaming::login(const std::string& nick,
                            const std::string& pwd,
                            bool reg,
+                           bool send_uuid,
                            const std::string& meta,
                            uint32_t port) {
-	assert(state_ == OFFLINE);
 
+	clientname_ = nick;
 	pwd_ = pwd;
 	reg_ = reg;
+	send_uuid_ = send_uuid;
 	meta_ = meta;
 	port_ = port;
 
-	// If we are not connecting to a registered account, create a random value
-	// to send as password. Used so the metaserver can match our IPv4 and IPv6 connections.
-	// See internet_gaming_protocol.h for more information
+
+	// If we are not connecting to a registered account, try to send the UUID. If the player does not
+	// want to use one, create a temporary id
 	if (!reg_) {
-		// Admittedly this is a pretty stupid generator. But it should be fine for us
-		static const std::string random_chars = "0123456789ABCDEF";
-		pwd_ = "";
-		std::random_device rd;
-		std::mt19937 gen(rd());
-		std::uniform_int_distribution<> dist(0, random_chars.length() - 1);
-		while (pwd_.length() < 8) {
-			pwd_.push_back(random_chars[dist(gen)]);
+		if (send_uuid_) {
+			pwd_ = crypto::hash(nick + pwd);
+		} else {
+			pwd_ = tmp_uuid_;
 		}
 	}
+	assert(!pwd_.empty());
+
+	return do_login();
+}
+
+bool InternetGaming::do_login(bool relogin) {
 
 	initialize_connection();
 
@@ -154,7 +169,7 @@
 	SendPacket s;
 	s.string(IGPCMD_LOGIN);
 	s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));
-	s.string(nick);
+	s.string(clientname_);
 	s.string(build_id());
 	s.string(bool2str(reg_));
 	s.string(pwd_);
@@ -169,9 +184,11 @@
 		// paperwork, so we put our feet up and just return. ;)
 		if (state_ != CONNECTING) {
 			if (state_ == LOBBY) {
-				format_and_add_chat(
-				   "", "", true, _("For hosting a game, please take a look at the notes at:"));
-				format_and_add_chat("", "", true, "http://wl.widelands.org/wiki/InternetGaming";);
+				if (!relogin) {
+					format_and_add_chat(
+						"", "", true, _("For hosting a game, please take a look at the notes at:"));
+					format_and_add_chat("", "", true, "http://wl.widelands.org/wiki/InternetGaming";);
+				}
 
 				// Try to establish a second connection to tell the metaserver about our IPv4 address
 				create_second_connection();
@@ -191,42 +208,11 @@
 		throw wexception("InternetGaming::relogin: This only makes sense if there was an error.");
 	}
 
-	initialize_connection();
-
-	// If we are here, a connection was established and we can send our login package through the
-	// socket.
-	log("InternetGaming: Sending relogin request.\n");
-	SendPacket s;
-	s.string(IGPCMD_RELOGIN);
-	s.string(boost::lexical_cast<std::string>(INTERNET_GAMING_PROTOCOL_VERSION));
-	s.string(clientname_);
-	s.string(build_id());
-	s.string(bool2str(reg_));
-	s.string(pwd_);
-	net->send(s);
-
-	// Now let's see, whether the metaserver is answering
-	uint32_t const secs = time(nullptr);
-	state_ = CONNECTING;
-	while (INTERNET_GAMING_TIMEOUT > time(nullptr) - secs) {
-		handle_metaserver_communication();
-		// Check if we are a step further... if yes handle_packet has taken care about all the
-		// paperwork, so we put our feet up and just return. ;)
-		if (state_ != CONNECTING) {
-			if (state_ == LOBBY) {
-				break;
-			} else if (error())
-				return false;
-		}
-	}
-
-	if (INTERNET_GAMING_TIMEOUT <= time(nullptr) - secs) {
-		log("InternetGaming: No answer from metaserver!\n");
+	if (!do_login(true)) {
 		return false;
 	}
 
-	create_second_connection();
-
+	state_ = LOBBY;
 	// Client is reconnected, so let's try resend the timeouted command.
 	if (waitcmd_ == IGPCMD_GAME_CONNECT)
 		join_game(gamename_);
@@ -238,6 +224,9 @@
 		set_game_playing();
 	}
 
+	log("InternetGaming: Reconnected to metaserver\n");
+	format_and_add_chat("", "", true, _("Successfully reconnected to the metaserver!"));
+
 	return true;
 }
 
@@ -416,19 +405,24 @@
 	if (state_ == CONNECTING) {
 		if (cmd == IGPCMD_LOGIN) {
 			// Clients request to login was granted
-			clientname_ = packet.string();
+			std::string assigned_name = packet.string();
+			if (clientname_ != assigned_name) {
+				format_and_add_chat("", "", true,
+					(boost::format(
+						_("You logged in as '%s' since your requested name is already in use or reserved."))
+							% assigned_name).str());
+			}
+			clientname_ = assigned_name;
 			clientrights_ = packet.string();
+			if (clientrights_ == INTERNET_CLIENT_UNREGISTERED) {
+				// Might happen that we log in with less rights than we wanted to.
+				// Happens when we are already logged in with another client.
+				reg_ = false;
+			}
 			state_ = LOBBY;
 			log("InternetGaming: Client %s logged in.\n", clientname_.c_str());
 			return;
 
-		} else if (cmd == IGPCMD_RELOGIN) {
-			// Clients request to relogin was granted
-			state_ = LOBBY;
-			log("InternetGaming: Client %s relogged in.\n", clientname_.c_str());
-			format_and_add_chat("", "", true, _("Successfully reconnected to the metaserver!"));
-			return;
-
 		} else if (cmd == IGPCMD_ERROR) {
 			std::string errortype = packet.string();
 			if (errortype != "LOGIN" && errortype != "RELOGIN") {
@@ -451,9 +445,8 @@
 			   cmd.c_str());
 		}
 	}
-
 	try {
-		if (cmd == IGPCMD_LOGIN || cmd == IGPCMD_RELOGIN) {
+		if (cmd == IGPCMD_LOGIN) {// || cmd == IGPCMD_RELOGIN) {
 			// Login specific commands but not in CONNECTING state...
 			log("InternetGaming: Received %s cmd although client is not in CONNECTING state.\n",
 			    cmd.c_str());
@@ -766,6 +759,10 @@
 
 /// ChatProvider: sends a message via the metaserver.
 void InternetGaming::send(const std::string& msg) {
+	// TODO(Notabilis): Messages can get lost when we are temporarily disconnected from the metaserver,
+	// even when we reconnect again. "Answered" messages like IGPCMD_GAME_CONNECT are resend but chat
+	// messages are not. Resend them after some time when we did not received the matching IGPCMD_CHAT
+	// command from the server?
 	if (!logged_in()) {
 		format_and_add_chat(
 		   "", "", true, _("Message could not be sent: You are not connected to the metaserver!"));
@@ -777,6 +774,7 @@
 
 	if (msg.size() && *msg.begin() == '@') {
 		// Format a personal message
+		// TODO(Notabilis): msg should be trimmed before checking for the space character
 		std::string::size_type const space = msg.find(' ');
 		if (space >= msg.size() - 1) {
 			format_and_add_chat(
@@ -834,6 +832,7 @@
 			SendPacket m;
 			m.string(IGPCMD_ANNOUNCEMENT);
 			m.string(arg);
+			// NOCOM(Notabilis): This looks like a bug to me
 			net->send(s);
 			return;
 		} else

=== modified file 'src/network/internet_gaming.h'
--- src/network/internet_gaming.h	2017-07-01 08:22:54 +0000
+++ src/network/internet_gaming.h	2017-10-13 19:50:29 +0000
@@ -57,10 +57,12 @@
 	void reset();
 
 	// Login and logout
+	// pwd should contain either the password for a registered account or the UUID otherwise
 	void initialize_connection();
 	bool login(const std::string& nick,
 	           const std::string& pwd,
 	           bool registered,
+	           bool send_uuid,
 	           const std::string& metaserver,
 	           uint32_t port);
 	bool relogin();
@@ -180,6 +182,13 @@
 	                         bool system,
 	                         const std::string& msg);
 
+	/**
+	 * Does the real work of the login.
+	 * \param relogin Whether this is a relogin. Only difference is that
+	 *                on first login a greeting is shown.
+	 */
+	bool do_login(bool relogin = false);
+
 	/// The connection to the metaserver
 	std::unique_ptr<NetClient> net;
 
@@ -189,6 +198,8 @@
 	/// data saved for possible relogin
 	std::string pwd_;
 	bool reg_;
+	bool send_uuid_;
+	std::string tmp_uuid_;
 	std::string meta_;
 	uint16_t port_;
 

=== modified file 'src/network/internet_gaming_protocol.h'
--- src/network/internet_gaming_protocol.h	2017-07-01 08:22:54 +0000
+++ src/network/internet_gaming_protocol.h	2017-10-13 19:50:29 +0000
@@ -28,12 +28,16 @@
 
 /**
  * The current version of the in-game network protocol. Client and metaserver
- * protocol versions must match.
+ * protocol versions must match. The metaserver supports the protocol version of latest stable build
+ * and the newest version in trunk.
  * Used Versions:
- * 0: Build 19 and before
+ * 0: Build 19 and before [stable, supported]
  * 1: Between build 19 and build 20 - IPv6 support added
+ * 2: Between build 19 and build 20 - Added UUID to allow reconnect with same username after crashes.
+ *                                    When logging twice with a registered account, the second connection
+ *                                    gets a free username assigned. Dropping RELOGIN command. [supported]
  */
-#define INTERNET_GAMING_PROTOCOL_VERSION 1
+#define INTERNET_GAMING_PROTOCOL_VERSION 2
 
 /**
  * The default timeout time after which the client tries to resend a package or even finally closes
@@ -99,27 +103,38 @@
  */
 
 /**
- * The nonce:
- *
- * The nonce is used on the metaserver to link multiple connections by the same client. This
- * normally
- * happens when the client supports IPv4 and IPv6 and connects with both protocol versions. This
- * way,
- * the metaserver knows that the clients supports both versions and can show games / offer his game
+ * The UUID:
+ *
+ * The UUID is a semi-permanent ID stored in the configuration file of Widelands.
+ * It has to be stored in the file since it should survive crashes of the game or computer.
+ * If the game is not started for 24 hours, a new one is created to increase privacy.
+ * Basically it allows the metaserver to identify the user even when multiple users try to join with
+ * the same username. This is only used for unregistered users, registered users can use their
+ * password instead.
+ * Note that the send UUID differs from the stored one: The send UUID is hash(username | stored-id).
+ * Use-cases of the UUID in detail:
+ *
+ * 1) The UUID replaced the nonce introduced with IPv6.
+ * The UUID is used on the metaserver to link multiple connections by the same client. This
+ * normally happens when the client supports IPv4 and IPv6 and connects with both protocol versions. This
+ * way, the metaserver knows that the clients supports both versions and can show games / offer his game
  * of/for clients with both protocol versions.
  *
- * When a network client connects to the metaserver with (RE)LOGIN he also sends a nonce.
- * When "another" netclient connects to the metaserver and sends TELL_IP containing the same nonce,
+ * When a network client connects to the metaserver with (RE)LOGIN he also sends the UUID.
+ * When "another" netclient connects to the metaserver and sends TELL_IP containing the same UUID,
  * it is considered the same game client connecting with another IP. This way, two connections by
- * IPv4 and
- * IPv6 can be matched so the server learns both addresses of the client.
- *
- * In the case of registered players, the password can be used instead of a random nonce. The
- * username alone
- * can not be used for this, especially not for unregistered users: The metaserver can not
- * differentiate
- * between a second connection by the user and an initial login of another user (with the same
- * name).
+ * IPv4 and IPv6 can be matched so the server learns both addresses of the client.
+ *
+ * In the case of registered players, the password can be used instead of a UUID. The username
+ * alone can not be used for this, especially not for unregistered users: The metaserver can not
+ * differentiate between a second connection by the user and an initial login of another user
+ * (with the same name).
+ *
+ * 2) Reconnect after crash / network problems.
+ * When Widelands breaks the connection without logging out, the server still assumes that the old
+ * connection is active. So when the player reconnects, another name is chosen. Sending the UUID allows
+ * to reclaim the old name, since the server recognizes that there isn't a second player trying to use
+ * the same name.
  */
 
 /**
@@ -132,15 +147,12 @@
  * closing the connection. The receiver of this command should just close the connection.
  *
  * \note that either party is allowed to close the connection without sending a \ref
- * IGPCMD_DISCONNECT
- *       command first (in any case, this can happen when the program crashes or network connection
- * is lost).
+ *       IGPCMD_DISCONNECT command first (in any case, this can happen when the program crashes
+ *       or network connection is lost).
  *
  * \note If you want to change the payload of this command, change it only by appending new items.
- * The
- *       reason is that this is the only command that can be sent by the metaserver even when the
- * protocol
- *       versions differ.
+ *       The reason is that this is the only command that can be sent by the metaserver even when the
+ *       protocol versions differ.
  *
  */
 static const std::string IGPCMD_DISCONNECT = "DISCONNECT";
@@ -153,62 +165,28 @@
  * \li string:    protocol version
  * \li string:    client name
  * \li string:    build_id of the client
- * \li string:    whether the client wants to login in to a registered account ("true" or "false" as
- * string)
+ * \li string:    whether the client wants to login in to a registered account
+ *                ("true" or "false" as string)
  * \li string:    for registered accounts: password in clear text
- *                for unregistered users a random nonce to recognized the matching IPv4 and IPv6
- * connections.
- *                for an explanation of the nonce, see above.
+ *                for unregistered users the UUID to recognize the matching IPv4 and IPv6
+ *                connections or to reclaim the username after a unintended disconnect.
+ *                For an explanation of the UUID, see above.
  *
  * If the metaserver accepts, it replies with a LOGIN command with the following payload:
  * \li string:    client name (might be different to the previously chosen one, if the client did
  *                NOT login to a registered account and either the chosen is registered or already
- * used.)
+ *                used.)
  * \li string:    clients rights  (see client rights section above)
  *
  * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will again try to login
  * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not
  * answer"
  *
- * For the case, that the metaserver does not accept the login, take a look at \ref IGPCMD_REJECTED
+ * For the case, that the metaserver does not accept the login, take a look at \ref IGPCMD_ERROR
  */
 static const std::string IGPCMD_LOGIN = "LOGIN";
 
 /**
- * Reinitiate a connection.
- *
- * Basically the difference to \ref IGPCMD_LOGIN is, that the metaserver allows the user to go on at
- * the
- * place it was before - so if the user was in a running game and only lost connection to the
- * metaserver,
- * but not to the game itself, statistics for that game can still be sent and saved.
- * To ensure everything is fine, the login information will still be checked + if a client with the
- * name
- * is still listed as online, the metaserver will ping the client and if the client does not answer
- * intime
- * it will be replaced by the user requesting the relogin
- *
- * sent by the client, with the following payload:
- * \li string:    protocol version
- * \li string:    client name - the one the metaserver replied at the first login
- * \li string:    build_id of the client
- * \li string:    whether the client wants to login in to a registered account ("false", "true")
- * \li string:    for registered accounts: password in clear text
- *                for unregistered users a random nonce to recognized the matching IPv4 and IPv6
- * connections.
- *                for an explanation of the nonce, see above.
- *
- * If the metaserver accepts, it replies with a RELOGIN command without any payload.
- *
- * If no answer is received in \ref INTERNET_GAMING_TIMEOUT s the client will try to relogin
- * \ref INTERNET_GAMING_RETRIES times until it finally bails out something like "server does not
- * answer"
- *
- * For the case, that the metaserver does not accept the login, it sends a \ref IGPCMD_ERROR "LOGIN"
- */
-static const std::string IGPCMD_RELOGIN = "RELOGIN";
-
-/**
  * Tells the metaserver about a secondary IP address.
  *
  * Assuming the client already has a connection over IPv6 and tries to establish a secondary
@@ -220,8 +198,8 @@
  * \li string:    protocol version
  * \li string:    client name - the one the metaserver replied at the first login
  * \li string:    for registered accounts: password in clear text
- *                for unregistered users the random nonce used on login
- *                for an explanation of the nonce, see above.
+ *                for unregistered users the UUID used on login
+ *                for an explanation of the UUID, see above.
  */
 static const std::string IGPCMD_TELL_IP = "TELL_IP";
 
@@ -291,15 +269,13 @@
  * The metaserver will echo the message if the client is allowed to send chat messages.
  *
  * The metaserver either broadcasts a chat message to all clients or sends it to the pm recipient
- * with the
- * following payload:
+ * with the following payload:
  * \li string:    sender (may be empty if it is a system message)
  * \li string:    the message
  * \li string:    type ("public", "private", "system")
  *
- * \note system messages are the motd (Sent by the metaserver to the client, after login (but not
- * relogin)
- *       and after the motd got changed) and announcements by superusers.
+ * \note system messages are the motd (Sent by the metaserver to the client, after login
+ * (but not relogin) and after the motd got changed) and announcements by superusers.
  */
 static const std::string IGPCMD_CHAT = "CHAT";
 

=== modified file 'src/third_party/CMakeLists.txt'
--- src/third_party/CMakeLists.txt	2016-04-08 18:00:30 +0000
+++ src/third_party/CMakeLists.txt	2017-10-13 19:50:29 +0000
@@ -83,3 +83,10 @@
     gettext/gettext.h
     gettext/dummy_gettext.cc
 )
+
+wl_library(third_party_crypto
+  THIRD_PARTY
+  SRCS
+    crypto/sha3.h
+    crypto/sha3.c
+)

=== added directory 'src/third_party/crypto'
=== added file 'src/third_party/crypto/README.txt'
--- src/third_party/crypto/README.txt	1970-01-01 00:00:00 +0000
+++ src/third_party/crypto/README.txt	2017-10-13 19:50:29 +0000
@@ -0,0 +1,3 @@
+Lightweight SHA3 library downloaded from:
+https://github.com/brainhub/SHA3IUF
+Split up into header and source file, self-tests removed
\ No newline at end of file

=== added file 'src/third_party/crypto/sha3.c'
--- src/third_party/crypto/sha3.c	1970-01-01 00:00:00 +0000
+++ src/third_party/crypto/sha3.c	2017-10-13 19:50:29 +0000
@@ -0,0 +1,302 @@
+/* -------------------------------------------------------------------------
+ * Works when compiled for either 32-bit or 64-bit targets, optimized for 
+ * 64 bit.
+ *
+ * Canonical implementation of Init/Update/Finalize for SHA-3 byte input. 
+ *
+ * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added.
+ *
+ * Based on code from http://keccak.noekeon.org/ .
+ *
+ * I place the code that I wrote into public domain, free to use. 
+ *
+ * I would appreciate if you give credits to this work if you used it to 
+ * write or test * your code.
+ *
+ * Aug 2015. Andrey Jivsov. crypto@xxxxxxxxxxxx
+ * ---------------------------------------------------------------------- */
+
+// Downloaded from
+// https://github.com/brainhub/SHA3IUF
+// Split up into header and source file by Notabilis, self-tests removed
+
+#include "sha3.h"
+
+#define SHA3_ASSERT( x )
+#if defined(_MSC_VER)
+#define SHA3_TRACE( format, ...)
+#define SHA3_TRACE_BUF( format, buf, l, ...)
+#else
+#define SHA3_TRACE(format, args...)
+#define SHA3_TRACE_BUF(format, buf, l, args...)
+#endif
+
+//#define SHA3_USE_KECCAK
+/* 
+ * Define SHA3_USE_KECCAK to run "pure" Keccak, as opposed to SHA3.
+ * The tests that this macro enables use the input and output from [Keccak]
+ * (see the reference below). The used test vectors aren't correct for SHA3, 
+ * however, they are helpful to verify the implementation.
+ * SHA3_USE_KECCAK only changes one line of code in Finalize.
+ */
+
+#if defined(_MSC_VER)
+#define SHA3_CONST(x) x
+#else
+#define SHA3_CONST(x) x##L
+#endif
+
+/* The following state definition should normally be in a separate 
+ * header file 
+ */
+
+
+
+#ifndef SHA3_ROTL64
+#define SHA3_ROTL64(x, y) \
+	(((x) << (y)) | ((x) >> ((sizeof(uint64_t)*8) - (y))))
+#endif
+
+static const uint64_t keccakf_rndc[24] = {
+    SHA3_CONST(0x0000000000000001UL), SHA3_CONST(0x0000000000008082UL),
+    SHA3_CONST(0x800000000000808aUL), SHA3_CONST(0x8000000080008000UL),
+    SHA3_CONST(0x000000000000808bUL), SHA3_CONST(0x0000000080000001UL),
+    SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008009UL),
+    SHA3_CONST(0x000000000000008aUL), SHA3_CONST(0x0000000000000088UL),
+    SHA3_CONST(0x0000000080008009UL), SHA3_CONST(0x000000008000000aUL),
+    SHA3_CONST(0x000000008000808bUL), SHA3_CONST(0x800000000000008bUL),
+    SHA3_CONST(0x8000000000008089UL), SHA3_CONST(0x8000000000008003UL),
+    SHA3_CONST(0x8000000000008002UL), SHA3_CONST(0x8000000000000080UL),
+    SHA3_CONST(0x000000000000800aUL), SHA3_CONST(0x800000008000000aUL),
+    SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008080UL),
+    SHA3_CONST(0x0000000080000001UL), SHA3_CONST(0x8000000080008008UL)
+};
+
+static const unsigned keccakf_rotc[24] = {
+    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62,
+    18, 39, 61, 20, 44
+};
+
+static const unsigned keccakf_piln[24] = {
+    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20,
+    14, 22, 9, 6, 1
+};
+
+/* generally called after SHA3_KECCAK_SPONGE_WORDS-ctx->capacityWords words 
+ * are XORed into the state s 
+ */
+static void
+keccakf(uint64_t s[25])
+{
+    int i, j, round;
+    uint64_t t, bc[5];
+#define KECCAK_ROUNDS 24
+
+    for(round = 0; round < KECCAK_ROUNDS; round++) {
+
+        /* Theta */
+        for(i = 0; i < 5; i++)
+            bc[i] = s[i] ^ s[i + 5] ^ s[i + 10] ^ s[i + 15] ^ s[i + 20];
+
+        for(i = 0; i < 5; i++) {
+            t = bc[(i + 4) % 5] ^ SHA3_ROTL64(bc[(i + 1) % 5], 1);
+            for(j = 0; j < 25; j += 5)
+                s[j + i] ^= t;
+        }
+
+        /* Rho Pi */
+        t = s[1];
+        for(i = 0; i < 24; i++) {
+            j = keccakf_piln[i];
+            bc[0] = s[j];
+            s[j] = SHA3_ROTL64(t, keccakf_rotc[i]);
+            t = bc[0];
+        }
+
+        /* Chi */
+        for(j = 0; j < 25; j += 5) {
+            for(i = 0; i < 5; i++)
+                bc[i] = s[j + i];
+            for(i = 0; i < 5; i++)
+                s[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5];
+        }
+
+        /* Iota */
+        s[0] ^= keccakf_rndc[round];
+    }
+}
+
+/* *************************** Public Inteface ************************ */
+
+/* For Init or Reset call these: */
+void
+sha3_Init256(sha3_context *priv)
+{
+    sha3_context *ctx = (sha3_context *) priv;
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->capacityWords = 2 * 256 / (8 * sizeof(uint64_t));
+}
+
+void
+sha3_Init384(sha3_context *priv)
+{
+    sha3_context *ctx = (sha3_context *) priv;
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->capacityWords = 2 * 384 / (8 * sizeof(uint64_t));
+}
+
+void
+sha3_Init512(sha3_context *priv)
+{
+    sha3_context *ctx = (sha3_context *) priv;
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->capacityWords = 2 * 512 / (8 * sizeof(uint64_t));
+}
+
+void
+sha3_Update(sha3_context *priv, uint8_t const *bufIn, size_t len)
+{
+    sha3_context *ctx = (sha3_context *) priv;
+
+    /* 0...7 -- how much is needed to have a word */
+    unsigned old_tail = (8 - ctx->byteIndex) & 7;
+
+    size_t words;
+    unsigned tail;
+    size_t i;
+
+    const uint8_t *buf = bufIn;
+
+    SHA3_TRACE_BUF("called to update with:", buf, len);
+
+    SHA3_ASSERT(ctx->byteIndex < 8);
+    SHA3_ASSERT(ctx->wordIndex < sizeof(ctx->s) / sizeof(ctx->s[0]));
+
+    if(len < old_tail) {        /* have no complete word or haven't started 
+                                 * the word yet */
+        SHA3_TRACE("because %d<%d, store it and return", (unsigned)len,
+                (unsigned)old_tail);
+        /* endian-independent code follows: */
+        while (len--)
+            ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);
+        SHA3_ASSERT(ctx->byteIndex < 8);
+        return;
+    }
+
+    if(old_tail) {              /* will have one word to process */
+        SHA3_TRACE("completing one word with %d bytes", (unsigned)old_tail);
+        /* endian-independent code follows: */
+        len -= old_tail;
+        while (old_tail--)
+            ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);
+
+        /* now ready to add saved to the sponge */
+        ctx->s[ctx->wordIndex] ^= ctx->saved;
+        SHA3_ASSERT(ctx->byteIndex == 8);
+        ctx->byteIndex = 0;
+        ctx->saved = 0;
+        if(++ctx->wordIndex ==
+                (SHA3_KECCAK_SPONGE_WORDS - ctx->capacityWords)) {
+            keccakf(ctx->s);
+            ctx->wordIndex = 0;
+        }
+    }
+
+    /* now work in full words directly from input */
+
+    SHA3_ASSERT(ctx->byteIndex == 0);
+
+    words = len / sizeof(uint64_t);
+    tail = len - words * sizeof(uint64_t);
+
+    SHA3_TRACE("have %d full words to process", (unsigned)words);
+
+    for(i = 0; i < words; i++, buf += sizeof(uint64_t)) {
+        const uint64_t t = (uint64_t) (buf[0]) |
+                ((uint64_t) (buf[1]) << 8 * 1) |
+                ((uint64_t) (buf[2]) << 8 * 2) |
+                ((uint64_t) (buf[3]) << 8 * 3) |
+                ((uint64_t) (buf[4]) << 8 * 4) |
+                ((uint64_t) (buf[5]) << 8 * 5) |
+                ((uint64_t) (buf[6]) << 8 * 6) |
+                ((uint64_t) (buf[7]) << 8 * 7);
+#if defined(__x86_64__ ) || defined(__i386__)
+        SHA3_ASSERT(memcmp(&t, buf, 8) == 0);
+#endif
+        ctx->s[ctx->wordIndex] ^= t;
+        if(++ctx->wordIndex ==
+                (SHA3_KECCAK_SPONGE_WORDS - ctx->capacityWords)) {
+            keccakf(ctx->s);
+            ctx->wordIndex = 0;
+        }
+    }
+
+    SHA3_TRACE("have %d bytes left to process, save them", (unsigned)tail);
+
+    /* finally, save the partial word */
+    SHA3_ASSERT(ctx->byteIndex == 0 && tail < 8);
+    while (tail--) {
+        SHA3_TRACE("Store byte %02x '%c'", *buf, *buf);
+        ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);
+    }
+    SHA3_ASSERT(ctx->byteIndex < 8);
+    SHA3_TRACE("Have saved=0x%016" PRIx64 " at the end", ctx->saved);
+}
+
+/* This is simply the 'update' with the padding block.
+ * The padding block is 0x01 || 0x00* || 0x80. First 0x01 and last 0x80 
+ * bytes are always present, but they can be the same byte.
+ */
+uint8_t const *
+sha3_Finalize(sha3_context *priv)
+{
+    sha3_context *ctx = (sha3_context *) priv;
+
+    SHA3_TRACE("called with %d bytes in the buffer", ctx->byteIndex);
+
+    /* Append 2-bit suffix 01, per SHA-3 spec. Instead of 1 for padding we
+     * use 1<<2 below. The 0x02 below corresponds to the suffix 01.
+     * Overall, we feed 0, then 1, and finally 1 to start padding. Without
+     * M || 01, we would simply use 1 to start padding. */
+
+#ifndef SHA3_USE_KECCAK
+    /* SHA3 version */
+    ctx->s[ctx->wordIndex] ^=
+            (ctx->saved ^ ((uint64_t) ((uint64_t) (0x02 | (1 << 2)) <<
+                            ((ctx->byteIndex) * 8))));
+#else
+    /* For testing the "pure" Keccak version */
+    ctx->s[ctx->wordIndex] ^=
+            (ctx->saved ^ ((uint64_t) ((uint64_t) 1 << (ctx->byteIndex *
+                                    8))));
+#endif
+
+    ctx->s[SHA3_KECCAK_SPONGE_WORDS - ctx->capacityWords - 1] ^=
+            SHA3_CONST(0x8000000000000000UL);
+    keccakf(ctx->s);
+
+    /* Return first bytes of the ctx->s. This conversion is not needed for
+     * little-endian platforms e.g. wrap with #if !defined(__BYTE_ORDER__)
+     * || !defined(__ORDER_LITTLE_ENDIAN__) || \
+     * __BYTE_ORDER__!=__ORDER_LITTLE_ENDIAN__ ... the conversion below ...
+     * #endif */
+    {
+        unsigned i;
+        for(i = 0; i < SHA3_KECCAK_SPONGE_WORDS; i++) {
+            const unsigned t1 = (uint32_t) ctx->s[i];
+            const unsigned t2 = (uint32_t) ((ctx->s[i] >> 16) >> 16);
+            ctx->sb[i * 8 + 0] = (uint8_t) (t1);
+            ctx->sb[i * 8 + 1] = (uint8_t) (t1 >> 8);
+            ctx->sb[i * 8 + 2] = (uint8_t) (t1 >> 16);
+            ctx->sb[i * 8 + 3] = (uint8_t) (t1 >> 24);
+            ctx->sb[i * 8 + 4] = (uint8_t) (t2);
+            ctx->sb[i * 8 + 5] = (uint8_t) (t2 >> 8);
+            ctx->sb[i * 8 + 6] = (uint8_t) (t2 >> 16);
+            ctx->sb[i * 8 + 7] = (uint8_t) (t2 >> 24);
+        }
+    }
+
+    SHA3_TRACE_BUF("Hash: (first 32 bytes)", ctx->sb, 256 / 8);
+
+    return (ctx->sb);
+}

=== added file 'src/third_party/crypto/sha3.h'
--- src/third_party/crypto/sha3.h	1970-01-01 00:00:00 +0000
+++ src/third_party/crypto/sha3.h	2017-10-13 19:50:29 +0000
@@ -0,0 +1,73 @@
+/* -------------------------------------------------------------------------
+ * Works when compiled for either 32-bit or 64-bit targets, optimized for
+ * 64 bit.
+ *
+ * Canonical implementation of Init/Update/Finalize for SHA-3 byte input.
+ *
+ * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added.
+ *
+ * Based on code from http://keccak.noekeon.org/ .
+ *
+ * I place the code that I wrote into public domain, free to use.
+ *
+ * I would appreciate if you give credits to this work if you used it to
+ * write or test * your code.
+ *
+ * Aug 2015. Andrey Jivsov. crypto@xxxxxxxxxxxx
+ * ---------------------------------------------------------------------- */
+
+#ifndef SHA3_H
+#define SHA3_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 'Words' here refers to uint64_t */
+#define SHA3_KECCAK_SPONGE_WORDS \
+	(((1600)/8/*bits to byte*/)/sizeof(uint64_t))
+typedef struct sha3_context_ {
+    uint64_t saved;             /* the portion of the input message that we
+                                 * didn't consume yet */
+    union {                     /* Keccak's state */
+        uint64_t s[SHA3_KECCAK_SPONGE_WORDS];
+        uint8_t sb[SHA3_KECCAK_SPONGE_WORDS * 8];
+    };
+    unsigned byteIndex;         /* 0..7--the next byte after the set one
+                                 * (starts from 0; 0--none are buffered) */
+    unsigned wordIndex;         /* 0..24--the next word to integrate input
+                                 * (starts from 0) */
+    unsigned capacityWords;     /* the double size of the hash output in
+                                 * words (e.g. 16 for Keccak 512) */
+} sha3_context;
+
+
+/* For Init or Reset call these: */
+void
+sha3_Init256(sha3_context *priv);
+
+void
+sha3_Init384(sha3_context *priv);
+
+void
+sha3_Init512(sha3_context *priv);
+
+void
+sha3_Update(sha3_context *priv, uint8_t const *bufIn, size_t len);
+
+/* This is simply the 'update' with the padding block.
+ * The padding block is 0x01 || 0x00* || 0x80. First 0x01 and last 0x80
+ * bytes are always present, but they can be the same byte.
+ */
+uint8_t const *
+sha3_Finalize(sha3_context *priv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // SHA3_H

=== modified file 'src/ui_fsmenu/internet_lobby.cc'
--- src/ui_fsmenu/internet_lobby.cc	2017-06-20 19:55:32 +0000
+++ src/ui_fsmenu/internet_lobby.cc	2017-10-13 19:50:29 +0000
@@ -35,7 +35,8 @@
 
 FullscreenMenuInternetLobby::FullscreenMenuInternetLobby(char const* const nick,
                                                          char const* const pwd,
-                                                         bool registered)
+                                                         bool registered,
+                                                         bool send_uuid)
    : FullscreenMenuBase(),
 
      // Values for alignment and size
@@ -103,7 +104,8 @@
      // Login information
      nickname_(nick),
      password_(pwd),
-     is_registered_(registered) {
+     is_registered_(registered),
+     send_uuid_(send_uuid) {
 	joingame_.sigclicked.connect(
 	   boost::bind(&FullscreenMenuInternetLobby::clicked_joingame, boost::ref(*this)));
 	hostgame_.sigclicked.connect(
@@ -192,8 +194,16 @@
 	Section& s = g_options.pull_section("global");
 	const std::string& metaserver = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());
 	uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT);
+	std::string pwd;
+	if (is_registered_) {
+		pwd = password_;
+	} else {
+		// Pass the uuid, even when it might not be used
+		pwd = s.get_string("uuid");
+	}
+	assert(!pwd.empty());
 
-	InternetGaming::ref().login(nickname_, password_, is_registered_, metaserver, port);
+	InternetGaming::ref().login(nickname_, pwd, is_registered_, send_uuid_, metaserver, port);
 }
 
 /// fills the server list

=== modified file 'src/ui_fsmenu/internet_lobby.h'
--- src/ui_fsmenu/internet_lobby.h	2017-01-25 18:55:59 +0000
+++ src/ui_fsmenu/internet_lobby.h	2017-10-13 19:50:29 +0000
@@ -36,7 +36,7 @@
 
 class FullscreenMenuInternetLobby : public FullscreenMenuBase {
 public:
-	FullscreenMenuInternetLobby(const char*, const char*, bool);
+	FullscreenMenuInternetLobby(const char*, const char*, bool, bool);
 
 	void think() override;
 
@@ -64,6 +64,7 @@
 	const char* nickname_;
 	const char* password_;
 	bool is_registered_;
+	bool send_uuid_;
 
 	void fill_games_list(const std::vector<InternetGame>*);
 	void fill_client_list(const std::vector<InternetClient>*);

=== modified file 'src/ui_fsmenu/multiplayer.cc'
--- src/ui_fsmenu/multiplayer.cc	2017-02-25 13:27:40 +0000
+++ src/ui_fsmenu/multiplayer.cc	2017-10-13 19:50:29 +0000
@@ -105,14 +105,17 @@
 		nickname_ = s.get_string("nickname", _("nobody"));
 		password_ = s.get_string("password", "nobody");
 		register_ = s.get_bool("registered", false);
+		send_uuid_ = s.get_bool("send_uuid", false);
 	} else {
 		LoginBox lb(*this);
 		if (lb.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kOk) {
 			nickname_ = lb.get_nickname();
 			password_ = lb.get_password();
 			register_ = lb.registered();
+			send_uuid_ = lb.send_uuid();
 
 			s.set_bool("registered", lb.registered());
+			s.set_bool("send_uuid", lb.send_uuid());
 			s.set_bool("auto_log", lb.set_automaticlog());
 		} else {
 			return;
@@ -122,7 +125,14 @@
 	// Try to connect to the metaserver
 	const std::string& meta = s.get_string("metaserver", INTERNET_GAMING_METASERVER.c_str());
 	uint32_t port = s.get_natural("metaserverport", INTERNET_GAMING_PORT);
-	InternetGaming::ref().login(nickname_, password_, register_, meta, port);
+	std::string pwd;
+	if (register_) {
+		pwd = password_;
+	} else {
+		pwd = s.get_string("uuid");
+	}
+	assert(!pwd.empty());
+	InternetGaming::ref().login(nickname_, pwd, register_, send_uuid_, meta, port);
 
 	// Check whether metaserver send some data
 	if (InternetGaming::ref().logged_in())

=== modified file 'src/ui_fsmenu/multiplayer.h'
--- src/ui_fsmenu/multiplayer.h	2017-01-25 18:55:59 +0000
+++ src/ui_fsmenu/multiplayer.h	2017-10-13 19:50:29 +0000
@@ -44,6 +44,9 @@
 	bool registered() {
 		return register_;
 	}
+	bool send_uuid() {
+		return send_uuid_;
+	}
 
 protected:
 	void clicked_ok() override;
@@ -61,6 +64,7 @@
 	std::string nickname_;
 	std::string password_;
 	bool register_;
+	bool send_uuid_;
 	bool auto_log_;
 };
 

=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc	2017-09-11 08:09:07 +0000
+++ src/wlapplication.cc	2017-10-13 19:50:29 +0000
@@ -29,6 +29,7 @@
 #include <fstream>
 #include <iostream>
 #include <memory>
+#include <random>
 #include <stdexcept>
 #include <string>
 
@@ -751,6 +752,8 @@
 	s.get_bool("write_syncstreams");
 	s.get_bool("sound_at_message");
 	s.get_bool("transparent_chat");
+	s.get_bool("send_uuid");
+	s.get_string("uuid");
 	s.get_string("registered");
 	s.get_string("nickname");
 	s.get_string("password");
@@ -763,6 +766,34 @@
 	s.get_natural("metaserverport");
 	// KLUDGE!
 
+	long int last_start = s.get_int("last_start", 0);
+	if (last_start + 24*60*60 < time(nullptr)) {
+		// First start of the game or not started for 24 hours. Create a (new) UUID.
+		// Admittedly this is a pretty stupid generator. But it should be fine for us.
+		// For the use of the UUID, see network/internet_gaming_protocol.h
+		static const std::string random_chars = "0123456789ABCDEF";
+		std::string uuid;
+		std::random_device rd;
+		std::mt19937 gen(rd());
+		std::uniform_int_distribution<> dist(0, random_chars.length() - 1);
+		while (uuid.length() < 16) {
+			uuid.push_back(random_chars[dist(gen)]);
+		}
+		s.set_string("uuid", uuid);
+	}
+	s.set_int("last_start", time(nullptr));
+
+	// Save configuration now. Otherwise, the UUID is not saved
+	// when the game crashes, loosing part of its advantage
+	// NOCOM(Sebastian): This might be broken. Starting a second game re-enables the music
+	try {
+		g_options.write("config", false);
+	} catch (const std::exception& e) {
+		log("WARNING: could not save configuration: %s\n", e.what());
+	} catch (...) {
+		log("WARNING: could not save configuration");
+	}
+
 	return true;
 }
 
@@ -1159,6 +1190,7 @@
 			std::string playername = mp.get_nickname();
 			std::string password(mp.get_password());
 			bool registered = mp.registered();
+			bool send_uuid = mp.send_uuid();
 
 			Section& s = g_options.pull_section("global");
 			s.set_string("nickname", playername);
@@ -1167,7 +1199,7 @@
 				s.set_string("password", password);
 
 			// reinitalise in every run, else graphics look strange
-			FullscreenMenuInternetLobby ns(playername.c_str(), password.c_str(), registered);
+			FullscreenMenuInternetLobby ns(playername.c_str(), password.c_str(), registered, send_uuid);
 			ns.run<FullscreenMenuBase::MenuTarget>();
 
 			if (InternetGaming::ref().logged_in())

=== modified file 'src/wui/login_box.cc'
--- src/wui/login_box.cc	2017-02-26 12:16:09 +0000
+++ src/wui/login_box.cc	2017-10-13 19:50:29 +0000
@@ -26,7 +26,7 @@
 #include "ui_basic/messagebox.h"
 
 LoginBox::LoginBox(Panel& parent)
-   : Window(&parent, "login_box", 0, 0, 500, 220, _("Metaserver login")) {
+   : Window(&parent, "login_box", 0, 0, 500, 270, _("Metaserver login")) {
 	center_to_parent();
 
 	int32_t margin = 10;
@@ -43,9 +43,12 @@
 
 	cb_register = new UI::Checkbox(this, Vector2i(margin, 110), _("Log in to a registered account"),
 	                               "", get_inner_w() - 2 * margin);
-	cb_auto_log = new UI::Checkbox(this, Vector2i(margin, 135),
+	cb_auto_log = new UI::Checkbox(this, Vector2i(margin, 145),
 	                               _("Automatically use this login information from now on."), "",
 	                               get_inner_w() - 2 * margin);
+	cb_send_uuid = new UI::Checkbox(this, Vector2i(margin, 180),
+	                               _("Send unique ID to reconnect with same nickname on errors."), "",
+	                               get_inner_w() - 2 * margin);
 
 	UI::Button* loginbtn =
 	   new UI::Button(this, "login", UI::g_fh1->fontset()->is_rtl() ?
@@ -65,6 +68,7 @@
 	eb_nickname->set_text(s.get_string("nickname", _("nobody")));
 	eb_password->set_text(s.get_string("password", ""));
 	cb_register->set_state(s.get_bool("registered", false));
+	cb_send_uuid->set_state(s.get_bool("send_uuid", false));
 	eb_nickname->focus();
 }
 

=== modified file 'src/wui/login_box.h'
--- src/wui/login_box.h	2017-06-24 08:47:46 +0000
+++ src/wui/login_box.h	2017-10-13 19:50:29 +0000
@@ -38,6 +38,9 @@
 	bool registered() {
 		return cb_register->get_state();
 	}
+	bool send_uuid() {
+		return cb_send_uuid->get_state();
+	}
 	bool set_automaticlog() {
 		return cb_auto_log->get_state();
 	}
@@ -53,6 +56,7 @@
 	UI::EditBox* eb_password;
 	UI::Checkbox* cb_register;
 	UI::Checkbox* cb_auto_log;
+	UI::Checkbox* cb_send_uuid;
 	UI::Textarea* ta_nickname;
 	UI::Textarea* ta_password;
 	UI::MultilineTextarea* pwd_warning;


Follow ups