Revision: 7496
          http://mahogany.svn.sourceforge.net/mahogany/?rev=7496&view=rev
Author:   vadz
Date:     2008-07-28 22:59:55 +0000 (Mon, 28 Jul 2008)

Log Message:
-----------
implement message signing (not exposed in the UI yet though)

Modified Paths:
--------------
    trunk/M/include/SendMessage.h
    trunk/M/include/SendMessageCC.h
    trunk/M/include/modules/MCrypt.h
    trunk/M/src/mail/SendMessageCC.cpp
    trunk/M/src/modules/crypt/PGPEngine.cpp

Modified: trunk/M/include/SendMessage.h
===================================================================
--- trunk/M/include/SendMessage.h       2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/include/SendMessage.h       2008-07-28 22:59:55 UTC (rev 7496)
@@ -218,11 +218,22 @@
    virtual void AddPart(MimeType::Primary type,
                         const void *buf, size_t len,
                         const String &subtype = M_EMPTYSTRING,
-                        const String &disposition = _T("INLINE"),
+                        const String &disposition = "INLINE",
                         MessageParameterList const *dlist = NULL,
                         MessageParameterList const *plist = NULL,
                         wxFontEncoding enc = wxFONTENCODING_SYSTEM) = 0;
 
+   /**
+      Indicate whether the message should be cryptographically signed.
+
+      By default the message is not signed, this method must be called to try
+      signing it.
+
+      @param user The user name to use for signing, can be empty to use the
+                  default one.
+    */
+   virtual void EnableSigning(const String& user = "") = 0;
+
    /** Writes the message to a String
        @param output string to write to
        @return true if ok, false if an error occured

Modified: trunk/M/include/SendMessageCC.h
===================================================================
--- trunk/M/include/SendMessageCC.h     2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/include/SendMessageCC.h     2008-07-28 22:59:55 UTC (rev 7496)
@@ -84,11 +84,13 @@
    virtual void AddPart(MimeType::Primary type,
                         const void *buf, size_t len,
                         const String &subtype = M_EMPTYSTRING,
-                        const String &disposition = _T("INLINE"),
+                        const String &disposition = "INLINE",
                         MessageParameterList const *dlist = NULL,
                         MessageParameterList const *plist = NULL,
                         wxFontEncoding enc = wxFONTENCODING_SYSTEM);
 
+   virtual void EnableSigning(const String& user = "");
+
    virtual bool WriteToString(String  &output);
 
    virtual bool WriteToFile(const String &filename, bool append = true);
@@ -125,9 +127,6 @@
    /// init the header and contents from an existing message
    void InitFromMsg(const Message *message, const wxArrayInt *partsToOmit);
 
-   /// common part of InitNew() and InitFromMsg()
-   void InitBody();
-
    //@}
 
    /** Sends the message.
@@ -138,16 +137,25 @@
    bool Send(int flags);
 
    /// set sender address fields
-   void SetupFromAddresses(void);
+   void SetupFromAddresses();
 
    /**
+      Sign the message cryptographically.
+
+      This is called by Build() if m_sign == true.
+
+      @return false if signing failed, this is a fatal error for this message
+    */
+   bool Sign();
+
+   /**
       Builds the message prior to sending or saving it.
 
-      @param forStorage if this is TRUE, store some extra information that is
+      @param forStorage if this is true, store some extra information that is
                         not supposed to be sent, like BCC header.
       @return true if ok or false if we failed to build the message
     */
-   bool Build(bool forStorage = FALSE);
+   bool Build(bool forStorage = false);
 
    /// translate the (wxWin) encoding to (MIME) charset
    String EncodingToCharset(wxFontEncoding enc);
@@ -184,13 +192,24 @@
    /// the envelope
    ENVELOPE *m_Envelope;
 
-   /// the body
-   BODY     *m_Body;
+   /**
+      The top level part.
 
-   /// the next and last body parts
-   PART     *m_NextPart,
-            *m_LastPart;
+      We only used the BODY member of this part but using a PART (which
+      contains a BODY and a pointer to the next PART) allows us to efficiently
+      change the structure of the message by just switching pointers and
+      without copying any data. E.g. the first part added to this message
+      becomes m_partTop but if another part is added later we change m_partTop
+      to be a MULTIPART/MIXED part and set its nested part pointer to the
+      previous value. And if we need to sign the message at the end it's again
+      enough to simply replace m_partTop with a MULTIPART/SIGNED part and move
+      the previous m_partTop value to be its nested part.
+    */
+   PART *m_partTop;
 
+   /// Return the message BODY structure.
+   BODY *GetBody() const { return &m_partTop->body; }
+
    //@}
 
    /// the profile containing our settings
@@ -287,6 +306,19 @@
    /// the parent frame (only used for the dialogs)
    wxFrame *m_frame;
 
