commit a8ef025a7ad9058dd2fb815ee48c355a49ba0661
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Wed Mar 9 01:03:44 2011 +0100

    [evol] Mail: move it into the Postman module

diff --git a/lib/cyborghood-postman/mail.rb b/lib/cyborghood-postman/mail.rb
index 47701f9..aa7cd15 100644
--- a/lib/cyborghood-postman/mail.rb
+++ b/lib/cyborghood-postman/mail.rb
@@ -28,247 +28,249 @@ require 'digest/md5'
 # This class handles RFC3156 signed/encrypted messages, validates them, and extract content properly.
 # It also implements a protection against replay attacks.
 module CyborgHood
-  class MailReport
-    attr_reader :error, :warn_sender, :user, :message
+  module PostmanLand
+    class MailReport
+      attr_reader :error, :warn_sender, :user, :message
 
-    def initialize(params = {})
-      @error = params[:error]
-      @warn_sender = params[:warn_sender]
-      @user = params[:user]
-      @message = params[:message]
+      def initialize(params = {})
+        @error = params[:error]
+        @warn_sender = params[:warn_sender]
+        @user = params[:user]
+        @message = params[:message]
 
-      @warn_sender = false
-    end
+        @warn_sender = false
+      end
 
-    def ok?
-      @error.nil? and @user and @message
-    end
+      def ok?
+        @error.nil? and @user and @message
+      end
 
-    def warn_sender
-      # send reply message if user is identified or in selected situations
-      # (@warn_sender acts as an override)
-      @warn_sender || @user
+      def warn_sender
+        # send reply message if user is identified or in selected situations
+        # (@warn_sender acts as an override)
+        @warn_sender || @user
+      end
     end
-  end
 
-  class Mail < Delegator
-    include I18nTranslation
+    class Mail < Delegator
+      include I18nTranslation
 
-    attr_accessor :user, :signature_timestamp
+      attr_accessor :user, :signature_timestamp
 
-    def initialize(msg = nil)
-      @config = Config.instance
+      def initialize(msg = nil)
+        @config = Config.instance
 
-      if msg.nil?
-        @mail = TMail::Mail.new
-        set_custom_headers
-      elsif msg.is_a? TMail::Mail
-        @mail = msg
-      else
-        @mail = TMail::Mail.parse(msg)
+        if msg.nil?
+          @mail = TMail::Mail.new
+          set_custom_headers
+        elsif msg.is_a? TMail::Mail
+          @mail = msg
+        else
+          @mail = TMail::Mail.parse(msg)
+        end
       end
-    end
 
-    def self.blank
-      m = self.new
-      m.clear
-      m
-    end
+      def self.blank
+        m = self.new
+        m.clear
+        m
+      end
 
-    def __getobj__
-      @mail
-    end
+      def __getobj__
+        @mail
+      end
 
-    def process
-      if is_marked?
-        if @config.debug.flags.include?('debug_ignorereplay')
-          logger.warn "Debug: ignoring replay"
-        else
-          return MailReport.new(:error => _("Replay detected."))
+      def process
+        if is_marked?
+          if @config.debug.flags.include?('debug_ignorereplay')
+            logger.warn "Debug: ignoring replay"
+          else
+            return MailReport.new(:error => _("Replay detected."))
+          end
         end
-      end
 
-      return process_signed() if is_pgp_signed?
-      return process_encrypted() if is_pgp_encrypted?
+        return process_signed() if is_pgp_signed?
+        return process_encrypted() if is_pgp_encrypted?
 
-      MailReport.new(:error => _("Mail not RFC3156 compliant."))
-    end
+        MailReport.new(:error => _("Mail not RFC3156 compliant."))
+      end
 
-    def create_reply
-      tmail_reply = @mail.create_reply
-      tmail_reply.from_addrs = TMail::Address.parse(@config.mail.from_address || self.to.first)
-      tmail_reply.to_addrs = (@mail.reply_to_addrs_pretty || @mail.from_addrs_pretty).collect do |a|
-        TMail::Address.parse(@mail.class.header_value_smtp(a))
+      def create_reply
+        tmail_reply = @mail.create_reply
+        tmail_reply.from_addrs = TMail::Address.parse(@config.mail.from_address || self.to.first)
+        tmail_reply.to_addrs = (@mail.reply_to_addrs_pretty || @mail.from_addrs_pretty).collect do |a|
+          TMail::Address.parse(@mail.class.header_value_smtp(a))
+        end
+        reply = self.class.new(tmail_reply.to_s)
+        reply.set_custom_headers
+        reply.user = @user
+        reply
       end
