/*
 *  ___  ___ _ ____   _____ _   _ _ __
 * / __|/ _ \ '__\ \ / / _ \ | | | '__|
 * \__ \  __/ |   \ V /  __/ |_| | |
 * |___/\___|_|    \_/ \___|\__,_|_|
 *
 * Auteurs : Aymeric
 *			 Samira
 * Date    : 25/11/2004
 * Fichier : serveur.c
 * Détails : serveur de surnom
 * 			 Pour le lancer il suffit de : ./serveur
 *
 */


#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>

/*
 * Variables statiques globales
 */
#define NB_CLIENT 12
#define TAILLE_SURNOM 50		// d'après la RFC, taille de 50
#define TAILLE_ETUDIANT 50		// d'après la RFC, taille de 50
#define TAILLE_TYPE 3			// d'après la RFC, taille de 3 (ASK,RES,ERR)
#define FICHIER "liste.txt"		// fichier contenant nos "étudiant-surnom"
#define PORT 15307				// port de notre serveur
#define DEBUG 0					// si un 1, on a un affichage qui permet de débugguer
#define DESTHOST "localhost"	// hostname du serveur distant
#define DEBUTPORT 15300			// début de la range des ports des serveurs
#define PORTMAX 15312			// fin de la range des ports des serveurs

#define SIZEBUF 512

/*
 * boolean n'existe pas en C, cette astuce nous permet de l'utiliser
 */
enum bool { false, true };

/*
 * struct trame represente la trame envoyee
 */
struct trame {
	char type[3];
	int source;
	char etudiant[TAILLE_ETUDIANT];
	char surnom[TAILLE_SURNOM];
};

/*
 * Definition des fonctions
 */
int init_serveur(char *, struct sockaddr_in *);
int connexion(char *, int);
ssize_t ecris(int, char *);
ssize_t lis(int, char *);
enum bool lisFichier(char *, char [], char []);
void discute(int, struct sockaddr_in *);


/*-----------------------------------------------------------------------*/
int main( int argc, char *argv[] )
{

    int sock;
    struct sockaddr_in sockaddr;

    if (DEBUG) printf("Initialisation\n");
    sock = init_serveur(argv[0], &sockaddr);
    if (DEBUG) printf("Fin initialisation\n");

    discute(sock,  &sockaddr);

    if (DEBUG) printf("C'est fini\n");

    /*
     * Ca arrete les echanges montant et descendant avec la socket
     */
    shutdown(sock,2);
    /*
     * Ferme la connexion avec la socket
     */
    close(sock);
    exit(0);
}


/*-----------------------------------------------------------------------*/
int init_serveur(char * argv0, struct sockaddr_in * sockaddr)
/*
 * Cree et initialise la socket sur un port aleatoire
 *
 * arguments :
 *	argv0 : le nom du programme lance
 *	sockaddr : vide, utilise par reference
 * retourne :
 *	int identifiant de la socket
 */
{
	int sock;
	int sockaddr_in_length = sizeof(struct sockaddr_in);
	char s[BUFSIZ];

	/*
	 * Ca cree une socket
	 */
	if ( (sock = socket( AF_INET, SOCK_STREAM, 0 )) < 0 )
	{
		sprintf( s, "%s: can't assign fd for socket", argv0 );
		perror(s);
		exit(__LINE__);
	}

	/*
	 * On configure la socket
	 */
	sockaddr->sin_family = AF_INET;
	sockaddr->sin_addr.s_addr = INADDR_ANY;
	sockaddr->sin_port = PORT; // ici le port est aleatoire quand = 0

	/*
	 * On met en place la socket avec les donnees precedentes
	 */
	if ( bind( sock, (struct sockaddr *) sockaddr, sizeof (*sockaddr) ) < 0 )
	{
		sprintf( s, "%s: can't bind socket (socket : %d)", argv0, sock );
		perror(s);
		exit(__LINE__);
	}

	/*
	 * On recupere le port alloue
	 */
	if ( getsockname( sock, (struct sockaddr *) sockaddr, (int*)&sockaddr_in_length ) < 0 )
	{
		sprintf( s, "%s: can't get port number of socket (%d)", argv0, sock );
		perror(s);
		exit(__LINE__);
	}

	printf("opened socket as fd (%d) on port (%d) for stream i/o\n", sock, ntohs(sockaddr->sin_port));

	if (DEBUG)
	{
		printf("struct sockaddr_in {\nsin_family = %d\n sin_addr.s_addr = %d\n sin_port = %d\n} sockaddr;\n"
			, sockaddr->sin_family
			, sockaddr->sin_addr.s_addr
			, ntohs(sockaddr->sin_port)
		);
	}

	/*
	 * On est pret a recevoir des connexions
	 */
	listen( sock, NB_CLIENT );

	return sock;

}