+
+   /// @name Cryptographic stuff.
+   //@{
+
+   /// If true, we must sign the message. False by default.
+   bool m_sign;
+
+   /// The user name to sign the message as, only used if m_sign and can be
+   /// empty even if m_sign is true.
+   String m_signAsUser;
+
+   //@}
+
    // it uses our ctor
    friend class SendMessage;
 

Modified: trunk/M/include/modules/MCrypt.h
===================================================================
--- trunk/M/include/modules/MCrypt.h    2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/include/modules/MCrypt.h    2008-07-28 22:59:55 UTC (rev 7496)
@@ -37,30 +37,20 @@
       OK = 0,
       CANNOT_EXEC_PROGRAM,
       OPERATION_CANCELED_BY_USER,
-      NO_PASSPHRASE,
-      OUT_OF_ENV_SPACE_ERROR,
-      BATCH_MODE_ERROR,
-      BAD_ARGUMENT_ERROR,
-      PROCESS_INTERRUPTED_ERROR,
-      OUT_OF_MEM_ERROR,
-      NONEXISTING_KEY_ERROR,
-      KEYRING_ADD_ERROR,
-      KEYRING_EXTRACT_ERROR,
-      KEYRING_EDIT_ERROR,
-      KEYRING_VIEW_ERROR,
-      KEYRING_CHECK_ERROR,
-      KEYRING_SIGNATURE_ERROR,
-      SIGNATURE_EXPIRED_ERROR,
-      SIGNATURE_UNTRUSTED_WARNING,
-      SIGNATURE_ERROR,
-      PUBLIC_KEY_ENCRIPTION_ERROR,
-      ENCRYPTION_ERROR,
-      COMPRESSION_ERROR,
-      SIGNATURE_CHECK_ERROR,
-      PUBLIC_KEY_DECRIPTION_ERROR,
-      DECRYPTION_ERROR,
-      DECOMPRESSION_ERROR,
+      BAD_ARGUMENT_ERROR,           // bad input data format
+      NONEXISTING_KEY_ERROR,        // no public key found
+
+      SIGNATURE_EXPIRED_ERROR,      // expired signature in VerifySignature()
+      SIGNATURE_UNTRUSTED_WARNING,  // valid signature from untrusted source
+      SIGNATURE_ERROR,              // incorrect signature
+      SIGNATURE_CHECK_ERROR,        // generic failure to verify signature
+
+      DECRYPTION_ERROR,             // generic failure of decryption
       NO_DATA_ERROR,                // or no signature in VerifySignature()
+
+      SIGN_ERROR,                   // generic signing error
+      SIGN_UNKNOWN_MICALG,          // unknown hash algorithm used in Sign()
+
       NOT_IMPLEMENTED_ERROR,        // operation not implemented by this engine
       MAX_ERROR
    };
@@ -117,6 +107,21 @@
       Just signs the message.
 
       Takes the user id to use and creates a signed message on output.
+
+      Notice that to create an RFC 3156 compliant message the caller must
+      retrieve the name of the hashing ("Message Integrity Check") algorithm
+      used during signing using MCryptoEngineOutputLog::GetMicAlg().
+
+      @param user
+         The user name to sign the message as, may be empty to use the default
+         user name.
+      @param messageIn
+         The contents of the message to sign, including any MIME headers (see
+         section 6.1 of the RFC 3156 for an example).
+      @param messageOut
+         Contains the input signature on successful return.
+      @param log
+         Optional sink for the messages generated during the operation.
     */
    virtual Status Sign(const String& user,
                        const String& messageIn,
@@ -206,6 +211,19 @@
    const String& GetPublicKey() const { return m_pubkey; }
    void  SetPublicKey(const String& pubkey) { m_pubkey = pubkey; }
 
+   /**
+      Return the name of the hashing algorithm used for generating the
+      signature.
+
+      This only makes sense to use after calling MCryptoEngine::Sign().
+
+      The name of the method comes from "micalg" parameter of multipart/signed
+      in OpenPGP message format, which in turn is an abbreviation of "Message
+      Integrity Check Algorithm".
+    */
+   const String& GetMicAlg() const { return m_micalg; }
+   void SetMicAlg(const String& micalg) { m_micalg = micalg; }
+
    wxWindow *GetParent() const { return m_parent; }
 
 private:
@@ -220,6 +238,9 @@
 
    // the public key mentioned in the PGP output
    String        m_pubkey;
+
+   // the name of the hash algorithm used for signing
+   String        m_micalg;
 };
 
 // ----------------------------------------------------------------------------

Modified: trunk/M/src/mail/SendMessageCC.cpp
===================================================================
--- trunk/M/src/mail/SendMessageCC.cpp  2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/src/mail/SendMessageCC.cpp  2008-07-28 22:59:55 UTC (rev 7496)
@@ -57,9 +57,12 @@
 #include "XFace.h"
 #include "gui/wxMDialogs.h"
 
+#include "modules/MCrypt.h"
+
 #include <wx/file.h>
 #include <wx/filename.h>
 #include <wx/datetime.h>
+#include <wx/scopeguard.h>
 
 extern bool InitSSL(); // from src/util/ssl.cpp
 
