Bug 428009, hook up ssltunnel to mochitest
☠☠ backed out by 6b0b6549341b ☠ ☠
authorhonzab@allpeers.com
Sat, 16 Aug 2008 07:38:03 +0200
changeset 16730 3770c42e3118c5c265d614f30594f99a85fb07ca
parent 16729 f593ae83554f9ac69edcc1de1b8316874502e76a
child 16731 6b0b6549341b368bef9c95b203a2ff5297144abd
push idunknown
push userunknown
push dateunknown
bugs428009
milestone1.9.1a2pre
Bug 428009, hook up ssltunnel to mochitest Initial work by Jeff Walden, Patch contributed by Honza Bambas r=jwalden, r=cbiesinger, r=sayrer, r=ted.mielczarek, r=kaie
build/Makefile.in
build/pgo/Makefile.in
build/pgo/automation.py.in
build/pgo/certs/Makefile.in
build/pgo/certs/pgoca.ca
build/pgo/certs/pgoca.p12
build/pgo/genpgocert.py.in
build/pgo/server-locations.txt
security/manager/Makefile.in
testing/mochitest/Makefile.in
testing/mochitest/server.js
testing/mochitest/ssltunnel/ssltunnel.cpp
toolkit/mozapps/installer/packager.mk
--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -74,19 +74,23 @@ browser_path = \"$(DIST)/$(MOZ_APP_DISPL
 else
 browser_path = \"$(DIST)/$(MOZ_APP_DISPLAYNAME).app/Contents/MacOS/$(PROGRAM)\"
 endif
 else
 browser_path = \"$(DIST)/bin/$(PROGRAM)\"
 endif
 endif
 
+_CERTS_DIR = _profile/pgo/certs
+
 AUTOMATION_PPARGS = 	\
 			-DBROWSER_PATH=$(browser_path) \
 			-DXPC_BIN_PATH=\"$(DIST)/bin\" \
+			-DBIN_SUFFIX=\"$(BIN_SUFFIX)\" \
+			-DCERTS_DIR=\"../$(_CERTS_DIR)\" \
 			$(NULL)
 
 ifeq ($(OS_ARCH),Darwin)
 AUTOMATION_PPARGS += -DIS_MAC=1
 else
 AUTOMATION_PPARGS += -DIS_MAC=0
 endif
 
--- a/build/pgo/Makefile.in
+++ b/build/pgo/Makefile.in
@@ -38,30 +38,37 @@
 
 DEPTH		= ../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir = build/pgo
 
 include $(DEPTH)/config/autoconf.mk
+
+DIRS = \
+		certs \
+		$(NULL)
+
 include $(topsrcdir)/config/rules.mk
 
 # Stuff to make a build with a profile
 _PROFILE_DIR = $(DEPTH)/_profile/pgo
+_CERTS_DIR = $(_PROFILE_DIR)/certs
+_CERTS_SRC_DIR = $(srcdir)/certs
 
 _PGO_FILES = 	\
 		automation.py \
 		profileserver.py \
+		genpgocert.py \
 		index.html \
 		quit.js \
 		server-locations.txt \
 		$(NULL)
 
-
 ifeq ($(USE_SHORT_LIBNAME), 1)
 PROGRAM = $(MOZ_APP_NAME)$(BIN_SUFFIX)
 else
 PROGRAM = $(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
 endif
 
 ifeq ($(MOZ_BUILD_APP),camino)
 browser_path = \"$(DIST)/Camino.app/Contents/MacOS/Camino\"
@@ -75,16 +82,19 @@ endif
 else
 browser_path = \"$(DIST)/bin/$(PROGRAM)\"
 endif
 endif
 
 AUTOMATION_PPARGS = 	\
 			-DBROWSER_PATH=$(browser_path) \
 			-DXPC_BIN_PATH=\"$(DIST)/bin\" \
+			-DBIN_SUFFIX=\"$(BIN_SUFFIX)\" \
+			-DCERTS_DIR=\"$(_CERTS_DIR)\" \
+			-DCERTS_SRC_DIR=\"$(_CERTS_SRC_DIR)\" \
 			$(NULL)
 
 ifeq ($(OS_ARCH),Darwin)
 AUTOMATION_PPARGS += -DIS_MAC=1
 else
 AUTOMATION_PPARGS += -DIS_MAC=0
 endif
 
@@ -97,16 +107,20 @@ endif
 ifeq ($(host_os), cygwin)
 AUTOMATION_PPARGS += -DIS_CYGWIN=1
 endif
 
 automation.py: automation.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
 
+genpgocert.py: genpgocert.py.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
+	$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
+
 profileserver.py: profileserver.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $^ > $@
 	chmod +x $@
 
-GARBAGE += automation.py profileserver.py
+GARBAGE += automation.py profileserver.py genpgocert.py
 
 libs:: $(_PGO_FILES)
 	$(INSTALL) $^ $(_PROFILE_DIR)
--- a/build/pgo/automation.py.in
+++ b/build/pgo/automation.py.in
@@ -69,20 +69,22 @@ for setting up the browser environment.
 #expand IS_WIN32 = len("__WIN32__") != 0
 #expand IS_MAC = __IS_MAC__ != 0
 #ifdef IS_CYGWIN
 #expand IS_CYGWIN = __IS_CYGWIN__ == 1
 #else
 IS_CYGWIN = False
 #endif
 #expand IS_CAMINO = __IS_CAMINO__ != 0
+#expand BIN_SUFFIX = __BIN_SUFFIX__
 
 UNIXISH = not IS_WIN32 and not IS_MAC
 
 #expand DEFAULT_APP = "./" + __BROWSER_PATH__
+#expand CERTS_DIR = __CERTS_DIR__
 
 ###########
 # LOGGING #
 ###########
 
 # We use the logging system here primarily because it'll handle multiple
 # threads, which is needed to process the output of the server and application
 # processes simultaneously.
@@ -98,42 +100,58 @@ log.addHandler(handler)
 
 class Process:
   """
   Represents a subprocess of this process.  We don't just directly use the
   subprocess module here because we want compatibility with Python 2.3 on
   non-Windows platforms.  :-(
   """
 
-  def __init__(self, command, args, env):
+  def __init__(self, command, args, env, inputdata = None):
     """
     Creates a process representing the execution of the given command, which
     must be an absolute path, with the given arguments in the given environment.
     The process is then started.
     """
     command = os.path.abspath(command)
     if IS_WIN32:
+      import tempfile
       import subprocess
+      
+      if inputdata:
+        inputfile = tempfile.TemporaryFile()
+        inputfile.write(inputdata)
+        inputfile.seek(0)
+      else:
+        inputfile = None
+        
       cmd = [command]
       cmd.extend(args)
       p = subprocess.Popen(cmd, env = env,
                            stdout = subprocess.PIPE,
-                           stderr = subprocess.STDOUT)
+                           stderr = subprocess.STDOUT,
+                           stdin = inputfile)
       self._out = p.stdout
     else:
       import popen2
       cmd = []
