commit c7904e2c61b6eaa81c193b40bf87c9332410cf9a
Author: Marc Dequenes <duck@Orfeo.duckcorp.org>
Date:   Mon Feb 23 15:41:52 2009 +0100

    [evol] replay protection #2 (should be done), and moved 'run' directory in the new workdir (temporary code until real path configuration)

diff --git a/.gitignore b/.gitignore
index c4936a0..a37de5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@
 \#*#
 config
 log
-run
+var
diff --git a/lib/cyborghood/base.rb b/lib/cyborghood/base.rb
index 73b69b2..6b0c28b 100644
--- a/lib/cyborghood/base.rb
+++ b/lib/cyborghood/base.rb
@@ -79,6 +79,7 @@ module CyborgHood
     include Singleton
 
     CFG_DIR = './config/'
+    WORK_DIR = './var/lib/cyborghood/'
 
     CONFFILE_GLOBAL = "cyborghood.conf"
     CONFFILE_BOT = "cyborghood_%s.conf"
@@ -103,6 +104,11 @@ module CyborgHood
       end
     end
 
+    # temporary
+    def work_dir
+      WORK_DIR
+    end
+
     private
 
     def self.fetch_config(name = nil)
diff --git a/lib/cyborghood/mail.rb b/lib/cyborghood/mail.rb
index 4c8f346..d12bfeb 100644
--- a/lib/cyborghood/mail.rb
+++ b/lib/cyborghood/mail.rb
@@ -14,6 +14,9 @@ require 'tmail_extra'
 require 'action_mailer/quoting'
 require 'action_mailer/utils'
 require 'net/smtp'
+require 'fileutils'
+require 'digest/md5'
+
 
 # This class handles RFC3156 signed messages, validates them, and extract orders properly.
 # Encrypted content are not implemented yet.
@@ -41,6 +44,11 @@ module CyborgHood
     end
 
     def parse
+      if is_marked?
+        logger.info "Replay detected"
+        return nil
+      end
+
       return parse_signed() if is_pgp_signed?
       return parse_encrypted() if is_pgp_encrypted?
       {:ok => false, :msg => "mail not RFC3156 compliant"}.to_ostruct
@@ -123,7 +131,7 @@ module CyborgHood
 
           drift = Time.new.to_i - sig_check.timestamp.to_i
           logger.debug "Signature drift time: #{drift}"
-          if drift > 0 and drift < MAX_DRIFT_TIME
+          if drift.abs < MAX_DRIFT_TIME
             signed_content = pgp_signed_part()
             if signed_content.multipart?
               if signed_content.parts[0].content_type == "text/plain"
@@ -149,14 +157,22 @@ module CyborgHood
               end
 
               logger.debug "Mail OK"
+              mark_processed(sig_check.timestamp)
               order = {:ok => true, :user => user, :commands => commands, :refs => refs}
             else
               order[:user] = user
               logger.info "Mail does not contain a proper MIME part for commands"
             end
           else
-            logger.info "Mail rejected as it may be a replay (signature timestamp is too old)"
-            order = {:ok => false, :user => user, :msg => "The signature was made too long ago (perhaps your system clock is not up-to-date). Rejected message to avoid replay attacks."}
+            if drift > 0
+              logger.info "Mail rejected as it may be a replay (signature timestamp is too old)"
+              order = {:ok => false, :user => user, :msg => "The signature was made too long ago (perhaps your system clock is not up-to-date). Rejected message to avoid replay attacks."}
+            else
+              # mark message to prevent later replay of the message
+              mark_processed(sig_check.timestamp)
+              logger.info "Mail rejected as it is coming from the future (and may allow later replay)"
+              order = {:ok => false, :user => user, :msg => "The signature was made in the future (perhaps your system clock is not up-to-date). Rejected message to avoid replay attacks."}
+            end
           end
         end
       else
@@ -186,6 +202,8 @@ module CyborgHood
 
           # create a fake mail and chain parsing operations
           clear_mail = self.class.new(clear_message)
+          # propagate message_id to be able to mark messages (replay protection)
+          clear_mail.message_id = @mail.message_id
           return clear_mail.parse
         rescue GPGME::Error, NotImplementedError => e
           raise CyberError.new(:unrecoverable, "protocol/mail", e.message)
@@ -194,5 +212,25 @@ module CyborgHood
 
       order.to_ostruct
     end
+
+    def mark_dir
+      File.join(@config.work_dir, "marks")
+    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
+      end
+      File.utime(Time.now.to_i, time.to_i, mark_filename())
+    end
+
+    def is_marked?
+      File.exists?(mark_filename())
+    end
   end
 end
diff --git a/postman_daemon b/postman_daemon
index 342055c..38d4091 100755
--- a/postman_daemon
+++ b/postman_daemon
@@ -6,7 +6,7 @@ require 'daemons'
 options = {
   :app_name     => "CyborgHood_Postman",
   :dir_mode     => :normal,
-  :dir          => "/opt/cyborghood/run",
+  :dir          => "/opt/cyborghood/var/run",
   :multiple     => false,
   :mode         => :exec,
   :monitor      => true