/*-----------------------------------------------------------------------*/
ssize_t ecris( int  soq, char *message)
/*
 * Envoie un mot passe en parametre a la connexion courante (donnee en parametre)
 *
 * arguments :
 *	int soq : identifiant de la socket
 *	char * message : le message a envoyer
 *
 * retourne :
 *	ssize_t le nombre de caractere envoye
 */
{
    ssize_t w;

    /*
     * Envoie le mot a la connexion identifie par soq
     * Si w < 0, alors il y a un probleme
     */
    if ( 0 > (w = write(soq,message,strlen(message))) )
	    printf("Fin premature du client\n");
    printf("--> J'ecris : %s\n",message);

    return w;
}

/*-----------------------------------------------------------------------*/
ssize_t lis(int soq, char *message)
{
 /*
  * Lis un message sur la connexion courante (donnee en parametre)
  *
  * arguments :
  *	int soq : identifiant de la socket
  *	char * message : le message qui est lu
  *
  * retourne :
  *	ssize_t le nombre de caractere lu
 */
  ssize_t w;

  /*
   * lis le mot a la connexion identifie par soq
   * Si w < 0, alors il y a un probleme
   */

  bzero(message,SIZEBUF-1);

  if ( 0 > (w = read(soq,message,SIZEBUF - 1)) )
    printf("Erreur lecture client\n");

  printf("--> Je lis : %s\n",message);

  return w;
}

/*-----------------------------------------------------------------------*/
enum bool lisFichier(char *file, char etudiant[], char surnom[])
/*
 * Lis le fichier passe en parametre et retourne le surnom trouve
 *
 * arguments :
 *	char *file : nom du fichier
 *	char *etudiant : nom cherche
 *
 * retourne :
 *	NULL si rien trouve, le surnom sinon
 */
{
	FILE *fp;
	char cSurnom[TAILLE_SURNOM];
	char cEtudiant[TAILLE_ETUDIANT];

	fp = fopen(file, "r");

	if ( fp == NULL ) return false;

	/*
	 * On parcourt tout le fichier en lecture
	 */
	while(!feof(fp))
	{
		/*
		 * Le fichier est de la forme : etudiant surnom"
		 */
		fscanf(fp,"%s %s",cEtudiant,cSurnom);

		if (DEBUG) printf("Etudiant trouve : %s (%s)\n",cEtudiant,etudiant);
		if (!strcmp(etudiant,cEtudiant))
		{
			/*
			 * On a trouve l'etudiant, on retourne le surnom
			 */
			 strcpy(surnom,cSurnom);

			 printf("Etudiant trouve dans la base: %s --> Surnom : %s\n",etudiant,surnom);

			 fclose(fp);

			 return true;
		}
	}

	strcpy(surnom," ");
	fclose(fp);

	return false;
}

/*-----------------------------------------------------------------------*/
int connexion(char *desthost, int destsource)
{
    /*
     * Cette fonctionne se connecte sur desthost sur le port destsource
     *
     * arguments :
     *	char *desthost : hostname destination
     *	int destsource : port destination
     *
     * retourne :
     *	INT -1 si la connexion est impossible
     */
	int soquette;
	struct sockaddr_in config_soquette;
	struct hostent *info_serveur;

	/*
     * On recupere l'ip du serveur
     */
	if ( (info_serveur = gethostbyname( desthost )) == (struct hostent *)NULL )
	    return(-1);

	/*
     * Creation de la socket
     */
	if ( (soquette = socket( AF_INET, SOCK_STREAM, 0 )) < 0 )
	    return(-1);


	/*
	 * Configuration de la connexion
	 */
	config_soquette.sin_family = AF_INET;
	(void) bcopy( (char *)info_serveur->h_addr,
		      (char *) &config_soquette.sin_addr,
		      info_serveur->h_length );
	config_soquette.sin_port = htons(destsource);

	/*
	 * Connection avec la socket distante
	 */
	if ( connect( soquette, (struct sockaddr *) &config_soquette, sizeof config_soquette ) < 0 )
		return(-1);

	return soquette;
}

