Files
Web-API/api.c
T

587 lines
18 KiB
C

#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include "errno.h"
#include <arpa/inet.h>
#include <openssl/ssl.h>
/* compilation : gcc -o api api.c -Wall -lssl -lcrypto */
#define TRUE 1
#define FALSE 0
char * certificat;
char * cleprivee;
// comm ssl
long mSockServeur; // 1 socket serveur mere en ecoute, 0 socket serveur fille connectee
int mSocket; // socket creee
int mSecu; // si comm securise (TLS OpenSSL)
unsigned int mIpClient; // ip du client qui se connecte (socket type 1 uniquement)
char mIp[16];
long mDateExpirationCert; // aaaammjjhhmmss
sigset_t mOldBlockSig,mBlockSig; // gestion des signaux sur socket serveur en ecoute
SSL_CTX * mSslCtx; /* contexte SSL */
SSL * mClientSsl; /* etat SSL du client */
// process
int mServeur; // TRUE si serveur, FALSE si client ou fork fils du serveur
int mFin; // pour arret du process
int mSignal; // indique si fin demandee par un signal (kill ou dmd arret depuis le serveur)
long mNbFils; // > 0 si serveur
long mPid; // pid du process
// -----------------------------------------------------------
void libereSecu()
{
if(!mSecu)
return;
// libere les ressources d'OpenSSL
if(mClientSsl != NULL)
{
SSL_shutdown(mClientSsl);
/* liberation liee a openssl */
SSL_free(mClientSsl);
}
//
if(mSslCtx != NULL)
SSL_CTX_free(mSslCtx);
/* EVP_cleanup(); */ /* ?? */
mSecu = FALSE;
}
void arret(int socket, int forceShutdown)
{
if(mSecu)
libereSecu();
if(0)
shutdown(socket, 2);
close(socket);
}
int ssl_verify(int ok, X509_STORE_CTX * ctx)
{
return 1;
}
int secuSocket()
{
int cr;
if((mClientSsl = SSL_new(mSslCtx)) == NULL)
{
printf("Erreur SSL_new\n");
/* erreur gestion etat SSL */
libereSecu();
return FALSE;
}
if(!SSL_set_fd(mClientSsl, mSocket))
{
printf("Erreur SSL_set_fd\n");
/* erreur assoc socket */
libereSecu();
return FALSE;
}
if((cr = SSL_accept(mClientSsl)) <= 0) /* accept pour la partie "serveur" */
{
int sslcr, cpt = 0;
/* erreur acceptation requetes SSL */
sslcr = SSL_get_error(mClientSsl, cr);
while(cr != 1 && sslcr == 2 && cpt < 3)
{
/* */
cpt++;
cr = SSL_accept(mClientSsl);
sslcr = SSL_get_error(mClientSsl, cr);
}
if(cr != 1)
{
printf("Erreur SSL_accept, code %d error %d\n", sslcr, errno);
libereSecu();
return FALSE;
}
}
return TRUE;
}
int securise(char * certificat, char * privateKey, int socket)
{
mSslCtx = NULL;
mClientSsl = NULL;
if(1)
{
if((mSslCtx = SSL_CTX_new(TLS_server_method())) == NULL)
{
printf("Erreur SSL_CTX_new\n");
libereSecu();
return FALSE;
}
SSL_CTX_set_options(mSslCtx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_COMPRESSION);
/* pas de controle du client ici par certificat (login/mdp ou sso) */
SSL_CTX_set_verify(mSslCtx, SSL_VERIFY_NONE, NULL); // NULL a la place de "ssl_verify" ??
/* pas de reutilisation de session (mode par defaut normalement) */
SSL_CTX_set_session_cache_mode(mSslCtx, SSL_SESS_CACHE_OFF);
/* */
SSL_CTX_set_ecdh_auto(mSslCtx, 1);
/* */
if (SSL_CTX_use_certificate_file(mSslCtx, certificat, SSL_FILETYPE_PEM) <= 0)
{
printf("Erreur SSL_CTX_use_certificate_file\n");
libereSecu();
return FALSE;
}
if (SSL_CTX_use_PrivateKey_file(mSslCtx, privateKey, SSL_FILETYPE_PEM) <= 0)
{
printf("Erreur SSL_CTX_use_PrivateKey_file\n");
libereSecu();
return FALSE;
}
if (!SSL_CTX_check_private_key(mSslCtx))
{
printf("Erreur SSL_CTX_check_private_key\n");
libereSecu();
return FALSE;
}
mSecu = TRUE;
if(socket)
{
return secuSocket();
}
else
{
return TRUE;
}
}
else
{
/* client */
/* option "non bloquant" empeche le bon fonctionnement en securise : remettre bloquant */
int ret, arg = 0;
ioctl(mSocket, FIONBIO, &arg); /* socket bloquant (arg = 0) */
if((mSslCtx = SSL_CTX_new(TLS_client_method())) == NULL)
{
libereSecu();
return FALSE; /* erreur creation contexte SSL */
}
if((mClientSsl = SSL_new(mSslCtx)) == NULL)
{
/* erreur gestion etat SSL */
libereSecu();
return FALSE;
}
SSL_set_fd(mClientSsl, mSocket);
if((ret = SSL_connect(mClientSsl)) != 1) /* connect pour partie "client" */
{
/* erreur connexion a un serveur TLS/SSL */
libereSecu();
return FALSE;
}
return TRUE;
}
}
int creerServeur(long port)
{
struct sockaddr_in addr;
struct linger l_s; /* Structure pour setsockopt */
int sockopt, sock, buf = 64000; /* taille buffer send/rec = 64 ko environ */
int arg = 1; /* pour ioctl */
// cree une socket serveur prete a ecouter
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
/* mise en place de la socket serveur d'ecoute */
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("Erreur creation socket\n");
return FALSE; // erreur
}
ioctl(sock, FIONBIO, &arg); /* socket non bloquant (sys/ioctl.h) */
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(int))) /* pour eliminer une erreur : permet de rouvrir des socket suite a fermeture avec des clients encore connectes */
{
printf("Erreur setsockopt SO_REUSEADDR\n");
/* erreur */
close(sock);
return FALSE;
}
if(setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&buf, sizeof(buf)))
{
printf("Erreur setsockopt SO_RCVBUF\n");
/* erreur */
close(sock);
return FALSE;
}
if(setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&buf, sizeof(buf)))
{
printf("Erreur setsockopt SO_SNDBUF\n");
/* erreur */
close(sock);
return FALSE;
}
// linger n'impacte QUE le close de la socket
l_s.l_onoff = 1;
l_s.l_linger = 0; // les 2 valeurs provoquent un close avec retour immediat, sans traiter les donnees eventuelles en attente d'envoi et envoi RST au client s'il est connecte en TCP
if(setsockopt(sock, SOL_SOCKET, SO_LINGER, (void*)&l_s, sizeof (l_s)))
{
printf("Erreur setsockopt SO_LINGER\n");
/* erreur */
close(sock);
return FALSE;
}
if(bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
printf("Erreur bind, errno %d\n", errno);
close(sock);
return FALSE;
}
else
if(listen(sock, 10) < 0)
{
printf("Erreur listen\n");
close(sock);
return FALSE;
}
else
{
mSocket = sock;
sigemptyset(&mOldBlockSig);
sigfillset(&mBlockSig);
return TRUE; // ok
}
}
long attente(long timeOut)
{
fd_set masque; /* struct sur aix */
struct timeval delai;
long cr;
// attend une activite sur la socket, timeOut en 1/10 de sec.
delai.tv_sec = timeOut / 10; /* x sec */
delai.tv_usec = (timeOut % 10) * 100000; /* y µsec */
FD_ZERO(&masque);
FD_SET(mSocket, &masque);
//
if(mSockServeur)
{
/* pour que le select() fonctionne a chaque fois (a chaque time-out), il faut remettre à jour */
/* les variables "delai" et "masque fd" ; il faut aussi imperativement */
/* bloquer les signaux !! la socket peut etre bloquante (mais il vaut mieux la mettre non bloquante) */
//sigprocmask(SIG_SETMASK, &mBlockSig, &mOldBlockSig);
cr = select(mSocket + 1, &masque, NULL, NULL, &delai);
//sigprocmask(SIG_SETMASK, &mOldBlockSig, NULL);
}
else
{
while((cr=select(mSocket + 1, &masque, NULL, NULL, &delai)) < 0 && errno == EINTR) errno = 0;
if(cr > 0)
cr = FD_ISSET(mSocket, &masque);
}
return cr; // si > 0 : ok pour continuer et lire la socket
}
int lireConnexion(char * data)
{
int nblu = 0, arg = 1;
int flagInProgress = FALSE;
int pas = 2000;
// lire par paquets de longueur fixe et s'arrêter dès que plus rien à lire
// lecture par paquets de <pas> octets
ioctl(mSocket, FIONBIO, &arg); // socket non bloquant (sys/ioctl.h) necessaire quand la longueur n'est pas connue (sinon lire sur la longueur et laisser(?) la socket bloquante)
do
{
//if(!data.ctrl(data.getLen() + pas, TRUE))
// return FALSE; // erreur mem
int lg = strlen(data);
if(lg + pas >= 64000)
; // TODO : taille maxi du buffer atteinte : quid ??
while((nblu = SSL_read(mClientSsl, data + lg, pas)) < 0 && errno == EWOULDBLOCK && (strlen(data) == 0 || flagInProgress)) ;
if(errno == EINPROGRESS) // 115
flagInProgress = TRUE; // indique un envoi en plusieurs parties : ne plus sortir quand getLen # 0 car lecture pas finie !
else if(nblu > 0)
data[lg + nblu] = 0; //data.setLen(data.getLen() + nblu);
else
{
int err = SSL_get_error(mClientSsl, nblu);
if(err == SSL_ERROR_ZERO_RETURN || err == SSL_ERROR_SYSCALL)
{
printf("Fermeture de la connexion par le client\n");
return FALSE;
}
}
//
} while(nblu > 0);
return TRUE;
}
int ecrireConnexion(char * data)
{
int nbecrit = 0;
int retSsl;
do
{
nbecrit = SSL_write(mClientSsl, data, strlen(data));
retSsl = SSL_get_error(mClientSsl, nbecrit);
// pour gerer les envois partiels de SSLwrite
} while(retSsl == SSL_ERROR_WANT_WRITE || retSsl == SSL_ERROR_WANT_READ);
return (nbecrit == strlen(data));
}
void setFin(int fin, int depuisSignal)
{
mFin = fin;
mSignal = depuisSignal;
}
void attenteFinFils()
{
if(mNbFils)
{
while(waitpid(-1, NULL, WNOHANG) > 0)
mNbFils--; /* compte tous les fils terminés "en meme temps" (si un fils se termine quand le pere est dans ce handler) */
}
}
void demandeArretFilsEtAttente()
{
/* demander aux fils de s'arrêter */
kill(0, SIGUSR1); // 0 = tous les process du groupe : tous les fils
// attente fin fils
while(mNbFils)
{
while(waitpid(-1, NULL, WNOHANG) > 0)
mNbFils--;
}
}
void verifierFinFils()
{
waitpid(-1, NULL, WNOHANG);
}
void trt_exit()
{
// comment sortir ?
demandeArretFilsEtAttente();
arret(mSocket, TRUE);
exit(-2);
}
void trt_sig(int sig)
{
switch(sig)
{
case SIGCHLD:
if(mServeur)
{
//un process fils se termine (forcement recu par le pere/le serveur)
attenteFinFils();
}
else
{
// fin depuis un fils (session client) d'un module/batch : waitpid ?
}
break;
//
case SIGINT:
case SIGHUP:
case SIGQUIT:
case SIGTERM: // (kill pid)
mFin = TRUE;
mSignal = TRUE;
break;
//
case SIGSEGV:
trt_exit();
//
case SIGTTOU:
break; // ignorer si sortie terminal depuis arriere plan
//
case SIGUSR1:
if(! mServeur)
{
mFin = TRUE;
mSignal = TRUE;
} // arret des fils a reception de ce signal
break;
// pour lister les numeros de signaux, taper "kill -l"
}
}
// -----------------------------------------------------------
char data[64000]; // buffer de lecture/ecriture sur la socket securisee
void trt_connexion()
{
//Dynamic req, methode, uri, rep, tmp, fld;
//long meth, cr, nb;
int keepAlive;
printf("trt demande\n");
//
*data = 0;
keepAlive = TRUE;
while(keepAlive)
{
//tmp.let("GET;HEAD;POST");
//// lire les donnees entrantes sur la socket // pbm ici bloquant : sans doute pbm de longueur attendue !? a tester
//printf("debut boucle\n");
if ( ! lireConnexion(data))
break; // fin du client
printf("lu:\n%s\n", data);
//if(strstr(data,"keep-alive") == NULL)
// keepAlive = FALSE;
//
strcpy(data, "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Type:text/html\r\nContent-Length:35\r\n\r\n<html><h2>Hello World !</h2></html>");
printf("Reponse:\n%s\n", data);
ecrireConnexion(data);
*data = 0;
//// faire une attente passive ici (sinon consomme trop de CPU)
//printf("Attente fin boucle\n");
//while(attente(10) <= 0 && ! mFin) ;
//if ( ! lireConnexion(data))
// break;
//if(mFin)
break; // sortie sur signal
}
printf("fin cnx client\n");
}
// ----------------------------------------------------------
int main(int argc, char *argv[])
{
if(argc != 4)
{
printf("Usage : %s <port> <certificat> <cleprivee>\n", argv[0]);
exit(1);
}
/* */
int port = atoi(argv[1]);
certificat = argv[2];
cleprivee = argv[3];
/* */
mServeur = TRUE;
mFin = FALSE;
mSignal = FALSE;
mPid = getpid();
mNbFils = 0;
signal(SIGHUP, trt_sig);
signal(SIGSEGV, trt_sig);
signal(SIGINT, trt_sig); /* ctrl-C */
signal(SIGQUIT, trt_sig);
signal(SIGTERM, trt_sig);
signal(SIGTTOU, trt_sig); /* ecriture terminal arriere plan */
signal(SIGCHLD, trt_sig); /* prevenu quand un process fils se termine */
signal(SIGUSR1, trt_sig); /* sert a demander au fils de s'arrêter suite arrêt du pere */
signal(SIGUSR2, trt_sig); /* sert a avertir un fils qu'une requête asynchrone est terminee */
signal(SIGPIPE, SIG_IGN); /* ignorer ce signal pour eviter une erreur lors du sock.write */
//
mSocket = 0;
mSockServeur = 1;
mSslCtx = NULL;
mClientSsl = NULL;
//
if(1)
{
if((mPid = fork()) != 0)
{
printf("Lancement en tache de fond du process pid %ld\n", mPid);
exit(0); /* sortie du pere */
}
mPid = getpid();
chdir("/"); // permet de ne pas bloquer un filesystem avec ce process
setsid();
for(int i=0;i < FOPEN_MAX; i++) /* OPEN_MAX sous AIX */
close(i);
}
//
printf("Demarrage serveur, port %d pid %d mode %d bits\n", port, getpid(), sizeof(long)==4?32:64);
/* initialisations internes openssl */
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
// verification du contexte securise avant de continuer
if( ! securise(certificat, cleprivee, FALSE))
{
printf("Erreur lors de la mise en place du contexte securise SSL/TLS\n");
exit(4);
}
else
{
libereSecu();
}
if(creerServeur(port))
{
while( ! mFin)
{
if(attente(10) > 0 && ! mFin) // 10 = 1 seconde
{
struct sockaddr_in addr_cnx;
unsigned int size_addr_cnx = sizeof(addr_cnx);
int sock_fils;
if((sock_fils = accept(mSocket, (struct sockaddr *)&addr_cnx, &size_addr_cnx)) > 0)
{
unsigned int ipClient = htonl(addr_cnx.sin_addr.s_addr);
long pid_fils = fork();
if(pid_fils > 0)
{
// dans le pere
mNbFils++;
signal(SIGCHLD, trt_sig); /* SIGCHLD : prevenu quand un process fils se termine */
printf("Connexion depuis ip %d.%d.%d.%d : creation process pid %ld\n", (ipClient >> 24) & 0xFF, (ipClient >> 16) & 0xFF, (ipClient >> 8) & 0xFF, (ipClient & 0xFF), pid_fils);
arret(sock_fils, TRUE);
}
else if(pid_fils == 0)
{
// dans le fils
mServeur = FALSE;
mNbFils = 0;
mPid = getpid();
printf("Connexion entrante pid %ld socket %d\n", mPid, sock_fils);
close(mSocket); // le fils n'a pas besoin de la socket serveur mere
mSocket = sock_fils;
mSockServeur = 0;
int arg = 0;
ioctl(mSocket, FIONBIO, &arg); // mettre bloquant : ok en secu SSL
//
if( ! securise(certificat, cleprivee, TRUE))
{
// erreur : handshake securise SSL/TLS ko avec le client
printf("Erreur de handshake securise SSL/TLS\n");
break;
}
//
trt_connexion();
//
break;
}
else
printf("Erreur de fork() creation fils - demande non traitee\n");
}
verifierFinFils();
}
else
printf("Attente... %d\n", mSocket);
}
// arret socket en cours
arret(mSocket, TRUE);
if(mServeur)
{
demandeArretFilsEtAttente();
printf("Arret du serveur\n");
}
else
printf("Arret du client\n");
}
else
{
printf("Erreur d'initialisation du socket serveur - ARRET\n");
}
return(0);
}