-      reply = self.class.new(tmail_reply.to_s)
-      reply.set_custom_headers
-      reply.user = @user
-      reply
-    end
 
-    def create_simple_reject_reply(msg)
-      reply = create_reply()
-      reply.set_content_type("text", "plain", {'charset' => "utf-8"})
-      reply.set_disposition("inline")
-      reply.quoted_printable_body = msg + self.default_body_signature
-      reply.sign
-      reply.crypt(reply.user.keyFingerPrint) unless reply.user.nil?
-      reply
-    end
+      def create_simple_reject_reply(msg)
+        reply = create_reply()
+        reply.set_content_type("text", "plain", {'charset' => "utf-8"})
+        reply.set_disposition("inline")
+        reply.quoted_printable_body = msg + self.default_body_signature
+        reply.sign
+        reply.crypt(reply.user.keyFingerPrint) unless reply.user.nil?
+        reply
+      end
 
-    def to_multipart!
-      self.set_content_type("multipart", "mixed", {'boundary' => TMail.new_boundary})
-      self.transfer_encoding = "7bit"
-      self.quoted_body = "This mail is in MIME format.\n"
-      self.parts.clear
-    end
+      def to_multipart!
+        self.set_content_type("multipart", "mixed", {'boundary' => TMail.new_boundary})
+        self.transfer_encoding = "7bit"
+        self.quoted_body = "This mail is in MIME format.\n"
+        self.parts.clear
+      end
 
-    def set_custom_headers
-      @mail['Organization'] = @config.mail.organization
-    end
+      def set_custom_headers
+        @mail['Organization'] = @config.mail.organization
+      end
 
-    def check_headers
-      @mail.header.keys.each do |h|
-        @mail[h] = @mail.class.quote_address_if_necessary(@mail[h].to_s, "utf-8")
+      def check_headers
+        @mail.header.keys.each do |h|
+          @mail[h] = @mail.class.quote_address_if_necessary(@mail[h].to_s, "utf-8")
+        end
       end
-    end
 
-    def default_body_signature
-      s = "\n" +
-        "-- \n" +
-        "#{CyborgHood::PRODUCT} v#{CyborgHood::VERSION}\n"
-      s += _("Contact eMail:").to_s + " \"#{@config.contact.name}\" <#{@config.contact.email}>\n" if @config.contact.email
-      s += _("Contact URL:").to_s + " #{@config.contact.url}\n" if @config.contact.url
-      s
-    end
+      def default_body_signature
+        s = "\n" +
+          "-- \n" +
+          "#{CyborgHood::PRODUCT} v#{CyborgHood::VERSION}\n"
+        s += _("Contact eMail:").to_s + " \"#{@config.contact.name}\" <#{@config.contact.email}>\n" if @config.contact.email
+        s += _("Contact URL:").to_s + " #{@config.contact.url}\n" if @config.contact.url
+        s
+      end
 
-    def deliver
-      check_headers
+      def deliver
+        check_headers
 
-      smtp_server = @config.mail.smtp_server
-      smtp_port = @config.mail.smtp_port
-      smtp_from = @mail.from_addrs.collect{|a| a.address}.join(", ")
-      smtp_to = @mail.to_addrs.collect{|a| a.address}
-      Net::SMTP.start(smtp_server, smtp_port) do |smtp|
-        smtp.send_message(@mail.to_s, smtp_from, smtp_to)
+        smtp_server = @config.mail.smtp_server
+        smtp_port = @config.mail.smtp_port
+        smtp_from = @mail.from_addrs.collect{|a| a.address}.join(", ")
+        smtp_to = @mail.to_addrs.collect{|a| a.address}
+        Net::SMTP.start(smtp_server, smtp_port) do |smtp|
+          smtp.send_message(@mail.to_s, smtp_from, smtp_to)
+        end
       end
-    end
 
-    def to_s
-      @mail.to_s
-    end
+      def to_s
+        @mail.to_s
+      end
 
