/* 
 * $Id: syntax_attrs.c,v 1.40 1999/05/17 12:56:38 labovit Exp $
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <regex.h>
#include <sys/socket.h>
#include <time.h>
#include <mrt.h>
#include <irr_check.h>

extern char *attr_name[];
extern regex_t re[];
extern char *countries[];


/* ---------------- single valued attributes ----------------- */

/*
 * *xx | longname <field>
 *
void xx_syntax (parse_info_t *obj) {
  char *token;
  
  if (obj->field_count > 0) {
    first_token (token);
    (*syntax_info[obj->curr_attr].syntax) (token, obj);
    if (obj->field_count > 1)
      error_msg_queue (obj, "Too many field(s)", ERROR_MSG);
  }
  else
    (*syntax_info[obj->curr_attr].empty_field) (obj);
}
*/

/* -------------individual field check functions ------------------*/

int country_syntax (char *country, parse_info_t *obj) {

  if (is_country (country, countries) >= 0)
    return 1;

  error_msg_queue (obj, "Unknown country", ERROR_MSG);  
  return 0;
}

/* Given an inetnum address range (ie, inetnum: a1 - a2 or a1 > a2)
 * determine if a1 is greater than a1.
 *
 * Return:
 *   1 if a1 is numerically <= a2 (ie, no errors)
 *   0 if a1 is numerically greater than a2 (ie, an error).
 */
int inetnum_syntax (parse_info_t *obj, char *prefix1, char *prefix2) {
  u_char dst1[4], dst2[4];
  u_int val1, val2;
  int i;

  if (irrd_inet_pton (AF_INET, prefix1, dst1) <= 0) {
    error_msg_queue (obj, "Malformed address in start of range", ERROR_MSG);
    return 0;
  }

  if (irrd_inet_pton (AF_INET, prefix2, dst2) <= 0) {
    error_msg_queue (obj, "Malformed address in end of range", ERROR_MSG);
    return 0;
  }
  
  val1 = val2 = 0;
  for (i = 0; i < 4; i++) {
    val1 |= dst1[i] << (24 - (i * 8));
    val2 |= dst2[i] << (24 - (i * 8));
  }

  if (val1 > val2) {
    error_msg_queue (obj, "Invalid address range", ERROR_MSG);
    return 0;
  }

  return 1;
}


/* ud - delete
 *
 * Return codes:
 *  0 means syntax failed, ie bad syntax
 *  1 means a warning was given
 *  2 means good syntax
 *  an empty attr generates a warning and
 *  is not an error.
 */
int delete_syntax (char *del, parse_info_t *obj) {
  char *p, *q;

  if ((p = strchr (del, '\n')) != NULL)
    *p = '\0';

  p = q = del;

  /* JW: I've always thought it was stupid to require the user
   * to supply text in the delete field.  
   */
  /* JW: take out
  if (irrcheck_find_token (&p, &q) < 0) {
    error_msg_queue (obj, "Empty attribute removed", EMPTY_ATTR_MSG);
    return 1;
  }
  */

  if (obj->op == NULL)
    obj->op = strdup (DEL_OP);
  
  return 2;
}

/* password
 *
 * Return codes:
 *  0 means syntax failed, ie bad syntax
 *  1 means a warning was given
 *  2 means good syntax
 *  an empty attr is silently ignored.
 */
int password_syntax (char *del, parse_info_t *obj) {
  char *p, *q;

  if ((p = strchr (del, '\n')) != NULL)
    *p = '\0';

  p = q = del;

  if (irrcheck_find_token (&p, &q) < 0)
    return 1;
  /* 
  if (irrcheck_find_token (&p, &q) < 0) {
    error_msg_queue (obj, "Empty attribute removed", EMPTY_ATTR_MSG);
    return 0;
  }
  */
  
  if (obj->password == NULL)
    obj->password = strdup (p);
  
  return 2;
}

/*
 * uo - override
 *
 * Return codes:
 *  0 means syntax failed, ie bad syntax
 *  1 means a warning was given
 *  2 means good syntax
 *  an empty attr generates a warning and
 *  is not an error.
 */
