/*
 * File:	address.c
 *
 * Author:	Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 * 
 * Contrib.:	Stefan Zehl (sec@42.org)
 *
 * History:	
 * 
 *   1995-08-12  Framstag	initial version
 *   1995-11-07  Framstag	added URL addressing
 *   1995-11-15  Framstag	added sendfile alias file
 *   1995-12-13  Framstag	correct bug when reading alias file
 *   1996-04-30  Framstag	checking elm alias only if configured
 *   1997-01-04  Framstag	renamed from destination.c to address.c
 * 				added check_forward()
 *   1997-01-22  Framstag	added connect-test to generic saft address
 *   1997-02-23  Framstag	modified str_* function names
 *   1997-02-24  Framstag	sprintf() -> snprintf()
 *   1997-03-18  Framstag	better URL parsing
 *   1997-11-22  Framstag	added saft2rfc822()
 * 				better SAFT URL parsing in check_forward()
 *   1997-11-27  Framstag	accept also saft.domain/user pseudo URLs 
 *				(idea by Eumel, thanx!)
 *   1998-01-04  Framstag	fixed bug in saft2rfc822()
 *   1998-01-13  Sec		look for generic saft.domain address
 *   1998-01-17  Framstag	check SAFT-URL for alternative tcp port
 *   1998-01-20  Framstag	check_forward() works now better with sendfiled
 *   1998-01-25  Framstag	better check_forward() 
 *				(accept user name without host)
 *   1998-03-07  Framstag	added finger_saft_port()
 *   1998-03-11  Framstag	aliases is now in $HOME/.sendfile/
 *   1998-07-11  Framstag	aliases may have an entry for CLI arguments
 *   1998-08-26  Framstag	allow port names, too (framstag@bofh:saft)
 *   1998-12-13	 Framstag	fixed port determination bug
 *   1999-08-07	 Framstag	fixed redirect message collecting bug
 * 				"forward address found" is now a Info
 *
 * Various address routines for sendfile.c and sendmsg.c
 *
 * Copyright © 1995-1999 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */

#include "config.h"		/* various #defines */

#include <ctype.h>
#include <stdio.h>
#include <pwd.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netdb.h>
#include <netinet/in.h>

#include "string.h"	/* Extended string functions */
#include "io.h"		/* misc IO routines */
#include "net.h"	/* the network routines */
#include "utf7.h"	/* UTF-7 coding */
#include "message.h"	/* information, warning and error messages */
#include "address.h"	/* address routines */

#if defined(SOLARIS2)
  int gethostname(char *, int);
#endif

#if defined(LINUX)
  int gethostname(char *, size_t);
  int symlink(const char *, const char *);
#endif

/* check an alias file */
int check_alias(char *, char *, char *, char *);

/* test if there is a forward address set */
int check_forward(int, char *, char *, char *);

/* finger user@host and look for user SAFT port */
int finger_saft_port(char *, char *);


extern int 
  client,		/* flag to determine client or server */
  verbose,		/* flag for verbose mode */
  quiet;		/* quiet mode flag */

extern char *prg;	/* name of the game */


/*
 * destination  - get recipient user and host
 *
 * INPUT:  argc		- shell argument count
 *         argv		- the shell arguments
 *
 * OUTPUT: user		- own user login and real name
 *         recipient	- recipient user name
 *         host		- recipient host name
 * 	   aopt		- alias options
 */
