Hi, I have encountered a problem in using SSL sockets in blocking mode. My application is multi threaded, with one thread waiting to read and another that is waiting to write. Upon some external input, a third thread tries to shutdown the connection and then close the socket. However, calling PR_Shutdown or PR_Close never returns from SSL_LOCK_READER(ss).
Following are a simple client and server that demonstrate the problem (search the client for 999 and the server for "delayed_close"). Thanks, Yahel Zamir, SW Engineer at CWNT === M_DIST = /usr/home/yahel/programs/nss/mozilla/dist M_DIST_LINUX = ${M_DIST}/Linux2.4_x86_glibc_PTH_DBG.OBJ M_INC = -I${M_DIST_LINUX}/include M_INC += -I${M_DIST}/public/nss M_INC += -I${M_DIST}/private/nss # for error strings M_FLAGS = -g -Wall -pthread M_LIBS = -L${M_DIST_LINUX}/lib M_LIBS += -lssl3 -lnss3 -lnspr4 -lplds4 -lplc4 -lsoftokn3 M_LIBS += -lfreebl3 -lsectool -lpthread -ldl -lc all: server client @echo done clean: rm -f client server client.o server.o server.o: server.c gcc server.c -c ${M_FLAGS} ${M_INC} -o server.o client.o: client.c gcc client.c -c ${M_FLAGS} ${M_INC} -o client.o server: server.o gcc server.o ${M_FLAGS} ${M_LIBS} -o server client: client.o gcc client.o ${M_FLAGS} ${M_LIBS} -o client === // client.c // -------- #include <unistd.h> #include <plgetopt.h> #include <nspr.h> #include <nss.h> #include <pk11pub.h> #include <ssl.h> #include <sslproto.h> #include <key.h> #include <secutil.h> // a private API needed for SECU_Strerror static char *s_progName = NULL; static char *s_password = NULL; static char *s_hostName = NULL; /* * errWarn() * * Print a warning message for NSS and NSPR errors. * More detailed explanations for the error can be found at: * http://www.mozilla.org/projects/security/pki/nss/ref/ssl/sslerr.html */ static void errWarn(char * funcString) { PRErrorCode perr = PR_GetError(); const char * errString = SECU_Strerror(perr); fprintf(stderr, "%s: %s returned error %d (%s)\n", s_progName, funcString, perr, errString); return; } static void errExit(char * funcString) { errWarn(funcString); exit(3); } static void Usage(const char *progName) { fprintf(stderr, "Usage: %s hostname -n cert_name -p port -d cert_dir - w password \n", progName); exit(1); } /* fakePasswd() * * This function is our custom password handler that is called by * SSL when retreiving private certs and keys from the database. Returns a * pointer to a string that with a password for the database. Password pointer * should point to dynamically allocated memory that will be freed later. * We set "arg" to give the correct password, using SSL_SetPKCS11PinArg(). */ char * fakePasswd(PK11SlotInfo *info, PRBool retry, void *arg) { char * passwd = NULL; if ((!retry) && (arg != NULL)) { passwd = PL_strdup((char *)arg); } return passwd; } /* Function: setupSSLSocket() * * Purpose: Configure a socket for SSL. */ PRFileDesc * setupSSLSocket() { PRFileDesc *sslSocket; PRFileDesc *tcpSocket; SECStatus secStatus; PRSocketOptionData socketOption; PRStatus prStatus; tcpSocket = PR_NewTCPSocket(); if (tcpSocket == NULL) { errWarn("PR_NewTCPSocket"); return NULL; } // Ensure the socket is blocking. socketOption.option = PR_SockOpt_Nonblocking; socketOption.value.non_blocking = PR_FALSE; prStatus = PR_SetSocketOption(tcpSocket, &socketOption); if (prStatus != PR_SUCCESS) { errWarn("PR_SetSocketOption"); PR_Close(tcpSocket); return NULL; } sslSocket = SSL_ImportFD(NULL, tcpSocket); if (sslSocket == NULL) { errWarn("SSL_ImportFD"); PR_Close(tcpSocket); return NULL; } // ensure original socket is not used tcpSocket = NULL; do { // handshake as client secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); if (secStatus != SECSuccess) { errWarn("SSL_OptionSet:SSL_HANDSHAKE_AS_CLIENT"); break; } // enable full duplex secStatus = SSL_OptionSet(sslSocket, SSL_ENABLE_FDX, PR_TRUE); if (secStatus != SECSuccess) { errWarn("SSL_OptionSet(SSL_ENABLE_FDX)"); break; } // allow communication without encryption (YZ - must be only upon client request) if (SECSuccess != SSL_CipherPrefSet(sslSocket, SSL_RSA_WITH_NULL_SHA, PR_TRUE) || SECSuccess != SSL_CipherPrefSet(sslSocket, SSL_RSA_WITH_NULL_MD5, PR_TRUE)) { errWarn("SSL_CipherPrefSet - Null Cipher"); break; } secStatus = SSL_SetPKCS11PinArg(sslSocket, s_password); if (secStatus != SECSuccess) { errWarn("SSL_SetPKCS11PinArg"); break; } secStatus = SSL_SetURL(sslSocket, s_hostName); if (secStatus != SECSuccess) { errWarn("SSL_SetURL"); break; } // Success return sslSocket; } while (0); // Failure PR_Close(sslSocket); return NULL; } /* Function: clientMain() * * Purpose: Setup an SSL socket and connect a server. * Send a small message and expect a reply message. */ void clientMain(unsigned short port) { SECStatus secStatus; PRStatus prStatus; PRInt32 rv; PRNetAddr addr; PRHostEnt hostEntry; PRFileDesc *sslSocket; char buffer[256]; sslSocket = setupSSLSocket(); if (sslSocket == NULL) { errExit("setupSSLSocket"); } prStatus = PR_GetHostByName(s_hostName, buffer, sizeof(buffer), &hostEntry); if (prStatus != PR_SUCCESS) { errExit("PR_GetHostByName"); } rv = PR_EnumerateHostEnt(0, &hostEntry, port, &addr); if (rv < 0) { errExit("PR_EnumerateHostEnt"); } prStatus = PR_Connect(sslSocket, &addr, PR_INTERVAL_NO_TIMEOUT); if (prStatus != PR_SUCCESS) { errExit("PR_Connect"); } secStatus = SSL_ResetHandshake(sslSocket, /* asServer */ PR_FALSE); if (secStatus != SECSuccess) { errExit("SSL_ResetHandshake"); } // single write, then single read sprintf(buffer, "hello from client"); int msgSize = strlen(buffer); sleep(999); rv = PR_Write(sslSocket, buffer, msgSize); if (rv != msgSize) { errWarn("PR_Write"); PR_Close(sslSocket); return; } printf("client sent: %s \n", buffer); rv = PR_Read(sslSocket, buffer, sizeof(buffer)); // (rv == 0) is EOF if (rv <= 0) { errWarn("PR_Read"); PR_Close(sslSocket); return; } buffer[rv] = 0; printf("client received: %s \n", buffer); prStatus = PR_Close(sslSocket); if (prStatus != PR_SUCCESS) { errExit("PR_Close"); } return; } int main(int argc, char **argv) { char * certName = NULL; char * certDir = "."; unsigned short port = 0; PLOptState * optstate; PLOptStatus status; SECStatus secStatus; s_progName = argv[0]; optstate = PL_CreateOptState(argc, argv, "d:p:n:w:"); while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { switch(optstate->option) { case 0: s_hostName = PL_strdup(optstate->value); break; case 'd': certDir = PL_strdup(optstate->value); break; case 'n': certName = PL_strdup(optstate->value); break; case 'p': port = atoi(optstate->value); break; case 'w': s_password = PL_strdup(optstate->value); break; case '?': Usage(s_progName); break; default: Usage(s_progName); break; } } if (certName == NULL || s_hostName == NULL || s_password == NULL || port == 0) { Usage(s_progName); } // Client set up // ------------- PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); PK11_SetPasswordFunc(fakePasswd); secStatus = NSS_Init(certDir); if (secStatus != SECSuccess) { errExit("NSS_Init"); } secStatus = NSS_SetExportPolicy(); if (secStatus != SECSuccess) { errExit("NSS_SetExportPolicy"); } // optional - clear client cache SSL_ClearSessionCache(); // Client main function // -------------------- clientMain(port); // Client shutdown // --------------- SSL_ClearSessionCache(); // otherwise, NSS_Shutdown fails. if (NSS_Shutdown() != SECSuccess) { errExit("NSS_Shutdown"); } PR_Cleanup(); if (certName) { free(certName); } if (certDir) { free(certDir); } if (s_hostName) { free(s_hostName); } printf("%s: normal termination\n", s_progName); return 0; } ==== // server.c // -------- #include <unistd.h> #include <pthread.h> #include <plgetopt.h> #include <nspr.h> #include <nss.h> #include <pk11pub.h> #include <ssl.h> #include <sslproto.h> #include <key.h> #include <secutil.h> // a private API needed for SECU_Strerror static char *s_progName = NULL; /* * errWarn() * * Print a warning message for NSS and NSPR errors. * More detailed explanations for the error can be found at: * http://www.mozilla.org/projects/security/pki/nss/ref/ssl/sslerr.html */ static void errWarn(char * funcString) { PRErrorCode perr = PR_GetError(); const char * errString = SECU_Strerror(perr); fprintf(stderr, "%s: %s returned error %d (%s)\n", s_progName, funcString, perr, errString); return; } static void errExit(char * funcString) { errWarn(funcString); exit(3); } static void Usage(const char *progName) { fprintf(stderr, "Usage: %s -n cert_name -p port -w password [-d cert_dir] \n", progName); exit(1); } /* fakePasswd() * * This function is our custom password handler that is called by * SSL when retreiving private certs and keys from the database. Returns a * pointer to a string that with a password for the database. Password pointer * should point to dynamically allocated memory that will be freed later. * We set "arg" to give the correct password, using SSL_SetPKCS11PinArg(). */ char * fakePasswd(PK11SlotInfo *info, PRBool retry, void *arg) { char * passwd = NULL; if ((!retry) && (arg != NULL)) { passwd = PL_strdup((char *)arg); } return passwd; } /* Function: setupSSLSocket() * * Purpose: Configure a socket for SSL. * NSS has 3 methods for configuring a TCP socket for SSL: * 1. Do the configuration step by step. * 2. Inherit a model socket using SSL_ImportFD. * 3. Do the configuration on the listen socket. This way, * sockets created by PR_Accept inherit the configuration. * * - this application uses method 3, so we configure the listen socket. */ PRFileDesc * setupSSLSocket(PRFileDesc *tcpSocket, CERTCertificate *cert, SECKEYPrivateKey *privKey, SSLKEAType sslKEA, char* password) { PRFileDesc *sslSocket; SECStatus secStatus; sslSocket = SSL_ImportFD(NULL, tcpSocket); if (sslSocket == NULL) { errWarn("SSL_ImportFD"); PR_Close(tcpSocket); return NULL; } // ensure original socket is not used tcpSocket = NULL; do { // handshake as server secStatus = SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_SERVER, PR_TRUE); if (secStatus != SECSuccess) { errWarn("SSL_OptionSet:SSL_HANDSHAKE_AS_SERVER"); break; } // enable full duplex secStatus = SSL_OptionSet(sslSocket, SSL_ENABLE_FDX, PR_TRUE); if (secStatus != SECSuccess) { errWarn("SSL_OptionSet(SSL_ENABLE_FDX)"); break; } // allow communication without encryption (YZ - must be only upon client request) if (SECSuccess != SSL_CipherPrefSet(sslSocket, SSL_RSA_WITH_NULL_SHA, PR_TRUE) || SECSuccess != SSL_CipherPrefSet(sslSocket, SSL_RSA_WITH_NULL_MD5, PR_TRUE)) { errWarn("SSL_CipherPrefSet - Null Cipher"); break; } /* // optional, see SSL reference. */ /* secStatus = SSL_AuthCertificateHook(sslSocket, myAuthCertificate, CERT_GetDefaultCertDB()); */ /* if (secStatus != SECSuccess) { */ /* errWarn("SSL_AuthCertificateHook"); */ /* break; */ / * } */ /* // optional, see SSL reference. */ /* secStatus = SSL_BadCertHook(sslSocket, */ /* (SSLBadCertHandler)myBadCertHandler, &certErr); */ /* if (secStatus != SECSuccess) { */ /* errWarn("SSL_BadCertHook"); */ /* break; */ / * } */ /* // optional, see SSL reference. */ /* secStatus = SSL_HandshakeCallback(sslSocket, */ /* (SSLHandshakeCallback)myHandshakeCallback, */ /* NULL); */ /* if (secStatus != SECSuccess) { */ /* errWarn("SSL_HandshakeCallback"); */ /* break; */ / * } */ secStatus = SSL_SetPKCS11PinArg(sslSocket, password); if (secStatus != SECSuccess) { errWarn("SSL_HandshakeCallback"); break; } secStatus = SSL_ConfigSecureServer(sslSocket, cert, privKey, sslKEA); if (secStatus != SECSuccess) { errWarn("SSL_ConfigSecureServer"); break; } // Success return sslSocket; } while (0); // Failure PR_Close(sslSocket); return NULL; } void* delayed_close(void *pSocket) { PRFileDesc *socket = (PRFileDesc *) pSocket; sleep(1); PR_Shutdown(socket, PR_SHUTDOWN_BOTH); PR_Close(socket); printf("\n" "socket closed .\n"); return NULL; } /* Function: handleConnection() * * Purpose: handle a single SSL connection. * */ SECStatus handleConnection(PRFileDesc *pSocket) { PRFileDesc *sslSocket = (PRFileDesc *) pSocket; PRStatus prStatus; PRSocketOptionData socketOption; char buffer[256]; PRInt32 rv; // ensure the socket is blocking. this should be the default. socketOption.option = PR_SockOpt_Nonblocking; socketOption.value.non_blocking = PR_FALSE; PR_SetSocketOption(sslSocket, &socketOption); /* // handshake as server - required in addition to the SSL option, */ /* // in case the listen socket was not an SSL socket. */ /* secStatus = SSL_ResetHandshake(sslSocket, PR_TRUE ); */ /* if (secStatus != SECSuccess) { */ /* errWarn("SSL_ResetHandshake"); */ /* return secStatus; */ / * } */ pthread_t pth; pthread_create(&pth, NULL, delayed_close, sslSocket); // use socket: single read, then single write rv = PR_Read(sslSocket, buffer, sizeof(buffer)); // (rv == 0) is EOF if (rv <= 0) { errWarn("PR_Read"); PR_Close(sslSocket); return SECFailure; } buffer[rv] = 0; printf("server received: %s \n", buffer); sprintf(buffer, "hello from server"); int msgSize = strlen(buffer); rv = PR_Write(sslSocket, buffer, msgSize); if (rv != msgSize) { errWarn("PR_Write"); PR_Close(sslSocket); return SECFailure; } printf("server sent: %s \n", buffer); // Finally // ------- printf("\n" "Closing client connection.\n"); prStatus = PR_Close(sslSocket); if (prStatus != PR_SUCCESS) { errWarn("PR_Close"); return SECFailure; } return SECSuccess; } /* Function: startListening() * * Purpose: Create a new socket and starts listening. */ PRFileDesc * startListening(unsigned short port) { PRFileDesc * listen_sock; PRStatus prStatus; PRNetAddr addr; PRSocketOptionData opt; addr.inet.family = PR_AF_INET; addr.inet.ip = PR_INADDR_ANY; addr.inet.port = PR_htons(port); listen_sock = PR_NewTCPSocket(); if (listen_sock == NULL) { errExit("PR_NewTCPSocket"); } // YZ set blocking mode. this should be the default anyway. opt.option = PR_SockOpt_Nonblocking; opt.value.non_blocking = PR_FALSE; prStatus = PR_SetSocketOption(listen_sock, &opt); if (prStatus < 0) { errExit("PR_SetSocketOption(PR_SockOpt_Nonblocking = PR_FALSE)"); } opt.option=PR_SockOpt_Reuseaddr; opt.value.reuse_addr = PR_TRUE; prStatus = PR_SetSocketOption(listen_sock, &opt); if (prStatus < 0) { errExit("PR_SetSocketOption(PR_SockOpt_Reuseaddr = PR_TRUE)"); } prStatus = PR_Bind(listen_sock, &addr); if (prStatus < 0) { errExit("PR_Bind"); } prStatus = PR_Listen(listen_sock, 5); if (prStatus < 0) { errExit("PR_Listen"); } return listen_sock; } /* Function: serverMain() * * Purpose: * Loop to accept connections. For every connection, * receive a message and then send a reply message. * Since listenSocket is an SSL socket, tcpSocket is an SSL socket too. */ void serverMain(PRFileDesc *listenSocket) { PRNetAddr addr; PRStatus prStatus; while (1) { PRFileDesc *tcpSocket; printf("\n" "Waiting for new connection.\n"); /* Accept a connection */ tcpSocket = PR_Accept(listenSocket, &addr, PR_INTERVAL_NO_TIMEOUT); if (tcpSocket == NULL) { errWarn("PR_Accept"); break; } // Handle one connection at a time. // Can be replaced with a new thread pthread_t pth; pthread_create(&pth, NULL, handleConnection, tcpSocket); } printf("\n" "Closing listen socket.\n"); prStatus = PR_Close(listenSocket); if (prStatus != PR_SUCCESS) { errWarn("PR_Close"); } return; } int main(int argc, char **argv) { char * password = NULL; char * certName = NULL; char * certDir = "."; char * cipherString = NULL; unsigned short port = 0; PLOptState * optstate; PLOptStatus status; SECStatus secStatus; CERTCertificate *cert = NULL; SSLKEAType sslKEA; SECKEYPrivateKey *privKey = NULL; PRFileDesc * listenSocket; PRFileDesc * sslSocket; s_progName = argv[0]; optstate = PL_CreateOptState(argc, argv, "c:d:p:n:w:"); while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { switch(optstate->option) { case 'c': cipherString = PL_strdup(optstate->value); break; case 'd': certDir = PL_strdup(optstate->value); break; case 'n': certName = PL_strdup(optstate->value); break; case 'p': port = atoi(optstate->value); break; case 'w': password = PL_strdup(optstate->value); break; case 0: break; case '?': Usage(s_progName); break; default: Usage(s_progName); break; } } if (certName == NULL || password == NULL || port == 0) { Usage(s_progName); } // Server set up // ------------- PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); PK11_SetPasswordFunc(fakePasswd); secStatus = NSS_Init(certDir); if (secStatus != SECSuccess) { errExit("NSS_Init"); } // Using export policy. secStatus = NSS_SetExportPolicy(); if (secStatus != SECSuccess) { errExit("NSS_SetExportPolicy"); } /* Get own certificate and private key. */ cert = PK11_FindCertFromNickname(certName, password); if (cert == NULL) { errExit("PK11_FindCertFromNickname"); } sslKEA = NSS_FindCertKEAType(cert); if (sslKEA == kt_null) { errExit("NSS_FindCertKEAType"); } privKey = PK11_FindKeyByAnyCert(cert, password); if (privKey == NULL) { errExit("PK11_FindKeyByAnyCert"); } // Set a session cache for single-process server. secStatus = SSL_ConfigServerSessionIDCache(100, 0, 0, 0); if (secStatus != SECSuccess) { errExit("SSL_ConfigServerSessionIDCache"); } listenSocket = startListening(port); if (listenSocket == NULL) { errExit("startListening"); } sslSocket = setupSSLSocket(listenSocket, cert, privKey, sslKEA, password); if (sslSocket == NULL) { errExit("setupSSLSocket"); } // Main server loop // ---------------- serverMain(sslSocket); // Server shutdown // --------------- if (cert) { CERT_DestroyCertificate(cert); } if (privKey) { SECKEY_DestroyPrivateKey(privKey); } SSL_ShutdownServerSessionIDCache(); if (NSS_Shutdown() != SECSuccess) { errExit("NSS_Shutdown"); } PR_Cleanup(); if (certName) { free(certName); } if (certDir) { free(certDir); } if (password) { free(password); } printf("%s: normal termination\n", s_progName); return 0; } _______________________________________________ dev-tech-crypto mailing list dev-tech-crypto@lists.mozilla.org https://lists.mozilla.org/listinfo/dev-tech-crypto