GuinnessServer/guinnessd.c
2020-04-20 23:30:44 +02:00

587 lines
17 KiB
C

/*
* guinnessd
* architecture clients/serveur guinness
* Thomas Nemeth -- le 15 juin 2001
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <strings.h>
#include <locale.h>
#include "defines.h"
#include "xmem.h"
#include "tools.h"
#include "printlog.h"
#include "lists.h"
#include "broadcast.h"
#include "drinks.h"
#include "clients.h"
#include "guinnessd.h"
#include "commands.h"
#include "config.h"
/* Config specifique serveur */
char *adr_ip = NULL;
int port = 0;
int online = TRUE;
char *fichierlog = NULL;
char *fichiercfg = NULL;
char *chemin = NULL;
char *admin_passwd = NULL;
FILE *logfile = NULL;
FILE *outerr = NULL;
Elt *clients_list = NULL;
Elt *drinks_list = NULL;
/* Config specifique connexion des clients */
char *pseudo = NULL;
char *drink = NULL;
char *logout = NULL;
#ifdef SunOS
char *crypt(const char *key, const char *salt);
#else
extern char *crypt __P ((__const char *__key, __const char *__salt));
#endif
void install_handler ();
/*
* Gestionnaire de signal SIGPIPE, SIGTERM, SIGQUIT et SIGINT
*
*/
void handler_signaux (int sig) {
switch (sig) {
case SIGPIPE:
printlog (LOG_NOTIFY, "Signal SIGPIPE recu...\n");
install_handler ();
break;
case SIGTERM:
case SIGQUIT:
case SIGINT:
online = FALSE;
printlog (LOG_NOTIFY, "Signal de terminaison recu...\n");
break;
default:
printlog (LOG_NOTIFY, "Signal recu...\n");
}
}
/*
* Lecture du fichier de config : recuperation des valeurs
*
*/
char *get_string_from_token (char *line) {
char *result = line;
char *tmp;
int keyval = 0;
while ( (keyval != 1) && (result [0] != 0) ) {
if (result [0] == '=') keyval = 1;
if (keyval == 0) result++;
}
tmp = result++;
while (tmp [0] != 0) {
if (tmp [0] == '\n') tmp [0] = 0;
tmp++;
}
return result;
}
/*
* Lecture du fichier de config
*
*/
void load_config () {
FILE *file;
char tmpstr[MAXSTRLEN + 1];
char *value;
/* Ouverture du fichier */
if ( (file = fopen (fichiercfg?fichiercfg:CONFIG_FILE, "r") ) != NULL) {
/* Lecture du fichier */
while (! feof (file) ) {
memset (tmpstr, 0, MAXSTRLEN + 1);
fgets (tmpstr, (int) MAXSTRLEN, file);
value = get_string_from_token (tmpstr);
if ( (strncmp (tmpstr, "port=", 5) == 0) && (port == 0) )
port = atoi (value);
if ( (strncmp (tmpstr, "passwd=", 7) == 0) &&
(admin_passwd == NULL) )
admin_passwd = xstrdup (crypt (value, "Gs") + 2);
if ( (strncmp (tmpstr, "rep=", 4) == 0) && (chemin == NULL) )
chemin = xstrdup (value);
if ( (strncmp (tmpstr, "pseudo=", 7) == 0) && (pseudo == NULL) )
pseudo = xstrdup (value);
if ( (strncmp (tmpstr, "drink=", 6) == 0) && (drink == NULL) )
drink = xstrdup (value);
if ( (strncmp (tmpstr, "logout=", 7) == 0) && (logout == NULL) )
logout = xstrdup (value);
}
fclose (file);
#ifdef DEBUG
} else {
fprintf (stderr, "Pas de fichier de config (%s).\n", CONFIG_FILE);
#endif
}
}
/*
* Aide
*
*/
void Usage (int help) {
printf ("guinnessd by Thomas Nemeth - v %s (compilation %s : %s)\n",
VERSION, OS_TYPE, COMPIL_DATE);
if (help)
printf ("Usage : guinnessd [-h] [-v] [-b] [-p port] "
"[-s passwd] [-d chemin] [-l fichier] [-f fichier]\n"
" -h : aide sur les parametres\n"
" -v : affiche la version\n"
" -b : detache le serveur du terminal\n"
" -a adresse : specifie l'adresse du serveur\n"
" -p port : specifie le numero du port\n"
" -s passwd : specifie le mot de passe d'aministration\n"
" -d chemin : indique le chemin ou se"
" trouvent les ascii-arts\n"
" -l fichier : fichier de log\n"
" -f fichier : fichier de configuration\n\n");
exit (1);
}
/*
* Traitement des arguments
*
*/
int traite_argv (int argc, char *argv[]) {
int option;
int detach = FALSE;
/* Verification des parametres */
while ((option = getopt (argc, argv, "hvba:p:s:d:l:f:")) != -1) {
switch (option) {
case 'h' :
Usage (TRUE);
break;
case 'v' :
Usage (FALSE);
break;
case 'b' :
detach = TRUE;
break;
case 'a' :
adr_ip = optarg;
break;
case 'p' :
port = atoi (optarg);
break;
case 's' :
admin_passwd = xstrdup (crypt (optarg, "Gs") + 2);
break;
case 'd' :
if (! chemin) {
if (optarg[0] == '/') {
chemin = xstrdup (optarg);
} else {
char *Pwd;
Pwd = xmalloc ((MAXSTRLEN + 1) * sizeof (char));
getcwd (Pwd, MAXSTRLEN);
chemin = xmalloc ( (strlen (Pwd) + strlen (optarg) + 2)
* sizeof (char) );
snprintf (chemin, MAXSTRLEN, "%s/%s",
Pwd, optarg);
}
}
break;
case 'l' :
if (! fichierlog) {
fichierlog = xstrdup (optarg);
}
break;
case 'f' :
if (! fichiercfg) {
fichiercfg = xstrdup (optarg);
}
break;
default:
Usage (TRUE);
break;
}
}
if (optind < argc) {
if (argc - optind == 1)
fprintf (stderr, "%s: option inconnue --", argv[0]);
else
fprintf (stderr, "%s: options inconnues --", argv[0]);
for ( ; optind < argc ; optind++)
fprintf (stderr, " %s", argv[optind]);
fprintf (stderr, "\n");
Usage (TRUE);
}
return detach;
}
/*
* Fonction initiant la connexion.
*
*/
int initiate (int socket_service, userinfos *infos) {
int cont = TRUE;
char userdatas[MAXSTRLEN];
char *datas, *token, *saveptr;
char delim[] = "\n\r";
char *nom_distant = NULL;
char *ip_distant = NULL;
int port_distant = 0;
int port_local = 0;
time_t now;
struct tm *today;
memset (userdatas, 0, MAXSTRLEN);
/* Recuperation des infos sur le connecte */
get_sock_infos (socket_service,
&nom_distant, &ip_distant,
&port_local, &port_distant);
if (ip_distant) {
snprintf (infos->ip, MAXSTRLEN, "%s", ip_distant);
free (ip_distant);
} else sprintf (infos->ip, "0.0.0.0");
if (nom_distant) {
snprintf (infos->host, MAXSTRLEN, "%s", nom_distant);
free (nom_distant);
} else sprintf (infos->host, "none");
infos->port = port_distant;
printlog (LOG_NOTIFY, "Connexion entrante : %s %s\n",
infos->ip, infos->host ? infos->host : "");
printlog (LOG_NOTIFY, "Ports (loc/dist) : %d / %d\n",
port_local, infos->port);
/* PROTOCOLE DE CONNEXION */
/* 1. envoi des commandes du serveur */
if ( (cont = send_servercmds (socket_service)) == FALSE)
return cont;
/* 2. attente des donnees du connecte */
if ( (cont = read_infos (socket_service, userdatas)) == FALSE)
return cont;
/* FIN DU PROTOCOLE : Vericifations / ajout dans la liste / affichage */
datas = xstrdup (userdatas);
/* Nom d'utilisateur */
token = strtok_r (datas, delim, &saveptr);
snprintf (infos->nom, MAXSTRLEN, "%s", token ? token : pseudo);
/* Boisson preferee */
token = strtok_r (NULL, delim, &saveptr);
snprintf (infos->prefb, MAXSTRLEN, "%s", token ? token : drink);
/* Message d'au-revoir */
token = strtok_r (NULL, delim, &saveptr);
snprintf (infos->logout, MAXSTRLEN, "%s", token ? token : logout);
/* Date de connexion */
time (&now);
today = localtime (&now);
strftime (infos->cxdate, MAXSTRLEN, "%a %d %b %Y %T", today);
printlog (LOG_NOTIFY, "Utilisateur : [%s]\n", infos->nom);
printlog (LOG_NOTIFY, "Boisson preferee : [%s]\n", infos->prefb);
printlog (LOG_NOTIFY, "Message de logout : [%s]\n", infos->logout);
printlog (LOG_NOTIFY, "Date de connexion : [%s]\n", infos->cxdate);
pthread_mutex_lock (&mutex_clients);
if (exist_elt (clients_list, infos->nom)) {
char nick_ok[MAXSTRLEN+1];
snprintf (nick_ok, MAXSTRLEN, "@%s", infos->nom);
snprintf (infos->nom, MAXSTRLEN, "%s", nick_ok);
send_infos (socket_service, "@Pseudo deja utilise !\n");
printlog (LOG_NOTIFY, "Pseudo deje utilise => %s\n", infos->nom);
pthread_mutex_unlock (&mutex_clients);
return FALSE;
}
send_infos (socket_service, "Bienvenue sur le serveur de Guinness.\n");
add_client (infos);
pthread_mutex_unlock (&mutex_clients);
if (! exist_elt (drinks_list, infos->prefb))
send_infos (socket_service,
"Votre boisson preferee n'est pas disponible sur ce"
" serveur !\nVous aurez des Guinness a la place.\n");
/* Passage en mode non bloquant */
fcntl (socket_service, F_SETFL, O_NONBLOCK);
return cont;
}
/*
* Fonction de traitement de la connexion avec les clients
*
*/
void thread_service (int *sock_serv) {
pthread_t tid = pthread_self ();
long old_id = get_broadcastid ();
int socket_service = (int) *sock_serv;
int cont = TRUE;
int nb;
userinfos infos;
char commande[MAXSTRLEN + 1];
pthread_detach (tid);
memset (infos.nom, 0, MAXSTRLEN + 1);
memset (infos.prefb, 0, MAXSTRLEN + 1);
memset (infos.logout, 0, MAXSTRLEN + 1);
infos.admin = FALSE;
infos.cold = FALSE;
cont = initiate (socket_service, &infos);
if (cont) {
snprintf (commande, MAXSTRLEN,
"%s a rejoint le serveur de Guinness.\n", infos.nom);
broadcast (MESSAGE, NULL, commande);
}
#ifdef DEBUG
printlog (LOG_NOTIFY, "Pret a recevoir des commandes.\n");
#endif
while (cont == TRUE) {
memset (commande, 0, MAXSTRLEN + 1);
/* Lecture des caracteres recus */
do {
sleep (1);
nb = read (socket_service, commande, MAXSTRLEN);
if ( (nb == -1) || (nb == 0) ) {
if ( (errno == EAGAIN) && (nb == -1) ) continue;
if ( (errno == EPIPE) || (nb == 0) ) {
cont = FALSE;
} else {
printlog (LOG_ERROR, "Erreur de connexion reception !\n");
}
}
} while ( (cont != FALSE) &&
(nb < 0) &&
(new_message (old_id) != TRUE) );
#ifdef DEBUG
printlog (LOG_NOTIFY, "Commande recue.\n");
#endif
if (cont == TRUE) {
/* Traitement de la commande */
if (nb > 0)
cont = reply (socket_service, commande, &infos);
/* Broadcast */
if (new_message (old_id) == TRUE) {
old_id = get_broadcastid ();
cont = send_broadcast (socket_service, &infos);
#ifdef DEBUG
printlog (LOG_ERROR, "Emission broadcast pour %s (id = %ld)\n",
infos.nom, old_id);
#endif
}
}
}
printlog (LOG_NOTIFY, "Bye bye %s...\n", infos.nom);
if (infos.nom[0] != '@') {
pthread_mutex_lock (&mutex_clients);
remove_client (&infos);
pthread_mutex_unlock (&mutex_clients);
broadcast (QUITTER, infos.nom, infos.logout);
}
close (socket_service);
}
/*
* Installation du gestionnaire de signal SIGPIPE et autres...
*
*/
void install_handler () {
signal (SIGPIPE, handler_signaux);
signal (SIGTERM, handler_signaux);
signal (SIGQUIT, handler_signaux);
signal (SIGINT, handler_signaux);
}
/*
* Initialisation generale
*
*/
void guinnessd_init (int argc, char *argv[]) {
pthread_mutexattr_t mutex_attr;
char *cptr;
setlocale (LC_ALL, "");
install_handler ();
/* Valeurs par defaut */
logfile = stdout;
outerr = stderr;
/* est-ce bien la place pour initialiser des trucs avant le parsing de
la ligne de commande ? Eg: la variable d'environnement DRINKS_DIR
*/
if (NULL!=(cptr=getenv("DRINKS_DIR"))) {
chemin = xstrdup(cptr);
}
/* Traitement des parametres */
if (traite_argv (argc, argv) == TRUE) {
switch (fork()) {
case -1: /* erreur */
perror ("fork()");
exit (-1);
case 0: /* le fils */
setsid ();
break;
default: /* le pere */
exit (0);
}
}
/* Lecture du fichier de configuration */
load_config ();
/* Initialisation des semaphores */
pthread_mutexattr_init (&mutex_attr);
pthread_mutex_init (&mutex_broadcast, &mutex_attr);
pthread_mutex_init (&mutex_clients, &mutex_attr);
/* Affectation des parametres */
if (IS_NOT_GOOD (pseudo)) SET_STRING (pseudo, DEFAULT_PSEUDO);
if (IS_NOT_GOOD (drink)) SET_STRING (drink, DEFAULT_DRINK);
if (IS_NOT_GOOD (logout)) SET_STRING (logout, DEFAULT_LOGOUT);
if (port == 0) port = DEFAULT_SERVER_PORT;
if (fichierlog) {
if ((logfile = fopen (fichierlog, "a"))) {
outerr = logfile;
} else {
fprintf (stderr, "Impossible d'ouvrir le fichier %s : %s\n",
fichierlog, strerror(errno));
}
}
/* Option pour le buffer de logs */
setvbuf (logfile, NULL, _IOLBF, 0);
/* Creation de la liste de boissons par defaut*/
add_elt (&drinks_list, "guinness");
if (! chemin) chemin = xstrdup (DRINKS_DIR);
drinks_list_files (chemin);
drinks_display_list ();
}
/*
* Fonction principale
*
*/
int main (int argc, char *argv[]) {
int socket_ecoute;
int socket_service;
socklen_t lg_adresse = sizeof (struct sockaddr_in);
struct sockaddr_in adresse;
pthread_t pthread_id;
guinnessd_init (argc, argv);
/* Installation de l'ecoute du serveur */
if ( (socket_ecoute = install_server (port, adr_ip, NULL)) == -1) {
printlog (LOG_ERROR, "Impossible d'installer le service.\n");
return -1;
}
/* Passage en mode non-bloquant */
fcntl (socket_ecoute, F_SETFL, O_NONBLOCK);
/* Boucle d'attente et d'ouverture de connexions */
printlog (LOG_NOTIFY, "Serveur en attente de connexions (port %d)...\n",
port);
while (online == TRUE) {
sleep (1);
/* Attente d'une connexion */
socket_service = accept (socket_ecoute, (struct sockaddr *) &adresse,
&lg_adresse);
/* SIGPIPE */
if ( (socket_service == -1) && (errno == EINTR) ) continue;
/* PAS DE CONNEXION => mode non bloquant pour attendre un ordre de
* terminaison
*/
if ( (socket_service == -1) && (errno == EAGAIN) ) continue;
/* Autre erreur socket */
if (socket_service == -1) {
/* Erreur */
printlog (LOG_ERROR,
"Erreur d'acceptation de socket, errno = %d.\n",
errno);
perror (__FILE__ " accept");
close (socket_ecoute);
return -1;
}
/* Ici connexion acceptee */
printlog (LOG_NOTIFY, "Connexion acceptee...\n");
/* Lancement d'une activite de traitement */
if (pthread_create (&pthread_id, NULL/*pthread_attr_default*/,
(void *) thread_service,
(void *) &socket_service) == -1) {
printlog (LOG_ERROR, "Erreur creation thread de service.\n");
close (socket_service);
}
/* fflush (logfile); */
sleep (2);
}
printlog (LOG_NOTIFY, "Arret du serveur demande.\n");
/* fflush (logfile); */
close (socket_ecoute);
free_list (&clients_list);
free_list (&drinks_list);
pthread_mutex_destroy (&mutex_broadcast);
pthread_mutex_destroy (&mutex_clients);
return 0;
}