@@ -124,10 +127,13 @@
 static long write_stream_output(void *, char *);
 static long write_str_output(void *, char *);
 
+namespace
+{
+
 // test if the header corresponds to one of the address headers
 //
 // NB: the header name must be in upper case
-static inline
+inline
 bool IsAddressHeader(const String& name)
 {
    return name == "FROM" ||
@@ -139,7 +145,7 @@
 // test if we allow this header to be set by user
 //
 // NB: the header name must be in upper case
-static inline
+inline
 bool HeaderCanBeSetByUser(const String& name)
 {
    return name != "MIME-VERSION" &&
@@ -150,7 +156,7 @@
 }
 
 // check if the header name is valid (as defined in 2.2 of RFC 2822)
-static bool IsValidHeaderName(const char *name)
+bool IsValidHeaderName(const char *name)
 {
    if ( !name )
       return false;
@@ -164,6 +170,19 @@
    return true;
 }
 
+// create a new BODY parameter and initialize it
+PARAMETER *CreateBodyParameter(const char *name, const char *value)
+{
+   PARAMETER * const par = mail_newbody_parameter();
+   par->attribute = strdup(name);
+   par->value = strdup(value);
+   par->next = NULL;
+
+   return par;
+}
+
+} // anonymous namespace
+
 // ----------------------------------------------------------------------------
 // private classes
 // ----------------------------------------------------------------------------
@@ -295,6 +314,7 @@
    m_encHeaders = wxFONTENCODING_SYSTEM;
 
    m_cloneOfExisting = false;
+   m_sign = false;
 
    m_headerNames =
    m_headerValues = NULL;
@@ -302,11 +322,8 @@
    m_wasBuilt = false;
 
    m_Envelope = mail_newenvelope();
-   m_Body = mail_newbody();
+   m_partTop = NULL;
 
-   m_NextPart =
-   m_LastPart = NULL;
-
    if ( !profile )
    {
       FAIL_MSG( _T("SendMessageCC::Create() requires profile") );
@@ -410,24 +427,8 @@
    }
 }
 