/*-----------------------------------------------------------------------*/
void discute(int sock, struct sockaddr_in * sockaddr )
/*
 * Etablit un dialogue avec un client
 *
 * arguments :
 *	int sock : identifiant de la connexion
 *	sockaddr : contient des informations relatives a la socket
 *
 * retourne :
 *	void
 */
{
	int ear;
	int parle;
	struct sockaddr_in caller;
	struct hostent * hcaller;
	char buf[BUFSIZ];
	char surnom[TAILLE_SURNOM];
	char etudiant[TAILLE_ETUDIANT];
	char type[TAILLE_TYPE];
	int source;
	int fromlen = sizeof(struct sockaddr_in);
	pid_t monpid,pidfils;
	int destport;
	int debut;
	char *message;
	message=(char *)calloc(sizeof(char),SIZEBUF);

	/*
	* On fait une boucle infinie car notre serveur doit tout le temps ecouter
	*/
	while(1)
	{
		/*
		* On accepte les connexions.
		* Si quelqu'un se connect, alors ear >= 0
		*/
		if ( (ear = accept( sock, (struct sockaddr *)&caller, &fromlen )) < 0 )
		{
			perror("accept refused");
			exit(__LINE__);
		}

		if (DEBUG)
		{
			printf("struct sockaddr_in {_n sin_family = %d\n sin_addr.s_addr = %s\nsin_port (!!!) = %d\n} caller;\n",
			caller.sin_family,
			inet_ntoa(caller.sin_addr),
			ntohs(caller.sin_port));
		}

		/*
		* On recupere le hostname du serveur
		*/
		gethostname(buf, sizeof(buf));
		hcaller = gethostbyaddr((char *) &(caller.sin_addr),
		sizeof(struct in_addr),AF_INET);

		int sourceport=ntohs(sockaddr->sin_port);

		signal(SIGPIPE,SIG_IGN);

		signal(SIGCLD, SIG_IGN);

		/*
		* On veut que le serveur accepte plusieurs clients, donc on cree un process fils
		* qui va gerer le client
		*/
		switch (pidfils = fork())
		{
			case -1 :
				perror("discute - fork ");
				break;
			case  0 :
				/*
				* Quand pidfils = 0, on entre dans le fils
				*/
				close(sock);
				monpid = getpid();

				/*
				* On lit ce qui arrive
				*/
				if (lis(ear,message) > 0)
				{
					/*
					* Vidage des differentes variables
					*/
					bzero(type,sizeof(type));
					bzero(etudiant,sizeof(etudiant));
					bzero(surnom,sizeof(surnom));

					/*
					* On répartit notre message dans les diverses variables
					*/
					sscanf(message,"%s\t%d\t%s\%s",type,&source,etudiant,surnom);

					if (DEBUG) printf("Le fils %d a lu %s\n", monpid,etudiant);
					if (source == 0) source=PORT;

					/*
					* Si on recoit un ASK, on regarde si on connait l'etudiant demande
					*/
					if ((!strcmp(type,"ASK")) && (lisFichier(FICHIER,etudiant,surnom) == true))
					{
						if (DEBUG) printf("J'ai trouve le surnom %s pour %s\n",surnom,etudiant);

						/*
						* On connait cet etudiant, donc on affiche le surnom
						* On construit donc le message pour envoyer la reponse
						*/
						sprintf(message,"RES\t%d\t%s\t%s",source,etudiant,surnom);
						if (ecris(ear,message) > 0) ;
					}
					else
					{
						/* si on a une demande mais pas de reponse a donner on demande au voisin */
						if (!strcmp(type,"ASK"))
						{
							/*
							* On demande au voisin.
							* On parcourt tous les ports définis entre DEBUTPORT
							* et PORTMAX jusqu'a ce qu'on puisse se connecter a
							* un serveur
							*/
							if (PORT==PORTMAX) debut=DEBUTPORT;
							else debut=PORT+1;
							destport=debut;

							do
							{
								if (DEBUG) printf("Source = %d et destport = %d\n",source,destport);

								if (destport == source)
								{
									/*
									* Si le destport == source, cela veut dire
									* qu'on a fait notre boucle, donc on retourne
									* ERR
									*/
									if (DEBUG) printf("Rien trouve-Fin de boucle je renvoie une erreur\n");
									sprintf(message,"ERR\t%d\t%s\t%s",source,etudiant,surnom);
									ecris(ear,message);
									parle=-1;
									break;
								}
								parle=connexion(DESTHOST,destport);
								if (parle == -1)
								{
									destport++;
									if (destport == PORTMAX+1) destport=DEBUTPORT;
								}
							} while (parle == -1);


							if (DEBUG) printf("L'etudiant etait : %s mais non present (%s)\nJe demande a %d\n",etudiant,surnom,destport);

							/*
							* On se connecte au voisin et on lui envoie la requete
							*/
							if (parle != -1)
							{
								/*
								* On forme le message a envoyer
								*/
								printf("Etudiant (%s) demande par %d non trouve, donc je transmets a %d\n",etudiant,source,destport);
								sprintf(message,"ASK\t%d\t%s\t%s",source,etudiant,surnom);

								/* On envoie notre message */
								if (ecris(parle,message) > 0) ;

								/* On attend une reponse */
								if (lis(parle,message) > 0)
								{
									/* on transmet la reponse */
									printf("Le serveur voisin a trouve le surnom %s pour %s\n",surnom,etudiant);

									if (ecris(ear,message) > 0) ;
								}

								/* on ferme la connexion */
								if (DEBUG) printf("Je ferme la socket 'parle'\n");
								shutdown(parle, 2);
								close(parle);
							}
						}
						else
						{
							/* on passe simplement la trame au suivant */
							sprintf(message,"%s\t%d\t%s\t%s",type,source,etudiant,surnom);

							if (DEBUG) printf("On me parle du type %s\n",type);
							if (ecris(ear,message) > 0) ;
						}
					}
				}
				if (DEBUG) printf("Je ferme la socket 'ear'\n");
				shutdown(ear, 2);
				close(ear);
				exit(EXIT_SUCCESS);
			default :
				/*
				* On ferme la connexion car plus utilisee
				*/
				if (DEBUG) printf("On ferme la socket 'ear'\n");
				close(ear);
		}
	}
}
