Crossfire Server, Trunk  R21024
account.c
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 "global.h"
51 
52 #include <ctype.h>
53 #include <errno.h>
54 #include <stdlib.h>
55 #include <string.h>
56 
57 #include "object.h"
58 #include "sproto.h"
59 
60 #include "output_file.h"
61 
63 #define NUM_ACCOUNT_FIELDS 6
64 
76 typedef struct account_struct {
77  char *name;
78  char *password;
79  time_t last_login;
84  time_t created;
85  struct account_struct *next;
87 
91 static account_struct *accounts=NULL;
92 
99 static int accounts_loaded = 0;
100 
105 #define ACCOUNT_FILE "accounts"
106 
112 void accounts_clear(void) {
113  accounts = NULL;
114  accounts_loaded = 0;
115 }
116 
121 void accounts_load(void) {
122  char fname[MAX_BUF], buf[VERY_BIG_BUF];
123  FILE *fp;
124  account_struct *ac, *last=NULL;
125  int fields=0;
126 
127  if (accounts != NULL) {
128  LOG(llevError, "account_load_entries(): Called when accounts has been set.\n");
129  return;
130  }
131  snprintf(fname, MAX_BUF,"%s/%s", settings.localdir, ACCOUNT_FILE);
132  fp=fopen(fname,"r");
133  if (!fp) {
134  /* This may not in fact be a critical error - on a new server,
135  * the accounts file may not yet exist.
136  */
137  LOG(llevInfo,"Warning: Unable to open %s [%s]\n", fname, strerror(errno));
138  return;
139  }
140  while (fgets(buf, VERY_BIG_BUF, fp)) {
141  char *tmp[NUM_ACCOUNT_FIELDS], *cp;
142  int result, i;
143 
144  /* Ignore any comment lines */
145  if (buf[0] == '#') continue;
146 
147  /* remove newline */
148  cp = strchr(buf, '\n');
149  if (cp) *cp='\0';
150 
151  fields = split_string(buf, tmp, NUM_ACCOUNT_FIELDS, ':');
152 
153  ac = malloc(sizeof(account_struct));
154  ac->name = strdup_local(tmp[0]);
155  ac->password = strdup_local(tmp[1]);
156  ac->last_login = strtoul(tmp[2], (char**)NULL, 10);
157 
158  /* While probably no one was using this code before this
159  * field was added, this provides a nice example of handling
160  * additional fields.
161  */
162  if (fields>4) ac->created = strtoul(tmp[4], (char**)NULL, 10);
163  else
164  ac->created = ac->last_login;
165 
166  ac->next = NULL;
167 
168  /* If this is a blank field, nothing to do */
169  if (tmp[3][0] == 0) {
170  ac->num_characters = 0;
171  for (i=0; i <= MAX_CHARACTERS_PER_ACCOUNT; i++)
172  ac->character_names[i] = NULL;
173  } else {
174  /* count up how many semicolons - this is the character
175  * seperator. We start at one, because these are seperators,
176  * so there will be one more name than seperators.
177  */
178  ac->num_characters=1;
179  for (cp = tmp[3]; *cp != '\0'; cp++) {
180  if (*cp == ';') ac->num_characters++;
181  }
182 
183  result = split_string(tmp[3], ac->character_names, ac->num_characters, ';');
184  /* This should never happen, but check for it. Even if we do get it, not necessarily
185  * a critical error - this is why we use calloc above.
186  */
187  if (result != ac->num_characters) {
188  LOG(llevError, "account_load_entries: split_string found different number of characters: %d != %d\n",
189  result, ac->num_characters);
190  }
191 
193  LOG(llevError,"account_load_entries: Too many characters set for account %s - truncating to %d\n",
196  }
197 
198  /* The string data that the names are stored in is currently temporary data
199  * that will go away, so we need to allocate some permanent data
200  * now. Also, if we have a NULL value, means we got the error above -
201  * NULL values would only be at the end of the split, so just reduce
202  * the character count.
203  */
204  for (i=0; i<ac->num_characters; i++) {
205  if (ac->character_names[i] != NULL) {
207  } else {
208  ac->num_characters = i;
209  break;
210  }
211  }
212  /* NULL terminate - in that way, we can just return ac->character_names to
213  * callers that want to know all characters associated with an account,
214  * and it can just iterate until it gets the NULL terminator.
215  * For safety, just set all remaining values to NULL
216  */
217  while (i <= MAX_CHARACTERS_PER_ACCOUNT) {
218  ac->character_names[i] = NULL;
219  i++;
220  }
221  }
222 
223  /* We tack on to the end of the list - in this way,
224  * the order of the file remains the same.
225  */
226  if (last)
227  last->next = ac;
228  else
229  accounts = ac;
230  last = ac;
231 
232  }
233  fclose(fp);
234  accounts_loaded = 1;
235 }
236 
246 static void account_write_entry(FILE *fp, account_struct *ac)
247 {
248  int i;
249 
250  fprintf(fp,"%s:%s:%u:", ac->name, ac->password, (uint32_t)ac->last_login);
251  for (i=0; i<ac->num_characters; i++) {
252  if (i != 0)
253  fprintf(fp,";%s", ac->character_names[i]);
254  else
255  fprintf(fp,"%s", ac->character_names[i]);
256  }
257  fprintf(fp,":%u:\n", (uint32_t) ac->created);
258 }
259 
260 
267 void accounts_save(void)
268 {
269  char fname[MAX_BUF];
270  FILE *fp;
271  OutputFile of;
272  account_struct *ac;
273 
274  if (accounts_loaded == 0)
275  return;
276 
277  snprintf(fname, MAX_BUF,"%s/%s", settings.localdir, ACCOUNT_FILE);
278 
279  fp = of_open(&of, fname);
280  if (fp == NULL)
281  return;
282 
283  fprintf(fp, "# IMPORTANT: Do not edit this file while the server is running. This file is\n"
284  "# only read when the server starts, and any changes will be overwritten when\n"
285  "# the server exits.\n");
286  fprintf(fp, "# Format:\n");
287  fprintf(fp, "# Account name:Password:Account last used:Characters (semicolon separated):created:expansion\n");
288  for (ac=accounts; ac; ac=ac->next) {
289  /* Don't write out accounts with no characters associated unless the
290  * account is at least a day old.
291  */
292  // 86400 seconds in a day, so no reason to make the game have to recalculate this all the time
293  // SilverNexus 2014-06-12
294  if (ac->num_characters || (ac->created > (time(NULL) - 86400)))
295  account_write_entry(fp, ac);
296  }
297  of_close(&of);
298 }
299 
309 const char *account_exists(const char *account_name)
310 {
311  account_struct *ac;
312 
313  for (ac=accounts; ac; ac=ac->next) {
314  if (!strcasecmp(ac->name, account_name)) return ac->name;
315  }
316  return NULL;
317 }
318 
333 int account_login(const char *account_name, const char *account_password) {
334  account_struct *ac;
335 
336  for (ac=accounts; ac; ac=ac->next) {
337  /* Look for a matching account name and check the password. */
338  if (!strcasecmp(ac->name, account_name)) {
339  if (check_password(account_password, ac->password)) {
340  ac->last_login = time(NULL);
341  return 1;
342  } else {
343  return 0;
344  }
345  }
346  }
347  return 0;
348 }
349 
369 int account_check_string(const char *str)
370 {
371  const char *cp = str;
372 
373  /* Require first character to be letter or number */
374  if (!isalnum(*str)) return 1;
375  for (; *str != '\0'; ++str) {
376  if (!isprint(*str)) return 1;
377  switch (*str){
378  case ':':
379  case ';':
380  case '/':
381  case '\'':
382  case '[':
383  return 1;
384  }
385  }
386  /* Don't allow space characters at end of string. */
387  if (isspace(*(str-1))) return 1;
388  if ((str - cp) > MAX_NAME) return 2;
389  return 0;
390 }
391 
392 
408 int account_new(const char *account_name, const char *account_password) {
409  account_struct *ac;
410 
411  // Check password for invalid characters because newhash() may just
412  // return the string in plaintext.
413  if (account_check_string(account_name) || account_check_string(account_password))
414  return 1;
415 
416  if (account_exists(account_name)) return 2;
417 
418  ac = malloc(sizeof(account_struct));
419  ac->name = strdup_local(account_name);
420  ac->password = strdup_local(newhash(account_password));
421  ac->last_login = time(NULL);
422  ac->created = ac->last_login;
423  ac->num_characters = 0;
424 
425  memset(ac->character_names, 0, MAX_CHARACTERS_PER_ACCOUNT+1 * sizeof(char*));
426 
427  /* We put this at the top of the list. This means recent accounts will be at
428  * the top of the file, which is likely a good thing.
429  * We don't do a save right now. When the player associates a character with
430  * the account, we will save them - until that point, not too much reason
431  * to save this out. Note it is still possible for this to get saved out if
432  * another player does something that forces writing out of the accounts file.
433  */
434  ac->next = accounts;
435  accounts = ac;
436 
437  /* mark that accounts should be saved through accounts_save(). */
438  accounts_loaded = 1;
439 
440  return 0;
441 }
442 
461 int account_link(const char *account_name, const char *player_name) {
462  account_struct *ac;
463 
464  for (ac=accounts; ac; ac=ac->next) {
465  if (!strcasecmp(ac->name, account_name)) break;
466  }
467  if (ac == NULL) return 1;
468 
469  if (ac->num_characters >= MAX_CHARACTERS_PER_ACCOUNT) return 2;
470 
471  ac->character_names[ac->num_characters] = strdup_local(player_name);
472  ac->num_characters++;
473  /* NULL terminate, as per notes above. In theory not necessary since data
474  * is all set to NULL */
475  ac->character_names[ac->num_characters] = NULL;
476  return 0;
477 }
478 
497 int account_remove_player(const char *account_name, const char *player_name) {
498  account_struct *ac;
499  int i, match=0;
500 
501  if (account_name == NULL)
502  return 0;
503 
504  for (ac=accounts; ac; ac=ac->next) {
505  if (!strcasecmp(ac->name, account_name)) break;
506  }
507  if (ac == NULL) return 1;
508 
509  /* Try to find the character name. Once we find it, we set match, and
510  * then move the remain character names down by one. The array is
511  * always null terminated, so this also makes sure we copy the null down.
512  */
513  for (i=0; i<ac->num_characters; i++) {
514  if (!strcmp(ac->character_names[i], player_name)) {
515  free(ac->character_names[i]);
516  match=1;
517  }
518  if (match == 1) {
519  ac->character_names[i] = ac->character_names[i+1];
520  }
521  }
522 
523  if (match) {
524  ac->num_characters--;
525  return 0;
526  }
527  /* Otherwise, did not find player name */
528  return 2;
529 }
530 
531 
543 char **account_get_players_for_account(const char *account_name)
544 {
545  account_struct *ac;
546 
547  for (ac=accounts; ac; ac=ac->next) {
548  if (!strcasecmp(ac->name, account_name)) return ac->character_names;
549  }
550  return NULL;
551 }
552 
562 const char *account_get_account_for_char(const char *charname)
563 {
564  account_struct *ac;
565  int i;
566 
567  for (ac=accounts; ac; ac=ac->next) {
568  for (i=0; i<ac->num_characters; i++) {
569  if (!strcmp(ac->character_names[i], charname)) {
570  return ac->name;
571  }
572  }
573  }
574  return NULL;
575 
576 }
577 
588 {
589  player *pl;
590 
591  for (pl = first_player; pl; pl=pl->next) {
592  if (pl->socket.account_name &&
593  !strcasecmp(pl->socket.account_name, name)) return pl;
594  }
595  return NULL;
596 }
597 
609 {
610  int i;
611 
612  for (i=0; i < socket_info.allocated_sockets; i++) {
613  if (init_sockets[i].status == Ns_Add &&
614  init_sockets[i].account_name &&
615  !strcasecmp(init_sockets[i].account_name, name)) return(&init_sockets[i]);
616  }
617  return NULL;
618 }
619 
630 int account_is_logged_in(const char *name)
631 {
632  if (account_get_logged_in_player(name)) return 1;
633 
634  if (account_get_logged_in_init_socket(name)!=NULL) return 1;
635 
636  return 0;
637 }
638 
658 int account_change_password(const char *account_name,
659  const char *current_password, const char *new_password) {
660  account_struct *ac;
661 
662  // Check password for invalid characters as in account_new().
663  if (account_check_string(account_name) || account_check_string(current_password) ||
664  account_check_string(new_password)) {
665  return 1;
666  }
667 
668  // Iterate through accounts list until a matching name is found.
669  for (ac = accounts; ac; ac = ac->next) {
670  if (!strcasecmp(ac->name, account_name)) {
671  break;
672  }
673  }
674 
675  // Check if the given account actually exists.
676  if (ac == NULL) {
677  return 2;
678  }
679 
680  // Return an error if the current password does not match.
681  if (!check_password(current_password, ac->password)) {
682  return 3;
683  }
684 
685  free(ac->password);
686  ac->password = strdup_local(newhash(new_password));
687 
688  return 0;
689 }
Definition: player.h:92
char * account_name
Definition: newserver.h:126
bool check_password(const char *typed, const char *crypted)
Definition: server.c:104
void accounts_clear(void)
Definition: account.c:112
#define strdup_local
Definition: compat.h:25
int num_characters
Definition: account.c:80
char * password
Definition: account.c:78
char * character_names[MAX_CHARACTERS_PER_ACCOUNT+1]
Definition: account.c:81
#define NUM_ACCOUNT_FIELDS
Definition: account.c:63
struct account_struct * next
Definition: account.c:85
socket_struct socket
Definition: player.h:94
void accounts_load(void)
Definition: account.c:121
int account_link(const char *account_name, const char *player_name)
Definition: account.c:461
player * account_get_logged_in_player(const char *name)
Definition: account.c:587
socket_struct * init_sockets
Definition: init.c:56
time_t created
Definition: account.c:84
const char * account_exists(const char *account_name)
Definition: account.c:309
time_t last_login
Definition: account.c:79
Socket_Info socket_info
Definition: init.c:47
int account_login(const char *account_name, const char *account_password)
Definition: account.c:333
#define ACCOUNT_FILE
Definition: account.c:105
#define snprintf
Definition: win32.h:46
void account_password(char *buf, int len, socket_struct *ns)
Definition: request.c:2879
int of_close(OutputFile *of)
Definition: output_file.c:61
static int accounts_loaded
Definition: account.c:99
int account_remove_player(const char *account_name, const char *player_name)
Definition: account.c:497
int allocated_sockets
Definition: newserver.h:144
struct account_struct account_struct
#define MAX_BUF
Definition: define.h:35
socket_struct * account_get_logged_in_init_socket(const char *name)
Definition: account.c:608
unsigned int uint32_t
Definition: win32.h:162
int account_new(const char *account_name, const char *account_password)
Definition: account.c:408
char ** account_get_players_for_account(const char *account_name)
Definition: account.c:543
int account_change_password(const char *account_name, const char *current_password, const char *new_password)
Definition: account.c:658
#define VERY_BIG_BUF
Definition: define.h:36
const char * localdir
Definition: global.h:243
struct Settings settings
Definition: init.c:40
FILE * of_open(OutputFile *of, const char *fname)
Definition: output_file.c:30
#define MAX_CHARACTERS_PER_ACCOUNT
Definition: account_char.h:20
void accounts_save(void)
Definition: account.c:267
EXTERN player * first_player
Definition: global.h:117
struct pl * next
Definition: player.h:93
int strcasecmp(const char *s1, const char *s2)
Definition: porting.c:256
size_t split_string(char *str, char *array[], size_t array_size, char sep)
Definition: utils.c:500
char * name
Definition: account.c:77
void LOG(LogLevel logLevel, const char *format,...)
Definition: logger.c:51
int account_is_logged_in(const char *name)
Definition: account.c:630
#define MAX_NAME
Definition: define.h:41
int account_check_string(const char *str)
Definition: account.c:369
static void account_write_entry(FILE *fp, account_struct *ac)
Definition: account.c:246
static account_struct * accounts
Definition: account.c:91
char const * newhash(char const *password)
const char * account_get_account_for_char(const char *charname)
Definition: account.c:562