Bug 783974, Log SSL errors to the error console, v9, mostly r=bsmith
authorKai Engert <kaie@kuix.de>
Tue, 28 Aug 2012 15:32:34 +0200
changeset 105689 c9e473186b60d28924429b5734e1163c29c58bb5
parent 105688 a1cd7986d2958b610629373875d7992fbfca23ca
child 105690 01c626bb5af90be5435cb67aaad5b9b000ab4bfa
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersbsmith
bugs783974
milestone18.0a1
Bug 783974, Log SSL errors to the error console, v9, mostly r=bsmith
security/manager/locales/en-US/chrome/pipnss/pipnss.properties
security/manager/ssl/src/SSLServerCertVerification.cpp
security/manager/ssl/src/TransportSecurityInfo.cpp
security/manager/ssl/src/TransportSecurityInfo.h
security/manager/ssl/src/nsNSSIOLayer.cpp
--- a/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
+++ b/security/manager/locales/en-US/chrome/pipnss/pipnss.properties
@@ -314,16 +314,17 @@ certErrorTrust_CaInvalid=The certificate
 certErrorTrust_Issuer=The certificate is not trusted because the issuer certificate is not trusted.
 certErrorTrust_SignatureAlgorithmDisabled=The certificate is not trusted because it was signed using a signature algorithm that was disabled because that algorithm is not secure.
 certErrorTrust_ExpiredIssuer=The certificate is not trusted because the issuer certificate has expired.
 certErrorTrust_Untrusted=The certificate does not come from a trusted source.
 
 certErrorMismatch=The certificate is not valid for the name %S.
 # LOCALIZATION NOTE (certErrorMismatchSingle2): Do not translate <a id="cert_domain_link" title="%1$S">%1$S</a>
 certErrorMismatchSingle2=The certificate is only valid for <a id="cert_domain_link" title="%1$S">%1$S</a>
+certErrorMismatchSinglePlain=The certificate is only valid for %S
 certErrorMismatchMultiple=The certificate is only valid for the following names:
 certErrorMismatchNoNames=The certificate is not valid for any server names.
 
 # LOCALIZATION NOTE (certErrorExpiredNow): Do not translate %1$S (date+time of expired certificate) or %2$S (current date+time)
 certErrorExpiredNow=The certificate expired on %1$S. The current time is %2$S.
 # LOCALIZATION NOTE (certErrorNotYetValidNow): Do not translate %1$S (date+time certificate will become valid) or %2$S (current date+time)
 certErrorNotYetValidNow=The certificate will not be valid until %1$S. The current time is %2$S.
 
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -103,16 +103,17 @@
 #include "nsRecentBadCerts.h"
 #include "nsNSSIOLayer.h"
 
 #include "mozilla/Assertions.h"
 #include "nsIThreadPool.h"
 #include "nsXPCOMCIDInternal.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
+#include "nsIConsoleService.h"
 #include "PSMRunnable.h"
 
 #include "ssl.h"
 #include "secerr.h"
 #include "secport.h"
 #include "sslerr.h"
 
 #ifdef PR_LOGGING