void destination(int argc, char **argv,
		 char *user, char *recipient, char *host, char *aopt) {
  char *cp,			/* simple char pointer */
       *at, 			/* @ character in recepient@host */
       *larg,			/* last argument */
       gecos[FLEN],		/* user real name */
       tmp[MAXLEN],		/* tmp string */
       line[MAXLEN],		/* one text line */
       localhost[FLEN], 	/* name of the local host */
       userconfig[MAXLEN],	/* user configuration directory */
       aliasfile[MAXLEN];	/* the alias file */
  FILE *inf;			/* input file */
  struct passwd *pwe;		/* password entry */
  struct stat finfo;		/* file information */
  
  /* get the own user name */
  if ((pwe=getpwuid(getuid())) == NULL)
    message(prg,'F',"could not determine own user name");

  /* translate the real name to UTF-7 and add it */
  iso2utf(gecos,pwe->pw_gecos);
  if ((cp=strchr(gecos,','))) *cp=0;
  snprintf(user,FLEN-1,"%s %s",pwe->pw_name,gecos);
  
  /* check user configuration directory */
  snprintf(MAXS(userconfig),"%s/.sendfile",pwe->pw_dir);
  snprintf(MAXS(tmp),SPOOL"/%s/config",pwe->pw_name);
  if (stat(userconfig,&finfo)<0 && stat(tmp,&finfo)==0)
    symlink(tmp,userconfig);
  
  /* trick: argc == 0, when message reply mode */
  if (argc==0) {
    if (gethostname(localhost,FLEN-1)<0) strcpy(localhost,"localhost");
    snprintf(MAXS(tmp),"%s/msg@%s",userconfig,localhost);
    if ((inf=rfopen(tmp,"r")) && fgetl(line,inf)) {
      if ((cp=strchr(line,'\n'))) *cp=0;
      if ((cp=strchr(line,'@'))) {
	*cp=0;
	snprintf(host,FLEN-1,"%s",cp+1);
      }
      snprintf(recipient,FLEN-1,"%s",line);
    }
    fclose(inf);
    return;
  }
  
  /* trick: argc < 0, when called from quak */
  if (argc<0)
    larg=argv[-argc];
  else
    larg=argv[argc-1];
  
  *host=0;
  
  /* user@host specified? */
  if ((at=strchr(larg,'@'))) {
   
   /* store recipient name and host */
    *recipient=0;
    strncat(recipient,larg,at-larg);
    strcpy(host,at+1);

  /* SAFT-URL specified? */
  } else if (str_neq_nocase(larg,"saft",4) && strchr(larg,'/')) {
    if (str_neq_nocase(larg,"saft://",7)) larg+=7;
    cp=strrchr(larg,'/');
    if (!cp || strchr(larg,'@')) message(prg,'F',"illegal SAFT-URL");
    strcpy(recipient,cp+1);
    *cp=0;
    while ((cp=strchr(larg,'/'))) *cp='.';
    strcpy(host,larg);
/*
    if ((cp=strchr(host,':'))) {
      *cp=0;
      *port=atoi(cp+1);
    }
*/

  /* local user or alias specified */
  } else {
   
    strcpy(recipient,larg);

    /* check the sendfile alias file */
    snprintf(MAXS(aliasfile),"%s/aliases",userconfig);
    if (check_alias(aliasfile,recipient,host,aopt)<0) {
     
#ifdef RESPECT_MAIL_ALIASES
      /* check the elm alias file */
      snprintf(MAXS(aliasfile),"%s/.elm/aliases.text",pwe->pw_dir);
      if (check_alias(aliasfile,recipient,host,aopt)<0) {
       
#endif
	/* store local recipient name and local host */
	/* trick: argc <= 0, when called from quak */
	if (argc<=0)
	  strcpy(recipient,argv[-argc]);
	else
	  strcpy(recipient,argv[argc-1]);
	strcpy(host,"127.0.0.1");

#ifdef RESPECT_MAIL_ALIASES
      }
#endif
    }
  }
  /*if (!*port) *port=SAFT;*/
}


/*
 * check_alias  - check an alias file
 *
 * INPUT:  aliasfile	- the alias file
 *         recipient	- recipient alias name
 *
 * OUTPUT: recipient	- recipient user name
 *         host		- recipient host name
 * 	   aopt		- alias options
 */
int check_alias(char *aliasfile, char *recipient, char *host, char *aopt) {
  char *cp, 			/* a character pointer */
       line[MAXLEN],		/* one line of the alias file */
       address[MAXLEN];		/* address from the alias */
  FILE *inf;			/* input file to read */

  /* if there is an alias file, open it (what else? :-) ) */
  inf=rfopen(aliasfile,"r");
  if (inf==NULL) return(-1);

  *address=0;
  if (aopt) *aopt=0;
  
  /* loop over all lines */
  while (fgetl(line,inf)) {
   
    /* trim line */
    if ((cp=strchr(line,'\n'))) *cp=0;
    if ((cp=strchr(line,'#'))) *cp=0;
    str_trim(line);
    if (!*line) continue;

    /* save the address and options */
    cp=strchr(line,' ');
    if (cp) 
      *cp=0;
    else
      continue;
    strcpy(address,cp+1);
    if ((cp=strchr(address,' '))) {
      *cp=0;
      if (aopt) snprintf(aopt,FLEN-1,"%s",cp+1);
    }

    /* is it the correct alias, we are ready */
    if (str_eq(recipient,line)) break;
    
    *address=0;
    if (aopt) *aopt=0;
  }
  
  fclose(inf);

  /* alias found? */
  if (*address) {

    /* convert SAFT to mail address */
    if (str_beq_nocase(address,"saft://")) saft2rfc822(address);
   
    /* store recipient name and host */
    cp=strchr(address,'@');
    if (cp) {
      *cp=0;
      strcpy(host,cp+1);
    } else
      strcpy(host,"0");
      
    strcpy(recipient,address);
    return(0);

  }
  return(-1);
}


/*
 * check_forward  - test if there is a forward address set
 *
 * INPUT:  sockfd	- socket file descriptor
 *         recipient	- recipient user name
 *	   host		- host to connect
 * 	   redirect	- redirect comment 
 *
 * OUTPUT: recipient	- new recipient user name
 *	   host		- new host to connect
 * 	   redirect	- new redirect comment
 *
 * RETURN: 1 if a forward is set, 0 if not, -1 on error
 */