-    def crypt(fingerprint = nil)
-      @mail = @mail.create_encrypted(fingerprint || @user.keyFingerPrint)
-    end
+      def crypt(fingerprint = nil)
+        @mail = @mail.create_encrypted(fingerprint || @user.keyFingerPrint)
+      end
 
-    def sign
-      # we don't check the uid, as there is only one signer
-      @mail = @mail.create_signed(@config.mail.key_id) do |uid_hint, passphrase_info, prev_was_bad|
-        @config.mail.key_passphrase
+      def sign
+        # we don't check the uid, as there is only one signer
+        @mail = @mail.create_signed(@config.mail.key_id) do |uid_hint, passphrase_info, prev_was_bad|
+          @config.mail.key_passphrase
+        end
       end
-    end
 
-    def sign_and_crypt(fingerprint = nil)
-      # not using sign_and_crypt(), to avoid repeating code
-      sign()
-      crypt(fingerprint || @user.keyFingerPrint)
-    end
+      def sign_and_crypt(fingerprint = nil)
+        # not using sign_and_crypt(), to avoid repeating code
+        sign()
+        crypt(fingerprint || @user.keyFingerPrint)
+      end
 
-    private
+      private
 
-    def process_signed
-      sigs_check = verify_pgp_signature()
-      return MailReport.new(:error => _("Mail not formatted correctly (signed part).")) if sigs_check.nil? or sigs_check.size != 1
+      def process_signed
+        sigs_check = verify_pgp_signature()
+        return MailReport.new(:error => _("Mail not formatted correctly (signed part).")) if sigs_check.nil? or sigs_check.size != 1
 
-      sig_check = sigs_check.first
-      return MailReport.new(:error => _("Mail content tampered or badly signed: %{sig_err}", :sig_err => sig_check.to_s)) unless sig_check.status == 0
+        sig_check = sigs_check.first
+        return MailReport.new(:error => _("Mail content tampered or badly signed: %{sig_err}", :sig_err => sig_check.to_s)) unless sig_check.status == 0
 
-      logger.info "Mail content was properly signed by key #{sig_check.fingerprint}"
-      user = Person.find_by_fingerprint(sig_check.fingerprint)
-      return MailReport.new(:error => _("Mail is from an unknown person."), :warn_sender => true) if user.nil?
+        logger.info "Mail content was properly signed by key #{sig_check.fingerprint}"
+        user = Person.find_by_fingerprint(sig_check.fingerprint)
+        return MailReport.new(:error => _("Mail is from an unknown person."), :warn_sender => true) if user.nil?
 
-      logger.info "Mail is from user #{user.uid} (#{user.cn})"
-      @user = user
+        logger.info "Mail is from user #{user.uid} (#{user.cn})"
+        @user = user
 
-      drift = Time.new.to_i - sig_check.timestamp.to_i
-      logger.debug "Signature drift time: #{drift}"
-      unless drift.abs < @config.mail.max_drift_time
-        if drift > 0
-          return MailReport.new(:error => _("The signature was made too long ago (check your system clock). Rejected message to avoid replay attacks."), :user => user)
-        else
-          # mark message to prevent later replay of the message
-          mark_processed(sig_check.timestamp)
-          return MailReport.new(:error => _("The signature was made in the future (check your system clock). Rejected message to avoid replay attacks."), :user => user)
+        drift = Time.new.to_i - sig_check.timestamp.to_i
+        logger.debug "Signature drift time: #{drift}"
+        unless drift.abs < @config.mail.max_drift_time
+          if drift > 0
+            return MailReport.new(:error => _("The signature was made too long ago (check your system clock). Rejected message to avoid replay attacks."), :user => user)
+          else
+            # mark message to prevent later replay of the message
+            mark_processed(sig_check.timestamp)
+            return MailReport.new(:error => _("The signature was made in the future (check your system clock). Rejected message to avoid replay attacks."), :user => user)
+          end
         end
-      end
 
-      logger.debug "Mail OK"
-      mark_processed(self.signature_timestamp)
+        logger.debug "Mail OK"
+        mark_processed(self.signature_timestamp)
 
-      signed_content = pgp_signed_part()
+        signed_content = pgp_signed_part()
 
