/* Asp Address Search Protocol Client (file: asp.c)                        */
/* Copyright (C) 1996-2001 Stenio Brunetta                                 */
/*                                                                         */
/*  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.                                    */
/*                                                                         */
/*  This program is distributed in the hope that it will be useful,        */
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of         */  
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
/*  GNU General Public License for more details.                           */
/*                                                                         */
/*  You should have received a copy of the GNU General Public License      */
/*  along with this program; if not, write to the Free Software            */
/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.              */
/*                                                                         */
/*  Author's email address: stenio@brunettaeperin.it                       */

static char rcsid[] = \
   "$Id: asp.c,v 1.51 2001/01/23 11:20:01 ste Exp $";

#include "asp.h"

#define ASPCMDV "ASPCMD"

int recv_delay = 5; /* Utilizzata da asp_recv */
char* prog_name;

static int send_delay = 2;
static int addresses = 12;
static int rip = 3; 
static int port;
static int force;
static int update;
static char* path = NULL;
static char* ext = NULL;
static char host2find[MSGSIZE];

int main(int argc, char *argv[]){ 
  int sock, in_stat, in, sent, bad, h2f_len, count, i;
  void parse_cmdline(int, char*[]);
  void parse_envar(int, char*[]);
  char** get_token(int*, char*);
  void update_hosts(char*, char*);
  char host2try[20], buf[MSGSIZE], *pcmd, **ppcmd;
  void redirect(char*);
  struct sockaddr_in l_name, r_name;
  struct servent* ps_servent;

  prog_name = *argv;

  if((ps_servent = getservbyname("asp", "udp")) == NULL){
    port = ASP_PORT;
  }else port = ntohs(ps_servent->s_port);

  if((pcmd = getenv(ASPCMDV)) != NULL){
    ppcmd = get_token(&i, pcmd);
    if(i != 0) parse_envar(i+1, ppcmd);
  }

  parse_cmdline(argc, argv);

  /* Crea la socket */
  if((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
    asp_perror("socket");
      
  /* Tipo della socket remota (Host byte order) */
  l_name.sin_family=AF_INET;

  /* Porta della socket remota (Network byte order) */
  l_name.sin_port=htons(port);

  if(force == 0) redirect(host2find);

  h2f_len = strlen(host2find);
  in = sent = bad = count = 0;

  /* Finche` ci sono indirizzi in input */
  while((in_stat = get_ip(host2try)) > 0){
    in = 1;

#ifdef HAVE_INET_ATON
    if(!inet_aton(host2try, &(l_name.sin_addr)))
      asp_perror("address");
#else
    if((l_name.sin_addr.s_addr = inet_addr(host2try)) == -1)
      asp_perror("address");
#endif
    
    /* Ripete l'invio del messaggio allo stesso indirizzo rip 
       volte */
    for(i=0; i<rip; i++, ++count){
      sent++;

      /* Si va in ricezione solo alla fine di un gruppo di pacchetti */
      if(count == addresses && addresses != 0){
	count = 0;
	
	if(verbose)
	  switch(send_delay){
	  case 0: break;
	  case 1: printf("Waiting one second...\n");
	    break;
	  default: printf("Waiting %i seconds...\n", send_delay);
	  }
	
	in_stat = asp_recv(sock, h2f_len, buf, host2find, \
			   (struct sockaddr *) &r_name, send_delay);
	
	/* Trovato! */
	if(in_stat == 1){
	  if(verbose) printf("Host %s found at address %s\n", \
			     host2find, inet_ntoa(r_name.sin_addr));
	  else printf("%s\n", inet_ntoa(r_name.sin_addr));

	  /* Aggiorna il file /etc/hosts */
	  if(update) update_hosts(host2find, inet_ntoa(r_name.sin_addr));
	  
	  close(sock);
	  return EXIT_SUCCESS;
	}
      }
      
      /* Invia il messaggio rip volte */
      bad += asp_send(sock, h2f_len, host2find, (struct sockaddr *) &l_name);

    } /* fine for */
  } /* fine while */
  
  /* input errato */
  if(in_stat == -1){
    fprintf(stderr, "get_ip: malformed address\n");
    exit(EXIT_FAILURE);
  }
  
  /* nel caso non sia stato passato nessun indirizzo */
  if(!in) exit(EXIT_FAILURE);

  /* Tutti i messaggi non sono stati accettati */
  if(bad == sent){
    if(verbose) printf("Host not found\n");
    exit(EXIT_FAILURE);
  }

  if(verbose)
    switch(recv_delay){
    case 0: break;
    case 1: printf("Waiting one second...\n");
      break;
    default: printf("Waiting %i seconds...\n", recv_delay);
    }

  in_stat = asp_recv(sock, h2f_len, buf, host2find, \
		     (struct sockaddr *) &r_name, recv_delay);

  /* Trovato! */
  if(in_stat == 1){
    if(verbose) printf("Host %s found at address %s\n", \
		       host2find, inet_ntoa(r_name.sin_addr));
    else printf("%s\n", inet_ntoa(r_name.sin_addr));
    
    /* Aggiorna /etc/hosts */
    if(update) update_hosts(host2find, inet_ntoa(r_name.sin_addr));
    close(sock);
    return EXIT_SUCCESS;
  }
  
  /* Non trovato o errore */
  if(in_stat == 0){
    if(verbose) printf("Host not found\n");
    /* Aggiorna /etc/hosts */
    if(update) update_hosts(host2find, NULL);
  }
  else asp_perror("input_timeout");
  
  close(sock);
  return EXIT_FAILURE;
}

/* Controlla che buf sia un IP address seguito dalla stringa host. */
int match(char* buf, char* host){
  char *s1, *s2, *s3;
  int i;

  if((s1 = strdup(buf)) == NULL) asp_perror("strdup");
  
  /* Elimina commenti o new line */
  if((s2 = strpbrk(s1, "#\n")) != NULL) *s2 = '\0';

  /* Controlla che host sia contenuto nella linea */
  if((s2 = strstr(s1, host)) == NULL){
    free(s1);
    return 0;
  }

  /* Controlla che ci siano due token */
  if((s2 = strtok(s1, " \t")) == NULL || \
     (s3 = strtok(NULL, " \t")) == NULL || \
     strtok(NULL, " \t") != NULL){
    free(s1);
    return 0;
  }

  /* Controlla che il secondo token sia l'host */
  if(strcasecmp(s3, host) != 0){
    free(s1);
    return 0;
  }
  
  /* Controlla che il primo token sia un indirizzo IP. */
  if(((i = atoi(s3 = strtok(s2, "."))) > 255 || i < 0) || \
     ((i = atoi(s3 = strtok(NULL, "."))) > 255 || i < 0) || \
     ((i = atoi(s3 = strtok(NULL, "."))) > 255 || i < 0) || \
     ((i = atoi(s3 = strtok(NULL, "."))) > 255 || i < 0)){
    free(s1);
    return 0;
  }
  
  /* Match! */
  free(s1);
  return 1;
    
}

/* update_hosts trova la linea host in /etc/hosts e cambia il relativo
   indirizzo. Se addr e` NULL la linea viene eliminata. */
void update_hosts(char* host, char* addr){
  char buf[BUFSIZ+1], *name = HOSTS_PATH ".asp";
  FILE *fp1, *fp2;
  int c, m;

  if((fp1 = fopen(HOSTS_PATH, "r+")) == NULL) asp_perror("fopen");

  /* CPhipps 2000/02/17 - Use temporary file in /etc, so we can 
   * rename(2) it to /etc/hosts cleanly once we're done.
   * I'm assuming there will only ever be one copy of asp running, if 
   * that's not true than this open should be O_EXCL and we need stale 
   * temp file handling too. But I doubt it's needed. */
  if((fp2 = fopen(name, "w")) == NULL) asp_perror("fopen");

  do{ 
    fgets(buf, BUFSIZ, fp1);
    if((m = match(buf, host))) break;
    fputs(buf, fp2);
  }while(!feof(fp1));

  if(m){
    if(addr != NULL) fprintf(fp2, "%s\t%s\n", addr, host);
    while((c = fgetc(fp1)) != EOF)
      fputc(c, fp2);
    /* CPhipps 2000/02/17 - quoting from the libc docs:

 One useful feature of `rename' is that the meaning of the name
 NEWNAME changes "atomically" from any previously existing file by
 that name to its new meaning (the file that was called OLDNAME).
 There is no instant at which NEWNAME is nonexistent "in between"
 the old meaning and the new meaning.  If there is a system crash
 during the operation, it is possible for both names to still
 exist; but NEWNAME will always be intact if it exists at all.

     * /etc/hosts is an important system file.. we want it 
     * to always exist, so we use this feature */
                                   
    if(rename(name, HOSTS_PATH) == -1) asp_perror("rename");
  }else{
    if(addr != NULL){
      if(strchr(buf, '\n')) fprintf(fp1, "%s\t%s\n", addr, host);
      else fprintf(fp1, "\n%s\t%s\n", addr, host);
	 }
    if(remove(name) == -1) asp_perror("remove");
  }

  fclose(fp1);
  fclose(fp2);
}

void about(){
  printf("Copyright (C) 1996-2001 Stenio Brunetta\n" \
	  "%s\n", rcsid);
  exit(EXIT_SUCCESS);
}

void use(void){
  fprintf(stderr, "Usage: %s [-uv] [-f | [-d dir] [-e ext]] [-a addresses] " \
	  "[-r repetitions]\n       [-s send_delay] [-w wait_time] " \
	  "[-p port] hostname\n       %s -V\n", prog_name, prog_name);
  exit(EXIT_FAILURE);
}

void envar_use(char* s){
  fprintf(stderr, "%s variable usage: [-u] [-f | [-d dir] [-e ext]] " \
	  "[-a addresses] [-r repetitions] [-s send_delay] " \
	  "[-w wait_time] [-p port]\n", s);
  exit(EXIT_FAILURE);
}

void parse_envar(int argc, char* argv[]){
  int c, prev_optind, d = 0, e = 0;

  /* we don't want getopt to write error messages */
  opterr = 0;

  /* save the previous value of optind because we have to use getopt
     in parse_cmdline also. On OSF1 getopt expect optind to be 1. */
  prev_optind = optind;

  while((c = getopt(argc, argv, "vfuw:p:d:e:a:r:s:")) != -1){
    switch(c){
    case 'v': /* verbose */
      verbose = !verbose;
      break;
    case 'f': /* force */
      if(d == 1 || e == 1) envar_use(ASPCMDV);
      force = !force;
      break;
    case 'u': /* update */
      update = !update;
      break;
    case 'w': /* wait */
      if(bound_check(optarg, 0, INT_MAX, &recv_delay) != 0){
	fprintf(stderr, "%s: bad wait_time number\n", prog_name);
	exit(EXIT_FAILURE);
      };
      break;
    case 'p': /* port */
      if(bound_check(optarg, 0, 65535, &port) != 0){
	fprintf(stderr, "%s: bad port number\n", prog_name);
	exit(EXIT_FAILURE);
      }
      break;
    case 'd': /* directory */
      if(force == 1) envar_use(ASPCMDV);
      path = optarg;
      d = 1;
      break;
    case 'e': /* extension */
      if(force == 1) envar_use(ASPCMDV);
      ext = optarg;
      e = 1;
      break;
    case 'a': /* number of addresses */
      if(bound_check(optarg, 0, 65535, &addresses) != 0){
        fprintf(stderr, "%s: bad addresses number\n", prog_name);
        exit(EXIT_FAILURE);
      }
      break;
    case 'r': /* repetitions */
      if(bound_check(optarg, 1, 65535, &rip) != 0){
        fprintf(stderr, "%s: bad repetition number\n", prog_name);
        exit(EXIT_FAILURE);
      }
      break;
    case 's': /* send delay */
      if(bound_check(optarg, 0, 65535, &send_delay) != 0){
        fprintf(stderr, "%s: bad send delay number\n", prog_name);
        exit(EXIT_FAILURE);
      }
      break;
    default:
      envar_use(ASPCMDV);
      break;
    }
  }
  if(optind != argc) envar_use(ASPCMDV);
  optind = prev_optind;
} 

void parse_cmdline(int argc, char* argv[]){
  int c, d = 0, e = 0;

  /* we don't want getopt to write error messages */
  opterr = 0;
  while((c = getopt(argc, argv, "vVfuw:p:d:e:a:r:s:")) != -1){
    switch(c){
    case 'V': /* Version */
      if(argc > 2 || strcmp(argv[1], "-V") != 0) use();
      else about();
      break;
    case 'v': /* verbose */
      verbose = !verbose;
      break;
    case 'f': /* force */
      if(!force && (d == 1 || e == 1)) use();
      force = !force;
      break;
    case 'u': /* update */
      update = !update;
      break;
    case 'w': /* wait */
      if(bound_check(optarg, 0, INT_MAX, &recv_delay) != 0){
	fprintf(stderr, "%s: bad wait_time number\n", prog_name);
	exit(EXIT_FAILURE);
      };
      break;
    case 'p': /* port */
      if(bound_check(optarg, 0, 65535, &port) != 0){
	fprintf(stderr, "%s: bad port number\n", prog_name);
	exit(EXIT_FAILURE);
      }
      break;
    case 'd': /* directory */
      if(force == 1) use();
      path = optarg;
      d = 1;
      break;
    case 'e': /* extension */
      if(force == 1) use();
      ext = optarg;
      e = 1;
      break;
    case 'a': /* number of addresses */
      if(bound_check(optarg, 0, 65535, &addresses) != 0){
        fprintf(stderr, "%s: bad addresses number\n", prog_name);
        exit(EXIT_FAILURE);
      }
      break;
    case 'r': /* repetitions */
      if(bound_check(optarg, 1, 65535, &rip) != 0){
        fprintf(stderr, "%s: bad repetition number\n", prog_name);
        exit(EXIT_FAILURE);
      }
      break;
    case 's': /* send delay */
      if(bound_check(optarg, 0, 65535, &send_delay) != 0){
        fprintf(stderr, "%s: bad send delay number\n", prog_name);
        exit(EXIT_FAILURE);
      }
      break;
    default:
      use();
      break;
    }
  }
  if(optind == argc-1){
    bzero(host2find, MSGSIZE);
    /* force zero termination */
    strncpy(host2find, argv[optind], MSGSIZE-1);
  }
  else use();
} 

void redirect(char* host){
  char *in_file, *path1, *ext1, *home, *exp_in_file;
  int path_len, ext_len = 0, home_len;

  if(path == NULL)
    if((home = getenv("HOME")) == NULL || strcmp(home, "") == 0){
      fprintf(stderr, "%s: getenv: No home dir set", prog_name);
      exit(EXIT_FAILURE);
    }else{
      home_len = strlen(home);
      if(*(home+home_len-1) == '/'){
	*(home+home_len-1) = '\0';
	home_len--;
      }

      path = emalloc(home_len+strlen("/.asp/")+1, "redirect");
      /* questo perche` strcat attacca dal primo '\0' che trova */
      *path = '\0';
      strcat(strcat(path, home), "/.asp/");
    }

  path_len = strlen(path);

  if(*(path+path_len-1) != '/'){
    path1 = emalloc(path_len+2, "redirect");
    strcpy(path1, path);
    *(path1+path_len) = '/';
    *(path1+path_len+1) = '\0';
    path = path1;
    path_len++;
  }

  if(ext != NULL){
    ext_len = strlen(ext);
    if(*ext != '.'){
      ext1 = emalloc(ext_len+2, "redirect");
      strcpy(ext1+1, ext);
      *ext1 = '.';
      ext = ext1;
      ext_len++;
    }
  }

  if(ext_len == 0)
    in_file = emalloc(path_len+strlen(host)+1, "redirect");
  else in_file = emalloc(path_len+strlen(host)+ext_len+1, "redirect");

  /* questo perche` strcat attacca dal primo '\0' che trova */
  *in_file = '\0';
  
  if(ext_len == 0)
    strcat(strcat(in_file, path), host);
  else strcat(strcat(strcat(in_file, path), host), ext);

  exp_in_file = tilde_expand(in_file);
  free(in_file);

  if(freopen(exp_in_file, "r", stdin) == NULL){
    fprintf(stderr, "%s: fopen %s: %s\n", prog_name, \
	    exp_in_file, strerror(errno));
    exit(EXIT_FAILURE);
  }

  free(exp_in_file);
}
