#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>
#include <sys/time.h> /* pour struct timeval */

/*	Toutes les fonctions necessaires d'affichage
	et gestion de certains signaux	+ les caracteres d'editions	*/
#include <termios.h>

/* Le programme ztalk se divise en deux parties:
	-une partie reseau: il s'agit d'obtenir le port et la machine
	du correspondant par l'intermediaire des Talk_Daemon. Il faut:
		-communiquer par udp
		-etre capable de lire des octets en mode non-bloquant
		-etablir la connexion sans Talk_Daemon ou avec un seul.

	-une partie semi-graphique ou il s'agit d'envoyer les caracteres
	tapes au clavier sur la socket connectees. En meme temps
	que l'affichage des caracteres du correspondant. Pour ce faire,
	il faut :
		-determiner les largeur/hauteur du terminal dynamiquement
		-lire en parallele la socket et le clavier

	Auteur: Benoit Papillault
	Creation: Mai 1995
	Derniere modification: Vendredi 28 Novembre 1997 sur cassis.maisel

	Historique:

	10/05/1997:	Correction pour que Control-G fasse beep()

	12/05/1997:	Amelioration diverses pour debugger (variable debug)
		Ajout de la variable d'environnement ZTALK_DAEMON pour
		indiquer la machine qui remplacera la machine locale.
		Utile dans le cas ou la machine locale n'as pas de talk_daemon

	13/05/1997:	La variable ZTALK_DAEMON permet de faire de 
		faire un talk en ayant un Talk_Daemon uniquement sur
		deux machines: celle du corespondant et ZTALK_DAEMON.

		La machine affichee une fois la connexion etablie est celle
		depuis laquelle la connexion tcp est effectue (le talk, quoi!)

	14/05/1997:	Les bugs: le nom de l'utilisateur ne peut pas comporter
		d'arobas (@). Si le nom de l'utilisateur est "", ztalk repond
		que l'utilisateur n'est pas logue, alors que talk marche. On
		ne peut pas repondre a un talk si le login (ZTALK_NAME)
		ne corespond pas au vrai login. En effet, le
		mec appelant ne peut talker qu'avec le vrai login.

	15/05/1997:	Option Regis Cosnier. Sur sa machine, gethostname
		ne donne pas le nom "reseau" de la machine. On utilise
		la variable d'environnement ZTALK_HOSTNAME a la place.

	18/05/1997:	On peut sauver la conversation dans des fichiers.
	La combinaison de touches est Es+s pour ce que l'autre dit,Esc+w pour
	ce que l'on dit et Esc+r pout envoyer un fichier.


	19/05/1997:	A faire: integration du protocole ntalk. La structure
	et le port utilise sont differents, mais la philosophie est strictement
	identique (cf ptalk.c pour experimenter). De plus, comme le protocole
	udp, on peut faire du broadcast et donc localiser automatiquement 
	un utilisateur sur un reseau donne, pour les 2 protocoles (talk + 
	ntalk) en meme temps.

	14/10/1997:	Lors du scrolling d'une seule unite, on ne reaffiche
	pas toutes les lignes de la fenetre concernee. On utilise les
	sequences vt100 d'insertion et de suppression de ligne pour
	supprimer la ligne du haut (par ex.), inserer une ligne en bas
	et y afficher le texte corespond a la ligne.
	
	26/11/1997:	A l'aide de ping, on peut voire que la machine
	appellee affiche: ICMP Port Unreachable from gateway 192.168.13.52
 	for udp from 192.44.75.144 to 192.168.13.52 port 1234. Ce qui veut
	dire que le Talk_Daemon teste la socket udp qu'on lui envoie. 
	
	Lors d'un second envoie de requetes, le protocole s'emballe. Et
	le point d'interrogation tue ztalk2 sous Linux.

	14/12/1997: Modification pour compiler sous FreeBSD 2.2.5

*/

typedef enum { NONE , ESCAPE , SCROLL , DIRECTION } escape_t;

struct fenetre {
	char	kill;
	char	cerase;
	char	werase;
	int	x0,y0; /* La position d'origine, qui corespond 
			au coin superieur gauche */
	int	x1,y1; /* La position du coin en bas a droite */
	int	x,y; /* La position courante du curseur */
	char	*buffer; /* Contient tous les caracteres a la queu leu leu */
	int	*ta;	/* ta[i] est l'indice du premier caractere de la
			(i+1)eme ligne affichee a l'ecran, la numeroataion
			est basee sur les lignes d'un ecran virtuelle qui
			afficherais de la premiere a la derniere ligne	*/
	int	num; /* Le numero de la ligne dans le tableau precedent
			affichee en premier dans la fenetre visible */
	int	nba;	/* Dimension du tableau precedent */
	int	indice;

/* La position courante dans le texte, sur le caractere qui va etre affiche.  Par exemple: On vient d'entrer "abc", buffer contient "abc" et indice vaut 3, 
i.e. texte[3] est libre et le curseur se situe apres le caractere c */

	int	alloc; /* Le nb d'octets alloues pour la variable buffer */
	escape_t	mode;
};

typedef struct fenetre fenetre;

#define TALK_TTY_SIZE 16
#define TALK_NAME_SIZE 9

struct talk_ctl_response {
	char	type;
	char	answer;
	int	id_num;
	struct	sockaddr_in addr;
};

struct talk_ctl_msg {
	char	type;
	char	l_name[TALK_NAME_SIZE];
	char	r_name[TALK_NAME_SIZE];
	char	pad;	/* permet d'aligner l'entier qui suit sur 4 octets */
	int	id_num;
	int	pid;
	char	r_tty[TALK_TTY_SIZE];
	struct	sockaddr_in addr;
	struct	sockaddr_in ctl_addr;
};

#define NTALK_NAME_SIZE	12
#define NTALK_TTY_SIZE	16

struct ntalk_ctl_msg {
	unsigned char		vers; /* protocol version */
	unsigned char		type; /* request type */
	unsigned char		answer; /* not used */
	unsigned char		pad;
	unsigned long		id_num; /* message id */
	struct sockaddr_in	addr; /* old 4.3 style */
	struct sockaddr_in	ctl_addr; /* old 4.3 style */
	long			pid; /* caller's process id */
	char 			l_name[NTALK_NAME_SIZE]; /* caller's name */
	char 			r_name[NTALK_NAME_SIZE]; /* callee's name */
	char 			r_tty[NTALK_TTY_SIZE]; /* callee's tty name */
};

