Crossfire Server, Trunk  1.75.0
account.cpp
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2014 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
50 #include <set>
51 
52 #include "global.h"
53 
55 static std::set<std::string> accounts_logged_in = std::set<std::string>();
56 
57 #include <ctype.h>
58 #include <stdlib.h>
59 #include <string.h>
60 
61 #include "object.h"
62 #include "sproto.h"
63 
64 #include "output_file.h"
65 
67 #define NUM_ACCOUNT_FIELDS 6
68 
81  char *name;
82  char *password;
83  time_t last_login;
86  char **character_names;
88  time_t created;
89 };
90 
94 static std::vector<account_struct *> accounts;
95 
102 static int accounts_loaded = 0;
103 
108 #define ACCOUNT_FILE "accounts"
109 
115 static void ensure_available_characters(account_struct *account, int count) {
116  if (count >= account->allocated_characters) { // The list is NULL-terminated
117  const int pa = account->allocated_characters;
118  account->allocated_characters = count + 1;
119  account->character_names = static_cast<char **>(realloc(account->character_names, account->allocated_characters * sizeof(account->character_names[0])));
120  if (!account->character_names) {
121  LOG(llevError, "Unable to allocate %d characters names!", account->allocated_characters);
123  }
124  for (int i = pa; i < account->allocated_characters; i++) {
125  account->character_names[i] = NULL;
126  }
127  }
128 }
129 
136  account_struct *ac = (account_struct *)calloc(1, sizeof(account_struct));
137  if (!ac) {
138  LOG(llevError, "Unable to allocate an account_struct!\n");
140  }
141  ac->last_login = time(NULL);
142  ac->created = ac->last_login;
144  return ac;
145 }
146 
152 void accounts_clear(void) {
153  accounts.clear();
154  accounts_loaded = 0;
155 }
156 
161 void accounts_load(void) {
162  char fname[MAX_BUF], *buf;
163  int fields=0;
164  BufferReader *br;
165 
166  if (!accounts.empty()) {
167  LOG(llevError, "account_load_entries(): Called when accounts has been set.\n");
168  return;
169  }
170  snprintf(fname, MAX_BUF,"%s/%s", settings.localdir, ACCOUNT_FILE);
171  if ((br = bufferreader_init_from_file(NULL, fname, "Unable to open %s [%s]. This may be because this is a new server and no accounts exist yet.\n", llevInfo)) == NULL) {
172  return;
173  }
174 
175  while ((buf = bufferreader_next_line(br))) {
176  char *tmp[NUM_ACCOUNT_FIELDS], *cp;
177  int i;
178 
179  /* Ignore any comment lines */
180  if (buf[0] == '#') continue;
181 
183 
185  ac->name = strdup_local(tmp[0]);
186  ac->password = strdup_local(tmp[1]);
187  ac->last_login = strtoul(tmp[2], (char**)NULL, 10);
188 
189  /* While probably no one was using this code before this
190  * field was added, this provides a nice example of handling
191  * additional fields.
192  */
193  if (fields>4) ac->created = strtoul(tmp[4], (char**)NULL, 10);
194  else
195  ac->created = ac->last_login;
196 
197  /* If this is a blank field, nothing to do */
198  if (tmp[3][0]) {
199  /* count up how many semicolons - this is the character
200  * separator. We start at one, because these are separators,
201  * so there will be one more name than separators.
202  */
203  ac->num_characters=1;
204  for (cp = tmp[3]; *cp != '\0'; cp++) {
205  if (*cp == ';') ac->num_characters++;
206  }
208 
209  split_string(tmp[3], ac->character_names, ac->num_characters, ';');
210 
211  /* The string data that the names are stored in is currently temporary data
212  * that will go away, so we need to allocate some permanent data now */
213  for (i=0; i<ac->num_characters; i++) {
215  }
216  }
217 
218  accounts.push_back(ac);
219  }
220 
222  accounts_loaded = 1;
223 }
224 
234 static void account_write_entry(FILE *fp, account_struct *ac)
235 {
236  int i;
237 
238  fprintf(fp,"%s:%s:%u:", ac->name, ac->password, (uint32_t)ac->last_login);
239  for (i=0; i<ac->num_characters; i++) {
240  if (i != 0)
241  fprintf(fp,";%s", ac->character_names[i]);
242  else
243  fprintf(fp,"%s", ac->character_names[i]);
244  }
245  fprintf(fp,":%u:\n", (uint32_t) ac->created);
246 }
247 
248 
255 void accounts_save(void)
256 {
257  char fname[MAX_BUF];
258  FILE *fp;
259  OutputFile of;
260 
261  if (accounts_loaded == 0)
262  return;
263 
264  snprintf(fname, MAX_BUF,"%s/%s", settings.localdir, ACCOUNT_FILE);
265 
266  fp = of_open(&of, fname);
267  if (fp == NULL)
268  return;
269 
270  fprintf(fp, "# IMPORTANT: Do not edit this file while the server is running. This file is\n"
271  "# only read when the server starts, and any changes will be overwritten when\n"
272  "# the server exits.\n");
273  fprintf(fp, "# Format:\n");
274  fprintf(fp, "# Account name:Password:Account last used:Characters (semicolon separated):created:expansion\n");
275  for (auto ac : accounts) {
276  /* Don't write out accounts with no characters associated unless the
277  * account is at least a day old.
278  */
279  // 86400 seconds in a day, so no reason to make the game have to recalculate this all the time
280  // SilverNexus 2014-06-12
281  if (ac->num_characters || (ac->created > (time(NULL) - 86400)))
282  account_write_entry(fp, ac);
283  }
284  of_close(&of);
285 }
286 
296 const char *account_exists(const char *account_name)
297 {
298  for (auto ac : accounts) {
299  if (!strcasecmp(ac->name, account_name)) return ac->name;
300  }
301  return NULL;
302 }
303 
318 int account_login(const char *account_name, const char *account_password) {
319  for (auto ac : accounts) {
320  /* Look for a matching account name and check the password. */
321  if (!strcasecmp(ac->name, account_name)) {
322  if (check_password(account_password, ac->password)) {
323  ac->last_login = time(NULL);
324  accounts_logged_in.insert(account_name);
325  return 1;
326  } else {
327  return 0;
328  }
329  }
330  }
331  return 0;
332 }
333 
337 void account_logout(const char *account_name) {
338  accounts_logged_in.erase(account_name);
339 }
340 
360 int account_check_string(const char *str)
361 {
362  const char *cp = str;
363 
364  /* Require first character to be letter or number */
365  if (!isalnum(*str)) return 1;
366  for (; *str != '\0'; ++str) {
367  if (!isprint(*str)) return 1;
368  switch (*str){
369  case ':':
370  case ';':
371  case '/':
372  case '\'':
373  case '[':
374  return 1;
375  }
376  }
377  /* Don't allow space characters at end of string. */
378  if (isspace(*(str-1))) return 1;
379  if ((str - cp) > MAX_NAME) return 2;
380  return 0;
381 }
382 
383 
399 int account_new(const char *account_name, const char *account_password) {
400  account_struct *ac;
401 
402  // Check password for invalid characters because newhash() may just
403  // return the string in plaintext.
405  return 1;
406 
407  if (account_exists(account_name)) return 2;
408 
409  ac = account_alloc();
410  ac->name = strdup_local(account_name);
412 
413  /* We put this at the top of the list. This means recent accounts will be at
414  * the top of the file, which is likely a good thing.
415  * We don't do a save right now. When the player associates a character with
416  * the account, we will save them - until that point, not too much reason
417  * to save this out. Note it is still possible for this to get saved out if
418  * another player does something that forces writing out of the accounts file.
419  */
420  accounts.insert(accounts.begin(), ac);
421 
422  /* mark that accounts should be saved through accounts_save(). */
423  accounts_loaded = 1;
424 
425  return 0;
426 }
427 
444 int account_link(const char *account_name, const char *player_name) {
445  for (auto ac : accounts) {
446  if (!strcasecmp(ac->name, account_name)) {
447  ensure_available_characters(ac, ac->num_characters + 1);
448  ac->character_names[ac->num_characters] = strdup_local(player_name);
449  ac->num_characters++;
450  return 0;
451  }
452  }
453  return 1;
454 }
455 
474 int account_remove_player(const char *account_name, const char *player_name) {
475  int i, match=0;
476 
477  if (account_name == NULL)
478  return 0;
479 
480  for (auto ac : accounts) {
481  if (!strcasecmp(ac->name, account_name)) {
482  /* Try to find the character name. Once we find it, we set match, and
483  * then move the remain character names down by one. The array is
484  * always null terminated, so this also makes sure we copy the null down.
485  */
486  for (i=0; i<ac->num_characters; i++) {
487  if (!strcmp(ac->character_names[i], player_name)) {
488  free(ac->character_names[i]);
489  match=1;
490  }
491  if (match == 1) {
492  ac->character_names[i] = ac->character_names[i+1];
493  }
494  }
495 
496  if (match) {
497  ac->num_characters--;
498  return 0;
499  }
500  /* Otherwise, did not find player name */
501  return 2;
502  }
503  }
504  return 1;
505 }
506 
507 
519 char **account_get_players_for_account(const char *account_name)
520 {
521  for (auto ac : accounts) {
522  if (!strcasecmp(ac->name, account_name)) return ac->character_names;
523  }
524  return NULL;
525 }
526 
533 static int char_in_list(const char *name, const std::vector<Account_Char *> chars) {
534  for (auto ch : chars) {
535  if (strcmp(ch->name, name) == 0) {
536  return 1;
537  }
538  }
539  return 0;
540 }
541 
550 linked_char *account_get_additional_chars(const char *account_name, const Account_Chars *chars, int *count) {
551  linked_char *ret = NULL;
552 
553  for (auto ac : accounts) {
554  if (!strcasecmp(ac->name, account_name)) {
555  for (int i = 0; i < ac->num_characters; i++) {
556  if (!char_in_list(ac->character_names[i], chars->chars)) {
557  linked_char *lc = (linked_char *)calloc(1, sizeof(linked_char));
558  lc->next = ret;
559  lc->name = ac->character_names[i];
560  ret = lc;
561  (*count)++;
562  }
563  }
564  return ret;
565  }
566  }
567  return NULL;
568 }
569 
579 const char *account_get_account_for_char(const char *charname)
580 {
581  int i;
582 
583  for (auto ac : accounts) {
584  for (i=0; i<ac->num_characters; i++) {
585  if (!strcmp(ac->character_names[i], charname)) {
586  return ac->name;
587  }
588  }
589  }
590  return NULL;
591 
592 }
593 
604 int account_is_logged_in(const char *name) {
605  return accounts_logged_in.find(name) != accounts_logged_in.end();
606 }
607 
627 int account_change_password(const char *account_name,
628  const char *current_password, const char *new_password) {
629 
630  // Check password for invalid characters as in account_new().
631  if (account_check_string(account_name) ||
632  (current_password != NULL && account_check_string(current_password)) ||
633  account_check_string(new_password)) {
634  return 1;
635  }
636 
637  // Iterate through accounts list until a matching name is found.
638  for (auto ac : accounts) {
639  if (!strcasecmp(ac->name, account_name)) {
640  // Return an error if the current password does not match.
641  if (current_password != NULL && !check_password(current_password, ac->password)) {
642  return 3;
643  }
644 
645  free(ac->password);
646  ac->password = strdup_local(newhash(new_password));
647 
648  return 0;
649  }
650  }
651 
652  return 2;
653 }
output_file.h
global.h
account_struct::name
char * name
Account name.
Definition: account.cpp:81
account_get_players_for_account
char ** account_get_players_for_account(const char *account_name)
Returns an array of strings for the characters on this account - the array is null terminated.
Definition: account.cpp:519
settings
struct Settings settings
Global settings.
Definition: init.cpp:139
account_logout
void account_logout(const char *account_name)
Remove 'account_name' from the list of logged in accounts.
Definition: account.cpp:337
account_struct
Structure that holds account data.
Definition: account.cpp:80
account_alloc
static account_struct * account_alloc()
Allocate a new account_struct item.
Definition: account.cpp:135
llevError
@ llevError
Error, serious thing.
Definition: logger.h:11
LOG
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.cpp:58
of_close
int of_close(OutputFile *of)
Closes an output file.
Definition: output_file.cpp:61
of_open
FILE * of_open(OutputFile *of, const char *fname)
Opens an output file.
Definition: output_file.cpp:30
strdup_local
#define strdup_local
Definition: compat.h:29
newhash
char const * newhash(char const *password)
Definition: server.cpp:101
account_get_additional_chars
linked_char * account_get_additional_chars(const char *account_name, const Account_Chars *chars, int *count)
Get a list of character names linked to the specified account which are not listed in chars.
Definition: account.cpp:550
accounts_save
void accounts_save(void)
Save all the account information.
Definition: account.cpp:255
account_struct::created
time_t created
When character was created.
Definition: account.cpp:88
fields
non standard information is not specified or uptime fields
Definition: arch-handbook.txt:204
time
non standard information is not specified or uptime this means how long since the executable has been started A particular host may have been running a server for quite a long time
Definition: arch-handbook.txt:206
bufferreader_destroy
void bufferreader_destroy(BufferReader *br)
Destroy a BufferReader.
Definition: bufferreader.cpp:40
account_exists
const char * account_exists(const char *account_name)
Checks the existing accounts, and see if this account exists.
Definition: account.cpp:296
accounts_loaded
static int accounts_loaded
Whether the account information was loaded or not.
Definition: account.cpp:102
account_struct::num_characters
int num_characters
Number of characters on this account.
Definition: account.cpp:84
account_remove_player
int account_remove_player(const char *account_name, const char *player_name)
Removes a player name from an account.
Definition: account.cpp:474
accounts
static std::vector< account_struct * > accounts
list of all accounts.
Definition: account.cpp:94
char_in_list
static int char_in_list(const char *name, const std::vector< Account_Char * > chars)
Check if a character name is in a list or not.
Definition: account.cpp:533
buf
StringBuffer * buf
Definition: readable.cpp:1565
name
Plugin animator file specs[Config] name
Definition: animfiles.txt:4
linked_char
Definition: global.h:98
Account_Chars::chars
std::vector< Account_Char * > chars
Characters of the account.
Definition: account_char.h:30
accounts_clear
void accounts_clear(void)
This is used purely by the test harness - by clearing the accounts, it can then verify that the data ...
Definition: account.cpp:152
account_password
void account_password(char *buf, int len, socket_struct *ns)
Handles the account password change.
Definition: request.cpp:3097
bufferreader_init_from_file
BufferReader * bufferreader_init_from_file(BufferReader *br, const char *filepath, const char *failureMessage, LogLevel failureLevel)
Initialize or create a BufferReader from a file path.
Definition: bufferreader.cpp:65
linked_char::name
const char * name
Definition: global.h:99
split_string
size_t split_string(char *str, char *array[], size_t array_size, char sep)
Splits a string delimited by passed in sep value into characters into an array of strings.
Definition: utils.cpp:473
account_struct::last_login
time_t last_login
Last time this account was logged in.
Definition: account.cpp:83
NUM_ACCOUNT_FIELDS
#define NUM_ACCOUNT_FIELDS
Number of fields in the accounts file.
Definition: account.cpp:67
linked_char::next
struct linked_char * next
Definition: global.h:100
MAX_NAME
#define MAX_NAME
Definition: define.h:41
sproto.h
ACCOUNT_FILE
#define ACCOUNT_FILE
Name of the accounts file.
Definition: account.cpp:108
fatal
void fatal(enum fatal_error err)
fatal() is meant to be called whenever a fatal signal is intercepted.
Definition: utils.cpp:590
MAX_BUF
#define MAX_BUF
Used for all kinds of things.
Definition: define.h:35
ensure_available_characters
static void ensure_available_characters(account_struct *account, int count)
Ensure an account can handle at least the specified count of character names.
Definition: account.cpp:115
llevInfo
@ llevInfo
Information.
Definition: logger.h:12
account_struct::allocated_characters
int allocated_characters
Number of allocated items in character_names.
Definition: account.cpp:85
accounts_logged_in
static std::set< std::string > accounts_logged_in
Set of accounts names that are currently logged in.
Definition: account.cpp:55
strcasecmp
int strcasecmp(const char *s1, const char *s2)
Account_Chars
Structure handling character information for an account.
Definition: account_char.h:27
account_get_account_for_char
const char * account_get_account_for_char(const char *charname)
This looks at all the accounts and sees if charname is associated with any of them.
Definition: account.cpp:579
check_password
bool check_password(const char *typed, const char *crypted)
Hash a password and compare it to the stored version.
Definition: server.cpp:114
account_is_logged_in
int account_is_logged_in(const char *name)
This checkes if an account is logged in.
Definition: account.cpp:604
account_struct::character_names
char ** character_names
Character names associated with this account, +1 added to allow for NULL termination.
Definition: account.cpp:86
account_change_password
int account_change_password(const char *account_name, const char *current_password, const char *new_password)
Change an account password.
Definition: account.cpp:627
account_write_entry
static void account_write_entry(FILE *fp, account_struct *ac)
This writes a single account entry to the given filepointer.
Definition: account.cpp:234
account_link
int account_link(const char *account_name, const char *player_name)
Adds a player name to an account.
Definition: account.cpp:444
OUT_OF_MEMORY
@ OUT_OF_MEMORY
Definition: define.h:48
account_login
int account_login(const char *account_name, const char *account_password)
Check if the given account exists, and whether the password is correct.
Definition: account.cpp:318
BufferReader
Definition: bufferreader.cpp:21
accounts_load
void accounts_load(void)
This loads all the account entries into memory.
Definition: account.cpp:161
object.h
account_check_string
int account_check_string(const char *str)
Checks a string to make sure it does not have any invalid characters.
Definition: account.cpp:360
account_new
int account_new(const char *account_name, const char *account_password)
Adds an account.
Definition: account.cpp:399
account_struct::password
char * password
Password for this account.
Definition: account.cpp:82
OutputFile
Definition: output_file.h:41
bufferreader_next_line
char * bufferreader_next_line(BufferReader *br)
Return the next line in the buffer, as separated by a newline.
Definition: bufferreader.cpp:102
Settings::localdir
const char * localdir
Read/write data files.
Definition: global.h:250