int check_forward(int sockfd, char *recipient, char *host, char *redirect) {
  char
    *cp,*at,		/* simple string pointer */
    *adr,		/* address in forwarding reply string */
    *reply,		/* reply string from server */
    tmp[MAXLEN];	/* temporary string */

  /* get reply from server */
  reply=getreply(sockfd);
  str_trim(reply);
  
  /* forward address set? */
  if (str_beq(reply,"510 ")) {
    
    adr=strrchr(reply,' ')+1;
    
    /* convert SAFT to mail address */
    if (str_beq_nocase(adr,"saft://")) saft2rfc822(adr);

    if (quiet<2) message(prg,'I',"forward address found");
    if (*redirect) {
      strcpy(tmp,redirect);
      snprintf(redirect,MAXLEN-1,"%s\r\nredirected by %s@%s",
	       tmp,recipient,host);
    } else {
      snprintf(redirect,MAXLEN-1,"redirected by %s@%s",recipient,host);
    }

    /* save new recipient and host name */
    if ((at=strchr(adr,'@'))) {
      *at=0;
      strcpy(host,at+1);
    }
    strcpy(recipient,adr);

    return(1);
  }

  /* illegal answer */
  if (!str_beq(reply,"200 ")) {
    snprintf(MAXS(tmp),"server error: %s",reply+4);
    errno=0;
    if (client) message(prg,'F',tmp);
    strcpy(redirect,reply+4);
    if ((cp=strrchr(redirect,'.'))) *cp=0;
    return(-1);
  }

  return(0);
}


/*
 * saft_connect  - look for correct SAFT server and open connection
 *
 * INPUT:  type		- file or message
 *         recipient	- recipient user name
 *	   user		- local user name
 *	   host		- host to connect
 * 	   redirect	- redirect comment
 *
 * OUTPUT: recipient	- new recipient user name
 *	   host		- new host to connect
 * 	   redirect	- redirect comment
 * 
 * RETURN: socket file descriptor; -1 on error
 */