struct ntalk_ctl_response {
	unsigned char		vers;	/* protocol version */
	unsigned char		type;	/* type of request message */
	unsigned char		answer;	/* response to request message */
	unsigned char		pad;
	unsigned long		id_num;	/* message id */
	struct sockaddr_in	addr; /* adress for establishing conversation */
};

#define NTALK_VERSION	1	/* protocol version */

/* les deux identificateurs qui vont servir lors de l'envoi des
	messages */

/* Thes msg.id's for the invitations on the local and remote machines. These
	are used to delete the invitations */

int local_id = 0;
int remote_id = 0;
struct talk_ctl_msg msg;
int	debug = 1;

#define DELAI_REPETITION	30 /* delai de repetition en secondes
	entre plusieurs envoi du message 'Message from Talk_Daemon...'
	si le corespondant ne repond pas */

#define	PROTOCOLE_TALK	1
#define PROTOCOLE_NTALK	2

int protocole = PROTOCOLE_TALK; /* indique le type de protocole a utiliser grace
	aux deux constantes definies plus hauts */

/*########### ADRESSES DES DEUX MACHINES ################################*/

char *daemon_machine_name = NULL;

struct in_addr my_machine_addr;
struct in_addr his_machine_addr;
struct in_addr daemon_machine_addr; /* le talk daemon qui recevra la
					requete LEAVE_INVITE */

#define PORT_TALK	517
#define PORT_NTALK	518
#define PORT_ZTALK	10517

unsigned short daemon_port;

void get_addrs(char *my_machine_name,
		char * his_machine_name,
		char *daemon_machine_name)
{
	register struct hostent *hp;

	msg.pid = (int)getpid();
	hp = gethostbyname(my_machine_name);
	if (hp == NULL)
	{
		fprintf(stderr,"%s est inconnue!\n",my_machine_name);
		exit(-1);
	}
	memcpy((char *)&my_machine_addr,hp->h_addr,sizeof(struct in_addr));
	if (strcmp(his_machine_name,my_machine_name)==0)
		his_machine_addr = my_machine_addr;
	else
	{
		hp = gethostbyname(his_machine_name);
		if (hp == NULL)
		{
			fprintf(stderr,"%s est inconnue!\n",his_machine_name);
			exit(-1);
		}
		memcpy((char *)&his_machine_addr,hp->h_addr,
			sizeof(struct in_addr));
	}

	if (strcmp(daemon_machine_name,my_machine_name)==0)
		daemon_machine_addr = my_machine_addr;
	else
	{
		hp = gethostbyname(daemon_machine_name);
		if (hp == NULL)
		{
			fprintf(stderr,"%s est inconnue!\n",
				daemon_machine_name);
			exit(-1);
		}
		memcpy((char *)&daemon_machine_addr,hp->h_addr,
			sizeof(struct in_addr));
		if (debug)
			printf("daemon_machine_addr = %s\n",
				inet_ntoa(daemon_machine_addr));
	}

	daemon_port = htons(PORT_TALK);
}

/*############### NOMS ET MACHINES DES DEUX CORRESPONDANT ###############*/

#define HOST_NAME_LENGTH 256

char *his_name = NULL;
char his_machine_name[HOST_NAME_LENGTH];
char *his_tty = NULL;
char *my_real_name = NULL;
char *my_nick_name = NULL;
char my_machine_name[HOST_NAME_LENGTH];
char *my_tty = NULL;

/* On affiche un petit message expliquant comment utiliser ztalk */

void usage()
{
	fprintf(stderr,"usage: ztalk user[@machine] [ttyname]\n");
	fprintf(stderr,"variables d'environnement :\n");
	fprintf(stderr,"ZTALK_NAME\t:votre login\n");
	fprintf(stderr,"ZTALK_HOSTNAME\t:le nom reseau de votre machine\n");
	fprintf(stderr,"ZTALK_DAEMON\t:la machine ou tourne un Talk_Daemon (in.talkd)\n");
	fprintf(stderr,"Aucune des variables d'environement n'est necessaire\n");
	fprintf(stderr,"Une fois connecte, on peut scroller avec fleche haut,bas,gauche,droite\n");
	fprintf(stderr,"On peut sauver ce que l'on tapes avec Esc+w et\n");
	fprintf(stderr,"ce que l'autre tapes avec Esc+s, on peut aussi\n");
	fprintf(stderr,"envoyer un fichier avec Esc+r\n");
}

/* les variables ci-dessus sont initialiser par la fonction suivante 
	qui analyse la ligne de commande */

void get_names(int argc,char *argv[])
{
	struct passwd *pw;
	char *ptr;

	if (argc <2)
	{
		usage();
		exit(-1);
	}
	if ((isatty(0) != 1) || (isatty(1) != 1))
	{
		fprintf(stderr,"L'entree standard doit etre un terminal\n");
		exit(-1);
	}

	pw = getpwuid(getuid());
	if (pw == NULL)
	{
		fprintf(stderr,"Je ne vous connais pas!\n");
		exit(-1);
	}
	my_real_name = strdup(pw->pw_name);

	ptr = getenv("ZTALK_NAME");
	if (ptr == NULL)
		my_nick_name = strdup(my_real_name);
	else
	{
		if (debug)
			printf("ZTALK_NAME=%s\n",ptr);
		my_nick_name = strdup(ptr);
	}

	ptr = getenv("ZTALK_HOSTNAME");
	if (ptr != NULL)
	{
		printf("ZTALK_HOSTNAME=%s\n",ptr);
		strncpy(my_machine_name,ptr,HOST_NAME_LENGTH);
	}
	else
	{
		if (gethostname(my_machine_name,HOST_NAME_LENGTH)!=0)
		{
			perror("gethostname");
			exit(-1);
		}
	}

	if ((ptr = getenv("ZTALK_DAEMON")) == NULL)
		daemon_machine_name = my_machine_name;
	else
	{
		if (debug)
			printf("ZTALK_DAEMON=%s\n",ptr);
		daemon_machine_name = ptr; /* un petit strdup ?? */
	}

	my_tty = ttyname(0);
	ptr = strchr(my_tty,'/');
	if (ptr != NULL)
		my_tty  = ptr+1;

	ptr = strrchr(argv[1],'@');
	if (ptr == NULL)
		ptr = argv[1]+strlen(argv[1]); /* pas bo */

	if (*ptr == 0)
	{
		his_name = argv[1];
		strcpy(his_machine_name,my_machine_name);
	}
	else
	{
		if (*ptr == '@')
		{
			his_name = argv[1];
			strncpy(his_machine_name,ptr+1,HOST_NAME_LENGTH);
		}
		else
		{
			his_name = ptr+1;
			strncpy(his_machine_name,argv[1],HOST_NAME_LENGTH);
		}
		*ptr = 0;
	}
	if (argc>2)
		his_tty = argv[2];
	else
		his_tty = "";
	get_addrs(my_machine_name,his_machine_name,daemon_machine_name);
	msg.id_num = 0;
	strncpy(msg.l_name, my_nick_name, TALK_NAME_SIZE);
	msg.l_name[TALK_NAME_SIZE - 1] = '\0';
	strncpy(msg.r_name, his_name, TALK_NAME_SIZE);
	msg.r_name[TALK_NAME_SIZE - 1] = '\0';
	strncpy(msg.r_tty, his_tty, TALK_TTY_SIZE);
	msg.r_tty[TALK_TTY_SIZE - 1] = '\0';
}

