/************************************************************************
*                 WHOIS parsing routine
*************************************************************************
* Author : Eric Smith
* Date   : 12/20/91
* Usage  : For instructions on using the WHOIS server, read the file
*          whois.help.
*          For instructions on creating the whois database and reconfiguring
*          the fields in the database, read the file whoisadmin.help.
************************************************************************/
#include <stdio.h>
#include "whoisd.h"

whois_parse(commandbuff)
  char *commandbuff;
{
  FILE *fp, *fpindex;

  int i, j, cc;
  int index;
  int search_name;
  int search_handle;
  int search_mailbox;
  int search_mailbox_name;
  int search_mailbox_host;
  int match_rest;
  int entries;
  int max_entries;
  int hold_entry;
  int entry_matches;
  int entry_position;
  int multi_name_format;
  int field_width;
  int whois_record_size;
  static int user_field_size[USER_FIELDS+1] = USER_FIELD_SIZES;

  char *buffptr;
  char *command;
  char tempbuff[200];

  char name[100];
  char first_name[100];
  char middle_name[100];
  char last_name[100];

  whois_record_type *whois_record;
  whois_record_type *save_whois_record;
/*
 * Variables used for index.
 */
  int key;
  int duplicate;
  int exitflag;
  int search_entry;
  int search_entries;
  int *search_list;
  int search_list_size;
  int mailbox_host_ptr;
  int last_filepos;
  int first_filepos;
  int handle_filepos;
  int mbox_name_filepos;
  int mbox_host_filepos;
  int last_filepos_start[26];
  int first_filepos_start[26];
  int handle_filepos_start[26];
  int mbox_name_filepos_start[26];
  int mbox_host_filepos_start[26];
  int last_index[26];
  int first_index[26];
  int handle_index[26];
  int mbox_name_index[26];
  int mbox_host_index[26];
/*
 * Read index data.
 */
  if ((fpindex = fopen(WHOIS_INDEX, "r")) == NULL) {
    sprintf(tempbuff, "Unable to open whois index file, %s\n", WHOIS_INDEX);
    output_string(tempbuff);
    return(0);
  }
/*
 * Read index data sizes.
 */
  for (i=0; i<26; i++)
    fread(&(last_index[i]), sizeof(int), 1, fpindex);
  for (i=0; i<26; i++)
    fread(&(first_index[i]), sizeof(int), 1, fpindex);
  for (i=0; i<26; i++)
    fread(&(handle_index[i]), sizeof(int), 1, fpindex);
  for (i=0; i<26; i++)
    fread(&(mbox_name_index[i]), sizeof(int), 1, fpindex);
  for (i=0; i<26; i++)
    fread(&(mbox_host_index[i]), sizeof(int), 1, fpindex);
/*
 * Calculate starting file positions for each of the file position arrays.
 */
  last_filepos_start[0] = ftell(fpindex);
  for (i=1; i<26; i++)
    last_filepos_start[i] =  last_filepos_start[i-1]
                           + last_index[i-1]*sizeof(int);

  first_filepos_start[0] =  last_filepos_start[25]
                          + last_index[25]*sizeof(int);
  for (i=1; i<26; i++)
    first_filepos_start[i] =  first_filepos_start[i-1]
                            + first_index[i-1]*sizeof(int);

  handle_filepos_start[0] =  first_filepos_start[25]
                           + first_index[25]*sizeof(int);
  for (i=1; i<26; i++)
    handle_filepos_start[i] =  handle_filepos_start[i-1]
                             + handle_index[i-1]*sizeof(int);

  mbox_name_filepos_start[0] =  handle_filepos_start[25]
                              + handle_index[25]*sizeof(int);
  for (i=1; i<26; i++)
    mbox_name_filepos_start[i] =  mbox_name_filepos_start[i-1]
                                + mbox_name_index[i-1]*sizeof(int);

  mbox_host_filepos_start[0] =  mbox_name_filepos_start[25]
                              + mbox_name_index[25]*sizeof(int);
  for (i=1; i<26; i++)
    mbox_host_filepos_start[i] =  mbox_host_filepos_start[i-1]
                                + mbox_host_index[i-1]*sizeof(int);
/*
 * Parse command line.
 */
  command = commandbuff;
  search_name = search_handle = search_mailbox = search_mailbox_name = 0;
  search_mailbox_host = match_rest = 0;
/*
 * Strip off trailing white space.
 */
  index = strlen(command)-1;
  while (is_space(command[index])) index--;
/*
 * Check for match rest flag at end of command.
 */
  while (command[index] == '.') {
    match_rest = 1;
    index--;
  }
  command[index+1] = 0;
/*
 * Convert input to upper case.
 */
  to_upper(command);
/*
 * Check if user needs help.
 */
  if ((command[0] == '?') || (!strncmp(command, "HELP", 4))) {
    print_help();
    return;
  }
/*
 * Check for mailbox search.
 */
  if ((i = search_string(command, '@')) != -1) {
    search_mailbox = 1;
    if (i != 0) search_mailbox_name = 1;
    if (i != strlen(command)-1) {
      search_mailbox_host = 1;
      mailbox_host_ptr = i+1;
    }
  }
  if (!search_mailbox) {
/*
 * Not mailbox search, so check if we are looking for handle, name, or both.
 */
    if (command[0] == '!') {
      search_handle = 1;
      command++;
    }
    else if (command[0] == '.') {
      search_name = 1;
      command++;
    }
    else {
      search_handle = 1;
      search_name = 1;
    }
/*
 * Check for comma in field.
 */
    multi_name_format = 0;
    if ((i = search_string(command, ',')) != -1) {
/*
 * Comma found in command, so format of command is "last, first [M.I.]"
 */
      search_handle = 0;
      multi_name_format = 1;
/*
 * Copy last name into the variable last_name.
 */
      index = 0;
      for (j=0; j<i; j++) {
        if (command[j] == ' ') continue;
        last_name[index++] = command[j];
      }
      last_name[index] = '\0';
/*
 * Copy first name into the variable first_name.
 */
      while ((command[i] == ',') || (command[i] == ' ')) i++;
      index = 0;
      for (j=i; j<strlen(command); j++) {
        if (command[j] == ' ') break;
        first_name[index++] = command[j];
      }
      first_name[index] = '\0';
/*
 * Copy middle name into the variable middle_name.
 */
      index = 0;
      if (j<strlen(command)) {
        while (command[j] == ' ') j++;
        while (j<strlen(command)) {
          if (command[j] == ' ') break;
          middle_name[index++] = command[j];
          j++;
        }
      }
      middle_name[index] = '\0';
    }
/*
 * No comma found, so name is either first, last, or handle.
 */
    else {
/*
 * Copy input line into the variable name.
 */
      index = 0;
      j=0;
      while (command[j] == ' ') j++;
      for (i=j; i<strlen(command); i++)
        name[index++] = command[i];
      name[index] = '\0';
    }
  }
/*
 * Create searchlist.
 */
  search_entries = 0;
/*
 * Initially allocate enough space for 100 entries.
 */
  search_list_size = 100;
  search_list = (int *)malloc(search_list_size*sizeof(int));
  if (search_list == NULL) {
    output_string("Unable to allocate memory for search_list.\n");
    output_string("Try adjusting process quotas.\n");
    exit();
  }
/*
 * Search through entries by name if the search_name flag is set.
 */
  if (search_name) {
/*
 * If multiname format, use the starting letter of the last name as the key.
 * Otherwise, use the starting letter of the name specified as the key into
 * both the last name and first name lists.
 */
    if (multi_name_format)
      key = last_name[0]-65;
    else
      key = name[0]-65;

    if ((key >= 0) && (key <= 25)) {
/*
 * Search all entries with the same last name starting letter.  Reallocate
 * space for search_list if required.
 */
      if ((search_entries+last_index[key]+first_index[key])>search_list_size) {
        search_list_size += last_index[key]+first_index[key];
        search_list = (int *)realloc(search_list, search_list_size*sizeof(int));
        if (search_list == NULL) {
          output_string("Unable to re-allocate memory for search_list.\n");
          output_string("Try adjusting process quotas.\n");
          exit();
        }
      }
      fseek(fpindex, last_filepos_start[key], 0);
      for (i=0; i<last_index[key]; i++) {
        fread(&last_filepos, sizeof(int), 1, fpindex);
        search_list[search_entries++] = last_filepos;
      }
/*
 * If not multiname format, search all entries with the same first name starting
 * letter.  Remove all duplicate entries.
 */
      if (!multi_name_format) {
        fseek(fpindex, first_filepos_start[key], 0);
        for (i=0; i<first_index[key]; i++) {
          fread(&first_filepos, sizeof(int), 1, fpindex);
          duplicate = 0;
          for (j=0; j<search_entries; j++)
            if (search_list[j] == first_filepos) {
              duplicate = 1;
              break;
            }
          if (!duplicate)
            search_list[search_entries++] = first_filepos;
        }
      }
    }
  }
/*
 * Search through entries by handle if the search_handle flag is set.
 */
  if (search_handle) {
    key = name[0]-65;
    if ((key >= 0) && (key <= 25)) {
/*
 * Reallocate space for search_list if required.
 */
      if ((search_entries+handle_index[key]) > search_list_size) {
        search_list_size += handle_index[key];
        search_list = (int *)realloc(search_list, search_list_size*sizeof(int));
        if (search_list == NULL) {
          output_string("Unable to re-allocate memory for search_list.\n");
          output_string("Try adjusting process quotas.\n");
          exit();
        }
      }
/*
 * Search all entries with the same handle starting letter.  Remove all
 * duplicate entries.
 */
      fseek(fpindex, handle_filepos_start[key], 0);
      for (i=0; i<handle_index[key]; i++) {
        fread(&handle_filepos, sizeof(int), 1, fpindex);
        duplicate = 0;
        for (j=0; j<search_entries; j++)
          if (search_list[j] == handle_filepos) {
            duplicate = 1;
            break;
          }
        if (!duplicate)
          search_list[search_entries++] = handle_filepos;
      }
    }
  }
  if (search_mailbox_name) {
    key = command[0]-65;
    if ((key >= 0) && (key <= 25)) {
/*
 * Reallocate space for search_list if required.
 */
      if ((search_entries+mbox_name_index[key]) > search_list_size) {
        search_list_size += mbox_name_index[key];
        search_list = (int *)realloc(search_list, search_list_size*sizeof(int));
        if (search_list == NULL) {
          output_string("Unable to re-allocate memory for search_list.\n");
          output_string("Try adjusting process quotas.\n");
          exit();
        }
      }
/*
 * Search all entries with the same mailbox name starting letter.  Remove all
 * duplicate entries.
 */
      fseek(fpindex, mbox_name_filepos_start[key], 0);
      for (i=0; i<mbox_name_index[key]; i++) {
        fread(&mbox_name_filepos, sizeof(int), 1, fpindex);
        duplicate = 0;
        for (j=0; j<search_entries; j++)
          if (search_list[j] == mbox_name_filepos) {
            duplicate = 1;
            break;
          }
        if (!duplicate)
          search_list[search_entries++] = mbox_name_filepos;
      }
    }
  }
  if (search_mailbox_host) {
    key = command[mailbox_host_ptr]-65;
    if ((key >= 0) && (key <= 25)) {
/*
 * Reallocate space for search_list if required.
 */
      if ((search_entries+mbox_host_index[key]) > search_list_size) {
        search_list_size += mbox_host_index[key];
        search_list = (int *)realloc(search_list, search_list_size*sizeof(int));
        if (search_list == NULL) {
          output_string("Unable to re-allocate memory for search_list.\n");
          output_string("Try adjusting process quotas.\n");
          exit();
        }
      }
/*
 * Search all entries with the same mailbox host starting letter.  Remove all
 * duplicate entries.
 */
      fseek(fpindex, mbox_host_filepos_start[key], 0);
      for (i=0; i<mbox_host_index[key]; i++) {
        fread(&mbox_host_filepos, sizeof(int), 1, fpindex);
        duplicate = 0;
        for (j=0; j<search_entries; j++)
          if (search_list[j] == mbox_host_filepos) {
            duplicate = 1;
            break;
          }
        if (!duplicate)
          search_list[search_entries++] = mbox_host_filepos;
      }
    }
  }
  fclose(fpindex);
/*
 * Allocate space for whois record.
 */
  whois_record_size = sizeof(whois_record_type)-1;
  for (i=0; i<USER_FIELDS; i++) whois_record_size += user_field_size[i]+1;

  whois_record = (whois_record_type *)malloc(whois_record_size);
  save_whois_record = (whois_record_type *)malloc(whois_record_size);
  if ((whois_record == NULL) || (save_whois_record == NULL)) {
    output_string("Unable to allocate memory for whois_record.\n");
    output_string("Try adjusting process quotas.\n");
    exit();
  }
/*
 * Open whois data base.  WHOIS_DATA is defined in whoisd.h
 */
  fp = fopen(WHOIS_DATA, "r");
/*
 * Search through data base for all matching entries.
 */
  entries = 0;
  search_entry = 0;
  exitflag = 0;
  do {
/*
 * Choose next entry;
 */
    if (search_entry == search_entries)
      exitflag = 1;
    else {
      entry_position = search_list[search_entry++];
      fseek(fp, entry_position, 0);
    }
/*
 * Read entry.
 */
    if (!exitflag)
      cc = fread(whois_record, whois_record_size, 1, fp);
    if (cc == 0) exitflag = 1;

    if (!exitflag) {
/*
 * Move copy of whois_record to temporary storage.
 */
      bytecopy(whois_record, save_whois_record, whois_record_size);
      entry_matches = 0;
/*
 * Mailbox search.
 */
      if (search_mailbox) {
/*
 * Convert whois_record net address to upper case.
 */
        to_upper(whois_record->net_address);
/*
 * If not looking for mailbox username, set buffptr to start of hostname field.
 * (Hostname field starts with an '@' character.
 */
        buffptr = whois_record->net_address;
        if (!search_mailbox_name)
          buffptr += search_string(whois_record->net_address, '@');
/*
 * Compare mailbox with mailbox in whois record.
 */
        if ((!search_mailbox_host) || match_rest)
          entry_matches = !strncmp(command, buffptr, strlen(command));
        else
          entry_matches = !strcmp(command, buffptr);
      }
/*
 * Name or handle search.
 */
      else {
        if (search_handle) {
/*
 * Convert whois_record handle to upper case and compare with input.
 */
          to_upper(whois_record->handle);
          if (match_rest)
            entry_matches =
               !strncmp(name, whois_record->handle, strlen(name));
          else
            entry_matches =
               !strcmp(name, whois_record->handle);
        }
/*
 * If handle search brought no match, do name search.
 */
        if (search_name && (!entry_matches)) {
/*
 * Multi-name format.
 */
          if (multi_name_format) {
/*
 * Compare last name.  A last name is considered a match if the name matches
 * exactly, or the name matches up to a space in the last_record.  This takes
 * care of cases such as "Smith III, Eric S."
 */
            to_upper(whois_record->last_name);
            if (   (!strcmp(last_name, whois_record->last_name))
                || (   (!strncmp(last_name, whois_record->last_name,
                                 strlen(last_name)))
                    && (whois_record->last_name[strlen(last_name)] == ' '))) {
/*
 * If match, and a first name was specified, compare first name.
 */
              if (strlen(first_name)) {
                to_upper(whois_record->first_name);
                if (match_rest)
                  entry_matches = !strncmp(first_name, whois_record->first_name,
                                           strlen(first_name));
                else
                  entry_matches = !strcmp(first_name, whois_record->first_name);
/*
 * If match, and a middle name was specified, compare middle name.
 */
                if (entry_matches && strlen(middle_name)) {
                  to_upper(whois_record->middle_initial);
                  entry_matches = !strncmp(middle_name,
                                           whois_record->middle_initial,
                                           strlen(middle_name));
                }
              }
            }
          }
/*
 * Single name format.  Match on first or last names.
 */
          else {
/*
 * Compare both last name and first name fields in whois_record to input.
 */
            to_upper(whois_record->last_name);
            to_upper(whois_record->first_name);
            if (match_rest)
              entry_matches =
                  (!strncmp(name, whois_record->first_name, strlen(name)))
                ||(!strncmp(name, whois_record->last_name, strlen(name)));
            else
              entry_matches =
                  (!strcmp(name, whois_record->first_name))
               || (!strcmp(name, whois_record->last_name))
               || (   (!strncmp(name, whois_record->last_name, strlen(name)))
                   && (whois_record->last_name[strlen(name)] == ' '));
          }
        }
      }
/*
 * If this entry matches, print out entry unless this is the first entry
 * found.  Hold first entry found until we know if we are going to be
 * using multi-line format or single line summary.  Multi-line format is
 * used if only one entry found.  Single line summary is used for multiple
 * found entries.
 */
      if (entry_matches) {
        entries++;
        if (entries == 1)
          hold_entry = entry_position;
        else {
          if (entries == 2) {
            fseek(fp, hold_entry, 0);
            fread(save_whois_record, 1, whois_record_size, fp);
            sprintf(tempbuff, "%s, %s %s  (%s)",
                 save_whois_record->last_name, save_whois_record->first_name,
                 save_whois_record->middle_initial, save_whois_record->handle);
            field_width = strlen(tempbuff);
            if (field_width < 32) {
              for (j=0; j<32-field_width; j++)
                tempbuff[field_width+j] = ' ';
              field_width = 32;
            }
            else {
              tempbuff[field_width] = ' ';
              tempbuff[field_width+1] = ' ';
              field_width += 2;
            }
            sprintf(tempbuff+field_width, "%s\n",
                    save_whois_record->net_address);
            output_string(tempbuff);
            fseek(fp, entry_position, 0);
            fread(save_whois_record, 1, whois_record_size, fp);
          }
          sprintf(tempbuff, "%s, %s %s  (%s)",
               save_whois_record->last_name, save_whois_record->first_name,
               save_whois_record->middle_initial, save_whois_record->handle);
          field_width = strlen(tempbuff);
          if (field_width < 32) {
            for (j=0; j<32-field_width; j++)
              tempbuff[field_width+j] = ' ';
            field_width = 32;
          }
          else {
            tempbuff[field_width] = ' ';
            tempbuff[field_width+1] = ' ';
            field_width += 2;
          }
          sprintf(tempbuff+field_width, "%s\n", save_whois_record->net_address);
          output_string(tempbuff);
        }
      }
    }
  } while (!exitflag);
/*
 * If only one entry found, print out full entry.
 */
  if (entries == 1) {
    fseek(fp, hold_entry, 0);
    fread(save_whois_record, 1, whois_record_size, fp);
    sprintf(tempbuff, "%s, %s %s  (%s)    %s\n",
           save_whois_record->last_name, save_whois_record->first_name,
           save_whois_record->middle_initial, save_whois_record->handle,
           save_whois_record->net_address);
    output_string(tempbuff);
    index = 0;
    for (i=0; i<USER_FIELDS; i++) {
      sprintf(tempbuff, "%s\n", save_whois_record->user_fields+index);
      output_string(tempbuff);
      index += user_field_size[i]+1;
    }
  }
  else if (entries == 0) {
    sprintf(tempbuff, "No match found for %s\n", commandbuff);
    output_string(tempbuff);
  }
}



