/***************************************************************************
 *   copyright           : (C) 2005 by Hendrik Sattler                     *
 *   mail                : post@hendrik-sattler.de                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "helper.h"
#include "scmxx.h"
#include "gtincl.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>

char* pinfile_get_data (char* filename) {
  int fd = open(filename,O_RDONLY);
  struct stat fdstat;
  ssize_t status = 0;
  ssize_t total = 0;
  char* data = NULL;
  char* i;
  char* k;

  if (fd == -1) return NULL;

  //check the file
  if (fstat(fd,&fdstat) == -1) {
    print_verbose(0,"%s: %s: %s\n",_("File check failed"),filename,strerror(errno));
    file_close(fd);
    return NULL;
  }
  if ((fdstat.st_mode&S_IFMT) != S_IFREG) {
    print_verbose(0,"%s: %s: %s\n",_("File check failed"),filename,_("file is not a regular file"));
    file_close(fd);
    return NULL;
  }
#if ! defined(WINDOWS_API)
  if ((fdstat.st_mode&(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) {
    print_verbose(0,"%s: %s: %s\n",_("File check failed"),filename,_("file is group or world accessible"));
    file_close(fd);
    return NULL;
  }

  if (fdstat.st_size > SSIZE_MAX) fdstat.st_size = SSIZE_MAX;
#endif
  //read the file
  data = mem_alloc(fdstat.st_size+1,1);
  do {
    status = read(fd,data,fdstat.st_size-total);
    if (status > 0) total += status;
  } while (status > 0 && total < fdstat.st_size);
  if (status == -1) {
    print_verbose(0,"%s: %s: %s\n",_("File read failed"),filename,strerror(errno));
    file_close(fd);
    return mem_realloc(data,0);
  }

  //removing all spaces, changing '\r' to '\n'
  i = data;
  for (k = data; *k != 0; ++k) {
    if (isascii((int)*k)) {
      if (*k == '\r') *k = '\n';
      if (isalnum((int)*k) ||
	  *k == '{' || *k == '}' ||
	  *k == '=' || *k == ';' ||
	  *k == '\n')
	*(i++) = *k;
    }
  }
  if (i == data) {
    file_close(fd);
    return mem_realloc(data,0);
  } else {
    *i = 0;
  }

  //removing all '\n', adding ';' where necessary
  i = data;
  for (k = data; *k != 0; ++k) {
    if (*k == '\n') {
      if (i != data && *(i-1) != '{' && *(i-1) != '}' && *(i-1) != ';')
	*(i++) = ';';
    } else {
      *(i++) = *k;
    }
  }
  *i = 0;

  file_close(fd);
  return data;
}

int pinfile_select_sub (const char* sub_type, const char* sub_id, char* data) {
  int level = 0;
  char* begin;
  char* sub_begin = NULL;
  char* sub_end = data;

  do {
    if (sub_end != NULL) {
      if (*sub_end == 0) return 0;
      else begin = sub_end;
    } else {
      break;
    }
    sub_begin = strchr(begin,'{');
    if (sub_begin != NULL) {
      ++level;
      for (sub_end = ++sub_begin; *sub_end != 0 && level > 0; ++sub_end) {
	switch (*sub_end) {
	case '{': ++level; break;
	case '}': --level; break;
	}
      }
    } else {
      break;
    }
  } while (strncasecmp(begin,sub_type,strlen(sub_type)) != 0 ||
	   strncasecmp(begin+strlen(sub_type),sub_id,strlen(sub_id)) != 0 ||
	   begin+strlen(sub_type)+strlen(sub_id)+1 != sub_begin);

  if (sub_begin != NULL) {
    if (sub_end != NULL) *(--sub_end) = 0;
    memmove(data,sub_begin,(sub_end-sub_begin+1)*sizeof(*data));
    return 1;
  } else {
    return 0;
  }
}

const char* pinfile_find_pinpuk (const char* type, const char* data) {
  const char* pin_begin = data;
  const char* pin_end;
  
  if (str_len(type) == 0 || str_len(data) == 0) return NULL;

  while (strncasecmp(type,pin_begin,strlen(type)) != 0 ||
	 *(pin_begin+strlen(type)) != '=')
  {
    pin_begin = strchr(pin_begin,';');
    if (pin_begin == NULL) return NULL;
    else ++pin_begin;
  }

  pin_end = strchr(pin_begin,';');
  if (pin_end == NULL) return NULL;
  else return strchr(pin_begin,'=')+1;
}

char* pinfile_get_subdata (const char* scope, /* "sim" or "device" */
			   const char* scope_id, /* IMSI or IMEI */
			   const char* class, /* ignored if scope="sim" */
			   const char* type) /* "PIN" or "PUK", also "PIN2" or "PUK2" if scope="sim" */
{
  char* filename;
  char* data = NULL;
  char* temp = NULL;
  char* pin = NULL;
  char* puk = NULL;
  char* retval;

  //check scope
  if (scope == NULL ||
      (strcasecmp(scope,"sim") != 0 &&
       strcasecmp(scope,"device") != 0))
    return NULL;
  //check scope_id
  if (is_number(scope_id) == 0)
    return NULL;
  //check class
  if (strcasecmp(scope,"device") == 0 &&
      str_len(class) == 0)
    return NULL;
  //check type
  if (strncasecmp(type,"PIN",3) != 0 &&
      strncasecmp(type,"PUK",3) != 0)
    return NULL;
  if (type[3] != 0 &&
      (strcasecmp(scope,"sim") != 0 || type[3] != '2'))
    return NULL;

  filename = configfile_get_path(PACKAGE_NAME,"pin");
  if (strcasecmp(scope,"sim") != 0) {
    print_verbose(0,_("Looking for %s for %s %s, %s %s in %s..."),
	     type,scope,scope_id,"type",class,filename);
  } else {
    print_verbose(0,_("Looking for %s for %s %s in %s..."),
	     type,scope,scope_id,filename);
  }
  data = pinfile_get_data(filename);
  mem_realloc(filename,0);
  if (data == NULL) return strdup("");

  //find the section for <scope><scope_id>
  if (pinfile_select_sub(scope,scope_id,data) == 0 ||
      (strcasecmp(scope,"sim") != 0 &&
       pinfile_select_sub("type",class,data) == 0)) {
    mem_realloc(data,0);
    print_verbose(0,"%s\n",_("not found"));
    return str_dup("");
  }

  //final steps to find the wanted item
  if (strncasecmp(type,"puk",3) == 0) {
    //get PUK and PIN
    puk = (char*)pinfile_find_pinpuk(type,data);
    if (puk == NULL) {
      mem_realloc(data,0);
      print_verbose(0,"%s\n",_("not found"));
      return strdup("");
    } else {
      temp = mem_alloc(5,1);
      //type[3] is either '2' or '\0', see above
      sprintf(temp,"pin%c",type[3]);
      pin = (char*)pinfile_find_pinpuk(temp,data);
      mem_realloc(temp,0);
      if (pin != NULL) {
	//find_pinpuk() already checked it
	puk = strn_dup(puk,strchr(puk,';')-puk);
      }
    }
  } else {
    //get PIN only
    pin = (char*)pinfile_find_pinpuk(type,data);
  }
  if (pin == NULL) {
    mem_realloc(data,0);
    print_verbose(0,"%s\n",_("not found"));
    return strdup("");
  } else {
    print_verbose(0,"%s\n",_("found"));
    //find_pinpuk() already checked it
    pin = strn_dup(pin,strchr(pin,';')-pin);
    mem_realloc(data,0);
    if (puk == NULL) {
      return pin;
    } else {
      retval = mem_alloc(str_len(puk)+1+str_len(pin)+1,1);
      sprintf(retval,"%s,%s",puk,pin);
      mem_realloc(pin,0);
      mem_realloc(puk,0);
      return retval;
    }
  }
}

char* pinfile_get (const char* request) {
  char* valcpy;
  char* scope = NULL;
  char* scope_id = NULL;
  char* class = NULL;
  char* type = NULL;
  char* retval;
  static int antiloop = 0;

  if (str_len(request) < 7) return NULL;
  else valcpy = str_dup(request);

  /* confirm that type is valid
   */
  type = strchr(valcpy,' ');
  if (type == NULL) return mem_realloc(class,0);
  else *(type++) = 0;

  /* look for the scope to use
   */
  if (strncasecmp(valcpy,"PH-",3) == 0) {
    scope = "device";
    scope_id = get_phoneserial();
    class = valcpy+3;
  } else {
    scope = "sim";
    /* The following might (but not necessarily) start a loop,
     * prevent that loop
     * TODO: not threadsafe but we don't use threads
     */
    ++antiloop;
    if (antiloop > 1) {
      --antiloop;
      return mem_realloc(valcpy,0);
    }
    scope_id = get_simserial();
    --antiloop;
  }

  retval = pinfile_get_subdata(scope,scope_id,class,type);
  mem_realloc(scope_id,0);
  mem_realloc(valcpy,0);
  return retval;
}