-      # create a fake mail and chain parsing operations
-      plain_mail = self.class.new(signed_content)
-      plain_mail.user = @user
-      plain_mail.signature_timestamp = sig_check.timestamp
-      # propagate message_id to be able to mark messages (replay protection)
-      plain_mail.message_id = @mail.message_id
+        # create a fake mail and chain parsing operations
+        plain_mail = self.class.new(signed_content)
+        plain_mail.user = @user
+        plain_mail.signature_timestamp = sig_check.timestamp
+        # propagate message_id to be able to mark messages (replay protection)
+        plain_mail.message_id = @mail.message_id
 
-      MailReport.new(:user => user, :message => plain_mail)
-    end
-
-    def process_encrypted
-      catch :notforme do
-        begin
-          # block is not passed to delegate (limitation ?)
-          clear_message = @mail.pgp_decrypt do |uid_hint, passphrase_info, prev_was_bad|
-            logger.info "Mail crypted for #{uid_hint}"
-
-            # check if requesting passphrase for the expected key
-            key_id = passphrase_info.split(" ")[1]
-            throw :notforme if key_id != @config.mail.key_id[-(key_id.size)..-1]
+        MailReport.new(:user => user, :message => plain_mail)
+      end
 
-            # sending key
-            @config.mail.key_passphrase
+      def process_encrypted
+        catch :notforme do
+          begin
+            # block is not passed to delegate (limitation ?)
+            clear_message = @mail.pgp_decrypt do |uid_hint, passphrase_info, prev_was_bad|
+              logger.info "Mail crypted for #{uid_hint}"
+
+              # check if requesting passphrase for the expected key
+              key_id = passphrase_info.split(" ")[1]
+              throw :notforme if key_id != @config.mail.key_id[-(key_id.size)..-1]
+
+              # sending key
+              @config.mail.key_passphrase
+            end
+
+            # create a fake mail and chain parsing operations
+            clear_mail = self.class.new(clear_message)
+            clear_mail.user = @user
+            # propagate message_id to be able to mark messages (replay protection)
+            clear_mail.message_id = @mail.message_id
+            decrypted_mail = clear_mail.process
+            # reverse propagate user information (convenience)
+            @user = decrypted_mail.user
+
+            return decrypted_mail
+          rescue GPGME::Error, NotImplementedError => e
+            raise CyberError.new(:unrecoverable, "protocol/mail", e.message)
           end
-
-          # create a fake mail and chain parsing operations
-          clear_mail = self.class.new(clear_message)
-          clear_mail.user = @user
-          # propagate message_id to be able to mark messages (replay protection)
-          clear_mail.message_id = @mail.message_id
-          decrypted_mail = clear_mail.process
-          # reverse propagate user information (convenience)
-          @user = decrypted_mail.user
-
-          return decrypted_mail
-        rescue GPGME::Error, NotImplementedError => e
-          raise CyberError.new(:unrecoverable, "protocol/mail", e.message)
         end
-      end
 
-      MailReport.new(:error => _("Mail not formatted correctly (encrypted part)."))
-    end
+        MailReport.new(:error => _("Mail not formatted correctly (encrypted part)."))
+      end
 
-    def mark_dir
-      File.join(Config::VAR_DIR, "marks")
-    end
+      def mark_dir
+        File.join(Config::VAR_DIR, "marks")
+      end
 
-    def mark_filename
-      File.join(mark_dir(), Digest::MD5.hexdigest(self.message_id))
-    end
+      def mark_filename
+        File.join(mark_dir(), Digest::MD5.hexdigest(self.message_id))
+      end
 
-    def mark_processed(time)
-      FileUtils.mkdir_p(mark_dir())
-      File.open(mark_filename(), "w") do |fp|
-        fp.write self.message_id
+      def mark_processed(time)
+        FileUtils.mkdir_p(mark_dir())
+        File.open(mark_filename(), "w") do |fp|
+          fp.write self.message_id
+        end
+        File.utime(Time.now.to_i, time.to_i, mark_filename())
       end
-      File.utime(Time.now.to_i, time.to_i, mark_filename())
-    end
 
-    def is_marked?
-      File.exists?(mark_filename())
+      def is_marked?
+        File.exists?(mark_filename())
+      end
     end
   end
 end
