Bug 456001 - Need automated testing for SSL client auth, r=jwalden
authorHonza Bambas <honzab@allpeers.com>
Mon, 12 Jan 2009 20:14:40 +0100
changeset 23550 4951644cab1d014c0c14bdb5d0620d4cc1814544
parent 23549 499ed590749f0f7e3524b1b4f427a8e7e49a0929
child 23551 3066ebd9e7f9ecbf0d9749862693446db17c6435
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs456001
milestone1.9.2a1pre
Bug 456001 - Need automated testing for SSL client auth, r=jwalden
build/pgo/automation.py.in
build/pgo/certs/Makefile.in
build/pgo/certs/cert8.db
build/pgo/certs/key3.db
build/pgo/certs/mochitest.client
build/pgo/server-locations.txt
testing/mochitest/ssltunnel/ssltunnel.cpp
--- a/build/pgo/automation.py.in
+++ b/build/pgo/automation.py.in
@@ -304,16 +304,17 @@ user_pref("browser.shell.checkDefaultBro
 user_pref("shell.checkDefaultClient", false);
 user_pref("browser.warnOnQuit", false);
 user_pref("accessibility.typeaheadfind.autostart", false);
 user_pref("javascript.options.showInConsole", true);
 user_pref("layout.debug.enable_data_xbl", true);
 user_pref("browser.EULA.override", true);
 user_pref("javascript.options.jit.content", true);
 user_pref("gfx.color_management.force_srgb", true);
+user_pref("security.default_personal_cert", "Select Automatically"); // Need to client auth test be w/o any dialogs
 
 user_pref("camino.warn_when_closing", false); // Camino-only, harmless to others
 """
   prefs.append(part)
 
   locations = readLocations()
 
   # Grant God-power to all the privileged servers on which tests run.
@@ -391,43 +392,57 @@ def fillCertificateDB(profileDir):
   sslTunnelConfigPath = os.path.join(CERTS_DIR, "ssltunnel.cfg")
   sslTunnelConfig = open(sslTunnelConfigPath, "w")
   
   sslTunnelConfig.write("httpproxy:1\n")
   sslTunnelConfig.write("certdbdir:%s\n" % CERTS_DIR)
   sslTunnelConfig.write("forward:127.0.0.1:8888\n")
   sslTunnelConfig.write("listen:*:4443:pgo server certificate\n")
 
-  # Generate automatic certificate and bond custom certificates
+  # Configure automatic certificate and bind custom certificates, client authentication
   locations = readLocations()
   locations.pop(0)
   for loc in locations:
     if loc.scheme == "https" and "nocert" not in loc.options:
       customCertRE = re.compile("^cert=(?P<nickname>[0-9a-zA-Z_ ]+)")
+      clientAuthRE = re.compile("^clientauth=(?P<clientauth>[a-z]+)")
       for option in loc.options:
         match = customCertRE.match(option)
         if match:
           customcert = match.group("nickname");
-          sslTunnelConfig.write("listen:%s:%s:4443:%s\n" % (loc.host, loc.port, customcert))
-          break
+          sslTunnelConfig.write("listen:%s:%s:4443:%s\n" %
+              (loc.host, loc.port, customcert))
+
+        match = clientAuthRE.match(option)
+        if match:
+          clientauth = match.group("clientauth");
+          sslTunnelConfig.write("clientauth:%s:%s:4443:%s\n" %
+              (loc.host, loc.port, clientauth))
 
   sslTunnelConfig.close()
 
   # Pre-create the certification database for the profile
   certutil = DIST_BIN + "/certutil" + BIN_SUFFIX
+  pk12util = DIST_BIN + "/pk12util" + BIN_SUFFIX
+
   status = Process(certutil, ["-N", "-d", profileDir, "-f", pwfilePath], environment()).wait()
   if status != 0:
     return status
 
-  # Walk the cert directory and add custom CAs as trusted
+  # Walk the cert directory and add custom CAs and client certs
   files = os.listdir(CERTS_DIR)
   for item in files:
     root, ext = os.path.splitext(item)
     if ext == ".ca":
-      Process(certutil, ["-A", "-i", os.path.join(CERTS_DIR, item), "-d", profileDir, "-f", pwfilePath, "-n", root, "-t", "CT,,"], environment())
+      Process(certutil, ["-A", "-i", os.path.join(CERTS_DIR, item),
+        "-d", profileDir, "-f", pwfilePath, "-n", root, "-t", "CT,,"],
+        environment()).wait()
+    if ext == ".client":
+      Process(pk12util, ["-i", os.path.join(CERTS_DIR, item), "-w", pwfilePath,
+        "-d", profileDir], environment()).wait()
 
   os.unlink(pwfilePath)
   return 0
 
 def environment(env = None):
   if env == None:
     env = dict(os.environ)
 
--- a/build/pgo/certs/Makefile.in
+++ b/build/pgo/certs/Makefile.in
@@ -41,24 +41,34 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 _PROFILE_DIR = $(DEPTH)/_profile/pgo
 _CERTS_DIR = $(_PROFILE_DIR)/certs
 
-# Extension of files must be '.ca'
+# Following files will be added as trusted Certificate Authorities
+# to the PGO profile.
+# Extension of those files MUST BE '.ca'.
 _CERT_AUTHORITIES = \
     pgoca.ca \
     $(NULL)
 
+
+# Following files will be added as user/client certificates
+# to the PGO profile to be used for client authentication.
+# Extension of those files MUST BE '.client'.
+_CLIENT_CERTS = \
+    mochitest.client \
+    $(NULL)
+
 _SERV_FILES = \
     pgoca.p12 \
     cert8.db \
     key3.db \
     secmod.db \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
-libs:: $(_SERV_FILES) $(_CERT_AUTHORITIES)
+libs:: $(_SERV_FILES) $(_CERT_AUTHORITIES) $(_CLIENT_CERTS)
 	$(INSTALL) $^ $(_CERTS_DIR)
index f932a32b5558d5882fb80c6063b0ee06492e7ee9..96486df5f6a84adddb45fbf3ef6fc770f2b54137
GIT binary patch
literal 65536
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
k00000000000000000000000000000000000002DtFPaqk(*OVf
index be32cd6bf0ba25e2c2896e045f499d7213100069..da98cabe9507b38c08e761dd66b1ccdb3892b4b5
GIT binary patch
literal 45056
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5d72cd2b739b3127724e12d63c0c7e79c62666ed
GIT binary patch
literal 2388
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
Qt|>;}`B>A+U<v^62lsS9EdT%j
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -97,25 +97,30 @@ http://sub2.test1.example.org:8000   pri
 http://sub2.test2.example.org:8000   privileged
 http://example.com:80                privileged
 http://test1.example.com:80          privileged
 http://test2.example.com:80          privileged
 http://sub1.test1.example.com:80     privileged
 http://sub1.test2.example.com:80     privileged
 http://sub2.test1.example.com:80     privileged
 http://sub2.test2.example.com:80     privileged
+http://nocert.example.com:80         privileged
+http://requestclientcert.example.com:80         privileged
+http://requireclientcert.example.com:80         privileged
 
 https://example.com:443                privileged
 https://test1.example.com:443          privileged
 https://test2.example.com:443          privileged
 https://sub1.test1.example.com:443     privileged
 https://sub1.test2.example.com:443     privileged
 https://sub2.test1.example.com:443     privileged
 https://sub2.test2.example.com:443     privileged
 https://nocert.example.com:443         privileged,nocert
+https://requestclientcert.example.com:443         privileged,clientauth=request
+https://requireclientcert.example.com:443         privileged,clientauth=require
 
 #
 # These are subdomains of <ält.example.org>.
 #
 http://sub1.xn--lt-uia.example.org:8000   privileged
 http://sub2.xn--lt-uia.example.org:80     privileged
 http://xn--exmple-cua.test:80             privileged
 http://sub1.xn--exmple-cua.test:80        privileged
--- a/testing/mochitest/ssltunnel/ssltunnel.cpp
+++ b/testing/mochitest/ssltunnel/ssltunnel.cpp
@@ -50,21 +50,28 @@
 #include "key.h"
 #include "keyt.h"
 #include "ssl.h"
 #include "plhash.h"
 
 using std::string;
 using std::vector;
 
+enum client_auth_option {
+  caNone = 0,
+  caRequire = 1,
+  caRequest = 2
+};
+
 // Structs for passing data into jobs on the thread pool
 typedef struct {
   PRInt32 listen_port;
   string cert_nickname;
   PLHashTable* host_cert_table;
+  PLHashTable* host_clientauth_table;
 } server_info_t;
 
 typedef struct {
   PRFileDesc* client_sock;
   PRNetAddr client_addr;
   server_info_t* server_info;
 } connection_info_t;
 
@@ -118,30 +125,42 @@ string nssconfigdir;
 vector<server_info_t> servers;
 PRNetAddr remote_addr;
 PRThreadPool* threads = NULL;
 PRLock* shutdown_lock = NULL;
 PRCondVar* shutdown_condvar = NULL;
 // Not really used, unless something fails to start
 bool shutdown_server = false;
 bool do_http_proxy = false;
-bool any_host_cert_mapping = false;
+bool any_host_spec_config = false;
+
+PR_CALLBACK PRIntn ClientAuthValueComparator(const void *v1, const void *v2)
+{
+  int a = *static_cast<const client_auth_option*>(v1) -
+          *static_cast<const client_auth_option*>(v2);
+  if (a == 0)
+    return 0;
+  if (a > 0)
+    return 1;
+  else // (a < 0)
+    return -1;
+}
 
 /*
  * Signal the main thread that the application should shut down.
  */
 void SignalShutdown()
 {
   PR_Lock(shutdown_lock);
   PR_NotifyCondVar(shutdown_condvar);
   PR_Unlock(shutdown_lock);
 }
 
 bool ReadConnectRequest(server_info_t* server_info, 
-    char* bufferhead, char* buffertail, PRInt32* result, string* certificate)
+    char* bufferhead, char* buffertail, PRInt32* result, string* certificate, client_auth_option* clientauth)
 {
   if (buffertail - bufferhead < 4)
     return false;
   if (strncmp(buffertail-4, "\r\n\r\n", 4))
     return false;
 
   *result = 400;
 
@@ -152,25 +171,31 @@ bool ReadConnectRequest(server_info_t* s
   if (strcmp(token, "CONNECT")) 
     return true;
 
   token = strtok(NULL, " ");
   void* c = PL_HashTableLookup(server_info->host_cert_table, token);
   if (c)
     *certificate = (char*)c;
 
+  c = PL_HashTableLookup(server_info->host_clientauth_table, token);
+  if (c)
+    *clientauth = *static_cast<client_auth_option*>(c);
+  else
+    *clientauth = caNone;
+
   token = strtok(NULL, "/");
   if (strcmp(token, "HTTP"))
     return true;
 
   *result = 200;
   return true;
 }
 
-bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, string &certificate)
+bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, string &certificate, client_auth_option clientAuth)
 {
   const char* certnick = certificate.empty() ?
       si->cert_nickname.c_str() : certificate.c_str();
 
   AutoCert cert(PK11_FindCertFromNickname(
       certnick, NULL));
   if (!cert) {
     fprintf(stderr, "Failed to find cert %s\n", si->cert_nickname.c_str());
@@ -194,16 +219,23 @@ bool ConfigureSSLServerSocket(PRFileDesc
       != SECSuccess) {
     fprintf(stderr, "Error configuring SSL server socket\n");
     return false;
   }
 
   SSL_OptionSet(ssl_socket, SSL_SECURITY, PR_TRUE);
   SSL_OptionSet(ssl_socket, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE);
   SSL_OptionSet(ssl_socket, SSL_HANDSHAKE_AS_SERVER, PR_TRUE);
+
+  if (clientAuth != caNone)
+  {
+    SSL_OptionSet(ssl_socket, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+    SSL_OptionSet(ssl_socket, SSL_REQUIRE_CERTIFICATE, clientAuth == caRequire);
+  }
+
   SSL_ResetHandshake(ssl_socket, PR_TRUE);
 
   return true;
 }
 
 bool ConnectSocket(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
 {
   PRStatus stat = PR_Connect(fd, addr, timeout);
@@ -231,16 +263,17 @@ void HandleConnection(void* data)
   PRIntervalTime connect_timeout = PR_SecondsToInterval(2);
 
   AutoFD other_sock(PR_NewTCPSocket());
   bool client_done = false;
   bool client_error = false;
   bool connect_accepted = !do_http_proxy;
   bool ssl_updated = !do_http_proxy;
   string certificateToUse;
+  client_auth_option clientAuth;
 
   if (other_sock) 
   {
     PRInt32 numberOfSockets = 1;
 
     struct relayBuffer
     {
       char *buffer, *bufferhead, *buffertail, *bufferend;
@@ -270,17 +303,17 @@ void HandleConnection(void* data)
       { 
         if (buffertail == bufferhead) 
           buffertail = bufferhead = buffer;
       }
     } buffers[2];
 
     if (!do_http_proxy)
     {
-      if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse))
+      if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone))
         client_error = true;
       else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
         client_error = true;
       else
         numberOfSockets = 2;
     }
 
     PRPollDesc sockets[2] = 
@@ -335,17 +368,17 @@ void HandleConnection(void* data)
           }
           else
           {
             buffers[s].buffertail += bytesRead;
 
             // We have to accept and handle the initial CONNECT request here
             PRInt32 response;
             if (!connect_accepted && ReadConnectRequest(ci->server_info, buffers[s].bufferhead, buffers[s].buffertail, 
-                &response, &certificateToUse))
+                &response, &certificateToUse, &clientAuth))
             {
               // Clean the request as it would be read
               buffers[s].bufferhead = buffers[s].buffertail = buffers[s].buffer;
 
               // Store response to the oposite buffer
               if (response != 200)
               {
                 client_done = true;
@@ -393,17 +426,17 @@ void HandleConnection(void* data)
             if (buffers[s2].present())
               in_flags |= PR_POLL_WRITE;              
             else
             {
               if (!ssl_updated)
               {
                 // Proxy response has just been writen, update to ssl
                 ssl_updated = true;
-                if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse))
+                if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, clientAuth))
                 {
                   client_error = true;
                   break;
                 }
 
                 numberOfSockets = 2;
               } // sslUpdate
 
@@ -523,76 +556,146 @@ int processConfigLine(char* configLine)
   // Configure the forward address of the target server
   if (!strcmp(keyword, "forward"))
   {
     char* ipstring = strtok(NULL, ":");
     if (PR_StringToNetAddr(ipstring, &remote_addr) != PR_SUCCESS) {
       fprintf(stderr, "Invalid remote IP address: %s\n", ipstring);
       return 1;
     }
-    char* portstring = strtok(NULL, ":");
-    int port = atoi(portstring);
+    char* serverportstring = strtok(NULL, ":");
+    int port = atoi(serverportstring);
     if (port <= 0) {
-      fprintf(stderr, "Invalid remote port: %s\n", portstring);
+      fprintf(stderr, "Invalid remote port: %s\n", serverportstring);
       return 1;
     }
     remote_addr.inet.port = PR_htons(port);
 
     return 0;
   }
 
   // Configure all listen sockets and port+certificate bindings
   if (!strcmp(keyword, "listen"))
   {
     char* hostname = strtok(NULL, ":");
     char* hostportstring = NULL;
     if (strcmp(hostname, "*"))
     {
-      any_host_cert_mapping = true;
+      any_host_spec_config = true;
       hostportstring = strtok(NULL, ":");
     }
 
-    char* portstring = strtok(NULL, ":");
+    char* serverportstring = strtok(NULL, ":");
     char* certnick = strtok(NULL, ":");
 
-    int port = atoi(portstring);
+    int port = atoi(serverportstring);
     if (port <= 0) {
-      fprintf(stderr, "Invalid port specified: %s\n", portstring);
+      fprintf(stderr, "Invalid port specified: %s\n", serverportstring);
       return 1;
     }
 
     if (server_info_t* existingServer = findServerInfo(port))
     {
       char *certnick_copy = new char[strlen(certnick)+1];
       char *hostname_copy = new char[strlen(hostname)+strlen(hostportstring)+2];
 
       strcpy(hostname_copy, hostname);
       strcat(hostname_copy, ":");
       strcat(hostname_copy, hostportstring);
       strcpy(certnick_copy, certnick);
 
-      PL_HashTableAdd(existingServer->host_cert_table, hostname_copy, certnick_copy);
+      PLHashEntry* entry = PL_HashTableAdd(existingServer->host_cert_table, hostname_copy, certnick_copy);
+      if (!entry) {
+        fprintf(stderr, "Out of memory");
+        return 1;
+      }
     }
     else
     {
       server_info_t server;
       server.cert_nickname = certnick;
       server.listen_port = port;
       server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL);
       if (!server.host_cert_table)
       {
         fprintf(stderr, "Internal, could not create hash table\n");
         return 1;
       }
+      server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, ClientAuthValueComparator, NULL, NULL);
+      if (!server.host_clientauth_table)
+      {
+        fprintf(stderr, "Internal, could not create hash table\n");
+        return 1;
+      }
       servers.push_back(server);
     }
 
     return 0;
   }
   
+  if (!strcmp(keyword, "clientauth"))
+  {
+    char* hostname = strtok(NULL, ":");
+    char* hostportstring = strtok(NULL, ":");
+    char* serverportstring = strtok(NULL, ":");
+
+    int port = atoi(serverportstring);
+    if (port <= 0) {
+      fprintf(stderr, "Invalid port specified: %s\n", serverportstring);
+      return 1;
+    }
+
+    if (server_info_t* existingServer = findServerInfo(port))
+    {
+      char* authoptionstring = strtok(NULL, ":");
+      client_auth_option* authoption = new client_auth_option;
+      if (!authoption) {
+        fprintf(stderr, "Out of memory");
+        return 1;
+      }
+
+      if (!strcmp(authoptionstring, "require"))
+        *authoption = caRequire;
+      else if (!strcmp(authoptionstring, "request"))
+        *authoption = caRequest;
+      else if (!strcmp(authoptionstring, "none"))
+        *authoption = caNone;
+      else
+      {
+        fprintf(stderr, "Incorrect client auth option modifier for host '%s'", hostname);
+        return 1;
+      }
+
+      any_host_spec_config = true;
+
+      char *hostname_copy = new char[strlen(hostname)+strlen(hostportstring)+2];
+      if (!hostname_copy) {
+        fprintf(stderr, "Out of memory");
+        return 1;
+      }
+
+      strcpy(hostname_copy, hostname);
+      strcat(hostname_copy, ":");
+      strcat(hostname_copy, hostportstring);
+
+      PLHashEntry* entry = PL_HashTableAdd(existingServer->host_clientauth_table, hostname_copy, authoption);
+      if (!entry) {
+        fprintf(stderr, "Out of memory");
+        return 1;
+      }
+    }
+    else
+    {
+      fprintf(stderr, "Server on port %d for client authentication option is not defined, use 'listen' option first", port);
+      return 1;
+    }
+
+    return 0;
+  }
+
   // Configure the NSS certificate database directory
   if (!strcmp(keyword, "certdbdir"))
   {
     nssconfigdir = strtok(NULL, "\n");
     return 0;
   }
 
   printf("Error: keyword \"%s\" unexpected\n", keyword);
@@ -628,31 +731,38 @@ int parseConfigFile(const char* filePath
 
   // Check mandatory items
   if (nssconfigdir.empty())
   {
     printf("Error: missing path to NSS certification database\n,use certdbdir:<path> in the config file\n");
     return 1;
   }
 
-  if (any_host_cert_mapping && !do_http_proxy)
+  if (any_host_spec_config && !do_http_proxy)
   {
-    printf("Warning: any host-specific certificate configurations are ignored, add httpproxy:1 to allow them\n");
+    printf("Warning: any host-specific configurations are ignored, add httpproxy:1 to allow them\n");
   }
 
   return 0;
 }
 
-PRIntn freeHashItems(PLHashEntry *he, PRIntn i, void *arg)
+PRIntn freeHostCertHashItems(PLHashEntry *he, PRIntn i, void *arg)
 {
   delete [] (char*)he->key;
   delete [] (char*)he->value;
   return HT_ENUMERATE_REMOVE;
 }
 
+PRIntn freeClientAuthHashItems(PLHashEntry *he, PRIntn i, void *arg)
+{
+  delete [] (char*)he->key;
+  delete (client_auth_option*)he->value;
+  return HT_ENUMERATE_REMOVE;
+}
+
 int main(int argc, char** argv)
 {
   char* configFilePath;
   if (argc == 1)
     configFilePath = "ssltunnel.cfg";
   else
     configFilePath = argv[1];
 
@@ -669,19 +779,25 @@ int main(int argc, char** argv)
       "       # Forward/proxy all requests in raw to 127.0.0.1:8888.\n"
       "       forward:127.0.0.1:8888\n\n"
       "       # Accept connections on port 4443 or 5678 resp. and authenticate\n"
       "       # to any host ('*') using the 'server cert' or 'server cert 2' resp.\n"
       "       listen:*:4443:server cert\n"
       "       listen:*:5678:server cert 2\n\n"
       "       # Accept connections on port 4443 and authenticate using\n"
       "       # 'a different cert' when target host is 'my.host.name:443'.\n"
-      "       # This works only in httpproxy mode and has higher priority\n"
-      "       # then the previews option.\n"
-      "       listen:my.host.name:443:4443:a different cert\n",
+      "       # This only works in httpproxy mode and has higher priority\n"
+      "       # than the previous option.\n"
+      "       listen:my.host.name:443:4443:a different cert\n\n"
+      "       # To make a specific host require or just request a client certificate\n"
+      "       # to authenticate use following options. This can only be used\n"
+      "       # in httpproxy mode and after the 'listen' option has been specified.\n"
+      "       # You also have to specify the tunnel listen port.\n"
+      "       clientauth:requesting-client-cert.host.com:443:4443:request\n"
+      "       clientauth:requiring-client-cert.host.com:443:4443:require\n",
       configFilePath);
     return 1;
   }
 
   // create a thread pool to handle connections
   threads = PR_CreateThreadPool(std::max<PRInt32>(INITIAL_THREADS,
                                                   servers.size()*2),
                                 std::max<PRInt32>(MAX_THREADS,
@@ -759,15 +875,17 @@ int main(int argc, char** argv)
   PR_DestroyLock(shutdown_lock);
   if (NSS_Shutdown() == SECFailure) {
     fprintf(stderr, "Leaked NSS objects!\n");
   }
   
   for (vector<server_info_t>::iterator it = servers.begin();
        it != servers.end(); it++) 
   {
-    PL_HashTableEnumerateEntries(it->host_cert_table, freeHashItems, NULL);
+    PL_HashTableEnumerateEntries(it->host_cert_table, freeHostCertHashItems, NULL);
+    PL_HashTableEnumerateEntries(it->host_clientauth_table, freeClientAuthHashItems, NULL);
     PL_HashTableDestroy(it->host_cert_table);
+    PL_HashTableDestroy(it->host_clientauth_table);
   }
 
   PR_Cleanup();
   return 0;
 }