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, "Could not open %s (%s)", llevDebug)) == 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  /* Ignore empty lines */
182  if (buf[0] == 0) continue;
183  /* Skip if account name is empty. This cleans up entries introduced by a bug fixed in patches/477 */
184  if (buf[0] == ':') {
185  LOG(llevDebug, "Ignoring accounts entry without account name: %s\n", buf);
186  continue;
187  }
188 
189  fields = split_string(buf, tmp, NUM_ACCOUNT_FIELDS, ':');
190  if (fields < 3) {
191  LOG(llevWarn, "Parse failure for line in %s starting with: %s\n", fname, buf);
192  continue;
193  }
194 
196  ac->name = strdup_local(tmp[0]);
197  ac->password = strdup_local(tmp[1]);
198  ac->last_login = strtoul(tmp[2], (char**)NULL, 10);
199 
200  /* While probably no one was using this code before this
201  * field was added, this provides a nice example of handling
202  * additional fields.
203  */
204  if (fields>4) ac->created = strtoul(tmp[4], (char**)NULL, 10);
205  else
206  ac->created = ac->last_login;
207 
208  /* If this is a blank field, nothing to do */
209  if (tmp[3][0]) {
210  /* count up how many semicolons - this is the character
211  * separator. We start at one, because these are separators,
212  * so there will be one more name than separators.
213  */
214  ac->num_characters=1;
215  for (cp = tmp[3]; *cp != '\0'; cp++) {
216  if (*cp == ';') ac->num_characters++;
217  }
219 
220  split_string(tmp[3], ac->character_names, ac->num_characters, ';');
221 
222  /* The string data that the names are stored in is currently temporary data
223  * that will go away, so we need to allocate some permanent data now */
224  for (i=0; i<ac->num_characters; i++) {
226  }
227  }
228 
229  accounts.push_back(ac);
230  }
231 
233  accounts_loaded = 1;
234 }
235 
245 static void account_write_entry(FILE *fp, account_struct *ac)
246 {
247  int i;
248 
249  fprintf(fp,"%s:%s:%" PRIu64 ":", ac->name, ac->password, (uint64_t)ac->last_login);
250  for (i=0; i<ac->num_characters; i++) {
251  if (i != 0)
252  fprintf(fp,";%s", ac->character_names[i]);
253  else
254  fprintf(fp,"%s", ac->character_names[i]);
255  }
256  fprintf(fp,":%" PRIu64 ":\n", (uint64_t)ac->created);
257 }
258 
259 
266 void accounts_save(void)
267 {
268  char fname[MAX_BUF];
269  FILE *fp;
270  OutputFile of;
271 
272  if (accounts_loaded == 0)
273  return;
274 
275  snprintf(fname, MAX_BUF,"%s/%s", settings.localdir, ACCOUNT_FILE);
276 
277  fp = of_open(&of, fname);
278  if (fp == NULL)
279  return;
280 
281  fprintf(fp, "# IMPORTANT: Do not edit this file while the server is running. This file is\n"
282  "# only read when the server starts, and any changes will be overwritten when\n"
283  "# the server exits.\n");
284  fprintf(fp, "# Format:\n");
285  fprintf(fp, "# Account name:Password:Account last used:Characters (semicolon separated):created:expansion\n");
286  for (auto ac : accounts) {
287  /* Don't write out accounts with no characters associated unless the
288  * account is at least a day old.
289  */
290  // 86400 seconds in a day, so no reason to make the game have to recalculate this all the time
291  // SilverNexus 2014-06-12
292  if (ac->num_characters || (ac->created > (time(NULL) - 86400)))
293  account_write_entry(fp, ac);
294  }
295  of_close(&of);
296 }
297 
307 const char *account_exists(const char *account_name)
308 {
309  for (auto ac : accounts) {
310  if (!strcasecmp(ac->name, account_name)) return ac->name;
311  }
312  return NULL;
313 }
314 
329 int account_login(const char *account_name, const char *account_password) {
330  for (auto ac : accounts) {
331  /* Look for a matching account name and check the password. */
332  if (!strcasecmp(ac->name, account_name)) {
333  if (check_password(account_password, ac->password)) {
334  ac->last_login = time(NULL);
335  accounts_logged_in.insert(account_name);
336  return 1;
337  } else {
338  return 0;
339  }
340  }
341  }
342  return 0;
343 }
344 
348 void account_logout(const char *account_name) {
349  accounts_logged_in.erase(account_name);
350 }
351 
371 int account_check_string(const char *str)
372 {
373  const char *cp = str;
374 
375  /* Require first character to be letter or number */
376  if (!isalnum(*str)) return 1;
377  for (; *str != '\0'; ++str) {
378  if (!isprint(*str)) return 1;
379  switch (*str){
380  case ':':
381  case ';':
382  case '/':
383  case '\'':
384  case '[':
385  return 1;
386  }
387  }
388  /* Don't allow space characters at end of string. */
389  if (isspace(*(str-1))) return 1;
390  if ((str - cp) > MAX_NAME) return 2;
391  return 0;
392 }
393 
394 
410 int account_new(const char *account_name, const char *account_password) {
411  account_struct *ac;
412 
413  // Check password for invalid characters because newhash() may just
414  // return the string in plaintext.
416  return 1;
417 
418  if (account_exists(account_name)) return 2;
419 
420  ac = account_alloc();
421  ac->name = strdup_local(account_name);
423 
424  /* We put this at the top of the list. This means recent accounts will be at
425  * the top of the file, which is likely a good thing.
426  * We don't do a save right now. When the player associates a character with
427  * the account, we will save them - until that point, not too much reason
428  * to save this out. Note it is still possible for this to get saved out if
429  * another player does something that forces writing out of the accounts file.
430  */
431  accounts.insert(accounts.begin(), ac);
432 
433  /* mark that accounts should be saved through accounts_save(). */
434  accounts_loaded = 1;
435 
436  return 0;
437 }
438 
455 int account_link(const char *account_name, const char *player_name) {
456  for (auto ac : accounts) {
457  if (!strcasecmp(ac->name, account_name)) {
458  ensure_available_characters(ac, ac->num_characters + 1);
459  ac->character_names[ac->num_characters] = strdup_local(player_name);
460  ac->num_characters++;
461  return 0;
462  }
463  }
464  return 1;
465 }
466 
485 int account_remove_player(const char *account_name, const char *player_name) {
486  int i, match=0;
487 
488  if (account_name == NULL)
489  return 0;
490 
491  for (auto ac : accounts) {
492  if (!strcasecmp(ac->name, account_name)) {
493  /* Try to find the character name. Once we find it, we set match, and
494  * then move the remain character names down by one. The array is
495  * always null terminated, so this also makes sure we copy the null down.
496  */
497  for (i=0; i<ac->num_characters; i++) {
498  if (!strcmp(ac->character_names[i], player_name)) {
499  free(ac->character_names[i]);
500  match=1;
501  }
502  if (match == 1) {
503  ac->character_names[i] = ac->character_names[i+1];
504  }
505  }
506 
507  if (match) {
508  ac->num_characters--;
509  return 0;
510  }
511  /* Otherwise, did not find player name */
512  return 2;
513  }
514  }
515  return 1;
516 }
517 
518 
530 char **account_get_players_for_account(const char *account_name)
531 {
532  for (auto ac : accounts) {
533  if (!strcasecmp(ac->name, account_name)) return ac->character_names;
534  }
535  return NULL;
536 }
537 
544 static int char_in_list(const char *name, const std::vector<Account_Char *> chars) {
545  for (auto ch : chars) {
546  if (strcmp(ch->name, name) == 0) {
547  return 1;
548  }
549  }
550  return 0;
551 }
552 
561 linked_char *account_get_additional_chars(const char *account_name, const Account_Chars *chars, int *count) {
562  linked_char *ret = NULL;
563 
564  for (auto ac : accounts) {
565  if (!strcasecmp(ac->name, account_name)) {
566  for (int i = 0; i < ac->num_characters; i++) {
567  if (!char_in_list(ac->character_names[i], chars->chars)) {
568  linked_char *lc = (linked_char *)calloc(1, sizeof(linked_char));
569  lc->next = ret;
570  lc->name = ac->character_names[i];
571  ret = lc;
572  (*count)++;
573  }
574  }
575  return ret;
576  }
577  }
578  return NULL;
579 }
580 
590 const char *account_get_account_for_char(const char *charname)
591 {
592  int i;
593 
594  for (auto ac : accounts) {
595  for (i=0; i<ac->num_characters; i++) {
596  if (!strcmp(ac->character_names[i], charname)) {
597  return ac->name;
598  }
599  }
600  }
601  return NULL;
602 
603 }
604 
615 int account_is_logged_in(const char *name) {
616  return accounts_logged_in.find(name) != accounts_logged_in.end();
617 }
618 
638 int account_change_password(const char *account_name,
639  const char *current_password, const char *new_password) {
640 
641  // Check password for invalid characters as in account_new().
642  if (account_check_string(account_name) ||
643  (current_password != NULL && account_check_string(current_password)) ||
644  account_check_string(new_password)) {
645  return 1;
646  }
647 
648  // Iterate through accounts list until a matching name is found.
649  for (auto ac : accounts) {
650  if (!strcasecmp(ac->name, account_name)) {
651  // Return an error if the current password does not match.
652  if (current_password != NULL && !check_password(current_password, ac->password)) {
653  return 3;
654  }
655 
656  free(ac->password);
657  ac->password = strdup_local(newhash(new_password));
658 
659  return 0;
660  }
661  }
662 
663  return 2;
664 }
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:530
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:348
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
Problems requiring server admin to fix.
Definition: logger.h:11
LOG
void LOG(LogLevel logLevel, const char *format,...)
Logs a message to stderr, or to file.
Definition: logger.cpp:82
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
time
same as sound ncom command like but with extra the client want tick commands so it knows animation timing the client wants to be informed of pickup mode changes Mode will be sent when the player successfully logs and afterward any time the value is but over time
Definition: protocol.txt:416
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:561
accounts_save
void accounts_save(void)
Save all the account information.
Definition: account.cpp:266
account_struct::created
time_t created
When character was created.
Definition: account.cpp:88
bufferreader_destroy
void bufferreader_destroy(BufferReader *br)
Destroy a BufferReader.
Definition: bufferreader.cpp:41
llevWarn
@ llevWarn
Warnings or major code issues.
Definition: logger.h:12
account_exists
const char * account_exists(const char *account_name)
Checks the existing accounts, and see if this account exists.
Definition: account.cpp:307
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:485
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:544
buf
StringBuffer * buf
Definition: readable.cpp:1564
name
Plugin animator file specs[Config] name
Definition: animfiles.txt:4
linked_char
Definition: global.h:102
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:3103
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:67
linked_char::name
const char * name
Definition: global.h:103
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:479
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:104
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:595
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
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:590
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:615
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:638
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:245
account_link
int account_link(const char *account_name, const char *player_name)
Adds a player name to an account.
Definition: account.cpp:455
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:329
BufferReader
Definition: bufferreader.cpp:22
accounts_load
void accounts_load(void)
This loads all the account entries into memory.
Definition: account.cpp:161
object.h
llevDebug
@ llevDebug
Only for debugging purposes.
Definition: logger.h:15
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:371
account_new
int account_new(const char *account_name, const char *account_password)
Adds an account.
Definition: account.cpp:410
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:110
Settings::localdir
const char * localdir
Read/write data files.
Definition: global.h:254