summaryrefslogtreecommitdiff
path: root/security/nss/cmd/tstclnt/tstclnt.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/cmd/tstclnt/tstclnt.c')
-rw-r--r--security/nss/cmd/tstclnt/tstclnt.c657
1 files changed, 657 insertions, 0 deletions
diff --git a/security/nss/cmd/tstclnt/tstclnt.c b/security/nss/cmd/tstclnt/tstclnt.c
new file mode 100644
index 000000000..18e1a15e7
--- /dev/null
+++ b/security/nss/cmd/tstclnt/tstclnt.c
@@ -0,0 +1,657 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation. Portions created by Netscape are
+ * Copyright (C) 1994-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above. If you wish to allow use of your
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL. If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+/*
+**
+** Sample client side test program that uses SSL and libsec
+**
+*/
+
+#include "secutil.h"
+
+#if defined(XP_UNIX)
+#include <unistd.h>
+#else
+#include "ctype.h" /* for isalpha() */
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+
+#include "nspr.h"
+#include "prio.h"
+#include "prnetdb.h"
+#include "nss.h"
+#include "ssl.h"
+#include "sslproto.h"
+#include "pk11func.h"
+#include "plgetopt.h"
+
+#define PRINTF if (verbose) printf
+#define FPRINTF if (verbose) fprintf
+
+int ssl2CipherSuites[] = {
+ SSL_EN_RC4_128_WITH_MD5, /* A */
+ SSL_EN_RC4_128_EXPORT40_WITH_MD5, /* B */
+ SSL_EN_RC2_128_CBC_WITH_MD5, /* C */
+ SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, /* D */
+ SSL_EN_DES_64_CBC_WITH_MD5, /* E */
+ SSL_EN_DES_192_EDE3_CBC_WITH_MD5, /* F */
+ 0
+};
+
+int ssl3CipherSuites[] = {
+ SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, /* a */
+ SSL_FORTEZZA_DMS_WITH_RC4_128_SHA, /* b */
+ SSL_RSA_WITH_RC4_128_MD5, /* c */
+ SSL_RSA_WITH_3DES_EDE_CBC_SHA, /* d */
+ SSL_RSA_WITH_DES_CBC_SHA, /* e */
+ SSL_RSA_EXPORT_WITH_RC4_40_MD5, /* f */
+ SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* g */
+ SSL_FORTEZZA_DMS_WITH_NULL_SHA, /* h */
+ SSL_RSA_WITH_NULL_MD5, /* i */
+ SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, /* j */
+ SSL_RSA_FIPS_WITH_DES_CBC_SHA, /* k */
+ TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, /* l */
+ TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, /* m */
+ 0
+};
+
+unsigned long __cmp_umuls;
+PRBool verbose = PR_TRUE;
+
+static char *progName;
+
+/* This exists only for the automated test suite. It allows us to
+ * pass in a password on the command line.
+ */
+
+char *password = NULL;
+
+char * ownPasswd( PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ char *passwd = NULL;
+ if ( (!retry) && arg ) {
+ passwd = PL_strdup((char *)arg);
+ }
+ return passwd;
+}
+
+void printSecurityInfo(PRFileDesc *fd)
+{
+ char * cp; /* bulk cipher name */
+ char * ip; /* cert issuer DN */
+ char * sp; /* cert subject DN */
+ int op; /* High, Low, Off */
+ int kp0; /* total key bits */
+ int kp1; /* secret key bits */
+ int result;
+
+/* statistics from ssl3_SendClientHello (sch) */
+extern long ssl3_sch_sid_cache_hits;
+extern long ssl3_sch_sid_cache_misses;
+extern long ssl3_sch_sid_cache_not_ok;
+
+/* statistics from ssl3_HandleServerHello (hsh) */
+extern long ssl3_hsh_sid_cache_hits;
+extern long ssl3_hsh_sid_cache_misses;
+extern long ssl3_hsh_sid_cache_not_ok;
+
+/* statistics from ssl3_HandleClientHello (hch) */
+extern long ssl3_hch_sid_cache_hits;
+extern long ssl3_hch_sid_cache_misses;
+extern long ssl3_hch_sid_cache_not_ok;
+
+ result = SSL_SecurityStatus(fd, &op, &cp, &kp0, &kp1, &ip, &sp);
+ if (result != SECSuccess)
+ return;
+ PRINTF("bulk cipher %s, %d secret key bits, %d key bits, status: %d\n"
+ "subject DN: %s\n"
+ "issuer DN: %s\n", cp, kp1, kp0, op, sp, ip);
+ PR_Free(cp);
+ PR_Free(ip);
+ PR_Free(sp);
+
+ PRINTF("%ld cache hits; %ld cache misses, %ld cache not reusable\n",
+ ssl3_hch_sid_cache_hits, ssl3_hch_sid_cache_misses,
+ ssl3_hch_sid_cache_not_ok);
+
+}
+
+void
+handshakeCallback(PRFileDesc *fd, void *client_data)
+{
+ printSecurityInfo(fd);
+}
+
+static void Usage(const char *progName)
+{
+ printf(
+"Usage: %s -h host [-p port] [-d certdir] [-n nickname] [-23ox] \n"
+" [-c ciphers] [-w passwd]\n", progName);
+ printf("%-20s Hostname to connect with\n", "-h host");
+ printf("%-20s Port number for SSL server\n", "-p port");
+ printf("%-20s Directory with cert database (default is ~/.netscape)\n",
+ "-d certdir");
+ printf("%-20s Nickname of key and cert for client auth\n", "-n nickname");
+ printf("%-20s Disable SSL v2.\n", "-2");
+ printf("%-20s Disable SSL v3.\n", "-3");
+ printf("%-20s Override bad server cert. Make it OK.\n", "-o");
+ printf("%-20s Use export policy.\n", "-x");
+ printf("%-20s Letter(s) chosen from the following list\n", "-c ciphers");
+ printf(
+"A SSL2 RC4 128 WITH MD5\n"
+"B SSL2 RC4 128 EXPORT40 WITH MD5\n"
+"C SSL2 RC2 128 CBC WITH MD5\n"
+"D SSL2 RC2 128 CBC EXPORT40 WITH MD5\n"
+"E SSL2 DES 64 CBC WITH MD5\n"
+"F SSL2 DES 192 EDE3 CBC WITH MD5\n"
+"\n"
+"a SSL3 FORTEZZA DMS WITH FORTEZZA CBC SHA\n"
+"b SSL3 FORTEZZA DMS WITH RC4 128 SHA\n"
+"c SSL3 RSA WITH RC4 128 MD5\n"
+"d SSL3 RSA WITH 3DES EDE CBC SHA\n"
+"e SSL3 RSA WITH DES CBC SHA\n"
+"f SSL3 RSA EXPORT WITH RC4 40 MD5\n"
+"g SSL3 RSA EXPORT WITH RC2 CBC 40 MD5\n"
+"h SSL3 FORTEZZA DMS WITH NULL SHA\n"
+"i SSL3 RSA WITH NULL MD5\n"
+"j SSL3 RSA FIPS WITH 3DES EDE CBC SHA\n"
+"k SSL3 RSA FIPS WITH DES CBC SHA\n"
+"l SSL3 RSA EXPORT WITH DES CBC SHA\t(new)\n"
+"m SSL3 RSA EXPORT WITH RC4 56 SHA\t(new)\n"
+ );
+ exit(-1);
+}
+
+void
+milliPause(PRUint32 milli)
+{
+ PRIntervalTime ticks = PR_MillisecondsToInterval(milli);
+ PR_Sleep(ticks);
+}
+
+void
+disableSSL2Ciphers(void)
+{
+ int i;
+
+ /* disable all the SSL2 cipher suites */
+ for (i = 0; ssl2CipherSuites[i] != 0; ++i) {
+ SSL_EnableCipher(ssl2CipherSuites[i], SSL_NOT_ALLOWED);
+ }
+}
+
+void
+disableSSL3Ciphers(void)
+{
+ int i;
+
+ /* disable all the SSL3 cipher suites */
+ for (i = 0; ssl3CipherSuites[i] != 0; ++i) {
+ SSL_EnableCipher(ssl3CipherSuites[i], SSL_NOT_ALLOWED);
+ }
+}
+
+/*
+ * Callback is called when incoming certificate is not valid.
+ * Returns SECSuccess to accept the cert anyway, SECFailure to reject.
+ */
+static SECStatus
+ownBadCertHandler(void * arg, PRFileDesc * socket)
+{
+ PRErrorCode err = PR_GetError();
+ /* can log invalid cert here */
+ printf("Bad server certificate: %d, %s\n", err, SECU_Strerror(err));
+ return SECSuccess; /* override, say it's OK. */
+}
+
+
+int main(int argc, char **argv)
+{
+ PRFileDesc * s;
+ PRFileDesc * std_out;
+ CERTCertDBHandle * handle;
+ char * host = NULL;
+ char * port = "443";
+ char * certDir = NULL;
+ char * nickname = NULL;
+ char * cipherString = NULL;
+ int multiplier = 0;
+ SECStatus rv;
+ PRStatus status;
+ PRInt32 filesReady;
+ PRInt32 ip;
+ int npds;
+ int o;
+ int override = 0;
+ int disableSSL2 = 0;
+ int disableSSL3 = 0;
+ int disableTLS = 0;
+ int useExportPolicy = 0;
+ int file_read = 0;
+ PRSocketOptionData opt;
+ PRNetAddr addr;
+ PRHostEnt hp;
+ PRPollDesc pollset[2];
+ char buf[PR_NETDB_BUF_SIZE];
+ PRBool useCommandLinePassword = PR_FALSE;
+ int error=0;
+ PLOptState *optstate;
+ PLOptStatus optstatus;
+
+ progName = strrchr(argv[0], '/');
+ if (!progName)
+ progName = strrchr(argv[0], '\\');
+ progName = progName ? progName+1 : argv[0];
+
+ optstate = PL_CreateOptState(argc, argv, "23Tfc:h:p:d:m:n:ow:x");
+ while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
+ switch (optstate->option) {
+ case '?':
+ default : Usage(progName); break;
+
+ case '2': disableSSL2 = 1; break;
+
+ case '3': disableSSL3 = 1; break;
+
+ case 'T': disableTLS = 1; break;
+
+ case 'c': cipherString = strdup(optstate->value); break;
+
+ case 'h': host = strdup(optstate->value); break;
+#ifdef _WINDOWS
+ case 'f': file_read = 1; break;
+#else
+ case 'f': break;
+#endif
+
+ case 'd':
+ certDir = strdup(optstate->value);
+ certDir = SECU_ConfigDirectory(certDir);
+ break;
+
+ case 'm':
+ multiplier = atoi(optstate->value);
+ if (multiplier < 0)
+ multiplier = 0;
+ break;
+
+ case 'n': nickname = strdup(optstate->value); break;
+
+ case 'o': override = 1; break;
+
+ case 'p': port = strdup(optstate->value); break;
+
+ case 'w':
+ password = optstate->value;
+ useCommandLinePassword = PR_TRUE;
+ break;
+
+ case 'x': useExportPolicy = 1; break;
+ }
+ }
+
+ if (!host || !port) Usage(progName);
+
+ if (!certDir) {
+ certDir = SECU_DefaultSSLDir(); /* Look in $SSL_DIR */
+ certDir = SECU_ConfigDirectory(certDir); /* call even if it's NULL */
+ }
+
+ PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
+
+ /* set our password function */
+ if ( useCommandLinePassword ) {
+ PK11_SetPasswordFunc(ownPasswd);
+ } else {
+ PK11_SetPasswordFunc(SECU_GetModulePassword);
+ }
+
+ /* open the cert DB, the key DB, and the secmod DB. */
+ rv = NSS_Init(certDir);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "unable to open cert database");
+#if 0
+ rv = CERT_OpenVolatileCertDB(handle);
+ CERT_SetDefaultCertDB(handle);
+#else
+ return -1;
+#endif
+ }
+ handle = CERT_GetDefaultCertDB();
+
+ /* set the policy bits true for all the cipher suites. */
+ if (useExportPolicy)
+ NSS_SetExportPolicy();
+ else
+ NSS_SetDomesticPolicy();
+
+ /* all the SSL2 and SSL3 cipher suites are enabled by default. */
+ if (cipherString) {
+ /* disable all the ciphers, then enable the ones we want. */
+ disableSSL2Ciphers();
+ disableSSL3Ciphers();
+ }
+
+ /* Lookup host */
+ status = PR_GetHostByName(host, buf, sizeof(buf), &hp);
+ if (status != PR_SUCCESS) {
+ SECU_PrintError(progName, "error looking up host");
+ return -1;
+ }
+ if (PR_EnumerateHostEnt(0, &hp, atoi(port), &addr) == -1) {
+ SECU_PrintError(progName, "error looking up host address");
+ return -1;
+ }
+
+ ip = PR_ntohl(addr.inet.ip);
+ printf("%s: connecting to %s:%d (address=%d.%d.%d.%d)\n",
+ progName, host, PR_ntohs(addr.inet.port),
+ (ip >> 24) & 0xff,
+ (ip >> 16) & 0xff,
+ (ip >> 8) & 0xff,
+ (ip >> 0) & 0xff);
+
+ /* Create socket */
+ s = PR_NewTCPSocket();
+ if (s == NULL) {
+ SECU_PrintError(progName, "error creating socket");
+ return -1;
+ }
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = PR_TRUE;
+ PR_SetSocketOption(s, &opt);
+ /*PR_SetSocketOption(PR_GetSpecialFD(PR_StandardInput), &opt);*/
+
+ s = SSL_ImportFD(NULL, s);
+ if (s == NULL) {
+ SECU_PrintError(progName, "error importing socket");
+ return -1;
+ }
+
+ rv = SSL_Enable(s, SSL_SECURITY, 1);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling socket");
+ return -1;
+ }
+
+ rv = SSL_Enable(s, SSL_HANDSHAKE_AS_CLIENT, 1);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling client handshake");
+ return -1;
+ }
+
+ /* all the SSL2 and SSL3 cipher suites are enabled by default. */
+ if (cipherString) {
+ int ndx;
+
+ while (0 != (ndx = *cipherString++)) {
+ int *cptr;
+ int cipher;
+
+ if (! isalpha(ndx))
+ Usage(progName);
+ cptr = islower(ndx) ? ssl3CipherSuites : ssl2CipherSuites;
+ for (ndx &= 0x1f; (cipher = *cptr++) != 0 && --ndx > 0; )
+ /* do nothing */;
+ if (cipher) {
+ SECStatus status;
+ status = SSL_CipherPrefSet(s, cipher, SSL_ALLOWED);
+ if (status != SECSuccess)
+ SECU_PrintError(progName, "SSL_CipherPrefSet()");
+ }
+ }
+ }
+
+ if (disableSSL2) {
+ rv = SSL_Enable(s, SSL_ENABLE_SSL2, 0);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error disabling SSLv2 ");
+ return -1;
+ }
+ }
+
+ if (disableSSL3) {
+ rv = SSL_Enable(s, SSL_ENABLE_SSL3, 0);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error disabling SSLv3 ");
+ return -1;
+ }
+ }
+ if (!disableTLS) {
+ rv = SSL_Enable(s, SSL_ENABLE_TLS, 1);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling TLS ");
+ return -1;
+ }
+ }
+
+#if 0
+ /* disable ssl2 and ssl2-compatible client hellos. */
+ rv = SSL_Enable(s, SSL_V2_COMPATIBLE_HELLO, 0);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error disabling v2 compatibility");
+ return -1;
+ }
+#endif
+
+ if (useCommandLinePassword) {
+ SSL_SetPKCS11PinArg(s, password);
+ }
+
+ SSL_AuthCertificateHook(s, SSL_AuthCertificate, (void *)handle);
+ if (override) {
+ SSL_BadCertHook(s, ownBadCertHandler, NULL);
+ }
+ SSL_GetClientAuthDataHook(s, NSS_GetClientAuthData, (void *)nickname);
+ SSL_HandshakeCallback(s, handshakeCallback, NULL);
+ SSL_SetURL(s, host);
+
+ /* Try to connect to the server */
+ status = PR_Connect(s, &addr, PR_INTERVAL_NO_TIMEOUT);
+ if (status != PR_SUCCESS) {
+ if (PR_GetError() == PR_IN_PROGRESS_ERROR) {
+ SECU_PrintError(progName, "connect");
+ milliPause(50 * multiplier);
+ pollset[0].fd = s;
+ pollset[0].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pollset[0].out_flags = 0;
+ while(1) {
+ printf("%s: about to call PR_Poll for connect completion!\n", progName);
+ filesReady = PR_Poll(pollset, 1, PR_INTERVAL_NO_TIMEOUT);
+ if (filesReady < 0) {
+ SECU_PrintError(progName, "unable to connect (poll)");
+ return -1;
+ }
+ printf("%s: PR_Poll returned 0x%02x for socket out_flags.\n",
+ progName, pollset[0].out_flags);
+ if (filesReady == 0) { /* shouldn't happen! */
+ printf("%s: PR_Poll returned zero!\n", progName);
+ return -1;
+ }
+ /* Must milliPause between PR_Poll and PR_GetConnectStatus,
+ * Or else winsock gets mighty confused.
+ * Sleep(0);
+ */
+ milliPause(1);
+ status = PR_GetConnectStatus(pollset);
+ if (status == PR_SUCCESS) {
+ break;
+ }
+ if (PR_GetError() != PR_IN_PROGRESS_ERROR) {
+ SECU_PrintError(progName, "unable to connect (poll)");
+ return -1;
+ }
+ SECU_PrintError(progName, "poll");
+ milliPause(50 * multiplier);
+ }
+ } else {
+ SECU_PrintError(progName, "unable to connect");
+ return -1;
+ }
+ }
+
+ pollset[0].fd = s;
+ pollset[0].in_flags = PR_POLL_READ;
+ pollset[1].fd = PR_GetSpecialFD(PR_StandardInput);
+ pollset[1].in_flags = PR_POLL_READ;
+ npds = 2;
+ std_out = PR_GetSpecialFD(PR_StandardOutput);
+
+
+ if (file_read) {
+ pollset[1].out_flags = PR_POLL_READ;
+ npds=1;
+ }
+
+ /*
+ ** Select on stdin and on the socket. Write data from stdin to
+ ** socket, read data from socket and write to stdout.
+ */
+ printf("%s: ready...\n", progName);
+
+ while (pollset[0].in_flags || pollset[1].in_flags) {
+ char buf[4000]; /* buffer for stdin */
+ int nb; /* num bytes read from stdin. */
+
+ pollset[0].out_flags = 0;
+ if (!file_read) {
+ pollset[1].out_flags = 0;
+ }
+
+ printf("%s: about to call PR_Poll !\n", progName);
+ if (pollset[1].in_flags && file_read) {
+ filesReady = PR_Poll(pollset, npds, PR_INTERVAL_NO_WAIT);
+ filesReady++;
+ } else {
+ filesReady = PR_Poll(pollset, npds, PR_INTERVAL_NO_TIMEOUT);
+ }
+ if (filesReady < 0) {
+ SECU_PrintError(progName, "select failed");
+ error=-1;
+ goto done;
+ }
+ if (filesReady == 0) { /* shouldn't happen! */
+ printf("%s: PR_Poll returned zero!\n", progName);
+ return -1;
+ }
+ printf("%s: PR_Poll returned!\n", progName);
+ if (pollset[1].in_flags) {
+ printf("%s: PR_Poll returned 0x%02x for stdin out_flags.\n",
+ progName, pollset[1].out_flags);
+#ifndef _WINDOWS
+ }
+ if (pollset[1].out_flags & PR_POLL_READ) {
+#endif
+ /* Read from stdin and write to socket */
+ nb = PR_Read(pollset[1].fd, buf, sizeof(buf));
+ printf("%s: stdin read %d bytes\n", progName, nb);
+ if (nb < 0) {
+ if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+ SECU_PrintError(progName, "read from stdin failed");
+ error=-1;
+ break;
+ }
+ } else if (nb == 0) {
+ pollset[1].in_flags = 0;
+ } else {
+ char * bufp = buf;
+ printf("%s: Writing %d bytes to server\n", progName, nb);
+ do {
+ PRInt32 cc = PR_Write(s, bufp, nb);
+ if (cc < 0) {
+ PRErrorCode err = PR_GetError();
+ if (err != PR_WOULD_BLOCK_ERROR) {
+ SECU_PrintError(progName,
+ "write to SSL socket failed");
+ error=-2;
+ goto done;
+ }
+ cc = 0;
+ }
+ bufp += cc;
+ nb -= cc;
+ if (nb <= 0)
+ break;
+ pollset[0].in_flags = PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pollset[0].out_flags = 0;
+ printf("%s: about to call PR_Poll on writable socket !\n", progName);
+ cc = PR_Poll(pollset, 1, PR_INTERVAL_NO_TIMEOUT);
+ printf("%s: PR_Poll returned with writable socket !\n", progName);
+ } while (1);
+ pollset[0].in_flags = PR_POLL_READ;
+ }
+ }
+
+ if (pollset[0].in_flags) {
+ printf("%s: PR_Poll returned 0x%02x for socket out_flags.\n",
+ progName, pollset[0].out_flags);
+ }
+ if ( (pollset[0].out_flags & PR_POLL_READ)
+ || (pollset[0].out_flags & PR_POLL_ERR)
+#ifdef PR_POLL_HUP
+ || (pollset[0].out_flags & PR_POLL_HUP)
+#endif
+ ) {
+ /* Read from socket and write to stdout */
+ nb = PR_Read(pollset[0].fd, buf, sizeof(buf));
+ printf("%s: Read from server %d bytes\n", progName, nb);
+ if (nb < 0) {
+ if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+ SECU_PrintError(progName, "read from socket failed");
+ error=-1;
+ goto done;
+ }
+ } else if (nb == 0) {
+ /* EOF from socket... bye bye */
+ pollset[0].in_flags = 0;
+ } else {
+ PR_Write(std_out, buf, nb);
+ puts("\n\n");
+ }
+ }
+ milliPause(50 * multiplier);
+ }
+
+ done:
+ PR_Close(s);
+ NSS_Shutdown();
+ PR_Cleanup();
+ return error;
+}