/* JW this is the routine we want after testing is done.
 * I am using the other to mimic our production server
 * which requires 2 fields instead of just the password
 */
int override_syntax_good (char *passwd, parse_info_t *obj) {
  char *p, *q, *x;

  if ((p = strchr (passwd, '\n')) != NULL)
    *p = '\0';

  p = q = passwd;

  if (irrcheck_find_token (&p, &q) < 0) {
    error_msg_queue (obj, "Empty attribute removed", EMPTY_ATTR_MSG);
    return 1;
  }

  x = p;
  if (irrcheck_find_token (&p, &q) > 0) {
    error_msg_queue (obj, "No spaces allowed in cleartext password", ERROR_MSG); 
    return 0;
  }  
  
  obj->override = strdup (x);

  return 2;
}

int authorise_syntax (char *passwd, parse_info_t *obj, int tokenpos) {
  char *p, *q;

  if ((p = strchr (passwd, '\n')) != NULL)
    *p = '\0';

  p = q = passwd;

  if (irrcheck_find_token (&p, &q) < 0) {
    error_msg_queue (obj, "Empty attribute removed", EMPTY_ATTR_MSG);
    return 0;
  }

  if (irrcheck_find_token (&p, &q) < 0) {
    obj->error_pos = (q - passwd) + tokenpos;
    error_msg_queue (obj, "Missing password", ERROR_MSG);
    return 0;
  }

  if (irrcheck_find_token (&p, &q) > 0) {
    obj->error_pos = (q - passwd) + tokenpos;
    error_msg_queue (obj, "No spaces allowed in cleartext password", ERROR_MSG); 
    return 0;
  }
  
  return 1;
}


int override_syntax (char *passwd, parse_info_t *obj, int tokenpos) {
  char *p, *q, *r;

  /* guard against multiple override passwords */
  if (obj->override != NULL) {
    error_msg_queue (obj, 
      "Multiple override fields ignored", EMPTY_ATTR_MSG);
    return 0;
  }

  if ((p = strchr (passwd, '\n')) != NULL)
    *p = '\0';

  p = q = passwd;

  if (irrcheck_find_token (&p, &q) < 0) {
    error_msg_queue (obj, "Empty attribute removed", EMPTY_ATTR_MSG);
    return 0;
  }

  if (irrcheck_find_token (&p, &q) < 0) {
    obj->error_pos = (q - passwd) + tokenpos;
    error_msg_queue (obj, "Missing password", ERROR_MSG);
    return 0;
  }

  r = p;
  if (irrcheck_find_token (&p, &q) > 0) {
    obj->error_pos = (q - passwd) + tokenpos;
    error_msg_queue (obj, "No spaces allowed in cleartext password", ERROR_MSG); 
    return 0;
  }
  
  obj->override = strdup (r);

  return 1;
}

/*
 * so - source
 */
void source_syntax (char *source_name, parse_info_t *obj) {
  source_t *src;
  extern config_info_t ci;

  if ((src = is_sourcedb (source_name, ci.srcs)) != NULL) {
    if (!src->authoritative)
      error_msg_queue (obj, "Non-authoritative database", ERROR_MSG);      
  }
  else
    error_msg_queue (obj, "Unknown database", ERROR_MSG);      
}



/*
 * em - e-mail
 * gd - guardian
 * mn - mnt-nfy
 * ny - notify
 * om - op-mail
 * dt - upd-to
 */
int email_syntax (char *email_addr, parse_info_t *obj) {
  char *p;

  /*if (verbose) fprintf (dfile, "JW: email_syntax(%s)\n", email_addr);*/
  if ((p = strchr (email_addr, '@')) == NULL) {
    error_msg_queue (obj, "Missing '@' in email address", ERROR_MSG);
    return 0;
  }

  if (strchr (p + 1, '@') != NULL) {
    error_msg_queue (obj, "Multiple '@' found in email address", ERROR_MSG);
    return 0;
  }

  if (regexec (&re[RE_EMAIL3], email_addr, (size_t) 0, NULL, 0)  ||
      (regexec (&re[RE_EMAIL1], email_addr, (size_t) 0, NULL, 0) &&
       regexec (&re[RE_EMAIL2], email_addr, (size_t) 0, NULL, 0))) {
    error_msg_queue (obj, "Malformed RFC822 email address", ERROR_MSG);       
    return 0;
  }

  return 1;
}