-      for (k, v) in env.iteritems():
-        cmd.append(k + "='" + v + "' ")
+      if env:
+        for (k, v) in env.iteritems():
+          cmd.append(k + "='" + v + "' ")
+          
       cmd.append("'" + command + "'")
       cmd.extend(map(lambda x: "'" + x + "'", args))
       cmd = " ".join(cmd)
       p = popen2.Popen4(cmd)
       self._out = p.fromchild
 
+      if inputdata:
+        p.tochild.write(inputdata)
+        p.tochild.close()
+
     self._process = p
     self.pid = p.pid
     
     self._thread = threading.Thread(target = lambda: self._run())
     self._thread.start()
 
   def _run(self):
     "Continues execution of this process on a separate thread."
@@ -160,18 +178,23 @@ class Process:
   def wait(self):
     "Waits for this process to finish, then returns the process's status."
     self._thread.join()
     return self._status
 
   def kill(self):
     "Kills this process."
     try:
-      if not IS_WIN32: # XXX
+      if not IS_WIN32:
         os.kill(self._process.pid, signal.SIGKILL)
+      else:
+        import subprocess
+        pid = "%i" % self.pid
+        process = subprocess.Popen(["taskkill", "/F", "/PID", pid])
+        process.wait()
     except:
       pass
 
 
 #################
 # PROFILE SETUP #
 #################
 
@@ -196,40 +219,40 @@ class Location:
 
   def __init__(self, scheme, host, port, options):
     self.scheme = scheme
     self.host = host
     self.port = port
     self.options = options
 
 
-def readLocations():
+def readLocations(locationsPath = "server-locations.txt"):
   """
   Reads the locations at which the Mochitest HTTP server is available from
   server-locations.txt.
   """
 
-  locationFile = codecs.open("server-locations.txt", "r", "UTF-8")
+  locationFile = codecs.open(locationsPath, "r", "UTF-8")
 
   # Perhaps more detail than necessary, but it's the easiest way to make sure
   # we get exactly the format we want.  See server-locations.txt for the exact
   # format guaranteed here.
   lineRe = re.compile(r"^(?P<scheme>[a-z][-a-z0-9+.]*)"
                       r"://"
                       r"(?P<host>"
                         r"\d+\.\d+\.\d+\.\d+"
                         r"|"
                         r"(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*"
                         r"[a-z](?:[-a-z0-9]*[a-z0-9])?"
                       r")"
                       r":"
                       r"(?P<port>\d+)"
                       r"(?:"
                       r"\s+"
-                      r"(?P<options>\w+(?:,\w+)*)"
+                      r"(?P<options>\S+(?:,\S+)*)"
                       r")?$")
   locations = []
   lineno = 0
   seenPrimary = False
   for line in locationFile:
     lineno += 1
     if line.startswith("#") or line == "\n":
       continue
@@ -313,23 +336,30 @@ function FindProxyForURL(url, host)
                          '://' +
                          '(?:[^/@]*@)?' +
                          '(.*?)' +
                          '(?::(\\\\\\\\d+))?/');
   var matches = regex.exec(url);
   if (!matches)
     return 'DIRECT';
   var isHttp = matches[1] == 'http';
+  var isHttps = matches[1] == 'https';
   if (!matches[3])
-    matches[3] = isHttp ? '80' : '443';
+  {
+    if (isHttp) matches[3] = '80';
+    if (isHttps) matches[3] = '443';
+  }
+    
   var origin = matches[1] + '://' + matches[2] + ':' + matches[3];
   if (origins.indexOf(origin) < 0)
     return 'DIRECT';
   if (isHttp)
-    return 'PROXY localhost:8888';
+    return 'PROXY 127.0.0.1:8888';
+  if (isHttps)
+    return 'PROXY 127.0.0.1:4443';
   return 'DIRECT';
 }""" % { "origins": origins }
   pacURL = "".join(pacURL.splitlines())
 
   part = """
 user_pref("network.proxy.type", 2);
 user_pref("network.proxy.autoconfig_url", "%(pacURL)s");
 
