Oops, I forgot some details - we are using NSS-3.11.4 and NSPR-4.6.4 on a RedHat Linux with Kernel 2.4, using gcc 3.4.5.
On Jul 10, 5:36 pm, Yahel Zamir <[EMAIL PROTECTED]> wrote: > 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