void regex_syntax (char *reg_exp, parse_info_t *obj) {
  regex_t comp_re;
  
  if (regcomp (&comp_re, reg_exp, REG_EXTENDED|REG_NOSUB) != 0) {
    error_msg_queue (obj, "Illegal regular expression", ERROR_MSG);     
    return;
  }
  
  regfree(&comp_re);
}

void cryptpw_syntax (char *cryptpw, parse_info_t *obj) {
  
  if (regexec(&re[RE_CRYPT_PW], cryptpw, (size_t) 0, NULL, 0))
      error_msg_queue (obj, "Illegal encrypted password ([./0-9A-Za-z]{13})", ERROR_MSG);
}

/* Give a 'date', deterimine if it is in the future.
 * 
 * Return:
 *   A non-NULL string date with today's date if the input
 *   date is in the future.
 *
 *   Otherwise return NULL
 */
char *date_in_future (char *date) {
  int intdate1 = 0, intdate2;
  char strdate[10];
  time_t now;
  struct tm *tmptr;

  sscanf (date, "%d", &intdate1);
  if (intdate1 == 0)
    return NULL;

  now = time (NULL);
  tmptr  = localtime (&now);
  intdate2 = (tmptr->tm_year * 10000) + (++tmptr->tm_mon * 100) + tmptr->tm_mday;

  if (intdate1 > intdate2) {
    sprintf (strdate, "%d", intdate2);
if (verbose) fprintf (dfile, "JW: return date in future %s\n", strdate);
    return strdup (strdate);
  }

  return NULL;
  if (verbose) fprintf (dfile, "JW: year (%d) month (%d) day (%d)\n", tmptr->tm_year, tmptr->tm_mon, tmptr->tm_mday);
}

/* Given a 'date' (ie, YYMMDD) make sure the date is in proper
 * format and syntax.  YY must be >= 1988.  If the date is
 * in the future, change the date to today's date and return 
 * the correct date.  Skip the future check if 'skip_future_check' is set
 * (ie, withdrawn: attr)
 *
 * Return:
 *   NULL if no errors in date and date is not in the future
 *   today's date if no errors in date and date is in the future
 */
char *date_syntax (char *date, parse_info_t *obj, int skip_future_check) {
  char year[5];
  char month[3];
  char day[3];
  char *now;
  int errors;

  /* ensure the date is 6 char's long and all numbers */
  if (regexec(&re[RE_DATE], date, (size_t) 0, NULL, 0)) {
      error_msg_queue (obj, "Malformed date (/^YYMMDD$/)", ERROR_MSG);
      return NULL;
  }

  errors = 0;

  /* check year part */
  strncpy (year, date, 2);
  year[2] = '\0';

  if (strncmp (year, "88", 2) < 0) {
      error_msg_queue (obj, "Year part of date is too old (YYMMDD)", ERROR_MSG);    
      errors++;
  }

  /* check month part */
  strncpy (month, &date[2], 2);
  month[2] = '\0';

  if ((strncmp (month, "01", 2) < 0) ||
      (strncmp (month, "12", 2) > 0)) {
      error_msg_queue (obj, "Syntax error in month part of date (YYMMDD)", ERROR_MSG);
      errors++;
  }

  /* check day part */
  strncpy (day, &date[4], 2);
  day[2] = '\0';

  if ((strncmp (day, "01", 2) < 0) ||
      (strncmp (day, "31", 2) > 0)) {
      error_msg_queue (obj, "Syntax error in day part of date (YYMMDD)", ERROR_MSG);
      errors++;
  }

  if (errors || skip_future_check)
    return NULL;

  if ((now = date_in_future (date)) != NULL) {
    error_msg_queue (obj, "Date is in the future, changed to today's date", WARN_OVERRIDE_MSG);
    return now;
  }

  return NULL;
}