/*################### SOCKETS POUR LA CONNEXION ###########################*/

struct sockaddr_in daemon_addr;
struct sockaddr_in ctl_addr; /* adresse et port de la socket UDP utilisee
	pour dialoguer avec les talk_daemon(s) */
struct sockaddr_in my_addr; /* adresse et port de la socket TCP utilisee
	pour transmettre les caracteres frappes au clavier */

/* ces variables designent dans l'ordre: l'adresse du daemon talkd,
	l'adresse de notre socket udp, puis celle de type tcp */

int sockt;
int ctl_sockt;

/* sockt est la socket tcp, ctl_sockt est la socket udp */

/* ouvre la socket du client pour l'utiliser en mode datagramme, elle
	sera utilisee pour le dialogue avec de daemon talkd. Initialise
	la variable ctl_addr a sa valeur correcte */

void open_ctl()
{
	int length;

	ctl_sockt = socket(AF_INET,SOCK_DGRAM,0);
	if (ctl_sockt <0)
	{
		fprintf(stderr,"Impossible de creer la socket\n");
		exit(-1); /* ne pas oublier que le terminal est sans echo */
	}

	ctl_addr.sin_family = AF_INET;
	ctl_addr.sin_addr = my_machine_addr;
	ctl_addr.sin_port = 0;

	if (bind(ctl_sockt,(struct sockaddr *)&ctl_addr,
		sizeof(struct sockaddr_in)))
	{
		perror("Impossible de nommer la socket");
		exit(-1); /* idem */
	}

	length = sizeof(struct sockaddr_in);

	if (getsockname(ctl_sockt,(struct sockaddr *)&ctl_addr,&length) < 0)
	{
		perror("Impossible d'obtenie le numero de port");
		exit(-1);
	}
}

/* creer la socket qui sera utilisee pour transferer les caracteres
	entre les deux machines */