-void SendMessageCC::InitBody()
-{
-   // this is some strange code: we start by creating a fake multipart and then
-   // flatten it in Build() if we realize that we don't have any other parts
-   //
-   // it could probably have been done simpler but this code is there since a
-   // long time and seems to work, so let's not touch it without reason
-   m_Body->type = TYPEMULTIPART;
-   m_Body->nested.part = mail_newbody_part();
-   m_Body->nested.part->next = NULL;
-   m_NextPart = m_Body->nested.part;
-   m_LastPart = m_NextPart;
-}
-
 void SendMessageCC::InitNew()
 {
-   InitBody();
-
    m_ReplyTo = READ_CONFIG_TEXT(m_profile, MP_REPLY_ADDRESS);
 
    /*
@@ -439,7 +440,7 @@
    if ( READ_CONFIG(m_profile, MP_GUESS_SENDER) )
    {
       m_Sender = READ_CONFIG_TEXT(m_profile, MP_SMTPHOST_LOGIN);
-      m_Sender.Trim().Trim(FALSE); // remove all spaces on begin/end
+      m_Sender.Trim().Trim(false); // remove all spaces on begin/end
 
       if ( Address::Compare(m_From, m_Sender) )
       {
@@ -512,14 +513,16 @@
 
    // now copy the body: note that we have to use ENC7BIT here to prevent
    // c-client from (re)encoding the body
-   m_Body->type = TYPETEXT;
-   m_Body->encoding = ENC7BIT;
-   m_Body->subtype = cpystr("PLAIN");
+   m_partTop = mail_newbody_part();
+   BODY& body = m_partTop->body;
+   body.type = TYPETEXT;
+   body.encoding = ENC7BIT;
+   body.subtype = cpystr("PLAIN");
 
-   // FIXME: we potentially copy a lot of data here!
-   String text = message->FetchText();
-   m_Body->contents.text.data = (unsigned char *)cpystr(text.To8BitData());
-   m_Body->contents.text.size = text.length();
+   // FIXME-OPT: we potentially copy a lot of data here!
+   const wxWX2MBbuf text8bit(message->FetchText().To8BitData());
+   body.contents.text.data = (unsigned char *)cpystr(text8bit);
+   body.contents.text.size = strlen(text8bit);
 }
 
 void
@@ -527,8 +530,6 @@
 {
    m_cloneOfExisting = true;
 
-   InitBody();
-
    // set the headers not supported by AddHeaderEntry()
 
    // VZ: I'm not sure at all about what exactly we're trying to do here so
@@ -632,8 +633,8 @@
 
 SendMessageCC::~SendMessageCC()
 {
-   mail_free_envelope (&m_Envelope);
-   mail_free_body (&m_Body);
+   mail_free_envelope(&m_Envelope);
+   mail_free_body_part(&m_partTop);
 
    if(m_headerNames)
    {
@@ -1019,6 +1020,90 @@
 }
 
 bool
+SendMessageCC::Sign()
+{
+   // get the text to sign
+   BODY * const bodyOrig = GetBody();
+   rfc822_encode_body_7bit(NULL /* env is unused */, bodyOrig);
+
+   String textToSign;
+   char tmp[MAILTMPLEN + 1];
+   RFC822BUFFER
+      buf = { write_str_output, &textToSign, tmp, tmp, tmp + MAILTMPLEN  };
+
+   if ( !rfc822_output_body_header(&buf, bodyOrig) ||
+        !rfc822_output_flush(&buf) ||
+        !(textToSign += "\r\n", rfc822_output_text(&buf, bodyOrig)) ||
+        !rfc822_output_flush(&buf) )
+   {
+      ERRORMESSAGE((_("Failed to create the text to sign.")));
+      return false;
+   }
+
+   // according to 5.1.1 of RFC 2046 the EOL before the boundary line in a
+   // multipart MIME message belongs to the boundary, and not the preceding
+   // line (so that it's possible to have content not terminating with EOL)
+   //
+   // as the old body will become the first part of multipart/signed message
+   // this EOL hence shouldn't be taken into account when signing it
+   if ( textToSign.EndsWith("\r\n") )
+      textToSign.erase(textToSign.length() - 2);
+
+
+   // sign it
+   MCryptoEngineFactory * const
+      factory = (MCryptoEngineFactory *)MModule::LoadModule("PGPEngine");
+   CHECK( factory, false, "failed to create PGPEngineFactory" );
+
+   wxON_BLOCK_EXIT_OBJ0(*factory, MCryptoEngineFactory::DecRef);
+
+   MCryptoEngine * const pgpEngine = factory->Get();
+   MCryptoEngineOutputLog log(m_frame);
+
+   String signature;
+   MCryptoEngine::Status status = pgpEngine->Sign
+                                             (
+                                                m_signAsUser,
+                                                textToSign,
+                                                signature,
+                                                &log
+                                             );
+   if ( status != MCryptoEngine::OK )
+   {
+      const size_t n = log.GetMessageCount();
+      String err = n == 0 ? String(_("no error information available"))
+                          : log.GetMessage(n - 1);
+      
+      ERRORMESSAGE((_("Signing the message failed: %s"), err.c_str()));
+
+      return false;
+   }
+
+   // create the new multipart/signed message
+   PART * const partOrig = m_partTop;
+
+   m_partTop = mail_newbody_part();
+   BODY& body = m_partTop->body;
+   body.type = TYPEMULTIPART;
+   body.subtype = cpystr("SIGNED");
+   body.nested.part = partOrig;
+
+   // fill in required parameters, see RFC 3156 section 5
+   body.parameter = CreateBodyParameter("protocol",
+                                        "application/pgp-signature");
+   body.parameter->next = CreateBodyParameter("micalg",
+                                              "pgp-" + log.GetMicAlg());
+
+   const wxWX2MBbuf asciiSig(signature.ToAscii());
+   AddPart(MimeType::APPLICATION,
+           asciiSig, strlen(asciiSig),
+           "PGP-SIGNATURE",
+           "" /* no disposition */);
+
+   return true;
+}
+
+bool
 SendMessageCC::Build(bool forStorage)
 {
    if ( m_wasBuilt )
@@ -1204,17 +1289,14 @@
    m_headerNames[h] = NULL;
    m_headerValues[h] = NULL;
 
-   mail_free_body_part(&m_LastPart->next);
-   m_LastPart->next = NULL;
 
-   // check if there is only one part, then we don't need multipart/mixed
-   if(m_LastPart == m_Body->nested.part)
-   {
-      BODY *oldbody = m_Body;
-      m_Body = &(m_LastPart->body);
-      oldbody->nested.part = NULL;
-      mail_free_body(&oldbody);
-   }
+   // after fully constructing everything check if we need to add a
+   // cryptographic signature
+   //
+   // notice that we shouldn't sign (or modify in any other way) messages being
+   // resent/bounced nor those we don't send right now
+   if ( !forStorage && m_sign && !m_Envelope->remail && !Sign() )
+      return false;
 
    return true;
 }
