#include #include #include #include #include #include #include #include #include #include #include #include "errno.h" #include #include /* compilation : gcc -o api api.c -Wall -lssl -lcrypto */ /* ./apid 50000 cert.pem server.key 0 */ char * flog = NULL; #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 trace(const char * fmt, ...) { va_list args; va_start(args, fmt); if(flog) { if(*flog == '0') return; // si fichier log = "0" : ne rien afficher ni logguer // FILE * f = fopen(flog, "a+"); if(f) { fprintf(f, "%s%d%s", mServeur ? "Serveur " : "Cnx ", getpid(), ": "); vfprintf(f, fmt, args); fclose(f); } else { printf("%s%d%s", mServeur ? "Serveur " : "Cnx ", getpid(), ": "); vprintf(fmt, args); } } else { printf("%s%d%s", mServeur ? "Serveur " : "Cnx ", getpid(), ": "); vprintf(fmt, args); } va_end(args); } // ----------------------------------------------------------- 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) { trace("Erreur SSL_new\n"); /* erreur gestion etat SSL */ libereSecu(); return FALSE; } if(!SSL_set_fd(mClientSsl, mSocket)) { trace("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) { trace("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) { trace("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) { trace("Erreur SSL_CTX_use_certificate_file\n"); libereSecu(); return FALSE; } if (SSL_CTX_use_PrivateKey_file(mSslCtx, privateKey, SSL_FILETYPE_PEM) <= 0) { trace("Erreur SSL_CTX_use_PrivateKey_file\n"); libereSecu(); return FALSE; } if (!SSL_CTX_check_private_key(mSslCtx)) { trace("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) { trace("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 */ { trace("Erreur setsockopt SO_REUSEADDR\n"); /* erreur */ close(sock); return FALSE; } if(setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&buf, sizeof(buf))) { trace("Erreur setsockopt SO_RCVBUF\n"); /* erreur */ close(sock); return FALSE; } if(setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&buf, sizeof(buf))) { trace("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))) { trace("Erreur setsockopt SO_LINGER\n"); /* erreur */ close(sock); return FALSE; } if(bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { trace("Erreur bind, errno %d\n", errno); close(sock); return FALSE; } else if(listen(sock, 10) < 0) { trace("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 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) { trace("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], rep[64000]; // buffer de lecture/ecriture sur la socket securisee void trt_connexion(char * secretfile, char * scriptttyd) { FILE * fp, * fd; // script => /root/create_ttyd_session.sh char redirectfile[] = "/tmp/ttyd.txt"; char redirection[] = " > /tmp/ttyd.txt"; char script[strlen(scriptttyd) + strlen(redirection) + 1]; strcpy(script, scriptttyd); strcat(script, redirection); //Dynamic req, methode, uri, rep, tmp, fld; //long meth, cr, nb; int keepAlive; // *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 //trace("debut boucle\n"); if ( ! lireConnexion(data)) break; // fin du client trace("lu:\n%s\n", data); // *rep = 0; strcpy(rep, "X-data: 1234567890123456789012345678901234567890123456789012345678901234"); if((fd = fopen(secretfile, "r"))) { fread(rep + 8, 64, 1, fd); fclose(fd); } else { // erreur sur le serveur : tout refuser strcpy(data, "HTTP/1.1 401 Unauthorized\r\n\r\n"); ecrireConnexion(data); break; } if(strstr(data, rep) == NULL) { // le client ne vient pas de wapi.php : refuser strcpy(data, "HTTP/1.1 401 Unauthorized\r\n\r\n"); ecrireConnexion(data); break; } // 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

Hello World !

"); remove(redirectfile); system(script); if((fp = fopen(redirectfile, "r")) == NULL) strcpy(data, "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Type:text/html\r\nContent-Length:97\r\n\r\n

Erreur, console de test non disponible, veuillez contacter l'administrateur

"); else { *data = 0; *rep = 0; fseek(fp, 0, SEEK_END); long lg = ftell(fp); fseek(fp, 0, SEEK_SET); fread(rep, lg, 1, fp); rep[lg] = 0; fclose(fp); // sprintf(data, "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Type:text/html\r\nContent-Length:%ld", lg); strcat(data, "\r\n\r\n"); strncat(data, rep, lg); } // trace("Reponse:\n%s\n", data); ecrireConnexion(data); *data = 0; //// faire une attente passive ici (sinon consomme trop de CPU) //trace("Attente fin boucle\n"); while(attente(10) <= 0 && ! mFin) ; //if ( ! lireConnexion(data)) // break; if(mFin) break; // sortie sur signal } trace("fin cnx client\n"); } void writePid(long pid) { char pidfile[] = "/run/apid.pid"; remove(pidfile); FILE * f = fopen(pidfile, "a+"); if(f) { fprintf(f, "%ld\n", pid); fclose(f); } } // ---------------------------------------------------------- int main(int argc, char *argv[]) { if(argc != 6 && argc != 7) { trace("Usage : %s []\n", argv[0]); exit(1); } int demon = (argv[0][strlen(argv[0]) - 1] == 'd'); if(argc == 7) { flog = argv[argc - 1]; if(*flog != '0') remove(flog); } /* */ int port = atoi(argv[1]); certificat = argv[2]; cleprivee = argv[3]; char * secretfile = argv[4]; char * scriptttyd = argv[5]; /* */ 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(demon) { if((mPid = fork()) != 0) { trace("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); writePid(mPid); } // trace("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)) { trace("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 */ trace("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(); trace("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 trace("Erreur de handshake securise SSL/TLS\n"); break; } // trt_connexion(secretfile, scriptttyd); // break; } else trace("Erreur de fork() creation fils - demande non traitee\n"); } verifierFinFils(); } else trace("Attente... %d\n", mSocket); } // arret socket en cours arret(mSocket, TRUE); if(mServeur) { demandeArretFilsEtAttente(); trace("Arret du serveur\n"); } else trace("Arret du client\n"); } else { trace("Erreur d'initialisation du socket serveur - ARRET\n"); } return(0); }