1 /*
2  * Copyright (C) 2002-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "wui/login_box.h"
21 
22 #include "base/i18n.h"
23 #include "graphic/font_handler.h"
24 #include "network/crypto.h"
25 #include "network/internet_gaming.h"
26 #include "network/internet_gaming_protocol.h"
27 #include "ui_basic/button.h"
28 #include "ui_basic/messagebox.h"
29 #include "wlapplication_options.h"
30 
LoginBox(Panel & parent)31 LoginBox::LoginBox(Panel& parent)
32    : Window(&parent, "login_box", 0, 0, 500, 280, _("Online Game Settings")) {
33 	center_to_parent();
34 
35 	int32_t margin = 10;
36 
37 	ta_nickname = new UI::Textarea(this, margin, margin, 0, 0, _("Nickname:"));
38 	ta_password = new UI::Textarea(this, margin, 70, 0, 0, _("Password:"));
39 	eb_nickname = new UI::EditBox(this, 150, margin, 330, UI::PanelStyle::kWui);
40 	eb_password = new UI::EditBox(this, 150, 70, 330, UI::PanelStyle::kWui);
41 
42 	cb_register = new UI::Checkbox(this, Vector2i(margin, 40), _("Log in to a registered account."),
43 	                               "", get_inner_w() - 2 * margin);
44 
45 	register_account = new UI::MultilineTextarea(
46 	   this, margin, 105, 470, 140, UI::PanelStyle::kWui,
47 	   (boost::format(_("In order to use a registered "
48 	                    "account, you need an account on the Widelands website. "
49 	                    "Please log in at %s and set an online "
50 	                    "gaming password on your profile page.")) %
51 	    "\n\nhttps://widelands.org/accounts/register/\n\n")
52 	      .str());
53 
54 	loginbtn =
55 	   new UI::Button(this, "login", UI::g_fh->fontset()->is_rtl() ?
56 	                                    (get_inner_w() / 2 - 200) / 2 :
57 	                                    (get_inner_w() / 2 - 200) / 2 + get_inner_w() / 2,
58 	                  get_inner_h() - 20 - margin, 200, 20, UI::ButtonStyle::kWuiPrimary, _("Save"));
59 
60 	cancelbtn =
61 	   new UI::Button(this, "cancel", UI::g_fh->fontset()->is_rtl() ?
62 	                                     (get_inner_w() / 2 - 200) / 2 + get_inner_w() / 2 :
63 	                                     (get_inner_w() / 2 - 200) / 2,
64 	                  loginbtn->get_y(), 200, 20, UI::ButtonStyle::kWuiSecondary, _("Cancel"));
65 
66 	loginbtn->sigclicked.connect([this]() { clicked_ok(); });
67 	cancelbtn->sigclicked.connect([this]() { clicked_back(); });
68 	eb_nickname->changed.connect([this]() { change_playername(); });
69 	cb_register->clickedto.connect([this](bool) { clicked_register(); });
70 
71 	eb_nickname->set_text(get_config_string("nickname", _("nobody")));
72 	cb_register->set_state(get_config_bool("registered", false));
73 	eb_password->set_password(true);
74 
75 	if (registered()) {
76 		eb_password->set_text(get_config_string("password_sha1", ""));
77 		loginbtn->set_enabled(false);
78 	} else {
79 		eb_password->set_can_focus(false);
80 		ta_password->set_style(g_gr->styles().font_style(UI::FontStyle::kDisabled));
81 	}
82 
83 	eb_nickname->focus();
84 
85 	eb_nickname->cancel.connect([this]() { clicked_back(); });
86 	eb_password->cancel.connect([this]() { clicked_back(); });
87 }
88 
89 /// think function of the UI (main loop)
think()90 void LoginBox::think() {
91 	verify_input();
92 }
93 
94 /**
95  * called, if "login" is pressed.
96  */
clicked_ok()97 void LoginBox::clicked_ok() {
98 	if (cb_register->get_state()) {
99 		if (check_password()) {
100 			set_config_string("nickname", eb_nickname->text());
101 			set_config_bool("registered", true);
102 			end_modal<UI::Panel::Returncodes>(UI::Panel::Returncodes::kOk);
103 		}
104 	} else {
105 		set_config_string("nickname", eb_nickname->text());
106 		set_config_bool("registered", false);
107 		set_config_string("password_sha1", "");
108 		end_modal<UI::Panel::Returncodes>(UI::Panel::Returncodes::kOk);
109 	}
110 }
111 
112 /// Called if "cancel" was pressed
clicked_back()113 void LoginBox::clicked_back() {
114 	end_modal<UI::Panel::Returncodes>(UI::Panel::Returncodes::kBack);
115 }
116 
117 /// Called when nickname was changed
change_playername()118 void LoginBox::change_playername() {
119 	cb_register->set_state(false);
120 	eb_password->set_can_focus(false);
121 	eb_password->set_text("");
122 }
123 
handle_key(bool down,SDL_Keysym code)124 bool LoginBox::handle_key(bool down, SDL_Keysym code) {
125 	if (down) {
126 		switch (code.sym) {
127 		case SDLK_KP_ENTER:
128 		case SDLK_RETURN:
129 			clicked_ok();
130 			return true;
131 		case SDLK_ESCAPE:
132 			clicked_back();
133 			return true;
134 		default:
135 			break;  // not handled
136 		}
137 	}
138 	return UI::Panel::handle_key(down, code);
139 }
140 
clicked_register()141 void LoginBox::clicked_register() {
142 	if (cb_register->get_state()) {
143 		ta_password->set_style(g_gr->styles().font_style(UI::FontStyle::kDisabled));
144 		eb_password->set_can_focus(false);
145 		eb_password->set_text("");
146 	} else {
147 		ta_password->set_style(g_gr->styles().font_style(UI::FontStyle::kLabel));
148 		eb_password->set_can_focus(true);
149 		eb_password->focus();
150 	}
151 }
152 
verify_input()153 void LoginBox::verify_input() {
154 	// Check if all neccessary input fields are valid
155 	loginbtn->set_enabled(true);
156 	eb_nickname->set_tooltip("");
157 	eb_password->set_tooltip("");
158 	eb_nickname->set_warning(false);
159 
160 	if (eb_nickname->text().empty()) {
161 		eb_nickname->set_warning(true);
162 		eb_nickname->set_tooltip(_("Please enter a nickname!"));
163 		loginbtn->set_enabled(false);
164 	} else if (!InternetGaming::ref().valid_username(eb_nickname->text())) {
165 		eb_nickname->set_warning(true);
166 		eb_nickname->set_tooltip(_("Enter a valid nickname. This value may contain only "
167 		                           "English letters, numbers, and @ . + - _ characters."));
168 		loginbtn->set_enabled(false);
169 	}
170 
171 	if (eb_password->text().empty() && cb_register->get_state()) {
172 		eb_password->set_tooltip(_("Please enter your password!"));
173 		loginbtn->set_enabled(false);
174 	}
175 
176 	if (eb_password->has_focus() && eb_password->text() == get_config_string("password_sha1", "")) {
177 		eb_password->set_text("");
178 	}
179 
180 	if (cb_register->get_state() && eb_password->text() == get_config_string("password_sha1", "")) {
181 		loginbtn->set_enabled(false);
182 	}
183 }
184 
185 /// Check password against metaserver
check_password()186 bool LoginBox::check_password() {
187 	// Try to connect to the metaserver
188 	const std::string& meta = get_config_string("metaserver", INTERNET_GAMING_METASERVER.c_str());
189 	uint32_t port = get_config_natural("metaserverport", kInternetGamingPort);
190 	std::string password = crypto::sha1(eb_password->text());
191 
192 	if (!InternetGaming::ref().check_password(get_nickname(), password, meta, port)) {
193 		// something went wrong -> show the error message
194 		// idealy it is about the wrong password
195 		ChatMessage msg = InternetGaming::ref().get_messages().back();
196 		UI::WLMessageBox wmb(this, _("Error!"), msg.msg, UI::WLMessageBox::MBoxType::kOk);
197 		wmb.run<UI::Panel::Returncodes>();
198 		eb_password->set_text("");
199 		eb_password->focus();
200 		return false;
201 	}
202 	// NOTE: The password is only stored (in memory and on disk) and transmitted (over the network to
203 	// the metaserver) as cryptographic hash. This does NOT mean that the password is stored securely
204 	// on the local disk. While the password should be secure while transmitted to the metaserver
205 	// (no-one can use the transmitted data to log in as the user) this is not the case for local
206 	// storage. The stored hash of the password makes it hard to look at the configuration file and
207 	// figure out the plaintext password to, e.g., log in on the forum. However, the stored hash can
208 	// be copied to another system and used to log in as the user on the metaserver. Further note:
209 	// SHA-1 is considered broken and shouldn't be used anymore. But since the passwords on the
210 	// server are protected by SHA-1 we have to use it here, too
211 	set_config_string("password_sha1", password);
212 	InternetGaming::ref().logout();
213 	return true;
214 }
215