@@ -1228,39 +1310,73 @@
                        MessageParameterList const *plist,
                        wxFontEncoding enc)
 {
-   BODY *bdy;
-   unsigned char *data;
+   // adjust the input parameters
 
-   // the text must be NUL terminated or it will not be encoded correctly and
-   // it won't hurt to add a NUL after the end of data in the other cases as
-   // well (note that cclient won't get this last NUL as len will be
-   // decremented below)
-   len += sizeof(char);
-   data = (unsigned char *) fs_get (len);
-   len -= sizeof(char);
+   // FIXME-OPT: we're copying a lot of data here, if we could ensure that
+   //            buf is already allocated with malloc() and is NUL-terminated
+   //            (this is important of encoding it wouldn't work correctly) we
+   //            would be able to avoid it
+   unsigned char * const data = (unsigned char *) fs_get(len + sizeof(char));
    data[len] = '\0';
    memcpy(data, buf, len);
 
    String subtype(subtype_given);
-   if( subtype.length() == 0 )
+   if( subtype.empty() )
    {
       if ( type == TYPETEXT )
-         subtype = _T("PLAIN");
+         subtype = "PLAIN";
       else if ( type == TYPEAPPLICATION )
-         subtype = _T("OCTET-STREAM");
+         subtype = "OCTET-STREAM";
       else
       {
          // shouldn't send message without MIME subtype, but we don't have any
          // and can't find the default!
          ERRORMESSAGE((_("MIME type specified without subtype and\n"
                          "no default subtype for this type.")));
-         subtype = _T("UNKNOWN");
+         subtype = "UNKNOWN";
       }
    }
 
-   bdy = &(m_NextPart->body);
+   // create a new MIME part
+
+   // if it's the first one, it [provisionally] becomes the top level one
+   BODY *bdy;
+   if ( !m_partTop )
+   {
+      m_partTop = mail_newbody_part();
+
+      bdy = &m_partTop->body;
+   }
+   else // we already have some part(s)
+   {
+      PART *part = m_partTop->body.nested.part;
+      if ( !part )
+      {
+         // we need to create a new multipart/mixed top part and make the old
+         // part and this one its subparts
+         PART * const partOrig = m_partTop;
+
+         m_partTop = mail_newbody_part();
+         m_partTop->body.type = TYPEMULTIPART;
+         m_partTop->body.subtype = cpystr("MIXED");
+
+         part =
+         m_partTop->body.nested.part = partOrig;
+      }
+      else // we already have a top-level multipart
+      {
+         // add this part after the existing subparts
+         while ( part->next )
+            part = part->next;
+      }
+
+      PART * const partNew = mail_newbody_part();
+      part->next = partNew;
+
+      bdy = &partNew->body;
+   }
+
    bdy->type = type;
-
    bdy->subtype = cpystr(subtype.c_str());
 
    bdy->contents.text.data = data;
@@ -1323,14 +1439,9 @@
          break;
 
       default:
-         bdy->encoding = ENCBINARY;
+         bdy->encoding = NeedsToBeEncoded(data) ? ENCBINARY : ENC7BIT;
    }
 
-   m_NextPart->next = mail_newbody_part();
-   m_LastPart = m_NextPart;
-   m_NextPart = m_NextPart->next;
-   m_NextPart->next = NULL;
-
    PARAMETER *lastpar = NULL,
              *par;
 
@@ -1398,7 +1509,8 @@
    }
 
    bdy->parameter = lastpar;
-   bdy->disposition.type = strdup(disposition.ToAscii());
+   if ( !disposition.empty() )
+      bdy->disposition.type = strdup(disposition.ToAscii());
    if ( dlist )
    {
       PARAMETER *lastpar = NULL,
@@ -1419,6 +1531,12 @@
    }
 }
 
+void SendMessageCC::EnableSigning(const String& user)
+{
+   m_sign = true;
+   m_signAsUser = user; // can be empty, ok
+}
+
 // ----------------------------------------------------------------------------
 // SendMessageCC sending
 // ----------------------------------------------------------------------------