/*
 * This routine returnes the position of a character within a string.  -1 is
 * returned if character not found.
 */
search_string(string, search_char)
  char *string;
  char search_char;
{
  static int i;
  static int char_pos;

  char_pos = -1;
  for (i=0; i<strlen(string); i++) {
    if (string[i] == search_char) {
      char_pos = i;
      break;
    }
  }

  return(char_pos);
}


/*
 * This routine prints the contents of the whois help file.  WHIOS_HELP is
 * defined in whoisd.h
 */
print_help()
{
  FILE *fp;
  char tempbuff[100];

  fp = fopen(WHOIS_HELP, "r");
  while (fgets(tempbuff, sizeof(tempbuff), fp) != NULL) {
    output_string(tempbuff);
  } 

  fclose(fp);
}



/*
 * This routine converts a string to upper case.
 */
to_upper(string)
  char *string;
{
  int i;

  for (i=0; i<strlen(string); i++) {
    if ((string[i]>='a') && (string[i]<='z'))
      string[i] -= 32;
  }
}



/*
 * This routine returns a 1 if the character is white_space and 0 if not.
 * White space is considered to be one of the following:  space, line feed,
 * carriage return, tab or form feed.
 */
is_space(value)
  char value;
{
  int i;
  static space_chars[5] = {' ', '\n', '\r', '\011', '\014'};

  for (i=0; i<5; i++)
    if (space_chars[i] == value)
      return(1);
  return(0);
}



bytecopy(source, target, size)
  char *source;
  char *target;
  int size;
{
  while (size--)
    *target++ = *source++;
}