void name_syntax (parse_info_t *obj, char *hdl) {
  char *p, *q, *r = NULL, c;
  int i;

  p = q = hdl;
  for (i = 0; irrcheck_find_token (&p, &q) > 0; i++, *q = c) {
    c = *q;
    *q = '\0';
    r = NULL;
    if (verbose) fprintf (dfile, "JW: field[%d]-(%s)\n", i, p);

    /* email names not allowed */
    if (strchr (p, '@') != NULL)
      error_msg_queue (obj, "Syntax error.  Looks like an email address", ERROR_MSG);
    /* no titles like Mr., Dr., sir, ... */
    else if (i == 0 && 
             !regexec(&re[RE_TITLES], p, (size_t) 0, NULL, 0))
      error_msg_queue (obj, "Syntax error.  Titles not allowed", ERROR_MSG);       
    /* check the basic structure and legal characters */
    else if (regexec(&re[RE_NAME], p, (size_t) 0, NULL, 0)) {
      error_msg_queue (obj, "Syntax error in name", ERROR_MSG);       
    }
    /* check for abbreviation on first and last name */
    else {
      r = p + strlen (p) - 1;
      if (i == 0 && *r == '.')
	error_msg_queue (obj, "Abbreviated first names not allowed", ERROR_MSG);
    }
  }

  if (r != NULL && *r == '.')
    error_msg_queue (obj, "Abbreviated last names not allowed", ERROR_MSG);

  /* legal names must have at least two components */
  if (i < 2)
      error_msg_queue (obj, "Names must have at least two components or bad handle", 
		       ERROR_MSG);
}

int is_nichdl (char *nichdl) {
  char *p;
  extern config_info_t ci;

  if (verbose) fprintf (dfile, "\n---JW: enter is_nichdl(%s): short circut, nichdl() always true!\n", nichdl);
  
  /* lower case alpha not allowed */
  if (!regexec(&re[RE_LCALPHA], nichdl, (size_t) 0, NULL, 0)) {
    if (verbose) fprintf (dfile, "JW: found lower case, is not a handle (%s)!\n", nichdl);
    return 0;
  }
  
  /* check for auto-RIPE nic handle */
  if (!regexec(&re[RE_RIPE_HDL], nichdl, (size_t) 0, NULL, 0)) {
    if (verbose) fprintf (dfile, "JW: is a auto RIPE handle (%s)!\n", nichdl);
    return 1;
  }
  
  /* check for ARIN handles */
  if (!regexec(&re[RE_ARIN_HDL], nichdl, (size_t) 0, NULL, 0)) {
    if (verbose) fprintf (dfile, "JW: is a ARIN handle (%s)!\n", nichdl);
    return 1;
  }
  
  /* check for APNIC handle */
  if (!regexec(&re[RE_APNIC_HDL], nichdl, (size_t) 0, NULL, 0)) {
    if (verbose) fprintf (dfile, "JW: is a jpnic handle (%s)!\n", nichdl);
    return 1;
  }
  
  /* check for standard handle */
  if (!regexec(&re[RE_STD_HDL], nichdl, (size_t) 0, NULL, 0)) {
    if (verbose) fprintf (dfile, "JW: is a std handle (%s)!\n", nichdl);
    if ((p = strchr (nichdl, '-')) != NULL) {
      if (verbose) fprintf (dfile, "JW: checking suffix of std hdl (%s)...\n", p);
      p++;
      if (is_sourcedb (p, ci.srcs) != NULL) {
	if (verbose) fprintf (dfile, "JW: hdl-ret (is a sourcedb!\n");
        return 1;
      }
      
      if (is_country (p, countries) >= 0) {
	if (verbose) fprintf (dfile, "JW: hdl-ret (is a country!\n");
        return 1;
      }
      
      if (is_special_suffix (p)) {
	if (verbose) fprintf (dfile, "JW: hdl-ret (is a spec suffix!\n");
        return 1;
      }
    }
    else
      return 1;
  }

  return 0;
}