@@ -1508,7 +1626,9 @@
 void SendMessageCC::Preview(String *text)
 {
    String textTmp;
-   WriteToString(textTmp);
+   if ( !WriteToString(textTmp) )
+      return;
+
    MDialog_ShowText(m_frame, _("Outgoing message text"), textTmp, 
"SendPreview");
 
    if ( text )
@@ -1787,13 +1907,13 @@
       switch ( m_Protocol )
       {
          case Prot_SMTP:
-            success = smtp_mail (stream,"MAIL",m_Envelope,m_Body) != 0;
+            success = smtp_mail(stream, "MAIL", m_Envelope, GetBody()) != NIL;
             reply = wxString::From8BitData(stream->reply);
             smtp_close (stream);
             break;
 
          case Prot_NNTP:
-            success = nntp_mail (stream,m_Envelope,m_Body) != 0;
+            success = nntp_mail(stream, m_Envelope, GetBody()) != NIL;
             reply = wxString::From8BitData(stream->reply);
             nntp_close (stream);
             break;
@@ -1869,7 +1989,8 @@
    // install our output routine temporarily
    Rfc822OutputRedirector redirect(true /* with bcc */, this);
 
-   return rfc822_output(headers, m_Envelope, m_Body, writer, where, NIL) != 
NIL;
+   return rfc822_output(headers, m_Envelope, GetBody(),
+                        writer, where, NIL) != NIL;
 }
 
 bool

Modified: trunk/M/src/modules/crypt/PGPEngine.cpp
===================================================================
--- trunk/M/src/modules/crypt/PGPEngine.cpp     2008-07-26 18:48:42 UTC (rev 
7495)
+++ trunk/M/src/modules/crypt/PGPEngine.cpp     2008-07-28 22:59:55 UTC (rev 
7496)
@@ -52,6 +52,30 @@
 extern const MPersMsgBox *M_MSGBOX_REMEMBER_PGP_PASSPHRASE;
 
 // ----------------------------------------------------------------------------
+// miscellaneous helper functions
+// ----------------------------------------------------------------------------
+
+namespace
+{
+
+void SkipSpaces(const wxChar *& pc)
+{
+   while ( *pc && wxIsspace(*pc) )
+      pc++;
+}
+
+String ReadNumber(const wxChar *& pc)
+{
+   const wxChar * const start = pc;
+   while ( *pc && ('0' <= *pc && *pc <= '9') )
+      pc++;
+
+   return String(start, pc - start);
+}
+
+} // anonymous namespace
+
+// ----------------------------------------------------------------------------
 // PassphraseManager: this class can be used to remember the passphrases
 //                    instead of asking the user to reenter them again and
 //                    again (and again...)
@@ -130,7 +154,7 @@
    /**
       Executes PGP/GPG with messageIn on stdin and puts stdout into messageOut.
 
-      @param options are hte PGP/GPG program options
+      @param options are the PGP/GPG program options
       @param messageIn will be written to child stdin
       @param messageOut will contain child stdout
       @param log may contain miscellaneous additional info
@@ -141,12 +165,6 @@
                       String& messageOut,
                       MCryptoEngineOutputLog *log);
 
-   /// this is the worker function used by ExecCommand()
-   Status DoExecCommand(const String& options,
-                        const String& messageIn,
-                        String& messageOut,
-                        MCryptoEngineOutputLog *log);
-
 private:
    DECLARE_CRYPTO_ENGINE(PGPEngine)
 };
@@ -168,7 +186,7 @@
 // ----------------------------------------------------------------------------
 
 /*
-   The format of "gnupg --status-fd" output is descrived in the DETAILS file of
+   The format of "gnupg --status-fd" output is described in the DETAILS file of
    gnupg distribution, see
 
    http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/gnupg/doc/DETAILS?rev=HEAD
@@ -193,15 +211,11 @@
 };
 
 PGPEngine::Status
-PGPEngine::DoExecCommand(const String& options,
-                         const String& messageIn,
-                         String& messageOut,
-                         MCryptoEngineOutputLog *log)
+PGPEngine::ExecCommand(const String& options,
+                       const String& messageIn,
+                       String& messageOut,
+                       MCryptoEngineOutputLog *log)
 {
-   // First copy from in to out, in case there is a problem and we can't
-   // execute the command. At least, the original message will be visible.
-   messageOut = messageIn;
-
    // check if we have a PGP command: it can be set to nothing to disable pgp
    // support
    const String pgp = READ_APPCONFIG_TEXT(MP_PGP_COMMAND);
@@ -247,7 +261,6 @@
    wxTextInputStream errText(*err);
 
    Status status = MAX_ERROR;
-   bool encryptedForSomeoneElse = false;
 
    // the user hint and the passphrase
    String user,
@@ -261,13 +274,14 @@
    const char *ptrIn = bufIn;
 
    bool outEof = false,
-        errEof = false;
+        errEof = false,
+        inEof = false;  // set to true when we can safely close child stdin
    while ( !process.IsDone() || !outEof || !errEof )
    {
       wxYieldIfNeeded();
 
       // the order is important here, lest we deadlock: first get everything
-      // gpg has for us and only then try to feed it more data
+      // GPG has for us and only then try to feed it more data
       if ( out->GetLastError() == wxSTREAM_EOF )
       {
          outEof = true;
@@ -292,10 +306,12 @@
 
          lenIn -= lenChunk;
          ptrIn += lenChunk;
-
-         if ( !lenIn )
-            process.CloseOutput();
       }
+      else if ( inEof && in )
+      {
+         process.CloseOutput();
+         in = NULL;
+      }
 
       if ( err->GetLastError() == wxSTREAM_EOF )
       {
@@ -328,23 +344,28 @@
                  code == _T("SIG_ID") ||
                  code == _T("DECRYPTION_OKAY") )
             {
-               if ( status != SIGNATURE_EXPIRED_ERROR ) {
+               if ( status != SIGNATURE_EXPIRED_ERROR )
+               {
+                  wxLogStatus(_("Valid signature from \"%s\""),
+                              log->GetUserID().c_str());
                   status = OK;
                }
-
             }
             else if ( code == _T("BADARMOR") )
             {
                status = BAD_ARGUMENT_ERROR;
                wxLogWarning(_("The PGP message is malformed, "
                               "processing aborted."));
-               // If the message is not correctly formatted, GPG does not 
+               // If the message is not correctly formatted, GPG does not
                // output the resulting message, so we must copy the input
                // into the output
                messageOut = messageIn;
             }
             else if ( code == _T("EXPSIG") || code == _T("EXPKEYSIG") )
             {
+               wxLogWarning(_("Expired signature from \"%s\""),
+                            log->GetUserID().c_str());
+
                status = SIGNATURE_EXPIRED_ERROR;
             }
             else if ( code == _T("BADSIG") )
@@ -366,6 +387,8 @@
                     code == _T("TRUST_NEVER") )
                {
                   status = SIGNATURE_UNTRUSTED_WARNING;
+                  wxLogStatus(_("Valid signature from (invalid) \"%s\""),
+                              log->GetUserID().c_str());
                }
                // else: "_MARGINAL, _FULLY and _ULTIMATE" do not trigger a 
warning
             }
@@ -375,8 +398,7 @@
                while ( *pc && !wxIsspace(*pc) )
                   pc++;
 
-               if ( *pc )
-                  pc++;
+               SkipSpaces(pc);
 
                // remember the user
                user = pc;
@@ -394,8 +416,6 @@
                {
                   status = OPERATION_CANCELED_BY_USER;
 
-                  process.CloseOutput();
-
                   break;
                }
             }
@@ -426,10 +446,15 @@
                   if ( wxStrcmp(pc, _T("passphrase.enter")) == 0 )
                   {
                      // we're being asked for a passphrase
-                     String pass2 = pass;
-                     pass2 += wxTextFile::GetEOL();
+                     const String passNL = pass + wxTextFile::GetEOL();
 
-                     in->Write(pass2.c_str(), pass2.length());
+                     wxWX2MBbuf buf(passNL.mb_str());
+                     const size_t len = strlen(buf);
+                     in->Write(buf, len);
+                     if ( in->LastWrite() != len )
+                     {
+                        wxLogSysError("Failed to communicate with PGP: ");
+                     }
                   }
                   else
                   {
@@ -466,8 +491,108 @@
             }
             else if ( code == _T("NO_SECKEY") )
             {
-               encryptedForSomeoneElse = true;
+               wxLogWarning(_("Secret key needed to decrypt this message is "
+                              "not available"));
             }
+            else if ( code == _T("BEGIN_SIGNING") )
+            {
+               // we don't need to send anything more to GPG, close its stdin
+               // so it knows that nothing more is coming
+               inEof = true;
+            }
+            else if ( code == _T("SIG_CREATED") )
+            {
+               status = OK;
+
+               // extract hash algorithm, the caller needs it to create OpenPGP
+               // message
+
+               // the format of this line is:
+               //
+               // SIG_CREATED <type> <pkalg> <micalg> <cls> <timestamp> <fp>
+               //
+               // where <type> is one of 'D' (detached), 'C' (clear text) and
+               // 'S' (standard); pkalg is a number (typical value is 17 for
+               // DSA) and the known hash algorithm values are below, see
+               // 
http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/trunk/include/cipher.h
+               static const struct
+               {
+                  unsigned micalg;
+                  const char *name;
+               } micalgNames[] =
+               {
+                  {  1, "md5" },
+                  {  2, "sha1" },
+                  {  3, "ripemd160" },
+                  {  8, "sha256" },
+                  {  9, "sha384" },
+                  { 10, "sha512" },
+               };
+
+               String err;
+               if ( *pc++ != 'D' )
+               {
+                  err.Printf(_("unexpected signature type '%c'"), pc[-1]);
+               }
+               else
+               {
+                  SkipSpaces(pc);
+
+                  const String pkalg(ReadNumber(pc));
+                  unsigned long n;
+                  if ( !pkalg.ToULong(&n) )
+                  {
+                     err.Printf(_("unexpected public key algorithm \"%s\""),
+                                pkalg.c_str());
+                  }
+                  else
+                  {
+                     SkipSpaces(pc);
+
+                     const String micalg(ReadNumber(pc));
+                     if ( !micalg.ToULong(&n) )
+                     {
+                        err.Printf(_("unexpected hash algorithm \"%s\""),
+                                   micalg.c_str());
+                     }
+                     else
+                     {
+                        bool found = false;
+                        for ( size_t i = 0; i < WXSIZEOF(micalgNames); i++ )
+                        {
+                           if ( micalgNames[i].micalg == n )
+                           {
+                              if ( log )
+                                 log->SetMicAlg(micalgNames[i].name);
+
+                              found = true;
+                              status = OK;
+                              break;
+                           }
+                        }
+
+                        if ( !found )
+                        {
+                           err.Printf(_("unsupported hash algorithm \"%s\", "
+                                        "please configure GPG to use a hash "
+                                        "algorithm compatible with RFC 3156"),
+                                      micalg.c_str());
+
+                           status = SIGN_UNKNOWN_MICALG;
+                        }
+                     }
+                  }
+               }
+
+               if ( !err.empty() )
+               {
+                  // don't overwrite a more specific error code if set above
+                  if ( status != SIGN_UNKNOWN_MICALG )
+                     status = SIGN_ERROR;
+
+                  wxLogError(_("Failed to sign message: %s"), err.c_str());
+               }
+            }
             else if ( code == _T("ENC_TO") ||
                       code == _T("BEGIN_DECRYPTION") ||
                       code == _T("END_DECRYPTION") ||
@@ -488,16 +613,14 @@
                             line.c_str());
             }
             // Extract user id used to sign
-            if ( log &&
-                 ( code == _T("GOODSIG") ||
-                   code == _T("BADSIG") ) )
+            if ( log && (code == _T("GOODSIG") || code == _T("BADSIG")) )
             {
                String userId = String(pc).AfterFirst(' ');
                log->SetUserID(userId);
             }
          }
-#if defined(NDEBUG) // In non-debug mode, log only free-form output
-         else // normal (free form) gpg output
+#ifndef DEBUG // In non-debug mode, log only free-form output
+         else // normal (free form) GPG output
          {
             // remember in the output log object if we have one
             if ( log )
@@ -505,35 +628,13 @@
                log->AddMessage(line);
             }
          }
-#endif
+#endif // release
       }
    }
 
-   switch (status) 
-   {
-      case OK:
-         wxLogStatus(_("Valid signature from \"%s\""),
-                     log->GetUserID().c_str());
-         break;
+   if ( in )
+      process.CloseOutput();
 
-      case SIGNATURE_UNTRUSTED_WARNING:
-         wxLogStatus(_("Valid signature from (invalid) \"%s\""),
-                     log->GetUserID().c_str());
-         break;
-
-      case SIGNATURE_EXPIRED_ERROR:
-         wxLogWarning(_("Expired signature from \"%s\""),
-                      log->GetUserID().c_str());
-         break;
-
-      default:
-         if ( encryptedForSomeoneElse )
-         {
-            wxLogWarning(_("Secret key needed to decrypt this message is "
-                           "not available"));
-         }
-   }
-
    // Removing this assert:
    // There is at least one case (signature not detached) where GPG does not 
output
    // any line beginning with "[GNUPG:] ". So we can't rely on getting one.
@@ -548,15 +649,6 @@
    return status;
 }
 
-PGPEngine::Status
-PGPEngine::ExecCommand(const String& options,
-                       const String& messageIn,
-                       String& messageOut,
-                       MCryptoEngineOutputLog *log)
-{
-   return DoExecCommand(options, messageIn, messageOut, log);
-}
-
 // ----------------------------------------------------------------------------
 // PGPEngine: encryption
 // ----------------------------------------------------------------------------
@@ -584,6 +676,10 @@
       return CANNOT_EXEC_PROGRAM;
    }
 
+   // First copy from in to out, in case there is a problem and we can't
+   // execute the command. At least, the original message will be visible.
+   messageOut = messageIn;
+
    return ExecCommand(tmpfname.GetName(), wxEmptyString, messageOut, log);
 }
 
@@ -605,14 +701,30 @@
 // ----------------------------------------------------------------------------
 
 PGPEngine::Status
-PGPEngine::Sign(const String& /* user */,
-                const String& /* messageIn */,
-                String& /* messageOut */,
-                MCryptoEngineOutputLog * /* log */)
+PGPEngine::Sign(const String& user,
+                const String& messageIn,
+                String& messageOut,
+                MCryptoEngineOutputLog *log)
 {
-   FAIL_MSG( _T("TODO") );
+   // as in Decrypt(), using stdin to pass both the input file contents and the
+   // passphrase doesn't work well, so use a temporary file
+   wxFile f;
+   MTempFileName tmpfname(&f);
+   if ( !tmpfname.IsOk() || !f.Write(messageIn) )
+   {
+      wxLogError(_("Can't pass the encrypted data to PGP."));
 
-   return NOT_IMPLEMENTED_ERROR;
+      return CANNOT_EXEC_PROGRAM;
+   }
+
+   f.Close(); // close it before calling gpg, otherwise it fails to open it
+
+   String options("--detach-sign");
+   if ( !user.empty() )
+      options += "--local-user " + user;
+
+   options += ' ' + tmpfname.GetName();
+   return ExecCommand(options, wxEmptyString, messageOut, log);
 }
 
 
@@ -656,8 +768,8 @@
 
    wxString messageOut;
 
-   return ExecCommand(_T("--verify ") + tmpfileSig.GetName() +
-                      _T(" ") + tmpfileText.GetName(),
+   return ExecCommand("--verify " + tmpfileSig.GetName() +
+                      " " + tmpfileText.GetName(),
                       wxEmptyString, messageOut, log);
 }
 
@@ -671,11 +783,11 @@
                         MCryptoEngineOutputLog *log) const
 {
    String dummyOut;
-   Status status = const_cast<PGPEngine *>(this)->DoExecCommand
+   Status status = const_cast<PGPEngine *>(this)->ExecCommand
                    (
                      wxString::Format
                      (
-                        _T("--keyserver %s --recv-keys %s"),
+                        "--keyserver %s --recv-keys %s",
                         keyserver.c_str(),
                         pk.c_str()
                      ),


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Mahogany-cvsupdates mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mahogany-cvsupdates

Reply via email to