int saft_connect(const char *type, 
		 char *recipient, char *user, char *host, char *redirect) {
  int
    port,		/* tcp port to connect */
    sockfd,     	/* socket file descriptor */
    hopcount;		/* count for forwarding addresses */
  char
    *cp,		/* simple character pointer */
    answer[FLEN],	/* answer string */
    tmp[MAXLEN],	/* temporary string */
    ahost[MAXLEN],	/* alternate host */
    line[MAXLEN]; 	/* one line of text */
  extern int client;	/* flag to determine client or server mode */
  struct servent *sinfo;/* service info */
      
  port=SAFT;
  hopcount=0;
  *answer=0;
  
  /* get port number */
  if ((cp=strchr(host,':'))) {
    cp++;
    if (*cp>'9') {
      if ((sinfo=getservbyname(cp,"tcp"))) port=ntohs(sinfo->s_port);
    } else
      port=atoi(cp);
  }
  
  /* try to connect to the recipient's server */
  for (hopcount=1; hopcount<11; hopcount++) {
     
    /* if the finger-port is specified get real port from there */
    if (port==79 || !port) {
      if ((cp=strchr(host,':'))) *cp=0;
      snprintf(MAXS(tmp),"opening connection to finger://%s/%s",host,recipient);
      if (quiet<2) message(prg,'I',tmp);
      port=finger_saft_port(recipient,host);
      if (port<1) {
	errno=0;
	message(prg,'F',"no SAFT port information");
      }
      snprintf(tmp,FLEN-1,"%s:%d",host,port);
      strcpy(host,tmp);
    }
      
    /* initiate the SAFT-connection to the server */
    snprintf(MAXS(tmp),"opening connection to saft://%s/%s",host,recipient);
    if (quiet<2) message(prg,'I',tmp);
    sockfd=open_connection(host,port);

    if (port==SAFT) {
      
      /* host has no ip-address, but we can try more ... */
      if (sockfd==-3 && str_eq(type,"file")) {
	if (client) {
	  snprintf(MAXS(tmp),"%s has no internet-address",host);
	  if (quiet<2) message(prg,'W',tmp);
	}
    
	/* try generic saft-address for this host/domain */
	if (port==SAFT) {
	  snprintf(MAXS(ahost),"saft.%s",host);
	  if (client) {
	    if(gethostbyname(ahost)){
	      if (!quiet) {
		printf("try sending to %s@%s ? ",recipient,ahost);
		fgetl(answer,stdin);
	      } else
		*answer='y';
	    } else
	      *answer='n';
	  }
	  if (tolower(*answer)!='n' || !client) {
	    strcpy(host,ahost);
	    if (client) {
	      snprintf(MAXS(tmp),"opening connection to %s@%s",recipient,host);
	      if (quiet<2) message(prg,'I',tmp);
	    }
	    sockfd=open_connection(host,port);
	  }
	}
	
      }
  
      /* try user SAFT port on connection failure */
      if (sockfd==-2 && str_eq(type,"file")) {
	if (verbose) 
	  message(prg,'I',"found no system SAFT server, try to finger user");
	port=finger_saft_port(recipient,host);
	if (port>0 && port!=SAFT) {
	  snprintf(MAXS(tmp),"%s has no system SAFT server, "
		   "trying user SAFT server on port %d",host,port);
	  if (quiet<2) message(prg,'W',tmp);
	  sockfd=open_connection(host,port);
	} else
	  port=SAFT;
      }
      
    }
    
    if (sockfd==-1) snprintf(MAXS(tmp),"cannot create a network socket");
    if (sockfd==-2) snprintf(MAXS(tmp),"cannot open connection to %s",host);
    if (sockfd==-3) snprintf(MAXS(tmp),"%s is unknown",host);
    if (sockfd<0) {
      if (client) {
	errno=0;
	message(prg,'F',tmp);
      } else
	return(-1);
    }

    /* no remote server or protocol error? */
    sock_getline(sockfd,line);
    if (!str_beq(line,"220 ") || !strstr(line,"SAFT")) {
      snprintf(MAXS(tmp),"No SAFT server on port %d at %s",port,host);
      if ((cp=strrchr(tmp,':'))) *cp=0;
      if (client) {
	errno=0;
	message(prg,'F',tmp);
      } else {
	strcpy(redirect,tmp);
	return(-1);
      }
    }

    /* send constant header lines */
    snprintf(MAXS(tmp),"FROM %s",user);
    sendheader(sockfd,tmp);
    snprintf(MAXS(tmp),"TO %s",recipient);
    sock_putline(sockfd,tmp);

    /* is there a forward set? */
    if (check_forward(sockfd,recipient,host,redirect)) {
      
      /* close current connection */
      sock_putline(sockfd,"QUIT");
      getreply(sockfd);
      shutdown(sockfd,2);
      continue;
    }

    /* sendfile connection? */
    if (str_eq(type,"file")) {

      if (verbose) message(prg,'I',"testing remote server");
      
      /* test if server can receive files */
      sock_putline(sockfd,"FILE test");
      if (check_forward(sockfd,recipient,host,redirect)) {
      
	/* close current connection */
	sock_putline(sockfd,"QUIT");
	getreply(sockfd);
	shutdown(sockfd,2);
	continue;
      }
      
    }

    /* connection is successfull */
    break;

  }

  if (hopcount>10) {
    strcpy(tmp,"maximum hopcount reached (forward loop?)");
    if (client) {
      errno=0;
      message(prg,'F',tmp);
    } else {
      strcpy(redirect,tmp);
      return(-1);
    }
  }
  
  return(sockfd);
}


/* 
 * finger_saft_port  - finger user@host and look for user SAFT port
 * 
 * INPUT:  user	- user login name
 * 
 * OUTPUT: host	- host to connect
 * 
 * RETURN: user saft port on success, -1 on failure
 */
int finger_saft_port(char *user, char *host) {
  int
    port,		/* user SAFT tcp port */
    sockfd;     	/* socket file descriptor */
  char
    *cp,		/* character pointer */
    line[MAXLEN];	/* one line of text */
  
  port=-1;
  *line=0;
  
  /* connect to the finger port of the remote host */
  sockfd=open_connection(host,79);
  if (sockfd>0) {
    sock_putline(sockfd,user);
    while (sock_getline(sockfd,line)>=0) {
      str_trim(line);
      if (str_beq_nocase(line,"saftport") && (cp=strchr(line,'='))) {
	port=atoi(cp+1);
	break;
      }
    }
    shutdown(sockfd,2);
  }
  
  return(port);
}


/*
 * saft2rfc822  - SAFT URL to RFC822 mail address translation
 * 
 * INPUT:  adr	- SAFT URL
 * 
 * OUTPUT: adr  - SAFT address in RFC822 format
 * 
 * RETURN: 0 on success, -1 on failure
 */
int saft2rfc822(char *adr) {
  char 
    *cp,		/* simple char pointer */
    *user,		/* user name */
    host[MAXLEN];	/* host name */
  
  if (!str_beq_nocase(adr,"saft://")) return(-1);
  strcpy(host,adr+7);
  cp=strchr(host,'/');
  if (!cp) return(-1);
  *cp=0;
  user=cp+1;
  sprintf(adr,"%s@%s",user,host);
  return(0);
}


syntax highlighted by Code2HTML, v. 0.9.1