/*
 * ac - admin-c
 * ph - person
 * ro - role
 * ah - author
 * tc - tech-c
 * zc - zone-c
 */
void hdl_syntax (parse_info_t *obj, char *hdl) {
  char *tok_s, *p, *q;

/* JW I am diabling all checking for pipeline testing  
return;
*/

  p = q = hdl;
  if (irrcheck_find_token (&p, &q) > 0) {
    if (verbose) fprintf (dfile, "JW: enter hdl_syntax(): %s %s\n", attr_name[obj->curr_attr], hdl);

    tok_s = p;
    /* see if we have a nic-hdl, illegal for role and person objs */
    if (irrcheck_find_token (&p, &q) < 0) {
      if (is_nichdl (tok_s)) {
	if (obj->curr_attr == F_RO || obj->curr_attr == F_PN)
	  error_msg_queue (obj, 
			   "Handles not allowed for \"person\" and \"role\" objects",
			   ERROR_MSG);
	return; /* nic-hdl's legal for admin-c, tech-c, zone-c, author */
      }
    }

    /* It's not a nic handle, so check for legal name syntax */
    name_syntax (obj, hdl);
  }
  else /* should never get here */
    error_msg_queue (obj, "Empty attribute removed", EMPTY_ATTR_MSG);
}

 /*
  * nh - nic-hdl
  */
void nichdl_syntax (char *nic_hdl, parse_info_t *obj) {

/* JW I am diabling all checking for pipeline testing 
return;
*/
  if (!is_nichdl (nic_hdl))
    error_msg_queue (obj, "Syntax error in NIC handle", ERROR_MSG);
}

/*
 * cm - community
 */
void comm_syntax (char *comm_name, parse_info_t *obj) {
  
  if (regexec(&re[RE_COMM1], comm_name, (size_t) 0, NULL, 0)) {
      error_msg_queue (obj, "Syntax error in community name(/^[A-Z][A-Z0-9_-]+$/) ", 
		       ERROR_MSG);
      return;
  }

  if (!regexec(&re[RE_COMM2], comm_name, (size_t) 0, NULL, 0))
      error_msg_queue (obj, 
	"Community names cannot start with (/^(ANY|AND|OR|NOT|AS|LIM-)/)", ERROR_MSG);
}


/* Given a comma seperated sources is (ie, rx-in: IRROrder(radb,ans,...)),
 * return an error if 'new_source' is already in the list 'sources_list'.
 *
 * Return
 *
 *   0 if new_source is already a member of 'sources_list'
 *     or DB is now defined in irrd.conf file (ie, is now known)
 *   1 otherwise
 *
 */
int irrorder_syntax (parse_info_t *obj, char *sources_list, char *new_source) {
  char buf[MAXLINE], *p;
  /* JW take out extern config_info_t ci */;

  /* See if we know of this DB */
  if (strcasecmp (new_source, "radb")  &&
      strcasecmp (new_source, "ans")   &&
      strcasecmp (new_source, "mci")   &&
      strcasecmp (new_source, "canet") &&
      strcasecmp (new_source, "ripe")) {
    sprintf (buf, "Unknown database \"%s\"", new_source);
    error_msg_queue (obj, buf, ERROR_MSG);
    return 1;
  }

  /* See if we know of this DB 
  if (is_sourcedb (new_source, ci.srcs) == NULL) {
    sprintf (buf, "Unknown database \"%s\"", new_source);
    error_msg_queue (obj, buf, ERROR_MSG);
    return 1;   
  }
  */

  /* Make sure the DB isn't duplicately defined */
  if (sources_list != NULL && strlen (sources_list) < MAXLINE) {
    strcpy (buf, sources_list);
    p = strtok (buf, ",");
    while (p != NULL) {
      if (*p == ' ')
	p++;
      if (!strcmp (p, new_source)) {
	sprintf (buf, "Duplicate database \"%s\"", new_source);
	error_msg_queue (obj, buf, ERROR_MSG);
	return 1;
      }
      p = strtok (NULL, ",");
    }
  }

  return 0;
}