@@ -337,22 +367,80 @@ user_pref("camino.use_system_proxy_setti
 """ % {"pacURL": pacURL}
   prefs.append(part)
 
   # write the preferences
   prefsFile = open(profileDir + "/" + "user.js", "a")
   prefsFile.write("".join(prefs))
   prefsFile.close()
 
+def fillCertificateDB(profileDir):
+
+  pwfilePath = os.path.join(profileDir, ".crtdbpw")
+  
+  pwfile = open(pwfilePath, "w")
+  pwfile.write("\n")
+  pwfile.close()
+
+  # Create head of the ssltunnel configuration file
+  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
+  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_ ]+)")
+      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.close()
+
+  # Pre-create the certification database for the profile
+  certutil = DIST_BIN + "/certutil" + BIN_SUFFIX
+  status = Process(certutil, ["-N", "-d", profileDir, "-f", pwfilePath], None).wait()
+  if status != 0:
+    return status
+
+  # Walk the cert directory and add custom CAs as trusted
+  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,,"], None)
+
+  os.unlink(pwfilePath)
+  return 0
+
 
 ###############
 # RUN THE APP #
 ###############
 
 def runApp(testURL, env, app, profileDir, extraArgs):
+  # create certificate database for the profile
+  certificateStatus = fillCertificateDB(profileDir)
+  if certificateStatus != 0:
+    log.info("ERROR FAIL Certificate integration")
+    return certificateStatus
+
+  ssltunnel = DIST_BIN + "/ssltunnel" + BIN_SUFFIX
+  ssltunnelProcess = Process(ssltunnel, [os.path.join(CERTS_DIR, "ssltunnel.cfg")], None)
+  log.info("SSL tunnel pid: %d", ssltunnelProcess.pid)
+  
   "Run the app, returning the time at which it was started."
   # mark the start
   start = datetime.now()
 
   # now run with the profile we created
   cmd = app
   if IS_MAC and not IS_CAMINO and not cmd.endswith("-bin"):
     cmd += "-bin"
@@ -374,9 +462,11 @@ def runApp(testURL, env, app, profileDir
     args.append((testURL))
   args.extend(extraArgs)
   proc = Process(cmd, args, env = env)
   log.info("Application pid: %d", proc.pid)
   status = proc.wait()
   if status != 0:
     log.info("ERROR FAIL Exited with code %d during test run", status)
 
+  ssltunnelProcess.kill()
+  
   return start
new file mode 100644
--- /dev/null
+++ b/build/pgo/certs/Makefile.in
@@ -0,0 +1,65 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# 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 Mozilla test code
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Honza Bambas <honzab@firemni.cz>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+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 '.server'
+_SERVER_CERTS = \
+    $(NULL)
+  
+# Extension of files must be '.ca'
+_CERT_AUTHORITIES = \
+    pgoca.ca \
+    $(NULL)
+
+_SERV_FILES = \
+    pgoca.p12 \
+    $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+
+libs:: $(_SERV_FILES) $(_SERVER_CERTS) $(_CERT_AUTHORITIES)
+	$(INSTALL) $^ $(_CERTS_DIR)
new file mode 100644
--- /dev/null
+++ b/build/pgo/certs/pgoca.ca
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICXTCCAcagAwIBAgIBATANBgkqhkiG9w0BAQUFADBqMSQwIgYDVQQLExtQcm9m
+aWxlIEd1aWRlZCBPcHRpbWl6YXRpb24xGDAWBgNVBAoTD01vemlsbGEgVGVzdGlu
+ZzEoMCYGA1UEAxMfVGVtcG9yYXJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0w
+ODA1MjIwMDM4MDVaFw0xODA1MjIwMDM4MDVaMGoxJDAiBgNVBAsTG1Byb2ZpbGUg
+R3VpZGVkIE9wdGltaXphdGlvbjEYMBYGA1UEChMPTW96aWxsYSBUZXN0aW5nMSgw
+JgYDVQQDEx9UZW1wb3JhcnkgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MIGfMA0GCSqG
+SIb3DQEBAQUAA4GNADCBiQKBgQDg6iipAXGZYmgTcHfx8M2hcLqmqDalcj7sZ1A7
+a3LiCBb+1uHKKy9hUxRUe61aJF4NgMAF5oc+HpXN0hpvkiNHxqqD7R6hrkP3gAJ3
+eczEFKsFUI6AqaCL0+xpyhaaZmmarcHxU+PL2h5zq6VssxfBAsO0DkzWzk6E8vM+
+jrku7QIDAQABoxMwETAPBgNVHRMECDAGAQH/AgEAMA0GCSqGSIb3DQEBBQUAA4GB
+ALPbn3Ztg0m8qDt8Vkf5You6HEqIxZe+ffDTrfq/L7ofHk/OXEpL7OWKRHU33pNG
+QS8khBG+sO461C51s6u9giW+eq2PaQv2HGASBpDbvPqc/Hf+zupZsdsXzHv6rt0V
+lu5B6nOpMse1nhA494i1ARSuBNzLv5mas38YWG8Rr6jR
+-----END CERTIFICATE-----
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4867c286bbf6950a51078caedff875a945b2d93c
GIT binary patch
literal 1758
zc${sMdpOgJAIHBN78`TF<T8=VgiM)DS-B^hOLCbbx0ujsNJz{!646d#WG<UHt|z%f
z9CM2-Dj^jPM@nvW)WZp3&e=K7`JLx?p5O2NJnug~@6Yr3{PljJXkd^CiUyH5kmi`S
zAqCn4;?i_zq72A26z%`SA_x>iL{(@d2<-2bBoqYZGQjsx3~)7SKLq-pSol^^ROU9y
z2lHW`n;59<9x_*?j{t)}q6{z%{L!UX{ZbSQno$zsG495qK>cr3%dSscZ=S^>BzLI4
z3sCViyS2pJ9(UUIh{%;R$XN5{YU#7&^(r)`;!cy!8Q7KKs=<lkQ2dE*L9l8Lr-@&*
zJ+^}Ol)c47stsgsoM&a7C{LM;mC?)CBp+uyFcUm4bZd7IQRjyt8;ka2U%cP8OR2f(
zH2_XG;UzEz4BE1uT7KO-6z=v(RB)48sZT2~w92;C8uSVTtPi+|3w+DN7Kvx-1gm*f
z%|G-n>50AF(Bl-0%@XZk3X{cW&#biEm+!Meut;nOJSic9cLXn0aIDtTtkEjLS$753
zd_DG}<CM2l9DJv5x1IkqJqfu~;v;E7aAa$dmQ$ZK%6s5n`lZ3LzNa5!<+AP;m@8X@
zkA^vHxp_y$fKs{*cN+}Qe(^@!vR{ZL78*V-rOR<66H0+@{iftWwQk#vJ9(aE#UORd
z>=PDSE6SqS2$#J@GxQfHql`T+tlEvtZQZw2&NNb#Ci4N^nnwza>Ws^$*PqDoMu;^q
zj-^4``S^h_IwA0Vs>CiPImf@@Rm;~NvB)pM!PcsC2YoUZNsPhC(iEmeMZZ;Z*_+-&
zhuJO0BCQn1X!we)J$Amg?uOD2ltw|sm-wkvka&gbshZP2GVkcvOxFw%d)ffJeb2f$
zr?XS#DqX|;=I~wW2R{?12?v(zIf-Ny!#W2zo|j5&zxQK%E@?cDz<{Fr3T;p;O6Oh{
zVQ!Ps5uZLkA#-FMheyeBY7C^_e%zWtgzVSG!LY5Wx@!o}nd;!0EXHQxQRUD9<KK?7
zxd6lVu1*v2v$fkV+OtT~8{$Uy<8v_=Pg%I~w4dtunR=m2{wcSFf~huQ9}o$g11P{g
zAO;`-7lac9hz5d$z9P(mfgoYG|2a@JRT5ROHa>Q>HX46IPQg%9pVC&J4p+qjf9b!$
zfpCQ!fkFSuK>y+d@+T)@<lZ`O=oM*=$=0Zj^hmal6Rwaq@FL&wSETP;{%p3h(}F(f
z6{c-AMN@2nFylJ0?PM_>E~B2=WV$N7xVeZboJz8jjBqf|t9~`v?#uztnW7~DD-Sp8
z`g6K+zWlubN}Fcr-4KdO#kYrc7Q=m&o=BEwJ4&J4$V1*v?Pzp{K@uQeh3x(;i3|Vs
z=y5}DYn7bz?s)94^j21Eb?LG*uA;0Ve3RanVc5=1S}M?NH7Hpxsec-{F*g$vb+NkG
zLaO&u{Sw6-pHHaiJD|d2)tDw8REfnl#i)&^wtS4d)V0L*TjuSAK1||k;-_C@bfPSt
zm{%zu<lR8qJhKTO+fg9YYTA$GHYC=6Zkf;6w{`&z)jAS_uL?M`YD~5}OPM`XS+1?&
zYdKU&)yp<xZGC|j+52Xhd7-U2g{QJH3-0Rk@9WDCl?C)T-}sq*n8GZRTqcaZXaN)K
zU5e+La?vrBR_wDOz2K?1d9fFY{z;FUeGok&<I^hbui5ev(dEZYhPXk9+D_f|oaG|S
z8e&^phpn~m0X%(>VLZyg;**uU^KTE}IEsrI#QyvsiJzJMuvjoii=40<N_RD{p00hT
z0QdhUZQEP(rj3Vt7MV$3<9O@2d-1nVnT#&D>*BD#(PwQ>GUu;eHR@DtUL^3SD%IJr
z2RF77zyIlSZm~-0Hyr4^fP=rB+7a<!D|q3g;wxPDllrce<GZT%#O>GN_omjUq0-Fg
zfgr=38O-zjCG;b!vD7Y`VA;FD!wern8SdS+lG~2e#<IyscUGXP1N?e_ic^TVQ`=ec
zVHp(iU6u@K%H~md-sH!ZAE_x{-p|bw;xn&1HBx)-B(TbIoH0zm&Uf+gcT)@BF|gqE
zd#u$#OI&k+mmpvn$^k%^m;~FdPMO1r9aC1!x3nea(7YiXSdNYwu{J`Ar1Q|sXY3_n
zB#>wty64t{zNEjanUppc>P4I<{v<VrIT;0EOO<x4D(`>$#)y3S6YhhCZB{^FzCcv`
z*wXW^Ll>|Z;D3ZdqqI;G5HUlz2v}7N5LLJ|>ZKi%KDZ&d%(iNJ9li*QgH?*kOqhM$
TquiYtshRQ|Rm^{w1pt2lWse6P
new file mode 100644
--- /dev/null
+++ b/build/pgo/genpgocert.py.in
@@ -0,0 +1,217 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# 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 mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Honza Bambas <honzab@firemni.cz>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import automation
+import os
+import re
+import sys
+
+#expand DIST_BIN = "./" + __XPC_BIN_PATH__
+#expand BIN_SUFFIX = __BIN_SUFFIX__
+#expand CERTS_DIR = __CERTS_DIR__
+#expand CERTS_SRC_DIR = __CERTS_SRC_DIR__
+
+SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
+
+dbFiles = ["cert8.db", "cert9.db", "key3.db", "key4.db", "secmod.db"]
+
+def dbPaths(dir):
+  return map(lambda f: os.path.join(dir, f), dbFiles)
+
+def runUtil(util, args, inputdata = None):
+  myEnvironment = dict(os.environ)
+  if automation.UNIXISH:
+    myEnvironment["LD_LIBRARY_PATH"] = os.path.join(SCRIPT_DIR, automation.DIST_BIN)
+
+  proc = automation.Process(util, args, myEnvironment, inputdata)
+  return proc.wait()
+
+
+def createRandomFile(randomFile):
+  import random
+  file = open(randomFile, "wb");
+  for count in xrange(0, 2048):
+    file.write(chr(random.randint(0, 255)))
+  file.close()
+
+
+def createCertificateAuthority(dbDir, srcDir):
+  certutil = DIST_BIN + "/certutil" + BIN_SUFFIX
+  pk12util = DIST_BIN + "/pk12util" + BIN_SUFFIX
+
+  tempDbDir = os.path.join(dbDir, ".temp")
+  if not os.path.exists(tempDbDir):
+    os.mkdir(tempDbDir)
+  
+  pwfilePath = os.path.join(tempDbDir, ".crtdbpw")
+  rndfilePath = os.path.join(tempDbDir, ".rndfile")
+  pgoCAModulePathSrc = os.path.join(srcDir, "pgoca.p12")
+  pgoCAPathSrc = os.path.join(srcDir, "pgoca.ca")
+  pgoCAModulePath = os.path.join(srcDir, "pgoca.p12")
+  pgoCAPath = os.path.join(srcDir, "pgoca.ca")
+  
+  pwfile = open(pwfilePath, "w")
+  pwfile.write("\n")
+  pwfile.close()
+
+  for dbFile in dbPaths(tempDbDir):
+    if os.path.exists(dbFile):
+      os.unlink(dbFile)
+
+  # Create temporary certification database for CA generation
+  status = runUtil(certutil, ["-N", "-d", tempDbDir, "-f", pwfilePath])
+  if status != 0:
+    return status
+
+  createRandomFile(rndfilePath);
+  status = runUtil(certutil, ["-S", "-d", tempDbDir, "-s", "CN=Temporary Certificate Authority, O=Mozilla Testing, OU=Profile Guided Optimization", "-t", "C,,", "-x", "-m", "1", "-v", "120", "-n", "pgo temporary ca", "-2", "-f", pwfilePath, "-z", rndfilePath], "Y\n0\nN\n")
+  if status != 0:
+    return status
+ 
+  status = runUtil(certutil, ["-L", "-d", tempDbDir, "-n", "pgo temporary ca", "-a", "-o", pgoCAPathSrc, "-f", pwfilePath])
+  if status != 0:
+    return status
+ 
+  status = runUtil(pk12util, ["-o", pgoCAModulePathSrc, "-n", "pgo temporary ca", "-d", tempDbDir, "-w", pwfilePath, "-k", pwfilePath])
+  if status != 0:
+    return status
+    
+  for dbFile in dbPaths(tempDbDir):
+    if os.path.exists(dbFile):
+      os.unlink(dbFile)
+  os.unlink(pwfilePath)
+  os.unlink(rndfilePath)
+  os.rmdir(tempDbDir)
+  return 0
+
+
+def createSSLServerCertificate(dbDir):
+  certutil = DIST_BIN + "/certutil" + BIN_SUFFIX
+  pk12util = DIST_BIN + "/pk12util" + BIN_SUFFIX
+
+  pwfilePath = os.path.join(dbDir, ".crtdbpw")
+  rndfilePath = os.path.join(dbDir, ".rndfile")
+  pgoCAPath = os.path.join(dbDir, "pgoca.p12")
+  
+  pwfile = open(pwfilePath, "w")
+  pwfile.write("\n")
+  pwfile.close()
+
+  for dbFile in dbPaths(dbDir):
+    if os.path.exists(dbFile):
+      os.unlink(dbFile)
+
+  # Create certification database for ssltunnel
+  status = runUtil(certutil, ["-N", "-d", dbDir, "-f", pwfilePath])
+  if status != 0:
+    return status
+
+  status = runUtil(pk12util, ["-i", pgoCAPath, "-w", pwfilePath, "-d", dbDir, "-k", pwfilePath])
+  if status != 0:
+    return status
+
+  # Generate automatic certificate
+  locations = automation.readLocations(os.path.join(dbDir, "../server-locations.txt"))
+  locations.pop(0)
+  locationsParam = ""
+  firstLocation = ""
+  for loc in locations:
+    if loc.scheme == "https" and "nocert" not in loc.options:
+      customCertOption = False
+      customCertRE = re.compile("^cert=(?:\w+)")
+      for option in loc.options:
+        match = customCertRE.match(option)
+        if match:
+          customCertOption = True
+          break
+
+      if not customCertOption:
+        if len(locationsParam) > 0:
+          locationsParam += ","
+        locationsParam += loc.host
+        
+        if firstLocation == "":
+          firstLocation = loc.host
+      
+  if firstLocation == "":
+    print "Nothing to generate, no automatic secure hosts specified"
+  else:
+    createRandomFile(rndfilePath);
+    status = runUtil(certutil, ["-S", "-s", "CN=%s" % firstLocation, "-t", "Pu,,", "-c", "pgo temporary ca", "-m", "2", "-8", locationsParam, "-v", "12", "-n", "pgo server certificate", "-d", dbDir, "-z", rndfilePath, "-f", pwfilePath])
+    if status != 0:
+      return status
+    
+  # Walk the cert directory and add what necessary
+  files = os.listdir(CERTS_DIR)
+  for item in files:
+    root, ext = os.path.splitext(item)
+    if ext == ".server":
+      runUtil(pk12util, ["-i", os.path.join(CERTS_DIR, item), "-d", dbDir, "-k", pwfilePath, "-w", pwfilePath])
+            
+  os.unlink(pwfilePath)
+  os.unlink(rndfilePath)
+  return 0
+
+
+if len(sys.argv) == 1:
+  print "Specify --gen-server or --gen-ca"
+  sys.exit(1)
+
+if sys.argv[1] == "--gen-server":
+  certificateStatus = createSSLServerCertificate(CERTS_DIR)
+  if certificateStatus != 0:
+    print "ERROR FAIL: SSL Server Certificate generation"
+  
+  sys.exit(certificateStatus)
+  
+if sys.argv[1] == "--gen-ca":
+  certificateStatus = createCertificateAuthority(CERTS_DIR, CERTS_SRC_DIR)
+  if certificateStatus != 0:
+    print "ERROR FAIL: Certificate Authority generation"
+  else:
+    print "\n\n"
+    print "==================================================="
+    print " IMPORTANT:"
+    print " To use this new certificate authority in tests"
+    print " run 'make' at testing/mochitest"
+    print "==================================================="
+
+  sys.exit(certificateStatus)
+
+print "Invalid option specified"
+sys.exit(1)
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -47,21 +47,34 @@
 # and a comma-separated list of options (if indeed any options are needed).
 #
 # The format of an origin is, referring to RFC 2396, a scheme (either "http" or
 # "https"), followed by "://", followed by a host, followed by ":", followed by
 # a port number.  The colon and port number must be present even if the port
 # number is the default for the protocol.
 #
 # Unrecognized options are ignored.  Recognized options are "primary" and
-# "privileged".  "primary" denotes a location which is the canonical location of
+# "privileged", "nocert", "cert=some_cert_nickname". 
+#
+# "primary" denotes a location which is the canonical location of
 # the server; this location is the one assumed for requests which don't
-# otherwise identify a particular origin (e.g. HTTP/1.0 requests).  "privileged"
-# denotes a location which should have the ability to request elevated
-# privileges; the default is no privileges.
+# otherwise identify a particular origin (e.g. HTTP/1.0 requests).  
+#
+# "privileged" denotes a location which should have the ability to request 
+# elevated privileges; the default is no privileges.
+#
+# "nocert" makes sense only for https:// hosts and means there is not
+# any certificate automatically generated for this host.
+#
+# "cert=nickname" tells the pgo server to use a particular certificate
+# for this host. The certificate is referenced by its nickname that must
+# not contain any spaces. The certificate  key files (PKCS12 modules)
+# for custom certification are loaded from build/pgo/ssltunnel/certs
+# directory. When new certificate is added to this dir pgo/ssltunnel
+# must be builded then.
 #
 
 #
 # This is the primary location from which tests run.
 #
 http://localhost:8888   primary,privileged
 
 #
@@ -85,32 +98,49 @@ http://sub2.test2.example.org:8000   pri
 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
 
+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
+
 #
 # 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
 
+https://xn--hxajbheg2az3al.xn--jxalpdlp:443        privileged
+https://sub1.xn--hxajbheg2az3al.xn--jxalpdlp:443   privileged
+
 #
 # These are subdomains of <παράδειγμα.δοκιμή>, the Greek IDN for example.test.
 #
 http://xn--hxajbheg2az3al.xn--jxalpdlp:80        privileged
 http://sub1.xn--hxajbheg2az3al.xn--jxalpdlp:80   privileged
 
 #
 # These hosts are used in tests which exercise privilege-granting functionality;
 # we could reuse some of the names above, but specific names make it easier to
 # distinguish one from the other in tests (as well as what functionality is
 # being tested).
 #
 http://sectest1.example.org:80       privileged
 http://sub.sectest2.example.org:80   privileged
 http://sectest2.example.org:80
 http://sub.sectest1.example.org:80
+
+https://sectest1.example.org:443       privileged
+https://sub.sectest2.example.org:443   privileged
+https://sectest2.example.org:443
+https://sub.sectest1.example.org:443
--- a/security/manager/Makefile.in
+++ b/security/manager/Makefile.in
@@ -276,18 +276,26 @@ ifdef MOZ_XUL
 	$(MAKE) -C pki $@
 endif
 
 libs::
 ifndef MOZ_NATIVE_NSS
 	$(MAKE) -C $(topsrcdir)/security/coreconf $(DEFAULT_GMAKE_FLAGS)
 	$(MAKE) -C $(topsrcdir)/security/dbm $(DEFAULT_GMAKE_FLAGS) 
 	$(MAKE) -C $(topsrcdir)/security/nss/lib $(DEFAULT_GMAKE_FLAGS)
+ifdef ENABLE_TESTS
+	# Need certutil binary for mochitest certificates generation
+	$(MAKE) -C $(topsrcdir)/security/nss/cmd/lib $(DEFAULT_GMAKE_FLAGS)
+	$(MAKE) -C $(topsrcdir)/security/nss/cmd/certutil $(DEFAULT_GMAKE_FLAGS)
+	$(MAKE) -C $(topsrcdir)/security/nss/cmd/pk12util $(DEFAULT_GMAKE_FLAGS)
+endif
 ifndef SKIP_CHK
+ifndef ENABLE_TESTS # Just avoid secondary compile
 	$(MAKE) -C $(topsrcdir)/security/nss/cmd/lib $(DEFAULT_GMAKE_FLAGS)
+endif
 	$(MAKE) -C $(topsrcdir)/security/nss/cmd/shlibsign $(DEFAULT_GMAKE_FLAGS)
 endif
 	$(INSTALL) -m 755 $(DIST)/lib/$(LOADABLE_ROOT_MODULE) $(DIST)/bin
 ifndef SKIP_CHK
 	$(INSTALL) -m 644 $(DIST)/lib/$(SOFTOKEN3_CHK) $(DIST)/bin
 endif
 	$(INSTALL) -m 755 $(DIST)/lib/$(SOFTOKEN3_LIB) $(DIST)/bin
 	$(INSTALL) -m 755 $(DIST)/lib/$(NSSDBM3_LIB) $(DIST)/bin
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -69,16 +69,17 @@ include $(topsrcdir)/config/rules.mk
 		redirect.js \
 		$(topsrcdir)/build/pgo/server-locations.txt \
 		$(topsrcdir)/netwerk/test/httpserver/httpd.js \
 		mozprefs.js \
 		$(NULL)	
 
 
 _DEST_DIR = $(DEPTH)/_tests/$(relativesrcdir)
+_CERTS_DIR = $(DEPTH)/_profile/pgo/certs
 
 ifeq ($(USE_SHORT_LIBNAME), 1)
 PROGRAM = $(MOZ_APP_NAME)$(BIN_SUFFIX)
 else
 PROGRAM = $(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
 endif
 
 ifeq ($(MOZ_BUILD_APP),camino)
@@ -94,16 +95,18 @@ else
 browser_path = \"../$(DIST)/bin/$(PROGRAM)\"
 endif
 endif
 
 # These go in _tests/ so they need to go up an extra path segement
 TEST_DRIVER_PPARGS = 	\
 			-DBROWSER_PATH=$(browser_path) \
 			-DXPC_BIN_PATH=\"../$(DIST)/bin\" \
+			-DBIN_SUFFIX=\"$(BIN_SUFFIX)\" \
+			-DCERTS_DIR=\"../$(_CERTS_DIR)\" \
 			$(NULL)
 
 ifeq ($(OS_ARCH),Darwin)
 TEST_DRIVER_PPARGS += -DIS_MAC=1
 else
 TEST_DRIVER_PPARGS += -DIS_MAC=0
 endif
 
@@ -124,8 +127,11 @@ runtests.py: runtests.py.in
 automation.py: $(topsrcdir)/build/pgo/automation.py.in
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(TEST_DRIVER_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
 
 GARBAGE += runtests.py automation.py
 
 libs:: $(_SERV_FILES)
 	$(INSTALL) $^ $(_DEST_DIR)
+
+libs::
+	$(PYTHON) $(DEPTH)/_profile/pgo/genpgocert.py --gen-server
--- a/testing/mochitest/server.js
+++ b/testing/mochitest/server.js
@@ -217,17 +217,17 @@ function processLocations(server)
                  "|" +
                  "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" +
                  "[a-z](?:[-a-z0-9]*[a-z0-9])?" +
                ")" +
                ":" +
                "(\\d+)" +
                "(?:" +
                "\\s+" +
-               "(\\w+(?:,\\w+)*)" +
+               "(\\S+(?:,\\S+)*)" +
                ")?$");
 
   var line = {};
   var lineno = 0;
   var seenPrimary = false;
   do
   {
     var more = lis.readLine(line);
--- a/testing/mochitest/ssltunnel/ssltunnel.cpp
+++ b/testing/mochitest/ssltunnel/ssltunnel.cpp
@@ -45,25 +45,26 @@
 #include "prio.h"
 #include "prnetdb.h"
 #include "prtpool.h"
 #include "nss.h"
 #include "pk11func.h"
 #include "key.h"
 #include "keyt.h"
 #include "ssl.h"
+#include "plhash.h"
 
 using std::string;
 using std::vector;
 
 // Structs for passing data into jobs on the thread pool
 typedef struct {
   PRInt32 listen_port;
-  PRNetAddr remote_addr;
   string cert_nickname;
+  PLHashTable* host_cert_table;
 } server_info_t;
 
 typedef struct {
   PRFileDesc* client_sock;
   PRNetAddr client_addr;
   server_info_t* server_info;
 } connection_info_t;
 
@@ -108,116 +109,340 @@ private:
 // These are suggestions. If the number of ports to proxy on * 2
 // is greater than either of these, then we'll use that value instead.
 const PRInt32 INITIAL_THREADS = 1;
 const PRInt32 MAX_THREADS = 5;
 const PRInt32 DEFAULT_STACKSIZE = (512 * 1024);
 const PRInt32 BUF_SIZE = 4096;
 
 // global data
+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;
 
 /*
  * 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)
+{
+  if (buffertail - bufferhead < 4)
+    return false;
+  if (strncmp(buffertail-4, "\r\n\r\n", 4))
+    return false;
+
+  *result = 400;
+
+  char* token;
+  token = strtok(bufferhead, " ");
+  if (!token) 
+    return true;
+  if (strcmp(token, "CONNECT")) 
+    return true;
+
+  token = strtok(NULL, " ");
+  void* c = PL_HashTableLookup(server_info->host_cert_table, token);
+  if (c)
+    *certificate = (char*)c;
+
+  token = strtok(NULL, "/");
+  if (strcmp(token, "HTTP"))
+    return true;
+
+  *result = 200;
+  return true;
+}
+
+bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, string &certificate)
+{
+  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());
+    return false;
+  }
+
+  AutoKey privKey(PK11_FindKeyByAnyCert(cert, NULL));
+  if (!privKey) {
+    fprintf(stderr, "Failed to find private key\n");
+    return false;
+  }
+
+  PRFileDesc* ssl_socket = SSL_ImportFD(NULL, socket);
+  if (!ssl_socket) {
+    fprintf(stderr, "Error importing SSL socket\n");
+    return false;
+  }
+
+  SSLKEAType certKEA = NSS_FindCertKEAType(cert);
+  if (SSL_ConfigSecureServer(ssl_socket, cert, privKey, certKEA)
+      != 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);
+  SSL_ResetHandshake(ssl_socket, PR_TRUE);
+
+  return true;
+}
+
+bool ConnectSocket(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
+{
+  PRStatus stat = PR_Connect(fd, addr, timeout);
+  if (stat != PR_SUCCESS)
+    return false;
+
+  PRSocketOptionData option;
+  option.option = PR_SockOpt_Nonblocking;
+  option.value.non_blocking = PR_TRUE;
+  PR_SetSocketOption(fd, &option);
+
+  return true;
+}
+
 /*
  * Handle an incoming client connection. The server thread has already
  * accepted the connection, so we just need to connect to the remote
  * port and then proxy data back and forth.
  * The data parameter is a connection_info_t*, and must be deleted
  * by this function.
  */
 void PR_CALLBACK HandleConnection(void* data)
 {
   connection_info_t* ci = static_cast<connection_info_t*>(data);
   PRIntervalTime connect_timeout = PR_SecondsToInterval(2);
   PRIntervalTime short_timeout = PR_MillisecondsToInterval(250);
 
   AutoFD other_sock(PR_NewTCPSocket());
   bool client_done = false;
   bool client_error = false;
-  PRUint8 buf[BUF_SIZE];
+  bool connect_accepted = !do_http_proxy;
+  bool ssl_updated = !do_http_proxy;
+  string certificateToUse;
+
+  if (other_sock) 
+  {
+    PRInt32 numberOfSockets = 1;
+
+    struct relayBuffer
+    {
+      char *buffer, *bufferhead, *buffertail, *bufferend;
+      relayBuffer() 
+      { 
+        bufferhead = buffertail = buffer = new char[BUF_SIZE]; 
+        bufferend = buffer + BUF_SIZE; 
+      }
+      ~relayBuffer() 
+      { 
+        delete [] buffer; 
+      }
+
+      bool empty() 
+      { 
+        return bufferhead == buffertail; 
+      }
+      PRInt32 free() 
+      { 
+        return bufferend - buffertail; 
+      }
+      PRInt32 present() 
+      { 
+        return buffertail - bufferhead; 
+      }
+      void compact() 
+      { 
+        if (buffertail == bufferhead) 
+          buffertail = bufferhead = buffer;
+      }
+    } buffers[2];
+
+    if (!do_http_proxy)
+    {
+      if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse))
+        client_error = true;
+      else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
+        client_error = true;
+      else
+        numberOfSockets = 2;
+    }
+
+    PRPollDesc sockets[2] = 
+    { 
+      {ci->client_sock, PR_POLL_READ, 0},
+      {other_sock, PR_POLL_READ, 0}
+    };
+
+    while (!((client_error||client_done) && buffers[0].empty() && buffers[1].empty()))
+    {
+      sockets[0].in_flags |= PR_POLL_EXCEPT;
+      sockets[1].in_flags |= PR_POLL_EXCEPT;
+      PRInt32 pollStatus = PR_Poll(sockets, numberOfSockets, PR_MillisecondsToInterval(1000));
+      if (pollStatus < 0)
+      {
+        client_error = true;
+        break;
+      }
 
-  if (other_sock &&
-      PR_Connect(other_sock, &ci->server_info->remote_addr, connect_timeout)
-      == PR_SUCCESS) {
-    PRInt32 bytes = PR_Recv(ci->client_sock, buf, BUF_SIZE, 0, short_timeout);
-    if (bytes > 0 &&
-        PR_Send(other_sock, buf, bytes, 0, short_timeout) > 0) {
-      bytes = PR_Recv(other_sock, buf, BUF_SIZE, 0, short_timeout);
-      while (bytes > 0) {
-        if (PR_Send(ci->client_sock, buf, bytes, 0, short_timeout) == -1) {
+      if (pollStatus == 0)
+        // timeout
+        continue;
+
+      for (PRInt32 s = 0; s < numberOfSockets; ++s)
+      {
+        PRInt32 s2 = s == 1 ? 0 : 1;
+        PRInt16 out_flags = sockets[s].out_flags;
+        PRInt16 &in_flags = sockets[s].in_flags;
+        PRInt16 &in_flags2 = sockets[s2].in_flags;
+        sockets[s].out_flags = 0;
+
+        if (out_flags & PR_POLL_EXCEPT)
+        {
           client_error = true;
-          break;
-        }
-        if (!client_done) {
-          bytes = PR_Recv(ci->client_sock, buf, BUF_SIZE, 0, short_timeout);
-          if (bytes > 0) {
-            if (PR_Send(other_sock, buf, bytes, 0, short_timeout) == -1)
+          continue;
+        } // PR_POLL_EXCEPT handling
+
+        if (out_flags & PR_POLL_READ && buffers[s].free())
+        {
+          PRInt32 bytesRead = PR_Recv(sockets[s].fd, buffers[s].buffertail, 
+              buffers[s].free(), 0, PR_INTERVAL_NO_TIMEOUT);
+
+          if (bytesRead == 0)
+          {
+            client_done = true;
+            in_flags &= ~PR_POLL_READ;
+          }
+          else if (bytesRead < 0)
+          {
+            if (PR_GetError() != PR_WOULD_BLOCK_ERROR)
+              client_error = true;
+          }
+          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))
+            {
+              // 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;
+                sprintf(buffers[s2].buffer, "HTTP/1.1 %d ERROR\r\nConnection: close\r\n\r\n", response);
+                buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
+                break;
+              }
+
+              strcpy(buffers[s2].buffer, "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n");
+              buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
+
+              if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
+              {
+                client_error = true;
+                break;
+              }
+
+              // Send the response to the client socket
+              in_flags |= PR_POLL_WRITE;
+              connect_accepted = true;
               break;
-          }
-          else if (bytes == 0) {
-            client_done = true;
+            } // end of CONNECT handling
+
+            if (!buffers[s].free()) // Do not poll for read when the buffer is full
+              in_flags &= ~PR_POLL_READ;
+
+            if (ssl_updated)
+              in_flags2 |= PR_POLL_WRITE;
           }
-          else  {// error
-            client_error = true;
-            break;
+        } // PR_POLL_READ handling
+
+        if (out_flags & PR_POLL_WRITE)
+        {
+          PRInt32 bytesWrite = PR_Send(sockets[s].fd, buffers[s2].bufferhead, 
+              buffers[s2].present(), 0, PR_INTERVAL_NO_TIMEOUT);
+
+          if (bytesWrite < 0)
+          {
+            if (PR_GetError() != PR_WOULD_BLOCK_ERROR)
+              client_error = true;
           }
-        }
-        bytes = PR_Recv(other_sock, buf, BUF_SIZE, 0, short_timeout);
-      }
-    }
-    else if (bytes == -1) {
-      client_error = true;
-    }
+          else
+          {
+            buffers[s2].bufferhead += bytesWrite;
+            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))
+                {
+                  client_error = true;
+                  break;
+                }
+
+                numberOfSockets = 2;
+              } // sslUpdate
+
+              in_flags &= ~PR_POLL_WRITE;              
+              in_flags2 |= PR_POLL_READ;
+              buffers[s2].compact();
+            }
+          }
+        } // PR_POLL_WRITE handling
+      } // for...
+    } // while, poll
   }
+  else
+    client_error = true;
+
   if (!client_error)
-    PR_Shutdown(ci->client_sock, PR_SHUTDOWN_BOTH);
+    PR_Shutdown(ci->client_sock, PR_SHUTDOWN_SEND);
   PR_Close(ci->client_sock);
 
   delete ci;
 }
 
 /*
  * Start listening for SSL connections on a specified port, handing
  * them off to client threads after accepting the connection.
  * The data parameter is a server_info_t*, owned by the calling
  * function.
  */
 void PR_CALLBACK StartServer(void* data)
 {
   server_info_t* si = static_cast<server_info_t*>(data);
 
   //TODO: select ciphers?
-  AutoCert cert(PK11_FindCertFromNickname(si->cert_nickname.c_str(),
-                                          NULL));
-  if (!cert) {
-    fprintf(stderr, "Failed to find cert %s\n", si->cert_nickname.c_str());
-    SignalShutdown();
-    return;
-  }
-
-  AutoKey privKey(PK11_FindKeyByAnyCert(cert, NULL));
-  if (!privKey) {
-    fprintf(stderr, "Failed to find private key\n");
-    SignalShutdown();
-    return;
-  }
-
   AutoFD listen_socket(PR_NewTCPSocket());
   if (!listen_socket) {
     fprintf(stderr, "failed to create socket\n");
     SignalShutdown();
     return;
   }
 
   PRNetAddr server_addr;
@@ -229,40 +454,31 @@ void PR_CALLBACK StartServer(void* data)
   }
 
   if (PR_Listen(listen_socket, 1) != PR_SUCCESS) {
     fprintf(stderr, "failed to listen on socket\n");
     SignalShutdown();
     return;
   }
 
-  PRFileDesc* ssl_socket = SSL_ImportFD(NULL, listen_socket);
-  if (!ssl_socket) {
-    fprintf(stderr, "Error importing SSL socket\n");
-    SignalShutdown();
-    return;
-  }
-  listen_socket.reset(ssl_socket);
-
-  if (SSL_ConfigSecureServer(listen_socket, cert, privKey, kt_rsa)
-      != SECSuccess) {
-    fprintf(stderr, "Error configuring SSL listen socket\n");
-    SignalShutdown();
-    return;
-  }
-
   printf("Server listening on port %d with cert %s\n", si->listen_port,
          si->cert_nickname.c_str());
 
   while (!shutdown_server) {
     connection_info_t* ci = new connection_info_t();
     ci->server_info = si;
     // block waiting for connections
     ci->client_sock = PR_Accept(listen_socket, &ci->client_addr,
                                 PR_INTERVAL_NO_TIMEOUT);
+    
+    PRSocketOptionData option;
+    option.option = PR_SockOpt_Nonblocking;
+    option.value.non_blocking = PR_TRUE;
+    PR_SetSocketOption(ci->client_sock, &option);
+
     if (ci->client_sock)
       // Not actually using this PRJob*...
       //PRJob* job =
       PR_QueueJob(threads, HandleConnection, ci, PR_TRUE);
     else
       delete ci;
   }
 }
@@ -271,55 +487,204 @@ void PR_CALLBACK StartServer(void* data)
 char* password_func(PK11SlotInfo* slot, PRBool retry, void* arg)
 {
   if (retry)
     return NULL;
 
   return "";
 }
 
-int main(int argc, char** argv)
+server_info_t* findServerInfo(int portnumber)
+{
+  for (vector<server_info_t>::iterator it = servers.begin();
+       it != servers.end(); it++) 
+  {
+    if (it->listen_port == portnumber)
+      return &(*it);
+  }
+
+  return NULL;
+}
+
+int processConfigLine(char* configLine)
 {
-  if (argc < 6) {
-    fprintf(stderr, "Error: not enough arguments\n"
-            "Usage: ssltunnel <NSS db path> <remote ip> <remote port> (<certname> <port>)+\n"
-            "       Provide SSL encrypted tunnels to <remote ip>:<remote port>\n"
-            "       from each port specified in a <certname>,<port> pair.\n"
-            "       <certname> must be the nickname of a server certificate\n"
-            "       installed in the NSS db pointed to by the <NSS db path>.\n");
-    return 1;
+  if (*configLine == 0 || *configLine == '#')
+    return 0;
+
+  char* keyword = strtok(configLine, ":");
+
+  // Configure usage of http/ssl tunneling proxy behavior
+  if (!strcmp(keyword, "httpproxy"))
+  {
+    char* value = strtok(NULL, ":");
+    if (!strcmp(value, "1"))
+      do_http_proxy = true;
+
+    return 0;
+  }
+
+  // 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);
+    if (port <= 0) {
+      fprintf(stderr, "Invalid remote port: %s\n", portstring);
+      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;
+      hostportstring = strtok(NULL, ":");
+    }
 
-  PRNetAddr remote_addr;
-  if (PR_StringToNetAddr(argv[2], &remote_addr) != PR_SUCCESS) {
-    fprintf(stderr, "Invalid remote IP address: %s\n", argv[2]);
+    char* portstring = strtok(NULL, ":");
+    char* certnick = strtok(NULL, ":");
+
+    int port = atoi(portstring);
+    if (port <= 0) {
+      fprintf(stderr, "Invalid port specified: %s\n", portstring);
+      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);
+    }
+    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;
+      }
+      servers.push_back(server);
+    }
+
+    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);
+  return 1;
+}
+
+int parseConfigFile(const char* filePath)
+{
+  FILE* f = fopen(filePath, "r");
+  if (!f)
+    return 1;
+
+  char buffer[1024], *b = buffer;
+  while (!feof(f))
+  {
+    char c;
+    fscanf(f, "%c", &c);
+    switch (c)
+    {
+    case '\n':
+      *b++ = 0;
+      if (processConfigLine(buffer))
+        return 1;
+      b = buffer;
+    case '\r':
+      continue;
+    default:
+      *b++ = c;
+    }
+  }
+
+  fclose(f);
+
+  // 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;
   }
 
-  int port = atoi(argv[3]);
-  if (port <= 0) {
-    fprintf(stderr, "Invalid remote port: %s\n", argv[2]);
-    return 1;
+  if (any_host_cert_mapping && !do_http_proxy)
+  {
+    printf("Warning: any host-specific certificate configurations are ignored, add httpproxy:1 to allow them\n");
   }
-  remote_addr.inet.port = PR_htons(port);
+
+  return 0;
+}
+
+PRIntn PR_CALLBACK freeHashItems(PLHashEntry *he, PRIntn i, void *arg)
+{
+  delete [] (char*)he->key;
+  delete [] (char*)he->value;
+  return HT_ENUMERATE_REMOVE;
+}
+
+int main(int argc, char** argv)
+{
+  char* configFilePath;
+  if (argc == 1)
+    configFilePath = "ssltunnel.cfg";
+  else
+    configFilePath = argv[1];
 
-  // get our list of cert:port from the remaining args
-  vector<server_info_t> servers;
-  for (int i=4; i<argc; i++) {
-    server_info_t server;
-    memcpy(&server.remote_addr, &remote_addr, sizeof(PRNetAddr));
-    server.cert_nickname = argv[i++];
-    port = atoi(argv[i]);
-    if (port <= 0) {
-      fprintf(stderr, "Invalid port specified: %s\n", argv[i]);
-      return 1;
-    }
-    server.listen_port = port;
-    servers.push_back(server);
+  if (parseConfigFile(configFilePath)) {
+    fprintf(stderr, "Error: config file \"%s\" missing or formating incorrect\n"
+      "Specify path to the config file as parameter to ssltunnel or \n"
+      "create ssltunnel.cfg in the working directory.\n\n"
+      "Example format of the config file:\n\n"
+      "       # Enable http/ssl tunneling proxy-like behavior.\n"
+      "       # If not specified ssltunnel simply does direct forward.\n"
+      "       httpproxy:1\n\n"
+      "       # Specify path to the certification database used.\n"
+      "       certdbdir:/path/to/certdb\n\n"
+      "       # 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",
+      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,
                                                   servers.size()*2),
                                 DEFAULT_STACKSIZE);
@@ -340,19 +705,17 @@ int main(int argc, char** argv)
     PR_ShutdownThreadPool(threads);
     PR_DestroyLock(shutdown_lock);
     return 1;
   }
 
   PK11_SetPasswordFunc(password_func);
 
   // Initialize NSS
-  char* configdir = argv[1];
-
-  if (NSS_Init(configdir) != SECSuccess) {
+  if (NSS_Init(nssconfigdir.c_str()) != SECSuccess) {
     PRInt32 errorlen = PR_GetErrorTextLength();
     char* err = new char[errorlen+1];
     PR_GetErrorText(err);
     fprintf(stderr, "Failed to init NSS: %s", err);
     delete[] err;
     PR_ShutdownThreadPool(threads);
     PR_DestroyCondVar(shutdown_condvar);
     PR_DestroyLock(shutdown_lock);
@@ -393,11 +756,19 @@ int main(int argc, char** argv)
   // cleanup
   PR_ShutdownThreadPool(threads);
   PR_JoinThreadPool(threads);
   PR_DestroyCondVar(shutdown_condvar);
   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_HashTableDestroy(it->host_cert_table);
+  }
+
   PR_Cleanup();
   return 0;
 }
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -240,16 +240,18 @@ NO_PKG_FILES += \
 	msmap* \
 	nm2tsv* \
 	nsinstall* \
 	rebasedlls* \
 	res/samples \
 	res/throbber \
 	shlibsign* \
 	ssltunnel* \
+	certutil* \
+	pk12util* \
 	winEmbed.exe \
 	os2Embed.exe \
 	chrome/chrome.rdf \
 	chrome/app-chrome.manifest \
 	chrome/overlayinfo \
 	components/compreg.dat \
 	components/xpti.dat \
 	content_unit_tests \