1 /*
2 * Save username and passwords details at login and allow selection from the login screen.
3 * Also store new character and successful password change details.
4 * http://www.eternal-lands.com/forum/index.php?/topic/61189-new-login-screen-for-multiple-characters/
5 *
6 * Author bluap/pjbroad March 2019.
7 *
8 */
9
10 #include <algorithm>
11 #include <fstream>
12 #include <iostream>
13 #include <iterator>
14 #include <sstream>
15 #include <string>
16 #include <vector>
17
18 #include "elloggingwrapper.h"
19 #include "elwindows.h"
20 #include "font.h"
21 #include "gl_init.h"
22 #include "loginwin.h"
23 #include "multiplayer.h"
24 #include "named_colours.h"
25 #include "io/elpathwrapper.h"
26 #include "password_manager.h"
27 #include "translate.h"
28 #include "sound.h"
29 #include "xor_cipher.hpp"
30
31 extern "C"
32 {
33 static int display_pm_handler(window_info *win);
34 static int ui_scale_pm_handler(window_info *win);
35 static int change_pm_font_handler(window_info *win, font_cat cat);
36 static int click_pm_handler(window_info *win, int mx, int my, Uint32 flags);
37 static int mouseover_pm_handler(window_info *win, int mx, int my);
38 static int click_show_password(widget_list *w, int mx, int my, Uint32 flags);
39 }
40
41 namespace Password_Manaager
42 {
43 // A simple class to hold a username and password pair
44 //
45 class Login
46 {
47 public:
Login(const std::string & the_name,const std::string & the_password)48 Login(const std::string& the_name, const std::string& the_password):
49 user_name(the_name), clear_password(the_password) {}
get_name(void) const50 const std::string & get_name(void) const { return user_name; }
get_password(void) const51 const std::string & get_password(void) const { return clear_password; }
52 static bool sort_compare(const Login &a, const Login &b);
53 private:
54 std::string user_name, clear_password;
55 };
56
57 // Used by the sort algorithm to alphabetically compare two Login names, case insensitive
58 //
sort_compare(const Login & a,const Login & b)59 bool Login::sort_compare(const Login &a, const Login &b)
60 {
61 std::string alower(a.get_name());
62 std::transform(alower.begin(), alower.end(), alower.begin(), tolower);
63 std::string blower(b.get_name());
64 std::transform(blower.begin(), blower.end(), blower.begin(), tolower);
65 return alower < blower;
66 }
67
68 // A simple class to storge and manage a collection of username/password pairs.
69 //
70 class Logins
71 {
72 public:
Logins(void)73 Logins(void) :
74 file_name(std::string(get_path_config()) + "passmngr_logins"),
75 cipher(std::string(get_path_config()) + "passmngr_key", MAX_USERNAME_LENGTH)
76 { load(); }
77 void load(void);
78 void set_details(void) const;
79 void add(const std::string& user_name, const std::string& password);
80 static bool is_valid_password(const std::string &password);
81 static bool get_validated_new_pasword(const char * old_and_new_password, std::string &new_password);
pending_change(const std::string & password)82 void pending_change(const std::string& password) { pending_new_password = password; }
83 bool confirm_change(void);
size(void) const84 size_t size(void) const { return logins.size(); }
begin() const85 std::vector<Login>::const_iterator begin() const { return logins.begin(); }
end() const86 std::vector<Login>::const_iterator end() const { return logins.end(); }
87 private:
88 bool common_add(const std::string& user_name, const std::string& password);
89 void save(void);
90 std::string pending_new_password;
91 std::vector<Login> logins;
92 std::string file_name;
93 XOR_Cipher::Cipher cipher;
94 };
95
96 // Add if the specified username/password are valid and new or modified.
97 // Return true if added.
98 //
common_add(const std::string & user_name,const std::string & password)99 bool Logins::common_add(const std::string& user_name, const std::string& password)
100 {
101 if ((user_name.size() >= MAX_USERNAME_LENGTH) || (password.size() >= MAX_USERNAME_LENGTH))
102 {
103 LOG_ERROR("%s: Error adding username [%s]\n", __PRETTY_FUNCTION__, user_name.c_str());
104 return false;
105 }
106 std::string lc_username = user_name;
107 std::transform(lc_username.begin(), lc_username.end(), lc_username.begin(), tolower);
108 for(std::vector<Login>::iterator i=logins.begin(); i<end(); ++i)
109 {
110 std::string temp_lower = i->get_name();
111 std::transform(temp_lower.begin(), temp_lower.end(), temp_lower.begin(), ::tolower);
112 if (temp_lower == lc_username)
113 {
114 if ((i->get_name() == user_name) && (i->get_password() == password))
115 return false;
116 logins.erase(i);
117 break;
118 }
119 }
120 logins.push_back(Login(user_name, password));
121 return true;
122 }
123
124 // Add a single new username/password pair if valid and new, then save. Not used when loading from file.
125 //
add(const std::string & user_name,const std::string & password)126 void Logins::add(const std::string& user_name, const std::string& password)
127 {
128 if (common_add(user_name, password))
129 {
130 std::sort(logins.begin(), logins.end(), Login::sort_compare);
131 save();
132 }
133 }
134
135 // Validates the string is a password, of the correct size and containing valid characters.
136 //
is_valid_password(const std::string & password)137 bool Logins::is_valid_password(const std::string &password)
138 {
139 if ((password.size() < MIN_PASSWORD_LEN) || (password.size() >= MAX_PASSWORD_LEN))
140 return false;
141 for (auto c: password)
142 if (!VALID_PASSWORD_CHAR(c))
143 return false;
144 return true;
145 }
146
147 // Common function to validate the string contains two words, and that the second at least is a valid password
148 // Returns true if the second word is a valid password and returned in the provided string
149 //
get_validated_new_pasword(const char * old_and_new_password,std::string & new_password)150 bool Logins::get_validated_new_pasword(const char * old_and_new_password, std::string &new_password)
151 {
152 std::istringstream iss(old_and_new_password);
153 std::vector<std::string> words((std::istream_iterator<std::string>(iss)), std::istream_iterator<std::string>());
154 if ((words.size() == 2) && is_valid_password(words[1]))
155 {
156 new_password = words[1];
157 return true;
158 }
159 else
160 return false;
161 }
162
163 // Called when the server sends the change password confirmation. Save the new password.
164 //
confirm_change(void)165 bool Logins::confirm_change(void)
166 {
167 bool ret_value;
168 if ((ret_value = is_valid_password(pending_new_password)))
169 {
170 add(get_username(), pending_new_password);
171 set_password(pending_new_password.c_str());
172 }
173 pending_new_password.clear();
174 return ret_value;
175 }
176
177 // Set the current password assiociated with the current username.
178 //
set_details(void) const179 void Logins::set_details(void) const
180 {
181 std::string user_name = std::string(get_username());
182 set_username(get_username()); // set all versions of the username
183 for(std::vector<Login>::const_iterator i=begin(); i<end(); ++i)
184 {
185 if (i->get_name() == user_name)
186 {
187 set_password(i->get_password().c_str());
188 return;
189 }
190 }
191 }
192
193 // Load username/password pairs from the file in the current config directory.
194 //
load(void)195 void Logins::load(void)
196 {
197 logins.clear();
198 std::ifstream in(file_name.c_str());
199 if (!in)
200 {
201 LOG_ERROR("%s: Failed to open [%s]\n", __PRETTY_FUNCTION__, file_name.c_str());
202 return;
203 }
204 std::string line;
205 while (getline(in, line))
206 {
207 std::istringstream iss(line);
208 std::vector<std::string> words((std::istream_iterator<std::string>(iss)), std::istream_iterator<std::string>());
209 if ((words.size() == 2))
210 {
211 if (!common_add(words[0], cipher.decrypt(cipher.hex_to_cipher(words[1]))))
212 LOG_ERROR("%s: Not loading username [%s]\n", __PRETTY_FUNCTION__, words[0].c_str());
213 }
214 else
215 LOG_ERROR("%s: Invalid line [%s]\n", __PRETTY_FUNCTION__, line.c_str());
216 }
217 std::sort(logins.begin(), logins.end(), Login::sort_compare);
218 in.close();
219 }
220
221 // Save username/password pairs to the file in the current config directory.
222 //
save(void)223 void Logins::save(void)
224 {
225 std::ofstream out(file_name.c_str(), std::ios_base::out | std::ios_base::trunc);
226 if (out)
227 {
228 for(std::vector<Login>::const_iterator i=begin(); i<end(); ++i)
229 out << i->get_name() << " " << cipher.cipher_to_hex(cipher.encrypt(i->get_password())) << std::endl;
230 out.close();
231 }
232 else
233 LOG_ERROR("%s: Failed to write [%s]\n", __PRETTY_FUNCTION__, file_name.c_str());
234 }
235
236 // A simple class to manage the username list section window.
237 //
238 class Window
239 {
240 public:
Window()241 Window() : window_id(-1), checkbox_id(0), checkbox_label_id(0), show_passwords(0), mouse_over_line(-1) {}
~Window()242 ~Window() { destroy(); }
243 void open(void);
244 int display(Logins *logins, window_info *win);
245 int click(Logins *logins, window_info *win, int mx, int my, Uint32 flags);
246 int mouseover(Logins *logins, window_info *win, int mx, int my);
247 int ui_scale(Logins *logins, window_info *win);
248 int change_font(Logins *logins, window_info *win, eternal_lands::FontManager::Category cat);
249 void toggle_show_password(Logins *logins, widget_list *w);
resize(Logins * logins)250 void resize(Logins *logins) { ui_scale(logins, &windows_list.window[window_id]); }
251 private:
252 void destroy(void);
253 int border_x, border_y, username_sep_y;
254 int window_id;
255 Uint32 scroll_id, checkbox_id, checkbox_label_id;
256 int show_passwords;
257 int mouse_over_line;
258 size_t max_displayed;
259 };
260
261 // Set and update the list window scale.
262 //
ui_scale(Logins * logins,window_info * win)263 int Window::ui_scale(Logins *logins, window_info *win)
264 {
265 border_x = border_y = username_sep_y = (int)(0.5 + win->current_scale * 10);
266 size_t max_available = static_cast<size_t>(0.8 * window_height - 2 * border_y + username_sep_y) / (username_sep_y + win->default_font_len_y);
267 max_displayed = std::min(max_available, logins->size());
268 int height = 2 * border_y + max_displayed * win->default_font_len_y + (max_displayed - 1) * username_sep_y;
269 int width = 2 * border_x + win->default_font_max_len_x * (MAX_USERNAME_LENGTH - 1) + win->box_size;
270 if (show_passwords)
271 width += border_x + win->default_font_max_len_x * (MAX_USERNAME_LENGTH - 1);
272 height = std::max(height, 4 * win->box_size);
273
274 int y_box, y_label;
275 if (checkbox_id > 0)
276 widget_destroy(win->window_id, checkbox_id);
277 if (checkbox_label_id > 0)
278 widget_destroy(win->window_id, checkbox_label_id);
279 if (win->box_size >= win->default_font_len_y)
280 {
281 y_box = height;
282 y_label = height + (win->box_size - win->default_font_len_y) / 2;
283 }
284 else
285 {
286 y_box = height + (win->default_font_len_y - win->box_size) / 2;
287 y_label = height;
288 }
289 checkbox_id = checkbox_add_extended(win->window_id, 2, NULL, border_x, y_box,
290 win->box_size, win->box_size, 0, win->current_scale, &show_passwords);
291 checkbox_label_id = label_add_extended(window_id, 3, NULL, 2 * border_x + win->box_size, y_label, 0, win->current_scale, show_passwords_str);
292 widget_set_OnClick(window_id, checkbox_id, (int (*)())&click_show_password);
293 widget_set_OnClick(window_id, checkbox_label_id, (int (*)())&click_show_password);
294 height += std::max(win->box_size, win->default_font_len_y) + border_y;
295 width = std::max(width, 4 * border_x + 2 * win->box_size + widget_get_width(win->window_id, checkbox_label_id));
296
297 widget_resize(win->window_id, scroll_id, win->box_size, height - win->box_size);
298 widget_move(win->window_id, scroll_id, width - win->box_size, win->box_size);
299 vscrollbar_set_bar_len(win->window_id, scroll_id, ((logins->size() < max_displayed) ?0: logins->size() - max_displayed));
300
301 resize_window(window_id, width, height);
302 move_window(window_id, win->pos_id, win->pos_loc, window_width / 2 + ((window_width / 2 - width) / 2), (window_height - height) / 2);
303 return 1;
304 }
305
change_font(Logins * logins,window_info * win,eternal_lands::FontManager::Category cat)306 int Window::change_font(Logins *logins, window_info *win, eternal_lands::FontManager::Category cat)
307 {
308 if (cat != win->font_category)
309 return 0;
310 ui_scale(logins, win);
311 return 1;
312 }
313
314 // Handle if the show password checkbox or label are clicked
315 //
toggle_show_password(Logins * logins,widget_list * w)316 void Window::toggle_show_password(Logins *logins, widget_list *w)
317 {
318 if (w->id == checkbox_label_id)
319 {
320 do_click_sound();
321 show_passwords ^= 1;
322 }
323 ui_scale(logins, &windows_list.window[window_id]);
324 }
325
326 // Handle mouse over events, highlighting the username under the mouse.
327 //
mouseover(Logins * logins,window_info * win,int mx,int my)328 int Window::mouseover(Logins *logins, window_info *win, int mx, int my)
329 {
330 if ((my > border_y) && (my < win->len_y - border_y))
331 {
332 if ((mx > border_x) && (mx < (win->len_x - win->box_size - border_x)))
333 {
334 mouse_over_line = (my - border_y) / (username_sep_y + win->default_font_len_y);
335 return 1;
336 }
337 }
338 return 0;
339 }
340
341 // Response to clicking on a username or scrolling the list window.
342 //
click(Logins * logins,window_info * win,int mx,int my,Uint32 flags)343 int Window::click(Logins *logins, window_info *win, int mx, int my, Uint32 flags)
344 {
345 if (flags & ELW_WHEEL_UP)
346 {
347 vscrollbar_scroll_up(win->window_id, scroll_id);
348 return 1;
349 }
350 else if (flags & ELW_WHEEL_DOWN)
351 {
352 vscrollbar_scroll_down(win->window_id, scroll_id);
353 return 1;
354 }
355 else if (((flags & ELW_LEFT_MOUSE) || (flags & ELW_RIGHT_MOUSE)) && (my > border_y) && (my < win->len_y - border_y))
356 {
357 if ((mx > border_x) && (mx < (win->len_x - win->box_size - border_x)))
358 {
359 size_t over_item = vscrollbar_get_pos(win->window_id, scroll_id) + (my - border_y) / (username_sep_y + win->default_font_len_y);
360 if (over_item < logins->size())
361 {
362 set_username((logins->begin() + over_item)->get_name().c_str());
363 set_password((logins->begin() + over_item)->get_password().c_str());
364 do_click_sound();
365 hide_window(window_id);
366 if (flags & ELW_LEFT_MOUSE)
367 send_login_info();
368 return 1;
369 }
370 }
371 }
372 return 0;
373 }
374
375 // Dispalay the username list window.
376 //
display(Logins * logins,window_info * win)377 int Window::display(Logins *logins, window_info *win)
378 {
379 if (!passmngr_enabled)
380 {
381 hide_window(window_id);
382 return 1;
383 }
384 int y = 0;
385 size_t scroll_off = vscrollbar_get_pos(win->window_id, scroll_id);
386 for (size_t i = scroll_off; i < (scroll_off + max_displayed); ++i)
387 {
388 if (i >= logins->size())
389 break;
390 std::vector<Login>::const_iterator curr = logins->begin() + i;
391 if (i == static_cast<size_t>(scroll_off + mouse_over_line))
392 elglColourN("global.mousehighlight");
393 else
394 glColor3f(1.0f, 1.0f, 1.0f);
395 draw_string_zoomed (border_x, border_y + y, (const unsigned char*)curr->get_name().c_str(), 1, win->current_scale);
396 if (show_passwords)
397 draw_string_zoomed (2 * border_x + win->default_font_max_len_x * (MAX_USERNAME_LENGTH - 1), border_y + y, (const unsigned char*)curr->get_password().c_str(), 1, win->current_scale);
398 y += win->default_font_len_y + username_sep_y;
399 }
400 mouse_over_line = -1;
401 return 1;
402 }
403
404 // Create or toggle open/close the username list window.
405 //
open(void)406 void Window::open(void)
407 {
408 if (window_id < 0)
409 {
410 window_id = create_window (login_select_window_str, -1, 0, 0, 0, 0, 0, ELW_USE_UISCALE|ELW_WIN_DEFAULT);
411 set_window_handler (window_id, ELW_HANDLER_DISPLAY, (int (*)())&display_pm_handler);
412 set_window_handler (window_id, ELW_HANDLER_MOUSEOVER, (int (*)())&mouseover_pm_handler);
413 set_window_handler (window_id, ELW_HANDLER_CLICK, (int (*)())&click_pm_handler);
414 set_window_handler (window_id, ELW_HANDLER_SHOW, (int (*)())&ui_scale_pm_handler);
415 set_window_handler (window_id, ELW_HANDLER_UI_SCALE, (int (*)())&ui_scale_pm_handler);
416 set_window_handler (window_id, ELW_HANDLER_FONT_CHANGE, (int (*)())&change_pm_font_handler);
417 scroll_id = vscrollbar_add_extended (window_id, 1, NULL, 0, 0, 0, 0, 0, 1.0, 0, 1, 0);
418 if (window_id >=0 && window_id < windows_list.num_windows)
419 ui_scale_pm_handler(&windows_list.window[window_id]);
420 }
421 else
422 toggle_window(window_id);
423 }
424
425 // Destroy the username list window.
426 //
destroy(void)427 void Window::destroy(void)
428 {
429 destroy_window(window_id);
430 window_id = -1;
431 }
432 }
433
434 static Password_Manaager::Logins * logins = 0;
435 static Password_Manaager::Window * pm_window = 0;
436
display_pm_handler(window_info * win)437 static int display_pm_handler(window_info *win) { return pm_window->display(logins, win); }
ui_scale_pm_handler(window_info * win)438 static int ui_scale_pm_handler(window_info *win) { return pm_window->ui_scale(logins, win); }
change_pm_font_handler(window_info * win,font_cat cat)439 static int change_pm_font_handler(window_info *win, font_cat cat) { return pm_window->change_font(logins, win, cat); }
click_pm_handler(window_info * win,int mx,int my,Uint32 flags)440 static int click_pm_handler(window_info *win, int mx, int my, Uint32 flags) { return pm_window->click(logins, win, mx, my, flags); }
mouseover_pm_handler(window_info * win,int mx,int my)441 static int mouseover_pm_handler(window_info *win, int mx, int my) { return pm_window->mouseover(logins, win, mx, my); }
click_show_password(widget_list * w,int mx,int my,Uint32 flags)442 static int click_show_password(widget_list *w, int mx, int my, Uint32 flags) { pm_window->toggle_show_password(logins, w); return 1; }
443
444 // The externam interface.
445 //
446 extern "C"
447 {
448 int passmngr_enabled = 0;
449
passmngr_open_window(void)450 void passmngr_open_window(void)
451 {
452 if (!passmngr_enabled)
453 return;
454 if (!pm_window)
455 pm_window = new Password_Manaager::Window();
456 passmngr_init();
457 logins->load();
458 pm_window->open();
459 }
460
passmngr_destroy_window(void)461 void passmngr_destroy_window(void)
462 {
463 if (pm_window)
464 {
465 delete pm_window;
466 pm_window = 0;
467 }
468 }
469
passmngr_save_login(void)470 void passmngr_save_login(void)
471 {
472 if (!passmngr_enabled)
473 return;
474 passmngr_init();
475 logins->add(get_username(), get_password());
476 }
477
passmngr_set_login(void)478 void passmngr_set_login(void)
479 {
480 if (!passmngr_enabled)
481 return;
482 passmngr_init();
483 logins->set_details();
484 }
485
passmngr_resize(void)486 void passmngr_resize(void)
487 {
488 if (!passmngr_enabled)
489 return;
490 if (pm_window)
491 {
492 passmngr_init();
493 pm_window->resize(logins);
494 }
495 }
496
passmngr_pending_pw_change(const char * old_and_new_password)497 int passmngr_pending_pw_change(const char * old_and_new_password)
498 {
499 std::string new_password;
500 if (!Password_Manaager::Logins::get_validated_new_pasword(old_and_new_password, new_password))
501 return 0;
502 if (!passmngr_enabled)
503 return 1;
504 passmngr_init();
505 logins->pending_change(new_password);
506 return 1;
507 }
508
passmngr_confirm_pw_change(void)509 int passmngr_confirm_pw_change(void)
510 {
511 if (!passmngr_enabled)
512 return 1;
513 passmngr_init();
514 return (logins->confirm_change()) ?1: 0;
515 }
516
passmngr_init(void)517 void passmngr_init(void)
518 {
519 if (passmngr_enabled && !logins)
520 logins = new Password_Manaager::Logins();
521 }
522
passmngr_destroy(void)523 void passmngr_destroy(void)
524 {
525 passmngr_destroy_window();
526 if (logins)
527 {
528 delete logins;
529 logins = 0;
530 }
531 }
532 }
533