Alessia: Ma che pure te c'hai a'spadalata?Ok, due notizie, una buona e una cattiva. La cattiva è che, esattamente come Enzo/Jeeg del gran Lo chiamavano Jeeg Robot, non c'abbiamo a'spadalata. La buona è che, per proteggere la privacy delle nostre comunicazioni ci abbiamo OpenSSL. Quindi possiamo stare (ragionevolmente) tranquilli.
Enzo/Jeeg: A spada che?
...Ma che pure te c'hai er OpenSSL?... |
Ed ora bando alle ciance: cominciamo con il codice del Server:
#include "../libmyssl/myssl.h" #include <stdio.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> #include <openssl/err.h> int main(int argc, char *argv[]) { // test argomenti if (argc != 2) { // errore args printf("%s: numero argomenti errato\n", argv[0]); printf("uso: %s port [i.e.: %s 8888]\n", argv[0], argv[0]); return EXIT_FAILURE; } // crea un socket int my_socket; if ((my_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { // errore socket() fprintf(stderr, "%s: non posso creare il socket (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // prepara la struttura sockaddr_in per questo server struct sockaddr_in server; // (locale) server socket info memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; // set address family server.sin_addr.s_addr = INADDR_ANY; // set server address per qualunque interfaccia server.sin_port = htons(atoi(argv[1])); // set port number del server // assegnazione di un indirizzo al socket creato if (bind(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { // errore bind() fprintf(stderr, "%s: errore bind (%s)", argv[0], strerror(errno)); close(my_socket); return EXIT_FAILURE; } // start ascolto con una coda di max BACKLOG connessioni if (listen(my_socket, BACKLOG) < 0) { // errore listen() fprintf(stderr, "%s: errore listen (%s)\n", argv[0], strerror(errno)); close(my_socket); return EXIT_FAILURE; } // accetta connessioni da un client entrante printf("%s: attesa connessioni entranti...\n", argv[0]); socklen_t socksize = sizeof(struct sockaddr_in); struct sockaddr_in client; // (remote) client socket info int client_sock; if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) { // errore accept() fprintf(stderr, "%s: errore accept (%s)\n", argv[0], strerror(errno)); close(my_socket); return EXIT_FAILURE; } // chiude il socket non più in uso close(my_socket); // preparazione per la SSL_accept(): crea un openssl context SSL_CTX *ctx; int error; if ((ctx = sslCreateCtx(SSL_SERVER, &error)) == NULL || error < 0) { // errore sslCreateCtx() fprintf(stderr, "%s: openssl errore creando il contesto SSL_CTX\n", argv[0]); ERR_print_errors_fp(stderr); sslClose(NULL, my_socket, ctx, false); return EXIT_FAILURE; } // preparazione per la SSL_accept(): crea la struttura dati corrispondente del openssl context SSL *ssl; if ((ssl = SSL_new(ctx)) == NULL) { // errore SSL_new() fprintf(stderr, "%s: openssl errore creando la struttura SSL\n", argv[0]); ERR_print_errors_fp(stderr); sslClose(ssl, client_sock, ctx, false); return EXIT_FAILURE; } // preparazione per la SSL_accept(): associa la struttura SSL con il socket corrente if (SSL_set_fd(ssl, client_sock) == 0) { // errore SSL_set_fd() fprintf(stderr, "%s: openssl errore SSL_set_fd\n", argv[0]); ERR_print_errors_fp(stderr); sslClose(ssl, client_sock, ctx, false); return EXIT_FAILURE; } // accetta una connessione OpenSSL con SSL_accept() int rc; if ((rc = sslFunc(SSL_accept, ssl)) != 1) { // errore sslAccept() fprintf(stderr, "%s: openssl errore accept (%d)\n", argv[0], SSL_get_error(ssl, rc)); ERR_print_errors_fp(stderr); sslClose(ssl, client_sock, ctx, false); return EXIT_FAILURE; } // loop di ricezione messaggi dal client char client_msg[MYBUFSIZE]; int recv_size; while ((recv_size = sslRead(ssl, client_msg, MYBUFSIZE)) > 0 ) { // send messaggio di ritorno al client printf("%s: ricevuto messaggio dal sock %d: %s\n", argv[0], client_sock, client_msg); char server_msg[MYBUFSIZE]; sprintf(server_msg, "mi hai scritto: %s", client_msg); if (sslWrite(ssl, server_msg, strlen(server_msg)) < 0) { // errore sslWrite() fprintf(stderr, "%s: errore send (%d)\n", argv[0], SSL_get_error(ssl, recv_size)); ERR_print_errors_fp(stderr); sslClose(ssl, client_sock, ctx, true); return EXIT_FAILURE; } // clear buffer memset(client_msg, 0, MYBUFSIZE); } // loop terminato: test motivo if (recv_size < 0) { // errore sslRead() fprintf(stderr, "%s: errore recv (%d)\n", argv[0], SSL_get_error(ssl, recv_size)); ERR_print_errors_fp(stderr); sslClose(ssl, client_sock, ctx, true); return EXIT_FAILURE; } else if (recv_size == 0) { // Ok: il client si è disconnesso printf("%s: client disconnesso\n", argv[0]); } // esco con Ok sslClose(ssl, client_sock, ctx, true); return EXIT_SUCCESS; }Credo che il codice sia abbastanza chiaro e ben commentato, quindi è il caso di soffermarsi solo sulle differenze rispetto al sorgente del TCP Server di riferimento (quello del vecchio post). Le uniche varianti significative sono:
- una fase di accept aggiuntiva per OpenSSL dopo la fase di accept classica. Questa fase include la creazione degli oggetti OpenSSL veri e propri: un contesto (di tipo SSL_CTX*) che la libreria OpenSSL usa per gestire tutti i meccanismi di funzionamento interni, e una struttura dati (di tipo SSL*) che è un po' l'equivalente OpenSSL del descrittore di file (di tipo int) della libreria Berkeley Socket (quello creato con la chiamata a socket() un po' più sopra). Notare che con la chiamata SSL_set_fd() la liberia OpenSSL riesce ad associare il descrittore di file socket (int client_sock) con la struttura dati OpenSSL (SSL* ssl).
- dal punto 1 in avanti, visto che il descrittore di file è stato sostituito da un pointer SSL, le chiamate classiche di Berkeley Socket recv(), send() e close() vengono sostituite da delle misteriose chiamate sslRead(), sslWrite() e sslClose(), che poi tanto misteriose non sono, visti i nomi.
#include "../libmyssl/myssl.h" #include <stdio.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> #include <openssl/err.h> int main(int argc, char *argv[]) { // test argomenti if (argc != 3) { // errore args printf("%s: numero argomenti errato\n", argv[0]); printf("uso: %s host port [i.e.: %s 127.0.0.1 8888]\n", argv[0], argv[0]); return EXIT_FAILURE; } // crea un socket int my_socket; if ((my_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { // errore socket() fprintf(stderr, "%s: non posso creare il socket (%s)\n", argv[0], strerror(errno)); return EXIT_FAILURE; } // prepara la struttura sockaddr_in per il server remoto struct sockaddr_in server; // (remoto) server socket info memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; // set address family server.sin_addr.s_addr = inet_addr(argv[1]); // set server address server.sin_port = htons(atoi(argv[2])); // set port number del server // connessione al server remoto if (connect(my_socket, (struct sockaddr *)&server, sizeof(server)) < 0) { // errore connect() fprintf(stderr, "%s: errore connect (%s)\n", argv[0], strerror(errno)); close(my_socket); return EXIT_FAILURE; } // preparazione per la SSL_connect(): crea un openssl context SSL_CTX *ctx; int error; if ((ctx = sslCreateCtx(SSL_CLIENT, &error)) == NULL || error < 0) { // errore sslCreateCtx() fprintf(stderr, "%s: openssl errore creando il contesto SSL_CTX\n", argv[0]); ERR_print_errors_fp(stderr); sslClose(NULL, my_socket, ctx, false); return EXIT_FAILURE; } // preparazione per la SSL_connect(): crea la struttura dati corrispondente del openssl context SSL *ssl; if ((ssl = SSL_new(ctx)) == NULL) { // errore SSL_new() fprintf(stderr, "%s: openssl errore creando la struttura SSL\n", argv[0]); ERR_print_errors_fp(stderr); sslClose(ssl, my_socket, ctx, false); return EXIT_FAILURE; } // preparazione per la SSL_connect(): associa la struttura SSL con il socket corrente if (SSL_set_fd(ssl, my_socket) == 0) { // errore SSL_set_fd() fprintf(stderr, "%s: openssl errore SSL_set_fd\n", argv[0]); ERR_print_errors_fp(stderr); sslClose(ssl, my_socket, ctx, false); return EXIT_FAILURE; } // avvia una connessione OpenSSL con SSL_connect() int rc; if ((rc = sslFunc(SSL_connect, ssl)) != 1) { // errore sslConnect() fprintf(stderr, "%s: openssl errore connect (%d)\n", argv[0], SSL_get_error(ssl, rc)); ERR_print_errors_fp(stderr); sslClose(ssl, my_socket, ctx, false); return EXIT_FAILURE; } // loop di comunicazione col server remoto for (;;) { // compone messaggio per il server remoto char my_msg[MYBUFSIZE]; printf("Scrivi un messaggio per il Server remoto: "); scanf("%s", my_msg); // send messaggio al server remoto if ((rc = sslWrite(ssl, my_msg, strlen(my_msg))) < 0) { // errore sslWrite() fprintf(stderr, "%s: errore send (%d)\n", argv[0], SSL_get_error(ssl, rc)); ERR_print_errors_fp(stderr); sslClose(ssl, my_socket, ctx, true); return EXIT_FAILURE; } // riceve una risposta dal server remoto memset(my_msg, 0, MYBUFSIZE); if ((rc = sslRead(ssl, my_msg, MYBUFSIZE)) < 0) { // errore sslRead() fprintf(stderr, "%s: errore recv (%d)\n", argv[0], SSL_get_error(ssl, rc)); ERR_print_errors_fp(stderr); sslClose(ssl, my_socket, ctx, true); return EXIT_FAILURE; } // mostra la risposta printf("%s: risposta Server: %s\n", argv[0], my_msg); } // esco con Ok sslClose(ssl, my_socket, ctx, true); return EXIT_SUCCESS; }Anche il Client è, evidentemente, molto simile al riferimento, e le uniche varianti significative sono:
- una fase di connect aggiuntiva per OpenSSL dopo la fase di connect classica. Questa fase include la creazione degli oggetti OpenSSL veri e propri già visti qua sopra nel punto 1 del Server. Notare anche qui che con la chiamata SSL_set_fd() la liberia OpenSSL riesce ad associare il descrittore di file socket (int my_socket) con la struttura dati OpenSSL (SSL* ssl).
- dal punto 1 in avanti le chiamate classiche di Berkeley Socket recv(), send() e close() sono sostituite, anche in questo caso, da delle misteriose chiamate sslRead(), sslWrite() e sslClose() che poi, ripeto, tanto misteriose non sono, visti i nomi.
Tutto qua per il Client, di nuovo veramente poco, no?
Riepilogando, sembra veramente facile: creiamo contesti e strutture dati OpenSSL, aggiungiamo le fasi OpenSSL aggiuntive (accept per il Server e connect per il Client), sostituiamo le chiamate classiche con quelle misteriose et voilá! missione compiuta!
Ma no, ci deve essere un trucco, non può essere così facile... cerchiamo nel manuale di OpenSSL come funzionano le funzioni misteriose... ma non esistono! Cosa è successo? Ecco, non voglio tenervi sulle spine e passo subito a spiegare l'arcano: il trucco è evidenziato dalla linea #include "../libmyssl/myssl.h" con cui si include l'header file della libreria speciale che ho scritto per sviluppare questi Server e Client: la libreria (che ha l'originalissimo nome di libmyssl) ci fornisce dei wrapper per alcune delle funzioni tipiche di OpenSSL. E non sono dei semplici wrapper per rinominare le funzioni e offrire, magari, una lista di argomenti più facile da usare: no, sono dei wrapper abbastanza complessi, alcuni dei quali fanno molta elaborazione interna, e questo perché OpenSSL ha un funzionamento abbastanza complesso e anche l'uso non è proprio immediato.
Ma niente paura! Tanto per cominciare il primo obbiettivo l'abbiamo già raggiunto: scrivere un Server e un Client minimali che abbiano un aspetto il più possibile simile a quello dei modelli Berkeley Socket. E per forzare questo abbiamo spostato un po' di complessità in una nuova libreria (unica per Server e Client) scritta ad-hoc. Quindi che cosa ci resta da fare? Solo il secondo passo, capire come funziona la nuova libreria (e visto che l'ho scritta io penso di poter dare sufficienti dritte per raggiungere lo scopo...). Ma questo lo vedremo nella seconda parte del post...
Ciao e al prossimo post!
Nessun commento:
Posta un commento