@@ -176,16 +177,37 @@ void StopSSLServerCertVerificationThread
   if (gCertVerificationThreadPool) {
     gCertVerificationThreadPool->Shutdown();
     NS_RELEASE(gCertVerificationThreadPool);
   }
 }
 
 namespace {
 
+void
+LogInvalidCertError(TransportSecurityInfo *socketInfo, 
+                    const nsACString &host, 
+                    const nsACString &hostWithPort,
+                    PRInt32 port,
+                    PRErrorCode errorCode,
+                    ::mozilla::psm::SSLErrorMessageType errorMessageType,
+                    nsIX509Cert* ix509)
+{
+  nsString message;
+  socketInfo->GetErrorLogMessage(errorCode, errorMessageType, message);
+  
+  if (!message.IsEmpty()) {
+    nsCOMPtr<nsIConsoleService> console;
+    console = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+    if (console) {
+      console->LogStringMessage(message.get());
+    }
+  }
+}
+
 // Dispatched to the STS thread to notify the infoObject of the verification
 // result.
 //
 // This will cause the PR_Poll in the STS thread to return, so things work
 // correctly even if the STS thread is blocked polling (only) on the file
 // descriptor that is waiting for this result.
 class SSLServerCertVerificationResult : public nsRunnable
 {
@@ -223,17 +245,17 @@ class CertErrorRunnable : public SyncRun
       mErrorCodeMismatch(errorCodeMismatch),
       mErrorCodeExpired(errorCodeExpired)
   {
   }
 
   virtual void RunOnTargetThread();
   nsRefPtr<SSLServerCertVerificationResult> mResult; // out
 private:
-  SSLServerCertVerificationResult* CheckCertOverrides();
+  SSLServerCertVerificationResult *CheckCertOverrides();
   
   const void * const mFdForLogging; // may become an invalid pointer; do not dereference
   const nsCOMPtr<nsIX509Cert> mCert;
   const nsRefPtr<TransportSecurityInfo> mInfoObject;
   const PRErrorCode mDefaultErrorCodeToReport;
   const uint32_t mCollectedErrors;
   const PRErrorCode mErrorCodeTrust;
   const PRErrorCode mErrorCodeMismatch;
@@ -350,19 +372,31 @@ CertErrorRunnable::CheckCertOverrides()
                                       mInfoObject->SSLStatus());
   }
 
   // pick the error code to report by priority
   PRErrorCode errorCodeToReport = mErrorCodeTrust    ? mErrorCodeTrust
                                 : mErrorCodeMismatch ? mErrorCodeMismatch
                                 : mErrorCodeExpired  ? mErrorCodeExpired
                                 : mDefaultErrorCodeToReport;
+                                
+  SSLServerCertVerificationResult *result = 
+    new SSLServerCertVerificationResult(mInfoObject, 
+                                        errorCodeToReport,
+                                        OverridableCertErrorMessage);
 
-  return new SSLServerCertVerificationResult(mInfoObject, errorCodeToReport,
-                                             OverridableCertErrorMessage);
+  LogInvalidCertError(mInfoObject,
+                      nsDependentCString(mInfoObject->GetHostName()),
+                      hostWithPortString,
+                      port,
+                      result->mErrorCode,
+                      result->mErrorMessageType,
+                      mCert);
+
+  return result;
 }
 
 void 
 CertErrorRunnable::RunOnTargetThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mResult = CheckCertOverrides();
