Example #1
0
   bool
      MimeBody::IsAttachment()  const
   {
      /*
      Previously we looked at the ContentDisposition header and the Name header to determine
      whether it's an attachment or not. This was not safe, since a lot of attachments did
      not have these headers but just a Content-type header. The new strategy is:


      1) If the ContentDisposition is of type attachment, we assume it's an attachment
      2) If the main ContentType is text or multipart, we assume that it's not an attachment
      3) In all other cases, we treat it as an attachment.

      discrete-type := "text" / "image" / "audio" / "video" / "application" / extension-token
      composite-type := "message" / "multipart" / extension-token
      */

      // If the content-disposition is set to attachment, we always treats it as an attachment
      // even if the main type is set to multipart or text.
      AnsiString sDisposition = GetRawFieldValue(CMimeConst::ContentDisposition());
      if (sDisposition.StartsWith(CMimeConst::Attachment()))
         return true;

      if (sDisposition.StartsWith(CMimeConst::Inline()))
      {
         AnsiString sFileName = GetParameter(CMimeConst::ContentDisposition(), "filename");

         if (!sFileName.IsEmpty())
            return true;
      }

      String sMainType = GetMainType();

      if (sMainType.CompareNoCase(_T("multipart")) == 0)
      {
         // Multipart ...
         return false;
      }

      if (sMainType.CompareNoCase(_T("text")) == 0)
      {
         // This is just a text part.
         return false;
      }

      return true;
   }
   int
   SpamAssassinClient::ParseFirstBuffer_(std::shared_ptr<ByteBuffer> pBuffer) const
   {
      // Don't send first line, since it's the Result header.
      char *pHeaderEndPosition = StringParser::Search(pBuffer->GetCharBuffer(), pBuffer->GetSize(), "\r\n\r\n");
      if (!pHeaderEndPosition)
      {
         LOG_DEBUG("The response from SpamAssasin was not valid. Aborting. Expected a header.\r\n");
         return -1;
      }
            
      size_t headerLength = pHeaderEndPosition - pBuffer->GetCharBuffer();
      AnsiString spamAssassinHeader(pBuffer->GetCharBuffer(), headerLength);

      std::vector<AnsiString> headerLines = StringParser::SplitString(spamAssassinHeader, "\r\n");
      AnsiString firstLine = headerLines[0];
      AnsiString secondLine = headerLines[1];

      if (firstLine.Compare("SPAMD/1.1 0 EX_OK") != 0)
      {
         // We should never get here, since we should always have
         // a header in the result

         LOG_DEBUG(Formatter::Format("The response from SpamAssasin was not valid. Aborting. Expected: SPAMD/1.1 0 EX_OK, Got: {0}\r\n", firstLine));
         return -1;
      }

      if (!secondLine.StartsWith("Content-length:"))
      {
         // We should never get here, since we should always have
         // a header in the result
         LOG_DEBUG(Formatter::Format("The response from SpamAssasin was not valid. Aborting. Expected: Content-Length:<value>, Got: {0}\r\n", secondLine));
         return -1;
      }

      // Extract the second line from the first buffer. This buffer
      // contains the result of the operation (success / failure).
      std::vector<AnsiString> contentLengthHeader = StringParser::SplitString(secondLine, ":");
      if (contentLengthHeader.size() != 2)
      {
         LOG_DEBUG(Formatter::Format("The response from SpamAssasin was not valid. Aborting. Content-Length header not properly formatted. Expected: Content-Length:<value>, Got: {0}\r\n", secondLine));
         return -1;
      }

      int contentLength;
      std::string sConSize = contentLengthHeader[1].Trim();
      if (!StringParser::TryParseInt(sConSize, contentLength))
      {
        LOG_DEBUG(Formatter::Format("The response from SpamAssasin was not valid. Aborting. Content-Length header not properly formatted. Expected: Content-Length:<value>, Got: {0}\r\n", secondLine));
	     return -1;
      }

      // Remove the SA header lines from the result.
      size_t iEndingBytesSize = pBuffer->GetSize() - headerLength - 4; // 4 due to header ending with \r\n\r\n.
      pBuffer->Empty(iEndingBytesSize);

      return contentLength;
   }
   VirusScanningResult
   ClamAVVirusScanner::Scan(const String &hostName, int primaryPort, const String &sFilename)
   {
      LOG_DEBUG("Connecting to ClamAV virus scanner...");

      int streamPort = 0;

      TimeoutCalculator calculator;

      SynchronousConnection commandConnection(calculator.Calculate(IniFileSettings::Instance()->GetClamMinTimeout(), IniFileSettings::Instance()->GetClamMaxTimeout()));
      if (!commandConnection.Connect(hostName, primaryPort))
      {
         return VirusScanningResult(_T("ClamAVVirusScanner::Scan"), 
            Formatter::Format("Unable to connect to ClamAV server at {0}:{1}.", hostName, primaryPort));
      }

      if (!commandConnection.Write("STREAM\r\n"))
         return VirusScanningResult("ClamAVVirusScanner::Scan", "Unable to write STREAM command.");

      AnsiString readData;
      if (!commandConnection.ReadUntil("\n", readData))
         return VirusScanningResult("ClamAVVirusScanner::Scan", "Unable to read STREAM command response.");

      if (!readData.StartsWith("PORT"))
         return VirusScanningResult("ClamAVVirusScanner::Scan", Formatter::Format("Protocol error. Unexpected response: {0}.", readData));
      
      readData.TrimRight("\n");

      // Determine port.
      std::string portString = readData.Mid(5);
      
      if (!StringParser::TryParseInt(portString, streamPort))
         return VirusScanningResult("ClamAVVirusScanner::Scan", Formatter::Format("Protocol error. Unexpected response: {0} (Unable to parse port).", readData));

      LOG_DEBUG("Connecting to ClamAV stream port...");
      SynchronousConnection streamConnection(15);
      if (!streamConnection.Connect(hostName, streamPort))
         return VirusScanningResult("ClamAVVirusScanner::Scan", Formatter::Format("Unable to connect to ClamAV stream port at {0}:{1}.", hostName, streamPort));

      // Send the file on the stream socket.
      File oFile;
      if (!oFile.Open(sFilename, File::OTReadOnly))
      {
         String sErrorMsg = Formatter::Format("Could not send file {0} via socket since it does not exist.", sFilename);
         return VirusScanningResult("ClamAVVirusScanner::Scan", sErrorMsg);
      }

      const int STREAM_BLOCK_SIZE = 4096;
      const int maxIterations = 100000;
      for (int i = 0; i < maxIterations; i++)
      {
         std::shared_ptr<ByteBuffer> pBuf = oFile.ReadChunk(STREAM_BLOCK_SIZE);

         if (!pBuf)
            break;

         // Send the request.
         if (!streamConnection.Write(*pBuf))
            return VirusScanningResult("ClamAVVirusScanner::Scan", "Unable to write data to stream port.");
      }

      streamConnection.Close();

      if (!commandConnection.ReadUntil("\n", readData))
         return VirusScanningResult("ClamAVVirusScanner::Scan", "Unable to read response (after streaming).");

      readData.TrimRight("\n");

      // Parse the response and see if a virus was reported.
      try
      {
         const regex expression("^stream.*: (.*) FOUND$"); 
         cmatch what; 
         if(regex_match(readData.c_str(), what, expression)) 
         {
            LOG_DEBUG("Virus detected: " + what[1]);
            return VirusScanningResult(VirusScanningResult::VirusFound, String(what[1]));
         }
         else
         {
            LOG_DEBUG("No virus detected: " + readData);
            return VirusScanningResult(VirusScanningResult::NoVirusFound, Formatter::Format("Result: {0}", readData));
         }
      }
      catch (std::runtime_error &) // regex_match will throw runtime_error if regexp is too complex.
      {
         return VirusScanningResult("ClamAVVirusScanner::Scan", "Unable to parse regular expression.");
      }

      
   }
   AnsiString 
   SimpleCanonicalization::CanonicalizeHeader(AnsiString header, const std::pair<AnsiString, AnsiString> &signatureField, const std::vector<AnsiString> &fieldsToInclude, AnsiString &fieldList)
   {
      // first build a formatted list of header lines.
      std::vector<AnsiString> formattedHeaderLines;

      AnsiString result;
      std::vector<AnsiString> headerLines = StringParser::SplitString(header, "\r\n");

      AnsiString foldedLines;
      for (size_t i = headerLines.size(); i > 0; i--)
      {
         AnsiString line = headerLines[i-1];

         if (line.StartsWith(" ") || line.StartsWith("\t"))
         {
            // line is folded. append to next.
            foldedLines = line + "\r\n" + foldedLines;
         }
         else
         {
            // we have a line!
            int colonPos = line.Find(":");
            if (colonPos < 0)
            {
               assert(0); // broken header.
               continue;
            }

            AnsiString entireHeaderField = line + "\r\n" + foldedLines;

            formattedHeaderLines.push_back(entireHeaderField);

            foldedLines = "";
         }
      }

      for(AnsiString fieldToInclude : fieldsToInclude)
      {
         fieldToInclude.Trim();

         // locate the header line.
         auto iter = formattedHeaderLines.begin();
         auto iterEnd = formattedHeaderLines.end();
         
         for (; iter != iterEnd; iter++)
         {
            AnsiString headerLine = (*iter);

            int colonPos = headerLine.Find(":");
            AnsiString headerName = headerLine.Mid(0, colonPos);

            if (headerName.CompareNoCase(fieldToInclude) == 0) 
            {
               result += headerLine;

               if (!fieldList.IsEmpty())
                  fieldList += ":";

               fieldList += headerName;

               formattedHeaderLines.erase(iter);
               break;
            }
         }

      }

      if (!signatureField.first.IsEmpty())
      {
         // Don't pick the value from the actual header, use the header we're verifying instead
         // If there are more than one DKIM-signature fields in the header, this will be important.
         AnsiString headerName = signatureField.first;

         AnsiString headerLine = headerName + ": " + GetDKIMWithoutSignature_(signatureField.second);

         if (headerLine.EndsWith("\r\n"))
            headerLine = headerLine.Mid(0, headerLine.GetLength()-2);

         result += headerLine;
      }


      return result;
   }