void open_sockt()
{
	int length;

	sockt = socket(AF_INET,SOCK_STREAM,0);
	if (sockt<0)
	{
		perror("Impossible de creer la socket");
		exit(-1); /* idem */
	}

	my_addr.sin_family = AF_INET;
	my_addr.sin_addr = my_machine_addr;
	my_addr.sin_port = 0;

	if (bind(sockt,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
	{
		perror("Impossible de nommer la socket");
		exit(-1);
	}

	length = sizeof(struct sockaddr_in);

	if (getsockname(sockt,(struct sockaddr *)&my_addr,&length) < 0)
	{
		perror("Impossible d'obtenie le numero de port");
		exit(-1);
	}
}

#define retour_chariot '\n'
#define	line_feed	'\r'
#define espace		' '
#define tabulation	'\t'
const char backspace[] = "\010 \010";
const char caractere_beep = 7;

fenetre	ma_fenetre;
fenetre	sa_fenetre;
fenetre	*fenetre_courante;
int	hauteur;
int	largeur;
struct termios	ancien_tty;

/*######### LES DEUX MESSAGES DES DEUX FENETRES #######################*/

char *string1 = NULL;
char *string2 = NULL;

/* positionne le curseur a la position voulue, x va de 1 a largeur, y va de
	1 a hauteur */

void se_placer_xy(int x,int y)
{
	char	buffer[25];

	sprintf(buffer,"\033[%d;%dH",y,x);
	write(1,buffer,strlen(buffer));
}

/* emet un petit beep */
void beep()
{
	write(1,&caractere_beep,1);
}

/* affiche le premier message en haut de l'ecran (sur la ligne 1),
	avant d'appeler cette
	fonction, daemon_machine_name et my_nick_name doivent etre initialisee,
	ainsi que hauteur et largeur */

void message1()
{
	int	i,nb,len1,len2;

	fenetre_courante = NULL;
	se_placer_xy(1,1);
	len1 = strlen(my_nick_name);
	len2 = strlen(daemon_machine_name);
	nb = (largeur-len1-len2-1)/2;

	for (i=0;i<nb;i++)
		write(1,"-",1);
	write(1,my_nick_name,len1);
	write(1,"@",1);
	write(1,daemon_machine_name,len2);

	for (i= nb+len1+len2+2;i<=largeur;i++)
		write(1,"-",1);
}

/* affiche le second message au milieu de l'ecran (sur la ligne hauteur/2),
	avant d'appeler cette
	fonction, his_machine_name et his_name doivent etre initialisee,
	ainsi que hauteur et largeur */

void message2()
{
	int	i,nb,len1,len2;

	fenetre_courante = NULL;
	se_placer_xy(1,hauteur/2);
	len1 = strlen(his_name);
	len2 = strlen(his_machine_name);
	nb = (largeur-len1-len2-1)/2;

	for (i=0;i<nb;i++)
		write(1,"-",1);
	write(1,his_name,len1);
	write(1,"@",1);
	write(1,his_machine_name,len2);

	for (i= nb+len1+len2+2;i<=largeur;i++)
		write(1,"-",1);
}

/*################ FONCTIONS DE COMMUNICATIONS AVEC TALK DAEMON ###########*/

#define CTL_WAIT	2

/* les valeurs possible du champ type */
#define LEAVE_INVITE	0
#define LOOK_UP		1
#define DELETE		2
#define ANNOUNCE	3

/* les valeurs possibles du champ answer */
#define SUCCESS		0
#define NOT_HERE	1
#define FAILED		2
#define MACHINE_UNKNOWN	3
#define PERMISSION_DENIED	4
#define UNKNOWN_REQUEST	5
#define BADVERSION	6
#define BADADDR		7
#define BADCTLADDR	8

/* envoie un message au daemon et attend une reponse du type correspondant */

#define ESSAI_MAX	5 /* soit 10 secondes (5*CTL_WAIT) */

void ctl_transact(struct in_addr target,struct talk_ctl_msg msg,int type,
	struct talk_ctl_response *response)
{
	int cc;
	fd_set read_mask;
	int nready = 0;
	struct timeval tps;
	struct sockaddr_in exp;
	int len = sizeof(struct sockaddr_in);
	register int	compteur;

	msg.type = type;
	daemon_addr.sin_family = AF_INET;
	daemon_addr.sin_addr = target;
	daemon_addr.sin_port = daemon_port;
	compteur = 0;

	do
	{

/* on envoie notre requete au daemon toutes les deux sec tant que l'on a pas
	recu de reponse */

		do
		{
			cc = sendto(ctl_sockt,(char *)&msg,
				sizeof(struct talk_ctl_msg),0,
				(struct sockaddr *)&daemon_addr,
				sizeof(daemon_addr));
			if (cc != sizeof(struct talk_ctl_msg))
			{
				if (errno == EINTR) continue;
				exit(-2);
			}
			FD_ZERO(&read_mask);
			FD_SET(ctl_sockt,&read_mask);
			
			tps.tv_sec = CTL_WAIT;
			tps.tv_usec = 0;
			
			if ((nready = select(FD_SETSIZE,&read_mask,0,0,&tps))<0)
			{
				if (errno == EINTR) continue;
				perror("ctl_transact:select");
				exit(-2);
			}
			compteur ++;
		}
		while (nready == 0 && compteur <= ESSAI_MAX);

/* la requete est emise 10 fois,ensuite on considere qu'il y a eu echec */

		if (compteur > ESSAI_MAX)
		{
			printf("Pas de reponse de la machine ~ %s\n",
				his_machine_name);
			exit(1);
		}

	/* lit les reponses jusqu'a trouver celle qui correspond au type voulu,
	s'il n'y a plus de reponse , on emet une nouvelle requete */

		do
		{
			cc = recvfrom(ctl_sockt,(char *)response,
				sizeof(struct talk_ctl_response),0,
				(struct sockaddr *)&exp,&len);
			if (cc <0)
			{
				if (errno == EINTR) continue;
				perror("ctl_transact:select");
				exit(-2);
			}

			FD_ZERO(&read_mask);
			FD_SET(ctl_sockt,&read_mask);
			tps.tv_sec = tps.tv_usec = 0;
			nready = select(FD_SETSIZE,&read_mask,0,0,&tps);
		}
		while (nready > 0 && response->type != msg.type);
	}
	while (response->type != type);
}

/*################ LES DIVERSES REQUETES TALK ############################*/

void initialise_message()
{
	/* char type a completer */
	strncpy(msg.l_name,my_nick_name,TALK_NAME_SIZE);
	msg.l_name[TALK_NAME_SIZE-1] = 0;
	strncpy(msg.r_name,his_name,TALK_NAME_SIZE);
	msg.r_name[TALK_NAME_SIZE-1] = 0;
	msg.id_num = 0;
	msg.pid = (int)getpid();
	strncpy(msg.r_tty,his_tty,TALK_TTY_SIZE);
	msg.addr = my_addr;
	msg.ctl_addr = ctl_addr;
}

/* renvoie 1 (true) si le daemon distant connait deja la socket
	de notre correspondant,renvoie 0 (false) sinon */

int look_for_invite(struct talk_ctl_response *response)
{
	printf("J'interroge %s: ",his_machine_name);

	if (debug)
		printf("LOOK_UP -> %s\n",his_machine_name);

	/* dans cette requete, on met le vrai nom de l'utilisateur, 
	puisque si on verifie que qqn nous talke, ce qqn ne peux nous talker
	qu'avec le vrai nom de l'utilisateur */

	strncpy(msg.l_name, my_real_name, TALK_NAME_SIZE);
	msg.l_name[TALK_NAME_SIZE-1] = 0;
	ctl_transact(his_machine_addr,msg,LOOK_UP,response);
	strncpy(msg.l_name, my_nick_name, TALK_NAME_SIZE);
	msg.l_name[TALK_NAME_SIZE-1] = 0;

	if (debug)
		printf("LOOK_UP(%s) returns %d\n",his_machine_name,
			response->answer);

	switch(response->answer)
	{
	case SUCCESS:
		printf("LOOK_UP:%s ecoutait\n",his_name);
		msg.id_num = response->id_num;
		return 1;
		break;

	case NOT_HERE:
		printf("LOOK_UP:%s n'est pas logue\n",his_name);
		return 0;
		break;
		
	default:
		printf("LOOK_UP:%s n'ecoutait pas\n",his_name);
		return 0;
		break;
	}
}

/* Verifie si le daemon distant connait la socket de notre corespondant,
	dans ce cas, une connexion est tente (tcp), en cas d'echec
	ou si le daemon ne connait pas la socket, 0 (false) est renvoye
	1 (true) est renvoye si la socket est connecte */

int check_local()
{
	struct talk_ctl_response response;
	struct hostent *hp;

	msg.ctl_addr = ctl_addr;
	if (!look_for_invite(&response))
		return 0;
	printf("Je me connecte a %s@%s\n",his_name,his_machine_name);
again:
	response.addr.sin_family = AF_INET;
	if (connect(sockt,(struct sockaddr *)&response.addr,
		sizeof(struct sockaddr_in)) != -1)
	{
		hp = gethostbyaddr((char *)&response.addr.sin_addr,
			sizeof(struct in_addr),AF_INET);
		if (hp == NULL)
			strncpy(his_machine_name,
				inet_ntoa(response.addr.sin_addr),
				sizeof(his_machine_name));
		else
			strncpy(his_machine_name,hp->h_name,
				sizeof(his_machine_name));
		return (1);
	}
	if (errno == EINTR) goto again;
	if (errno == ECONNREFUSED)
	{
		if (debug)
			printf("DELETE -> %s\n",daemon_machine_name);
		ctl_transact(daemon_machine_addr,msg,DELETE,&response);
		close(sockt);
		open_sockt();
		return (0);
	}
	exit(-1);
	return 0;
}

void announce_invite()
{
	struct talk_ctl_response response;
	struct sockaddr_in daemon_addr;
	int result;

	printf("J'envoie un message a %s@%s\n",his_name,his_machine_name);

	/* on triche dans le cas ou daemon_machine_name n'est pas 
		my_machine_name, comme le Talk_Daemon utilise le champ
		ctl_addr a la fois pour repondre et pour identifier l'origine
		du talk, on envoie un message dont on n'aura pas la reponse
		d'abord */

	daemon_addr.sin_family = AF_INET;
	daemon_addr.sin_addr = his_machine_addr;
	daemon_addr.sin_port = daemon_port;
	memset(daemon_addr.sin_zero,0,8);

	msg.ctl_addr.sin_family = AF_INET;
	msg.ctl_addr.sin_addr = daemon_machine_addr;
	msg.ctl_addr.sin_port = htons(1234); /* au hasard */
	memset(&msg.ctl_addr.sin_zero,0,8);
	msg.type = ANNOUNCE;

	result = sendto(ctl_sockt,(char *)&msg,
			sizeof(struct talk_ctl_msg),0,
			(struct sockaddr *)&daemon_addr,
			sizeof(daemon_addr));
	if (result != sizeof(msg) && debug)
		perror("sendto:ANNOUNCE");

	msg.ctl_addr = ctl_addr; /* on remet comme c'etait avant */

	if (debug)
		printf("ANNOUNCE -> %s\n",his_machine_name);
	ctl_transact(his_machine_addr,msg,ANNOUNCE,&response);
	remote_id = response.id_num;
	if (response.answer != SUCCESS)
	{
		switch(response.answer)
		{
		case NOT_HERE:/* Your party is not logged on */
			printf("%s n'est pas logue sur %s\n",
				his_name,his_machine_name);
			break;
		case FAILED:
			printf("Echec\n");
			break;
		case MACHINE_UNKNOWN: /* Target machine can not handle
			remote talk */
			printf("%s est inconnu!\n",his_machine_name);
			break;
		case PERMISSION_DENIED:
			printf("%s ne veut pas recevoir de message\n",his_name);
			break;
		case UNKNOWN_REQUEST:
			printf("Requete inconnue de %s\n",his_machine_name);
			break;
		case BADVERSION:
			printf("Votre version n'est pas compatible avec celle de %s\n",his_machine_name);
			break;
		case BADADDR:
			printf("Mauvaise adresse\n");
			break;
		case BADCTLADDR:
			printf("Mauvaise adresse cliente\n");
			break;
		default:
			printf("Erreur inconnue!\n");
			break;
		/* a completer */
		}
		exit(-1);
	}
	if (debug)
		printf("LEAVE_INVITE -> %s\n",daemon_machine_name);
	ctl_transact(daemon_machine_addr,msg,LEAVE_INVITE,&response);
	local_id = response.id_num;
}

void invite_remote()
{
	int new_sockt;
	struct talk_ctl_response response;
	fd_set	ens;
	struct timeval	tps;
	int	n;
	struct sockaddr_in from;
	int fromlen;
	struct hostent *hp;

	if (listen(sockt,5) != 0)
	{
		perror("listen");
		exit(-1);
	}

	msg.addr = my_addr;
	msg.id_num = htonl(-1); /* une valeur impossible */

	tps.tv_usec = 0;
	tps.tv_sec = DELAI_REPETITION;

	for(;;)
	{
		announce_invite();
		msg.id_num = remote_id+1; /* on prepare le prochain message */
		FD_ZERO(&ens);
		FD_SET(sockt,&ens);

		n  = select(sockt+1,&ens,NULL,NULL,&tps);

		if (n == 0 || (n<0 && errno == EINTR))	continue;
		if (n<0)
		{
			perror("invite_remote:select");
			exit(-1);
		}

		fromlen = sizeof(from);
		while ((new_sockt = accept(sockt,(struct sockaddr *)&from,
			&fromlen)) < 0)
		{
			if (errno == EINTR)
				continue;
			else
			{
				perror("invite_remote:accept");
				exit(-1);
			}
		}
		break;
	}
	 /* c'est pas une horreur la ligne for(;;) ?,
		en gros, je veux que la fonction announce_invite soit
		rappelee apres un certain delai si personne ne s'est connecte
		sur sockt (accept) */

	close(sockt);
	sockt = new_sockt;

	/* A quoi servent les deux lignes precedentes ??? */

	msg.id_num = local_id;
	if (debug)
		printf("DELETE -> %s\n",daemon_machine_name);
	ctl_transact(daemon_machine_addr,msg,DELETE,&response);
	msg.id_num = remote_id;
	if (debug)
		printf("DELETE -> %s\n",his_machine_name);
	ctl_transact(his_machine_addr,msg,DELETE,&response);

	hp = gethostbyaddr((char *)&from.sin_addr,
		sizeof(from.sin_addr),AF_INET);
	if (hp == NULL)
		strncpy(his_machine_name,inet_ntoa(from.sin_addr),
			sizeof(his_machine_name));
	else
		strncpy(his_machine_name,hp->h_name,
			sizeof(his_machine_name));
	printf("%s a repondu depuis %s\n",his_name,his_machine_name);
}

/*############## POUR LES ERREURS INTERNES #############################*/

void erreur_interne(const char *string)
{
	perror(string);
	exit(-1);
}

void efface_ecran()
{
	write(1,"\033[1;1H\033[2J",10);
}

/* Efface l'integralite de la ligne ou se situe le curseur physique
	sans modifier la position de celui-ci	*/

void efface_ligne()
{
	write(1,"\033[2K",4);
}

void efface_fin_ligne()
{
	write(1,"\033[0K",4);
}

void supprimer_ligne()
{
	write(1,"\033[M",3);
}

void inserer_ligne()
{
	write(1,"\033[L",3);
}

void print_ta(fenetre *f)
{
/*	int	i;

	for (i=0;i<f->nba;i++)
		fprintf(debug,"ta[%d] = %d\n",i,f->ta[i]);	*/
}

void init_fenetre(fenetre *f,int x0,int y0,int x1,int y1)
{
	f->x = f->x0 = x0;
	f->y = f->y0 = y0;
	f->x1 = x1;
	f->y1 = y1;
	if ((f->buffer = (char *)malloc(1024)) == NULL)
		erreur_interne("malloc");
	f->indice = 0;
	f->alloc = 1024;
	if ((f->ta = (int *)malloc(4)) == NULL)
		erreur_interne("malloc");
	f->ta[0] = 0;
	f->num = 1;
	f->nba = 1;
	f->mode = NONE;
}

void set_edit_chars()
{
	char	buf[3];
	int	cc;
	struct termios	tty;

	tcgetattr(0,&tty);
	ma_fenetre.cerase = tty.c_cc[VERASE];
	ma_fenetre.kill = tty.c_cc[VKILL];
#ifdef VWERASE
	if (tty.c_cc[VWERASE] == (unsigned char) -1)
#endif
		ma_fenetre.werase = '\027';
#ifdef VWERASE
	else
		ma_fenetre.werase = tty.c_cc[VWERASE];
#endif
	buf[0] = ma_fenetre.cerase;
	buf[1] = ma_fenetre.kill;
	buf[2] = ma_fenetre.werase;
	cc = write(sockt,buf,sizeof(buf));
	if (cc != sizeof(buf))
		erreur_interne("Lost the connection");
	cc = read(sockt,buf,sizeof(buf));
	if (cc != sizeof(buf))
		erreur_interne("Lost the connection");
	sa_fenetre.cerase = buf[0];
	sa_fenetre.kill = buf[1];
	sa_fenetre.werase = buf[2];
/*	fprintf(debug,"cerase = %d,kill = %d,werase = %d\n",
		sa_fenetre.cerase,sa_fenetre.kill,sa_fenetre.werase);	*/
}

void quitter()
{
	se_placer_xy(1,hauteur);
	tcsetattr(0,TCSANOW,&ancien_tty);
	exit(0);
}

/* i est l'indice de la ligne dans la fenetre (a partir de 0) */

void afficher_ligne(fenetre *f,int i)
{
	int nb;

	if (f->num+i <= f->nba)
	{
		if (f->num+i == f->nba)
			nb = f->indice - f->ta[f->num+i-1];
		else
		{
			nb = f->ta[f->num+i] - f->ta[f->num+i-1];
			if (f->buffer[f->ta[f->num+i]-1] == retour_chariot)
				nb--;
		}
		write(1,&f->buffer[f->ta[f->num+i-1]],nb);
	}
}

void scrollons(fenetre *f,int dec)
{
	int	i;
	int	nb;

	if ( dec == 0 )
		return;
	if (( f->num+dec < 1 ) || (f->num+dec > f->nba))
		return;
	f->num += dec;
	se_placer_xy(1,f->y0);

	if (dec == 1)
	{
		se_placer_xy(1,f->y0);
		supprimer_ligne();
		se_placer_xy(1,f->y1);
		inserer_ligne();
		afficher_ligne(f,f->y1-f->y0);
	}
	else if (dec == -1)
	{
		se_placer_xy(1,f->y1);
		supprimer_ligne();
		se_placer_xy(1,f->y0);
		inserer_ligne();
		afficher_ligne(f,0);
	}
	else
	for(i=0;i<=f->y1-f->y0;i++)
	{
		se_placer_xy(1,f->y0+i);
		efface_ligne();
		if (f->num+i <= f->nba)
		{
			if (f->num+i == f->nba)
				nb = f->indice - f->ta[f->num+i-1];
			else
			{
				nb = f->ta[f->num+i] - f->ta[f->num+i-1];
				if (f->buffer[f->ta[f->num+i]-1] == retour_chariot)
					nb--;
			}
			write(1,&f->buffer[f->ta[f->num+i-1]],nb);
		}
	}
	fenetre_courante = NULL;
}

void curseur_courant(fenetre *f)
{
	int	dec;

	if (fenetre_courante != f)
	{
		if (f->nba < f->y1-f->y0-1)
			dec = 1-f->num;
		else
			dec = f->y0-f->y1+f->nba-f->num;
		scrollons(f,dec);
		se_placer_xy(f->x,f->y);
		fenetre_courante = f;
	}
}

/* Ajoute un debut de ligne dans le tableau ta avec l'indice courant	*/

void ajoute_debut_ligne(fenetre *f,int i)
{
	int	*pi;

	pi = (int *)realloc(f->ta,4*(f->nba+1));
	if (pi == NULL)
		erreur_interne("realloc");
	else
	{
		f->ta = pi;
		f->ta[f->nba++] = i;
	}
}

/* Ajoute un caractere a l'indice courant quelqu'il soit au buffer 
	en verifiant les allocations memoires	*/

void ajoute_buffer(fenetre *f,char c)
{
	char	*pc;

	f->buffer[f->indice++] = c;
	if (f->indice >= f->alloc)
	{
		pc = (char *)realloc(f->buffer,f->alloc+1024);
		if (pc == NULL)
			erreur_interne("realloc");
		else
		{
			f->buffer = pc;
			f->alloc += 1024;
		}
	}
}

/* Reaffiche la fenetre en tenant compte de ses nouvelles dimensions par 
l'intermediaire de x0,y0 et x1,y1.  Le tableau des indices de debut de chaque 
ligne affichee est recalculee.	*/

void reaffiche_fenetre(fenetre *f)
{
	int	ancien_indice = f->ta[f->num-1];
	int	i = 0;
	int	nb;

	f->nba = 1;
	f->ta[0] = 0;
	f->x = f->x0;
	f->y = f->y0;
	for (i=0;i<f->indice;i++)
		if ( f->buffer[i] == retour_chariot)
		{
			ajoute_debut_ligne(f,i+1);
			f->x = f->x0;
			if (++f->y > f->y1)
				f->y = f->y1;
		}
		else
		{
			if (++f->x > f->x1)
			{
				f->x = f->x0;
				ajoute_debut_ligne(f,i+1);
				if (++f->y > f->y1)
					f->y = f->y1;
			}
		}
	for (i=0;i<f->nba;i++)
		if (f->ta[i] <= ancien_indice)
			f->num = i+1;
	for(i=0;i<=f->y1-f->y0;i++)
	{
		se_placer_xy(1,f->y0+i);
		efface_ligne();
		if (f->num+i <= f->nba)
		{
			if (f->num+i == f->nba)
				nb = f->indice - f->ta[f->num+i-1];
			else
			{
				nb = f->ta[f->num+i] - f->ta[f->num+i-1];
				if (f->buffer[f->ta[f->num+i]-1] == retour_chariot)
					nb--;
			}
	write(1,&f->buffer[f->ta[f->num+i-1]],nb);
		}
	}
	fenetre_courante = NULL;
}

void reaffiche()
{
	fenetre_courante = NULL;
	efface_ecran();
	message1();
	reaffiche_fenetre(&ma_fenetre);
	message2();
	reaffiche_fenetre(&sa_fenetre);
}

/* Avance le curseur d'une position vers la droite, avec retour au debut
	de la ligne an cas de fin de ligne et ajout dans le tableau de
	debut des lignes	*/

void caractere_suivant(fenetre *f)
{
	if (++f->x >f->x1)
	{
		f->x = f->x0;
		ajoute_debut_ligne(f,f->indice);
		if (++f->y > f->y1)
		{
			f->y = f->y1;
			scrollons(f,1);
		}
		else
			se_placer_xy(f->x,f->y);
	}
}


/* on affiche le caractere c dans la fenetre f, on le sauvegarde dans la 
	fenetre si besoin et on renvoit le caractere a envoyer sur la socket
	corespondante, 0 sinon */

int traite_caractere(fenetre *f,char c)
{
	int	i;
	int	nb;
	char	autre;

	curseur_courant(f);
	if (c == retour_chariot || c == line_feed)
	{
	/* comme le contenu du buffer, va etre sauve dans un fichier,
		on aimerait bien que ce soit 1 retour_chariot, om remplace
		donc c par un retour chariot */

		c = retour_chariot;
		f->x = f->x0;
		ajoute_buffer(f,c);
		ajoute_debut_ligne(f,f->indice);
		if (++f->y > f->y1)
		{
			f->y = f->y1;
			scrollons(f,1);
		}
		else
			write(1,&c,1);
		return(c);
	}
	if (c == f->cerase)
	{
		if (f->x == f->x0)
			return (0);
		else
		{
			f->x--;
			write(1,&backspace,3);
			f->indice--;
			return(c);
		}
	}
	if (c == f->werase)
	{
		while ((f->x > f->x0) && (f->buffer[f->indice-1] == espace))
		{
			f->x--;
			f->indice--;
		}
		while ((f->x > f->x0) && (f->buffer[f->indice-1] != espace)) 
		{
			f->indice--;
			f->x--;	
		}
		se_placer_xy(f->x,f->y);
		efface_fin_ligne();
		return(c);
	}
	if (c == f->kill)
	{
		if (f->x != f->x0)
		{
			f->x = f->x0;
			f->indice = f->ta[f->nba-1];
			se_placer_xy(f->x,f->y);
			efface_fin_ligne();
			return(c);
		}
		return (0);
	}
	if (c == '\f')
	{
		reaffiche();
		return (0);
	}
	if (c == tabulation)
	{
		nb = 8 - (f->x-1) % 8;
		for (i=0;i<nb;i++)
		{
			ajoute_buffer(f,espace);
			caractere_suivant(f);
		}
		se_placer_xy(f->x,f->y);
		return(c);
	}
	if (c == caractere_beep)
	{
		beep();
		return c;
	}
	if (c < espace && c!=7)
	{
		ajoute_buffer(f,'^');
		write(1,"^",1);
		caractere_suivant(f);
		autre = c + 64;
		ajoute_buffer(f,autre);
		write(1,&autre,1);	
		caractere_suivant(f);
		return(c);
	}
	write(1,&c,1);
	ajoute_buffer(f,c);
	caractere_suivant(f);
	return(c);
}

/* affiche un message et lit une chaine au clavier, de longueur maximum,
	l'affichage se fait sur les lignes -----titi@tata---------.
	la ligne est ensuite reaffichee grace a message1 ou message2.
	La ligne renvoyee est termine par un zero, sa longueur maximum
	est donc la taille du buffer -1.
 */

#define	MA_FENETRE	1
#define	SA_FENETRE	2

void prompt_string(const char *msg,char *buffer,int len,int f)
{
	int i,n,y;
	char c;

	if (f == MA_FENETRE)
		y = 1;
	else
		y = hauteur/2;
	se_placer_xy(1,y);
	efface_ligne();

	n = strlen(msg);
	write(1,msg,n);

	i = 0; /* l'indice dans buffer ou l'on inserera le prochain caractere */

	for (;;)
	{
		c = getchar(); /* doit retourner des qu'1 caractere
					est disponible */
		if (c == retour_chariot || c == line_feed)
		{
			buffer[i] = 0;
			break;
		}

		if (i<len-1)
		{
			buffer[i++] = c;
			write(1,&c,1); /* on fait l'echo */
		}
		else
			beep();
	}

	/* on affiche le message qui etait la avant nous, on a choisi
	les deux lignes d'"indication" car la fonction qui les reaffichent
	est tres simples */

	if (f == MA_FENETRE)
		message1();
	else
		message2();

	/* on indique que la position du curseur n'est pas l'endroit
		ou le prochain caractere sera insere */
	
	fenetre_courante = NULL;
}

/* sauve le contenu de la fenetre dans un fichier qui est demande a l'ecran,
	fenetre doit valoir MA_FENETRE ou SA_FENETRE */

void sauver_fenetre(int f)
{
	fenetre *la_fenetre;
	char msg[] = "Sauver dans:";
	char fichier[256];
	FILE *fp;
	int n;
	
	prompt_string(msg,fichier,sizeof(fichier),f);

	if (f == MA_FENETRE)
		la_fenetre = &ma_fenetre;
	else
		la_fenetre = &sa_fenetre;

	/* fichier contient le nom du fichier */
	fp = fopen(fichier,"wt");
	if (fp != NULL)
	{
		n = fwrite(la_fenetre->buffer,la_fenetre->indice,1,fp);
		if (n != 1)
		{
			/* y'a une erreur */
		}
		fclose(fp);
	}
}

/* tout ce passe comme si chaque caractere du fichier etait tape par
	l'utilisateur */

void envoie_fichier()
{
	char msg[] = "Envoie de:";
	char buffer[256];
	FILE *fp;
	int i,n;
	char c;

	prompt_string(msg,buffer,sizeof(buffer),MA_FENETRE);

	fp = fopen(buffer,"rt");
	if (fp != NULL)
	{
		while (fgets(buffer,sizeof(buffer),fp)!=NULL)
		{
			n = strlen(buffer);
			for (i=0;i<n;i++)
			{
				c = traite_caractere(&ma_fenetre,buffer[i]);
				if (c != 0)
					write(sockt,&c,1);
			}
		}
		fclose(fp);
	}
}

void traite_mes_caracteres(char *buf,int nb)
{
	int	i;
	char	c;

/* Cette derniere variable nous renseigne sur l'evolution des sequences
	d`echapement, ceci est indispensable pour pouvoir gerer des
	sequences dont les differents caracteres nous arrivent un par un
	a cause du mode non bloquant choisi.  On realisera un PARCOURS,cf
	I104 	*/

	for(i=0;i<nb;i++)
	{
		switch (ma_fenetre.mode)
		{
		case ESCAPE:
			switch (buf[i])
			{
			case '[':
				ma_fenetre.mode = DIRECTION;
				break;
			case 'w': /* sauve ce que j'ai ecrit */
				sauver_fenetre(MA_FENETRE);
				ma_fenetre.mode = NONE;
				break;
			case 's': /* sauve ce que l'autre a ecrit */
				sauver_fenetre(SA_FENETRE);
				ma_fenetre.mode = NONE;
				break;
			case 'r': /* envoie un fichier */
				envoie_fichier();
				ma_fenetre.mode = NONE;
				break;
			default:
				ma_fenetre.mode = NONE;
				break;
			}
			break;
		case DIRECTION:
			ma_fenetre.mode = NONE;
			switch (buf[i])
			{
			case 'A':
				scrollons(&ma_fenetre,-1);
				break;
			case 'B':
				scrollons(&ma_fenetre,1);
				break;
			case 'C':
				scrollons(&sa_fenetre,1);
				break;
			case 'D':
				scrollons(&sa_fenetre,-1);
				break;
			}
			break;
		case SCROLL:
		case NONE:
			if (buf[i] == '\033')
				ma_fenetre.mode = ESCAPE;
			else if (buf[i] == '\f')
				reaffiche();
			else
			{
				c = traite_caractere(&ma_fenetre,buf[i]);
				if (c != 0)
					write(sockt,&c,1);
			}
			break;
		}
	}
}

void traite_ses_caracteres(char *buf,int nb)
{
	int	i;

	for(i=0;i<nb;i++)
	{
		traite_caractere(&sa_fenetre,buf[i]);
	}
}

void calculer_dimension()
{
	struct winsize	size;

	if (ioctl(0,TIOCGWINSZ,&size)<0)
	{
		largeur = 80;
		hauteur = 24;
	}
	else
	{
		largeur = size.ws_col;
		hauteur = size.ws_row;
	}
}
	
void redimensionnement()
{
	signal(SIGWINCH,redimensionnement);
	calculer_dimension();
	ma_fenetre.x0 = 1;
	ma_fenetre.y0 = 2;
	ma_fenetre.x1 = largeur;
	ma_fenetre.y1 = hauteur/2-1;
	sa_fenetre.x0 = 1;
	sa_fenetre.y0 = hauteur/2+1;
	sa_fenetre.x1 = largeur;
	sa_fenetre.y1 = hauteur;
	reaffiche();
}

/* initialise l'ecran , ainsi que les structures qui lui sont associes */

void initialisation()
{
	struct termios	tty;

	calculer_dimension();
	tcgetattr(0,&tty);
	ancien_tty = tty;
	tty.c_lflag &= ~(ECHO | ICANON);
	tty.c_cc[VMIN] = 1;
	tty.c_oflag |= ONLCR; /* obligatoire */
	/* tty.c_oflag &= ~OCRNL; */ /* Pour FreeBSD 2.2.5 */
	tty.c_iflag |= INLCR;
	tty.c_iflag &= ~INLCR;
	tcsetattr(0,TCSANOW,&tty);
	string2 = "Pas encore connecte";
	init_fenetre(&ma_fenetre,1,2,largeur,hauteur/2-1);
	init_fenetre(&sa_fenetre,1,hauteur/2+1,largeur,hauteur);
	signal(SIGWINCH,(void (*)(int))redimensionnement);
	signal(SIGINT,(void (*)(int))quitter);
	reaffiche();
}

void talk()
{
	int nb;
	char buf[BUFSIZ];
	fd_set read_set,read_template;

	message2();

	FD_ZERO(&read_template);
	FD_SET(0,&read_template);
	FD_SET(sockt,&read_template);
	se_placer_xy(ma_fenetre.x0,ma_fenetre.y0);
	for(;;)
	{
		read_set = read_template;

/* Cette instruction permet d'attendre des caracteres sur les deux 
   descripteurs : entree standard et sockt , meme en mode non-bloquant	*/
		nb = select(sockt+1,&read_set,NULL,NULL,NULL);
		if (nb <= 0)
		{
			if (errno == EINTR)
			{
				read_set = read_template;
				continue;
			}
			/* panic, we don't know what happened */
			erreur_interne("Unexpected error from select");
			quitter();
		}
		if (FD_ISSET(sockt,&read_set))
		{ 
			nb = read(sockt, buf, sizeof buf);
			if (nb <= 0)
				quitter();
			traite_ses_caracteres(buf,nb);
		}
		if (FD_ISSET(0,&read_set))
		{
			nb = read(0, buf, nb);
			if (nb <= 0)
				quitter();
			traite_mes_caracteres(buf,nb);
		}
	}
}

int main(int argc,char *argv[])
{
	get_names(argc,argv);
	open_ctl();
	open_sockt();
	if  (!check_local())
		invite_remote();

	initialisation();
	set_edit_chars();
	talk();
	return 0;
}

/* Version de base (idem talk/ntalk, seule la structure change)

1) Envoyer le message LOOK_UP a la machine distante
	Dans cette requete, le daemon cherche s'il a recu une requete
	de type LEAVE_INVITE tq r_name et l_name soit inverse (le tty 
	ne joue pas)
	Reponse positive -> 2)
	Reponse negative -> 3)

2) Tenter un connexion avec la socket obtenue
	Reponse positive -> 7)
	Reponse negative -> 3)

3) remote_id = -1
   Envoyer le message ANNOUNCE a la machine distante
	Reponse positive -> 4)
	Reponse negative -> remote_id = htonl(ntohl(remote_id)+1) puis 3) 
		(ou exit())
	Le champ r_tty est utilise pour localiser l'utilisateur distant, 
	sauf si r_tty[0] = 0;

4) Envoyer le message LEAVE_INVITE a la machine locale
	local_id = response.id_num
	Reponse positive -> 5)
	sinon echec total


5) Envoyer le message DELETE aux deux machines (locale + distante)
	avec local_id, remote_id respectivement. Seul le champ id_num
	est utilise pour effacer la requete !!! (d'apres les sources
	que j'ai, bien sur). Il doit corespondre au champ
	id_num de la reponse a la requete ANNOUNCE ou LEAVE_INVITE 

6) listen() puis accept() sur la socket tcp

7) TALK TALK TALK now....

*/

/* Autre protocole possible ???? */