--- a/security/manager/ssl/src/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/src/TransportSecurityInfo.cpp
@@ -240,62 +240,92 @@ TransportSecurityInfo::GetErrorMessage(P
 
   if (!NS_IsMainThread()) {
     NS_ERROR("nsNSSSocketInfo::GetErrorMessage called off the main thread");
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   MutexAutoLock lock(mMutex);
 
-  nsresult rv = formatErrorMessage(lock);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (mErrorMessageCached.IsEmpty()) {
+    nsresult rv = formatErrorMessage(lock, 
+                                     mErrorCode, mErrorMessageType,
+                                     true, true, mErrorMessageCached);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   *aText = ToNewUnicode(mErrorMessageCached);
   return *aText != nullptr ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
+void
+TransportSecurityInfo::GetErrorLogMessage(PRErrorCode errorCode,
+                                          SSLErrorMessageType errorMessageType,
+                                          nsString &result)
+{
+  if (!NS_IsMainThread()) {
+    NS_ERROR("nsNSSSocketInfo::GetErrorLogMessage called off the main thread");
+    return;
+  }
+
+  MutexAutoLock lock(mMutex);
+  (void) formatErrorMessage(lock, errorCode, errorMessageType,
+                            false, false, result);
+}
+
 static nsresult
 formatPlainErrorMessage(nsXPIDLCString const & host, int32_t port,
-                        PRErrorCode err, nsString &returnedMessage);
+                        PRErrorCode err, 
+                        bool suppressPort443,
+                        nsString &returnedMessage);
 
 static nsresult
 formatOverridableCertErrorMessage(nsISSLStatus & sslStatus,
                                   PRErrorCode errorCodeToReport, 
                                   const nsXPIDLCString & host, int32_t port,
+                                  bool suppressPort443,
+                                  bool wantsHtml,
                                   nsString & returnedMessage);
 
 // XXX: uses nsNSSComponent string bundles off the main thread when called by
-//      nsNSSSocketInfo::Write(). When we remove the error message from the
-//      serialization of nsNSSSocketInfo (bug 697781) we can inline
-//      formatErrorMessage into GetErrorMessage().
+//      nsNSSSocketInfo::Write().
 nsresult
-TransportSecurityInfo::formatErrorMessage(MutexAutoLock const & proofOfLock)
+TransportSecurityInfo::formatErrorMessage(MutexAutoLock const & proofOfLock, 
+                                          PRErrorCode errorCode,
+                                          SSLErrorMessageType errorMessageType,
+                                          bool wantsHtml, bool suppressPort443, 
+                                          nsString &result)
 {
-  if (mErrorCode == 0 || !mErrorMessageCached.IsEmpty()) {
+  if (errorCode == 0) {
+    result.Truncate();
     return NS_OK;
   }
 
   nsresult rv;
   NS_ConvertASCIItoUTF16 hostNameU(mHostName);
-  NS_ASSERTION(mErrorMessageType != OverridableCertErrorMessage || 
+  NS_ASSERTION(errorMessageType != OverridableCertErrorMessage || 
                 (mSSLStatus && mSSLStatus->mServerCert &&
                  mSSLStatus->mHaveCertErrorBits),
-                "GetErrorMessage called for cert error without cert");
-  if (mErrorMessageType == OverridableCertErrorMessage && 
+                "GetErrorLogMessage called for cert error without cert");
+  if (errorMessageType == OverridableCertErrorMessage && 
       mSSLStatus && mSSLStatus->mServerCert) {
-    rv = formatOverridableCertErrorMessage(*mSSLStatus, mErrorCode,
+    rv = formatOverridableCertErrorMessage(*mSSLStatus, errorCode,
                                            mHostName, mPort,
-                                           mErrorMessageCached);
+                                           suppressPort443,
+                                           wantsHtml,
+                                           result);
   } else {
-    rv = formatPlainErrorMessage(mHostName, mPort, mErrorCode,
-                                 mErrorMessageCached);
+    rv = formatPlainErrorMessage(mHostName, mPort, 
+                                 errorCode,
+                                 suppressPort443,
+                                 result);
   }
 
   if (NS_FAILED(rv)) {
-    mErrorMessageCached.Truncate();
+    result.Truncate();
   }
 
   return rv;
 }
 
 /* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */
 NS_IMETHODIMP
 TransportSecurityInfo::GetInterface(const nsIID & uuid, void * *result)
@@ -367,17 +397,19 @@ TransportSecurityInfo::Write(nsIObjectOu
   // This mask value has been chosen as mSecurityState could
   // never be assigned such value.
   uint32_t version = 3;
   stream->Write32(version | 0xFFFF0000);
   stream->Write32(mSecurityState);
   stream->WriteWStringZ(mShortDesc.get());
 
   // XXX: uses nsNSSComponent string bundles off the main thread
-  nsresult rv = formatErrorMessage(lock); 
+  nsresult rv = formatErrorMessage(lock, 
+                                   mErrorCode, mErrorMessageType,
+                                   true, true, mErrorMessageCached);
   NS_ENSURE_SUCCESS(rv, rv);
   stream->WriteWStringZ(mErrorMessageCached.get());
 
   stream->WriteCompoundObject(NS_ISUPPORTS_CAST(nsISSLStatus*, status),
                               NS_GET_IID(nsISupports), true);
 
   stream->Write32((uint32_t)mSubRequestsHighSecurity);
   stream->Write32((uint32_t)mSubRequestsLowSecurity);
@@ -580,17 +612,19 @@ TransportSecurityInfo::SetSSLStatus(nsSS
 
 /* Formats an error message for non-certificate-related SSL errors
  * and non-overridable certificate errors (both are of type
  * PlainErrormMessage). Use formatOverridableCertErrorMessage
  * for overridable cert errors.
  */
 static nsresult
 formatPlainErrorMessage(const nsXPIDLCString &host, int32_t port,
-                        PRErrorCode err, nsString &returnedMessage)
+                        PRErrorCode err, 
+                        bool suppressPort443,
+                        nsString &returnedMessage)
 {
   const PRUnichar *params[1];
   nsresult rv;
 
   nsCOMPtr<nsINSSComponent> component = do_GetService(kNSSComponentCID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (host.Length())
@@ -600,17 +634,17 @@ formatPlainErrorMessage(const nsXPIDLCSt
     // For now, hide port when it's 443 and we're reporting the error.
     // In the future a better mechanism should be used
     // to make a decision about showing the port number, possibly by requiring
     // the context object to implement a specific interface.
     // The motivation is that Mozilla browser would like to hide the port number
     // in error pages in the common case.
 
     hostWithPort.AssignASCII(host);
-    if (port != 443) {
+    if (!suppressPort443 || port != 443) {
       hostWithPort.AppendLiteral(":");
       hostWithPort.AppendInt(port);
     }
     params[0] = hostWithPort.get();
 
     nsString formattedString;
     rv = component->PIPBundleFormatStringFromName("SSLConnectionErrorPrefix", 
                                                   params, 1, 
@@ -775,16 +809,17 @@ GetSubjectAltNames(CERTCertificate *nssC
   PORT_FreeArena(san_arena, false);
   return true;
 }
 
 static void
 AppendErrorTextMismatch(const nsString &host,
                         nsIX509Cert* ix509,
                         nsINSSComponent *component,
+                        bool wantsHtml,
                         nsString &returnedMessage)
 {
   const PRUnichar *params[1];
   nsresult rv;
 
   CERTCertificate *nssCert = NULL;
   CERTCertificateCleaner nssCertCleaner(nssCert);
 
@@ -837,19 +872,25 @@ AppendErrorTextMismatch(const nsString &
       returnedMessage.Append(NS_LITERAL_STRING("\n  "));
       returnedMessage.Append(allNames);
       returnedMessage.Append(NS_LITERAL_STRING("  \n"));
     }
   }
   else if (nameCount == 1) {
     const PRUnichar *params[1];
     params[0] = allNames.get();
+    
+    const char *stringID;
+    if (wantsHtml)
+      stringID = "certErrorMismatchSingle2";
+    else
+      stringID = "certErrorMismatchSinglePlain";
 
     nsString formattedString;
-    rv = component->PIPBundleFormatStringFromName("certErrorMismatchSingle2", 
+    rv = component->PIPBundleFormatStringFromName(stringID, 
                                                   params, 1, 
                                                   formattedString);
     if (NS_SUCCEEDED(rv)) {
       returnedMessage.Append(formattedString);
       returnedMessage.Append(NS_LITERAL_STRING("\n"));
     }
   }
   else { // nameCount == 0
@@ -973,32 +1014,34 @@ AppendErrorTextCode(PRErrorCode errorCod
 /* Formats an error message for overridable certificate errors (of type
  * OverridableCertErrorMessage). Use formatPlainErrorMessage to format
  * non-overridable cert errors and non-cert-related errors.
  */
 static nsresult
 formatOverridableCertErrorMessage(nsISSLStatus & sslStatus,
                                   PRErrorCode errorCodeToReport, 
                                   const nsXPIDLCString & host, int32_t port,
+                                  bool suppressPort443,
+                                  bool wantsHtml,
                                   nsString & returnedMessage)
 {
   const PRUnichar *params[1];
   nsresult rv;
   nsAutoString hostWithPort;
   nsAutoString hostWithoutPort;
 
   // For now, hide port when it's 443 and we're reporting the error.
   // In the future a better mechanism should be used
   // to make a decision about showing the port number, possibly by requiring
   // the context object to implement a specific interface.
   // The motivation is that Mozilla browser would like to hide the port number
   // in error pages in the common case.
   
   hostWithoutPort.AppendASCII(host);
-  if (port == 443) {
+  if (suppressPort443 && port == 443) {
     params[0] = hostWithoutPort.get();
   } else {
     hostWithPort.AppendASCII(host);
     hostWithPort.Append(':');
     hostWithPort.AppendInt(port);
     params[0] = hostWithPort.get();
   }
 
@@ -1023,17 +1066,17 @@ formatOverridableCertErrorMessage(nsISSL
     AppendErrorTextUntrusted(errorCodeToReport, hostWithoutPort, ix509, 
                              component, returnedMessage);
   }
 
   bool isDomainMismatch;
   rv = sslStatus.GetIsDomainMismatch(&isDomainMismatch);
   NS_ENSURE_SUCCESS(rv, rv);
   if (isDomainMismatch) {
-    AppendErrorTextMismatch(hostWithoutPort, ix509, component, returnedMessage);
+    AppendErrorTextMismatch(hostWithoutPort, ix509, component, wantsHtml, returnedMessage);
   }
 
   bool isNotValidAtThisTime;
   rv = sslStatus.GetIsNotValidAtThisTime(&isNotValidAtThisTime);
   NS_ENSURE_SUCCESS(rv, rv);
   if (isNotValidAtThisTime) {
     AppendErrorTextTime(ix509, component, returnedMessage);
   }
--- a/security/manager/ssl/src/TransportSecurityInfo.h
+++ b/security/manager/ssl/src/TransportSecurityInfo.h
@@ -54,16 +54,21 @@ public:
   nsresult GetHostName(char **aHostName);
   nsresult SetHostName(const char *aHostName);
 
   int32_t GetPort() const { return mPort; }
   nsresult GetPort(int32_t *aPort);
   nsresult SetPort(int32_t aPort);
 
   PRErrorCode GetErrorCode() const;
+  
+  void GetErrorLogMessage(PRErrorCode errorCode,
+                          ::mozilla::psm::SSLErrorMessageType errorMessageType,
+                          nsString &result);
+  
   void SetCanceled(PRErrorCode errorCode,
                    ::mozilla::psm::SSLErrorMessageType errorMessageType);
   
   /* Set SSL Status values */
   nsresult SetSSLStatus(nsSSLStatus *aSSLStatus);
   nsSSLStatus* SSLStatus() { return mSSLStatus; }
   void SetStatusErrorBits(nsIX509Cert & cert, uint32_t collected_errors);
 
@@ -86,17 +91,21 @@ private:
   int32_t mSubRequestsLowSecurity;
   int32_t mSubRequestsBrokenSecurity;
   int32_t mSubRequestsNoSecurity;
   nsString mShortDesc;
 
   PRErrorCode mErrorCode;
   ::mozilla::psm::SSLErrorMessageType mErrorMessageType;
   nsString mErrorMessageCached;
-  nsresult formatErrorMessage(::mozilla::MutexAutoLock const & proofOfLock);
+  nsresult formatErrorMessage(::mozilla::MutexAutoLock const & proofOfLock, 
+                              PRErrorCode errorCode,
+                              ::mozilla::psm::SSLErrorMessageType errorMessageType,
+                              bool wantsHtml, bool suppressPort443, 
+                              nsString &result);
 
   int32_t mPort;
   nsXPIDLCString mHostName;
   PRErrorCode mIsCertIssuerBlacklisted;
 
   /* SSL Status */
   nsRefPtr<nsSSLStatus> mSSLStatus;
 
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/src/nsNSSIOLayer.cpp
@@ -18,16 +18,17 @@
 #include "SSLServerCertVerification.h"
 #include "nsNSSCertHelper.h"
 #include "nsNSSCleaner.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsISecureBrowserUI.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsCharSeparatedTokenizer.h"
+#include "nsIConsoleService.h"
 #include "PSMRunnable.h"
 
 #include "ssl.h"
 #include "secerr.h"
 #include "sslerr.h"
 #include "secder.h"
 #include "keyhi.h"
 
@@ -480,17 +481,19 @@ void nsSSLIOLayerHelpers::Cleanup()
 
   if (mutex) {
     delete mutex;
     mutex = nullptr;
   }
 }
 
 static void
-nsHandleSSLError(nsNSSSocketInfo *socketInfo, PRErrorCode err)
+nsHandleSSLError(nsNSSSocketInfo *socketInfo, 
+                 ::mozilla::psm::SSLErrorMessageType errtype, 
+                 PRErrorCode err)
 {
   if (!NS_IsMainThread()) {
     NS_ERROR("nsHandleSSLError called off the main thread");
     return;
   }
 
   // SetCanceled is only called by the main thread or the socket transport
   // thread. Whenever this function is called on the main thread, the SSL
@@ -524,18 +527,29 @@ nsHandleSSLError(nsNSSSocketInfo *socket
       nsCString hostWithPortString = hostName;
       hostWithPortString.AppendLiteral(":");
       hostWithPortString.AppendInt(port);
     
       bool suppressMessage = false; // obsolete, ignored
       rv = sel->NotifySSLError(csi, err, hostWithPortString, &suppressMessage);
     }
   }
-
+  
+  // We must cancel first, which sets the error code.
   socketInfo->SetCanceled(err, PlainErrorMessage);
+  nsXPIDLString errorString;
+  socketInfo->GetErrorLogMessage(err, errtype, errorString);
+  
+  if (!errorString.IsEmpty()) {
+    nsCOMPtr<nsIConsoleService> console;
+    console = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+    if (console) {
+      console->LogStringMessage(errorString.get());
+    }
+  }
 }
 
 namespace {
 
 enum Operation { reading, writing, not_reading_or_writing };
 
 int32_t checkHandshake(int32_t bytesTransfered, bool wasReading,
                        PRFileDesc* ssl_layer_fd,
@@ -801,27 +815,32 @@ isTLSIntoleranceError(int32_t err, bool 
   }
   
   return false;
 }
 
 class SSLErrorRunnable : public SyncRunnableBase
 {
  public:
-  SSLErrorRunnable(nsNSSSocketInfo * infoObject, PRErrorCode errorCode)
-    : mInfoObject(infoObject), mErrorCode(errorCode)
+  SSLErrorRunnable(nsNSSSocketInfo * infoObject, 
+                   ::mozilla::psm::SSLErrorMessageType errtype, 
+                   PRErrorCode errorCode)
+    : mInfoObject(infoObject)
+    , mErrType(errtype)
+    , mErrorCode(errorCode)
   {
   }
 
   virtual void RunOnTargetThread()
   {
-    nsHandleSSLError(mInfoObject, mErrorCode);
+    nsHandleSSLError(mInfoObject, mErrType, mErrorCode);
   }
   
   nsRefPtr<nsNSSSocketInfo> mInfoObject;
+  ::mozilla::psm::SSLErrorMessageType mErrType;
   const PRErrorCode mErrorCode;
 };
 
 namespace {
 
 int32_t checkHandshake(int32_t bytesTransfered, bool wasReading,
                        PRFileDesc* ssl_layer_fd,
                        nsNSSSocketInfo *socketInfo)
@@ -885,16 +904,17 @@ int32_t checkHandshake(int32_t bytesTran
     // do the synchronous dispatch to the main thread unnecessarily after we've
     // already handled a certificate error. (SSLErrorRunnable calls
     // nsHandleSSLError, which has logic to avoid replacing the error message,
     // so without the !socketInfo->GetErrorCode(), it would just be an
     // expensive no-op.)
     if (!wantRetry && (IS_SSL_ERROR(err) || IS_SEC_ERROR(err)) &&
         !socketInfo->GetErrorCode()) {
       nsRefPtr<SyncRunnableBase> runnable = new SSLErrorRunnable(socketInfo,
+                                                                 PlainErrorMessage,
                                                                  err);
       (void) runnable->DispatchToMainThreadAndWait();
     }
   }
   else if (wasReading && 0 == bytesTransfered) // zero bytes on reading, socket closed
   {
     if (handleHandshakeResultNow)
     {