astro-ruby-sasl 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +22 -0
 - data/lib/sasl.rb +4 -0
 - data/lib/sasl/anonymous.rb +14 -0
 - data/lib/sasl/base.rb +144 -0
 - data/lib/sasl/base64.rb +32 -0
 - data/lib/sasl/digest_md5.rb +168 -0
 - data/lib/sasl/plain.rb +14 -0
 - data/spec/anonymous_spec.rb +19 -0
 - data/spec/digest_md5_spec.rb +97 -0
 - data/spec/mechanism_spec.rb +56 -0
 - data/spec/plain_spec.rb +39 -0
 - metadata +66 -0
 
    
        data/README.markdown
    ADDED
    
    | 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Simple Authentication and Security Layer (RFC 4422) for Ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            ============================================================
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            Goal
         
     | 
| 
      
 5 
     | 
    
         
            +
            ----
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Have a reusable library for client implementations that need to do
         
     | 
| 
      
 8 
     | 
    
         
            +
            authentication over SASL, mainly targeted at Jabber/XMPP libraries.
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            All class carry just state, are thread-agnostic and must also work in
         
     | 
| 
      
 11 
     | 
    
         
            +
            asynchronous environments.
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            Usage
         
     | 
| 
      
 14 
     | 
    
         
            +
            -----
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            Derive from **SASL::Preferences** and overwrite the methods. Then,
         
     | 
| 
      
 17 
     | 
    
         
            +
            create a mechanism instance:
         
     | 
| 
      
 18 
     | 
    
         
            +
                # mechanisms => ['DIGEST-MD5', 'PLAIN']
         
     | 
| 
      
 19 
     | 
    
         
            +
                sasl = SASL.new(mechanisms, my_preferences)
         
     | 
| 
      
 20 
     | 
    
         
            +
                content_to_send = sasl.start
         
     | 
| 
      
 21 
     | 
    
         
            +
                # [...]
         
     | 
| 
      
 22 
     | 
    
         
            +
                content_to_send = sasl.challenge(received_content)
         
     | 
    
        data/lib/sasl.rb
    ADDED
    
    
| 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SASL
         
     | 
| 
      
 2 
     | 
    
         
            +
              ##
         
     | 
| 
      
 3 
     | 
    
         
            +
              # SASL ANONYMOUS where you only send a username that may not get
         
     | 
| 
      
 4 
     | 
    
         
            +
              # evaluated by the server.
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # RFC 4505:
         
     | 
| 
      
 7 
     | 
    
         
            +
              # http://tools.ietf.org/html/rfc4505
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Anonymous < Mechanism
         
     | 
| 
      
 9 
     | 
    
         
            +
                def start
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @state = nil
         
     | 
| 
      
 11 
     | 
    
         
            +
                  ['auth', preferences.username.to_s]
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/sasl/base.rb
    ADDED
    
    | 
         @@ -0,0 +1,144 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ##
         
     | 
| 
      
 2 
     | 
    
         
            +
            # RFC 4422:
         
     | 
| 
      
 3 
     | 
    
         
            +
            # http://tools.ietf.org/html/rfc4422
         
     | 
| 
      
 4 
     | 
    
         
            +
            module SASL
         
     | 
| 
      
 5 
     | 
    
         
            +
              ##
         
     | 
| 
      
 6 
     | 
    
         
            +
              # You must derive from class Preferences and overwrite methods you
         
     | 
| 
      
 7 
     | 
    
         
            +
              # want to implement.
         
     | 
| 
      
 8 
     | 
    
         
            +
              class Preferences
         
     | 
| 
      
 9 
     | 
    
         
            +
                ##
         
     | 
| 
      
 10 
     | 
    
         
            +
                # Authorization identitiy ('username@domain' in XMPP)
         
     | 
| 
      
 11 
     | 
    
         
            +
                def authzid
         
     | 
| 
      
 12 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                ##
         
     | 
| 
      
 16 
     | 
    
         
            +
                # Realm ('domain' in XMPP)
         
     | 
| 
      
 17 
     | 
    
         
            +
                def realm
         
     | 
| 
      
 18 
     | 
    
         
            +
                  raise AbstractMethod
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                ##
         
     | 
| 
      
 23 
     | 
    
         
            +
                # digest-uri: serv-type/serv-name | serv-type/host/serv-name
         
     | 
| 
      
 24 
     | 
    
         
            +
                # ('xmpp/domain' in XMPP)
         
     | 
| 
      
 25 
     | 
    
         
            +
                def digest_uri
         
     | 
| 
      
 26 
     | 
    
         
            +
                  raise AbstractMethod
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def username
         
     | 
| 
      
 30 
     | 
    
         
            +
                  raise AbstractMethod
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def has_password?
         
     | 
| 
      
 34 
     | 
    
         
            +
                  false
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def allow_plaintext?
         
     | 
| 
      
 38 
     | 
    
         
            +
                  false
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def password
         
     | 
| 
      
 42 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def want_anonymous?
         
     | 
| 
      
 46 
     | 
    
         
            +
                  false
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
              ##
         
     | 
| 
      
 51 
     | 
    
         
            +
              # Will be raised by SASL.new_mechanism if mechanism passed to the
         
     | 
| 
      
 52 
     | 
    
         
            +
              # constructor is not known.
         
     | 
| 
      
 53 
     | 
    
         
            +
              class UnknownMechanism < RuntimeError
         
     | 
| 
      
 54 
     | 
    
         
            +
                def initialize(mechanism)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @mechanism = mechanism
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 59 
     | 
    
         
            +
                  "Unknown mechanism: #{@mechanism.inspect}"
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
              def SASL.new(mechanisms, preferences)
         
     | 
| 
      
 64 
     | 
    
         
            +
                best_mechanism = if preferences.want_anonymous? && mechanisms.include?('ANONYMOUS')
         
     | 
| 
      
 65 
     | 
    
         
            +
                                   'ANONYMOUS'
         
     | 
| 
      
 66 
     | 
    
         
            +
                                 elsif preferences.has_password?
         
     | 
| 
      
 67 
     | 
    
         
            +
                                   if mechanisms.include?('DIGEST-MD5')
         
     | 
| 
      
 68 
     | 
    
         
            +
                                     'DIGEST-MD5'
         
     | 
| 
      
 69 
     | 
    
         
            +
                                   elsif preferences.allow_plaintext?
         
     | 
| 
      
 70 
     | 
    
         
            +
                                     'PLAIN'
         
     | 
| 
      
 71 
     | 
    
         
            +
                                   else
         
     | 
| 
      
 72 
     | 
    
         
            +
                                     raise UnknownMechanism.new(mechanisms)
         
     | 
| 
      
 73 
     | 
    
         
            +
                                   end
         
     | 
| 
      
 74 
     | 
    
         
            +
                                 else
         
     | 
| 
      
 75 
     | 
    
         
            +
                                   raise UnknownMechanism.new(mechanisms)
         
     | 
| 
      
 76 
     | 
    
         
            +
                                 end
         
     | 
| 
      
 77 
     | 
    
         
            +
                new_mechanism(best_mechanism, preferences)
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
              ##
         
     | 
| 
      
 81 
     | 
    
         
            +
              # Create a SASL Mechanism for the named mechanism
         
     | 
| 
      
 82 
     | 
    
         
            +
              #
         
     | 
| 
      
 83 
     | 
    
         
            +
              # mechanism:: [String] mechanism name
         
     | 
| 
      
 84 
     | 
    
         
            +
              def SASL.new_mechanism(mechanism, preferences)
         
     | 
| 
      
 85 
     | 
    
         
            +
                mechanism_class = case mechanism
         
     | 
| 
      
 86 
     | 
    
         
            +
                                  when 'DIGEST-MD5'
         
     | 
| 
      
 87 
     | 
    
         
            +
                                    DigestMD5
         
     | 
| 
      
 88 
     | 
    
         
            +
                                  when 'PLAIN'
         
     | 
| 
      
 89 
     | 
    
         
            +
                                    Plain
         
     | 
| 
      
 90 
     | 
    
         
            +
                                  when 'ANONYMOUS'
         
     | 
| 
      
 91 
     | 
    
         
            +
                                    Anonymous
         
     | 
| 
      
 92 
     | 
    
         
            +
                                  else
         
     | 
| 
      
 93 
     | 
    
         
            +
                                    raise UnknownMechanism.new(mechanism)
         
     | 
| 
      
 94 
     | 
    
         
            +
                                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
                mechanism_class.new(mechanism, preferences)
         
     | 
| 
      
 96 
     | 
    
         
            +
              end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
              class AbstractMethod < Exception # :nodoc:
         
     | 
| 
      
 100 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 101 
     | 
    
         
            +
                  "Abstract method is not implemented"
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
              end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
              ##
         
     | 
| 
      
 106 
     | 
    
         
            +
              # Common functions for mechanisms
         
     | 
| 
      
 107 
     | 
    
         
            +
              #
         
     | 
| 
      
 108 
     | 
    
         
            +
              # Mechanisms implement handling of methods start and receive. They
         
     | 
| 
      
 109 
     | 
    
         
            +
              # return: [message_name, content] or nil where message_name is
         
     | 
| 
      
 110 
     | 
    
         
            +
              # either 'auth' or 'response' and content is either a string which
         
     | 
| 
      
 111 
     | 
    
         
            +
              # may transmitted encoded as Base64 or nil.
         
     | 
| 
      
 112 
     | 
    
         
            +
              class Mechanism
         
     | 
| 
      
 113 
     | 
    
         
            +
                attr_reader :mechanism
         
     | 
| 
      
 114 
     | 
    
         
            +
                attr_reader :preferences
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def initialize(mechanism, preferences)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  @mechanism = mechanism
         
     | 
| 
      
 118 
     | 
    
         
            +
                  @preferences = preferences
         
     | 
| 
      
 119 
     | 
    
         
            +
                  @state = nil
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                def success?
         
     | 
| 
      
 123 
     | 
    
         
            +
                  @state == :success
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
                def failure?
         
     | 
| 
      
 126 
     | 
    
         
            +
                  @state == :failure
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                def start
         
     | 
| 
      
 130 
     | 
    
         
            +
                  raise AbstractMethod
         
     | 
| 
      
 131 
     | 
    
         
            +
                end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                def receive(message_name, content)
         
     | 
| 
      
 135 
     | 
    
         
            +
                  case message_name
         
     | 
| 
      
 136 
     | 
    
         
            +
                  when 'success'
         
     | 
| 
      
 137 
     | 
    
         
            +
                    @state = :success
         
     | 
| 
      
 138 
     | 
    
         
            +
                  when 'failure'
         
     | 
| 
      
 139 
     | 
    
         
            +
                    @state = :failure
         
     | 
| 
      
 140 
     | 
    
         
            +
                  end
         
     | 
| 
      
 141 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
              end
         
     | 
| 
      
 144 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/sasl/base64.rb
    ADDED
    
    | 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # =XMPP4R - XMPP Library for Ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Website::http://home.gna.org/xmpp4r/
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            begin
         
     | 
| 
      
 6 
     | 
    
         
            +
              require 'base64'
         
     | 
| 
      
 7 
     | 
    
         
            +
            rescue LoadError
         
     | 
| 
      
 8 
     | 
    
         
            +
              ##
         
     | 
| 
      
 9 
     | 
    
         
            +
              # Ruby 1.9 has dropped the Base64 module,
         
     | 
| 
      
 10 
     | 
    
         
            +
              # this is a replacement
         
     | 
| 
      
 11 
     | 
    
         
            +
              #
         
     | 
| 
      
 12 
     | 
    
         
            +
              # We could replace all call by Array#pack('m')
         
     | 
| 
      
 13 
     | 
    
         
            +
              # and String#unpack('m'), but this module
         
     | 
| 
      
 14 
     | 
    
         
            +
              # improves readability.
         
     | 
| 
      
 15 
     | 
    
         
            +
              module Base64
         
     | 
| 
      
 16 
     | 
    
         
            +
                ##
         
     | 
| 
      
 17 
     | 
    
         
            +
                # Encode a String
         
     | 
| 
      
 18 
     | 
    
         
            +
                # data:: [String] Binary
         
     | 
| 
      
 19 
     | 
    
         
            +
                # result:: [String] Binary in Base64
         
     | 
| 
      
 20 
     | 
    
         
            +
                def self.encode64(data)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  [data].pack('m')
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                ##
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Decode a Base64-encoded String
         
     | 
| 
      
 26 
     | 
    
         
            +
                # data64:: [String] Binary in Base64
         
     | 
| 
      
 27 
     | 
    
         
            +
                # result:: [String] Binary
         
     | 
| 
      
 28 
     | 
    
         
            +
                def self.decode64(data64)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  data64.unpack('m').first
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,168 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'digest/md5'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module SASL
         
     | 
| 
      
 4 
     | 
    
         
            +
              ##
         
     | 
| 
      
 5 
     | 
    
         
            +
              # RFC 2831:
         
     | 
| 
      
 6 
     | 
    
         
            +
              # http://tools.ietf.org/html/rfc2831
         
     | 
| 
      
 7 
     | 
    
         
            +
              class DigestMD5 < Mechanism
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_writer :cnonce
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def initialize(*a)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  super
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @nonce_count = 0
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def start
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @state = nil
         
     | 
| 
      
 17 
     | 
    
         
            +
                  unless defined? @nonce
         
     | 
| 
      
 18 
     | 
    
         
            +
                    ['auth', nil]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  else
         
     | 
| 
      
 20 
     | 
    
         
            +
                    # reauthentication
         
     | 
| 
      
 21 
     | 
    
         
            +
                    receive('challenge', '')
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def receive(message_name, content)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  if message_name == 'challenge'
         
     | 
| 
      
 27 
     | 
    
         
            +
                    c = decode_challenge(content)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    unless c['rspauth']
         
     | 
| 
      
 30 
     | 
    
         
            +
                      response = {}
         
     | 
| 
      
 31 
     | 
    
         
            +
                      if defined?(@nonce) && response['nonce'].nil?
         
     | 
| 
      
 32 
     | 
    
         
            +
                        # Could be reauth
         
     | 
| 
      
 33 
     | 
    
         
            +
                      else
         
     | 
| 
      
 34 
     | 
    
         
            +
                        # No reauth:
         
     | 
| 
      
 35 
     | 
    
         
            +
                        @nonce_count = 0
         
     | 
| 
      
 36 
     | 
    
         
            +
                      end
         
     | 
| 
      
 37 
     | 
    
         
            +
                      @nonce ||= c['nonce']
         
     | 
| 
      
 38 
     | 
    
         
            +
                      response['nonce'] = @nonce
         
     | 
| 
      
 39 
     | 
    
         
            +
                      response['charset'] = 'utf-8'
         
     | 
| 
      
 40 
     | 
    
         
            +
                      response['username'] = preferences.username
         
     | 
| 
      
 41 
     | 
    
         
            +
                      response['realm'] = c['realm'] || preferences.realm
         
     | 
| 
      
 42 
     | 
    
         
            +
                      @cnonce = generate_nonce unless defined? @cnonce
         
     | 
| 
      
 43 
     | 
    
         
            +
                      response['cnonce'] = @cnonce
         
     | 
| 
      
 44 
     | 
    
         
            +
                      @nc = next_nc
         
     | 
| 
      
 45 
     | 
    
         
            +
                      response['nc'] = @nc
         
     | 
| 
      
 46 
     | 
    
         
            +
                      @qop = c['qop'] || 'auth'
         
     | 
| 
      
 47 
     | 
    
         
            +
                      response['qop'] = @qop
         
     | 
| 
      
 48 
     | 
    
         
            +
                      response['digest-uri'] = preferences.digest_uri
         
     | 
| 
      
 49 
     | 
    
         
            +
                      response['response'] = response_value(response['nonce'], response['nc'], response['cnonce'], response['qop'])
         
     | 
| 
      
 50 
     | 
    
         
            +
                      ['response', encode_response(response)]
         
     | 
| 
      
 51 
     | 
    
         
            +
                    else
         
     | 
| 
      
 52 
     | 
    
         
            +
                      rspauth_expected = response_value(@nonce, @nc, @cnonce, @qop, '')
         
     | 
| 
      
 53 
     | 
    
         
            +
                      p :rspauth_received=>c['rspauth'], :rspauth_expected=>rspauth_expected
         
     | 
| 
      
 54 
     | 
    
         
            +
                      if c['rspauth'] == rspauth_expected
         
     | 
| 
      
 55 
     | 
    
         
            +
                        @state = :success
         
     | 
| 
      
 56 
     | 
    
         
            +
                        ['success', nil]
         
     | 
| 
      
 57 
     | 
    
         
            +
                      else
         
     | 
| 
      
 58 
     | 
    
         
            +
                        @state = :failure
         
     | 
| 
      
 59 
     | 
    
         
            +
                        ['failure', nil]
         
     | 
| 
      
 60 
     | 
    
         
            +
                      end
         
     | 
| 
      
 61 
     | 
    
         
            +
                    end
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                private
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def decode_challenge(text)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  challenge = {}
         
     | 
| 
      
 69 
     | 
    
         
            +
                  
         
     | 
| 
      
 70 
     | 
    
         
            +
                  state = :key
         
     | 
| 
      
 71 
     | 
    
         
            +
                  key = ''
         
     | 
| 
      
 72 
     | 
    
         
            +
                  value = ''
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  text.scan(/./) do |ch|
         
     | 
| 
      
 75 
     | 
    
         
            +
                    if state == :key
         
     | 
| 
      
 76 
     | 
    
         
            +
                      if ch == '='
         
     | 
| 
      
 77 
     | 
    
         
            +
                        state = :value
         
     | 
| 
      
 78 
     | 
    
         
            +
                      elsif ch =~ /\S/
         
     | 
| 
      
 79 
     | 
    
         
            +
                        key += ch
         
     | 
| 
      
 80 
     | 
    
         
            +
                      end
         
     | 
| 
      
 81 
     | 
    
         
            +
                      
         
     | 
| 
      
 82 
     | 
    
         
            +
                    elsif state == :value
         
     | 
| 
      
 83 
     | 
    
         
            +
                      if ch == ','
         
     | 
| 
      
 84 
     | 
    
         
            +
                        challenge[key] = value
         
     | 
| 
      
 85 
     | 
    
         
            +
                        key = ''
         
     | 
| 
      
 86 
     | 
    
         
            +
                        value = ''
         
     | 
| 
      
 87 
     | 
    
         
            +
                        state = :key
         
     | 
| 
      
 88 
     | 
    
         
            +
                      elsif ch == '"' and value == ''
         
     | 
| 
      
 89 
     | 
    
         
            +
                        state = :quote
         
     | 
| 
      
 90 
     | 
    
         
            +
                      else
         
     | 
| 
      
 91 
     | 
    
         
            +
                        value += ch
         
     | 
| 
      
 92 
     | 
    
         
            +
                      end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    elsif state == :quote
         
     | 
| 
      
 95 
     | 
    
         
            +
                      if ch == '"'
         
     | 
| 
      
 96 
     | 
    
         
            +
                        state = :value
         
     | 
| 
      
 97 
     | 
    
         
            +
                      else
         
     | 
| 
      
 98 
     | 
    
         
            +
                        value += ch
         
     | 
| 
      
 99 
     | 
    
         
            +
                      end
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
                  challenge[key] = value unless key == ''
         
     | 
| 
      
 103 
     | 
    
         
            +
                  
         
     | 
| 
      
 104 
     | 
    
         
            +
                  p :decode_challenge => challenge
         
     | 
| 
      
 105 
     | 
    
         
            +
                  challenge
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                def encode_response(response)
         
     | 
| 
      
 109 
     | 
    
         
            +
                  p :encode_response => response
         
     | 
| 
      
 110 
     | 
    
         
            +
                  response.collect do |k,v|
         
     | 
| 
      
 111 
     | 
    
         
            +
                    if v.include?('"')
         
     | 
| 
      
 112 
     | 
    
         
            +
                      v.sub!('\\', '\\\\')
         
     | 
| 
      
 113 
     | 
    
         
            +
                      v.sub!('"', '\\"')
         
     | 
| 
      
 114 
     | 
    
         
            +
                      "#{k}=\"#{v}\""
         
     | 
| 
      
 115 
     | 
    
         
            +
                    else
         
     | 
| 
      
 116 
     | 
    
         
            +
                      "#{k}=#{v}"
         
     | 
| 
      
 117 
     | 
    
         
            +
                    end
         
     | 
| 
      
 118 
     | 
    
         
            +
                  end.join(',')
         
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                def generate_nonce
         
     | 
| 
      
 122 
     | 
    
         
            +
                  nonce = ''
         
     | 
| 
      
 123 
     | 
    
         
            +
                  while nonce.length < 16
         
     | 
| 
      
 124 
     | 
    
         
            +
                    c = rand(128).chr
         
     | 
| 
      
 125 
     | 
    
         
            +
                    nonce += c if c =~ /^[a-zA-Z0-9]$/
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                  nonce
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                ##
         
     | 
| 
      
 131 
     | 
    
         
            +
                # Function from RFC2831
         
     | 
| 
      
 132 
     | 
    
         
            +
                def h(s); Digest::MD5.digest(s); end
         
     | 
| 
      
 133 
     | 
    
         
            +
                ##
         
     | 
| 
      
 134 
     | 
    
         
            +
                # Function from RFC2831
         
     | 
| 
      
 135 
     | 
    
         
            +
                def hh(s); Digest::MD5.hexdigest(s); end
         
     | 
| 
      
 136 
     | 
    
         
            +
                
         
     | 
| 
      
 137 
     | 
    
         
            +
                ##
         
     | 
| 
      
 138 
     | 
    
         
            +
                # Calculate the value for the response field
         
     | 
| 
      
 139 
     | 
    
         
            +
                def response_value(nonce, nc, cnonce, qop, a2_prefix='AUTHENTICATE')
         
     | 
| 
      
 140 
     | 
    
         
            +
                  p :response_value => {:nonce=>nonce,
         
     | 
| 
      
 141 
     | 
    
         
            +
                    :cnonce=>cnonce,
         
     | 
| 
      
 142 
     | 
    
         
            +
                    :qop=>qop,
         
     | 
| 
      
 143 
     | 
    
         
            +
                    :username=>preferences.username,
         
     | 
| 
      
 144 
     | 
    
         
            +
                    :realm=>preferences.realm,
         
     | 
| 
      
 145 
     | 
    
         
            +
                    :password=>preferences.password,
         
     | 
| 
      
 146 
     | 
    
         
            +
                    :authzid=>preferences.authzid}
         
     | 
| 
      
 147 
     | 
    
         
            +
                  a1_h = h("#{preferences.username}:#{preferences.realm}:#{preferences.password}")
         
     | 
| 
      
 148 
     | 
    
         
            +
                  a1 = "#{a1_h}:#{nonce}:#{cnonce}"
         
     | 
| 
      
 149 
     | 
    
         
            +
                  if preferences.authzid
         
     | 
| 
      
 150 
     | 
    
         
            +
                    a1 += ":#{preferences.authzid}"
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end
         
     | 
| 
      
 152 
     | 
    
         
            +
                  if qop && (qop.downcase == 'auth-int' || qop.downcase == 'auth-conf')
         
     | 
| 
      
 153 
     | 
    
         
            +
                    a2 = "#{a2_prefix}:#{preferences.digest_uri}:00000000000000000000000000000000"
         
     | 
| 
      
 154 
     | 
    
         
            +
                  else
         
     | 
| 
      
 155 
     | 
    
         
            +
                    a2 = "#{a2_prefix}:#{preferences.digest_uri}"
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
                  hh("#{hh(a1)}:#{nonce}:#{nc}:#{cnonce}:#{qop}:#{hh(a2)}")
         
     | 
| 
      
 158 
     | 
    
         
            +
                end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                def next_nc
         
     | 
| 
      
 161 
     | 
    
         
            +
                  @nonce_count += 1
         
     | 
| 
      
 162 
     | 
    
         
            +
                  s = @nonce_count.to_s
         
     | 
| 
      
 163 
     | 
    
         
            +
                  s = "0#{s}" while s.length < 8
         
     | 
| 
      
 164 
     | 
    
         
            +
                  s
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
              end
         
     | 
| 
      
 167 
     | 
    
         
            +
            end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
    
        data/lib/sasl/plain.rb
    ADDED
    
    | 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SASL
         
     | 
| 
      
 2 
     | 
    
         
            +
              ##
         
     | 
| 
      
 3 
     | 
    
         
            +
              # RFC 4616:
         
     | 
| 
      
 4 
     | 
    
         
            +
              # http://tools.ietf.org/html/rfc4616
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Plain < Mechanism
         
     | 
| 
      
 6 
     | 
    
         
            +
                def start
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @state = nil
         
     | 
| 
      
 8 
     | 
    
         
            +
                  message = [preferences.authzid.to_s,
         
     | 
| 
      
 9 
     | 
    
         
            +
                             preferences.username,
         
     | 
| 
      
 10 
     | 
    
         
            +
                             preferences.password].join("\000")
         
     | 
| 
      
 11 
     | 
    
         
            +
                  ['auth', message]
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'sasl'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'spec'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe SASL::Anonymous do
         
     | 
| 
      
 5 
     | 
    
         
            +
              class MyAnonymousPreferences < SASL::Preferences
         
     | 
| 
      
 6 
     | 
    
         
            +
                def username
         
     | 
| 
      
 7 
     | 
    
         
            +
                  'bob'
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
              preferences = MyAnonymousPreferences.new
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              it 'should authenticate anonymously' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                sasl = SASL::Anonymous.new('ANONYMOUS', preferences)
         
     | 
| 
      
 14 
     | 
    
         
            +
                sasl.start.should == ['auth', 'bob']
         
     | 
| 
      
 15 
     | 
    
         
            +
                sasl.success?.should == false
         
     | 
| 
      
 16 
     | 
    
         
            +
                sasl.receive('success', nil).should == nil
         
     | 
| 
      
 17 
     | 
    
         
            +
                sasl.success?.should == true
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,97 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'sasl'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'spec'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe SASL::DigestMD5 do
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Preferences from http://tools.ietf.org/html/rfc2831#section-4
         
     | 
| 
      
 6 
     | 
    
         
            +
              class MyDigestMD5Preferences < SASL::Preferences
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_writer :serv_type
         
     | 
| 
      
 8 
     | 
    
         
            +
                def realm
         
     | 
| 
      
 9 
     | 
    
         
            +
                  'elwood.innosoft.com'
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
                def digest_uri
         
     | 
| 
      
 12 
     | 
    
         
            +
                  "#{@serv_type}/elwood.innosoft.com"
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
                def username
         
     | 
| 
      
 15 
     | 
    
         
            +
                  'chris'
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
                def has_password?
         
     | 
| 
      
 18 
     | 
    
         
            +
                  true
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
                def password
         
     | 
| 
      
 21 
     | 
    
         
            +
                  'secret'
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
              preferences = MyDigestMD5Preferences.new
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              it 'should authenticate (1)' do
         
     | 
| 
      
 27 
     | 
    
         
            +
                preferences.serv_type = 'imap'
         
     | 
| 
      
 28 
     | 
    
         
            +
                sasl = SASL::DigestMD5.new('DIGEST-MD5', preferences)
         
     | 
| 
      
 29 
     | 
    
         
            +
                sasl.start.should == ['auth', nil]
         
     | 
| 
      
 30 
     | 
    
         
            +
                sasl.cnonce = 'OA6MHXh6VqTrRk'
         
     | 
| 
      
 31 
     | 
    
         
            +
                response = sasl.receive('challenge',
         
     | 
| 
      
 32 
     | 
    
         
            +
                                        'realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
         
     | 
| 
      
 33 
     | 
    
         
            +
                                         algorithm=md5-sess,charset=utf-8')
         
     | 
| 
      
 34 
     | 
    
         
            +
                response[0].should == 'response'
         
     | 
| 
      
 35 
     | 
    
         
            +
                response[1].should =~ /charset="?utf-8"?/
         
     | 
| 
      
 36 
     | 
    
         
            +
                response[1].should =~ /username="?chris"?/
         
     | 
| 
      
 37 
     | 
    
         
            +
                response[1].should =~ /realm="?elwood.innosoft.com"?/
         
     | 
| 
      
 38 
     | 
    
         
            +
                response[1].should =~ /nonce="?OA6MG9tEQGm2hh"?/
         
     | 
| 
      
 39 
     | 
    
         
            +
                response[1].should =~ /nc="?00000001"?/
         
     | 
| 
      
 40 
     | 
    
         
            +
                response[1].should =~ /cnonce="?OA6MHXh6VqTrRk"?/
         
     | 
| 
      
 41 
     | 
    
         
            +
                response[1].should =~ /digest-uri="?imap\/elwood.innosoft.com"?/
         
     | 
| 
      
 42 
     | 
    
         
            +
                response[1].should =~ /response=d388dad90d4bbd760a152321f2143af7"?/
         
     | 
| 
      
 43 
     | 
    
         
            +
                response[1].should =~ /"?qop=auth"?/
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                sasl.receive('challenge',
         
     | 
| 
      
 46 
     | 
    
         
            +
                             'rspauth=ea40f60335c427b5527b84dbabcdfffd').should ==
         
     | 
| 
      
 47 
     | 
    
         
            +
                  ['success', nil]
         
     | 
| 
      
 48 
     | 
    
         
            +
                sasl.success?.should == true
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              it 'should authenticate (2)' do
         
     | 
| 
      
 52 
     | 
    
         
            +
                preferences.serv_type = 'acap'
         
     | 
| 
      
 53 
     | 
    
         
            +
                sasl = SASL::DigestMD5.new('DIGEST-MD5', preferences)
         
     | 
| 
      
 54 
     | 
    
         
            +
                sasl.start.should == ['auth', nil]
         
     | 
| 
      
 55 
     | 
    
         
            +
                sasl.cnonce = 'OA9BSuZWMSpW8m'
         
     | 
| 
      
 56 
     | 
    
         
            +
                response = sasl.receive('challenge',
         
     | 
| 
      
 57 
     | 
    
         
            +
                                        'realm="elwood.innosoft.com",nonce="OA9BSXrbuRhWay",qop="auth",
         
     | 
| 
      
 58 
     | 
    
         
            +
                                         algorithm=md5-sess,charset=utf-8')
         
     | 
| 
      
 59 
     | 
    
         
            +
                response[0].should == 'response'
         
     | 
| 
      
 60 
     | 
    
         
            +
                response[1].should =~ /charset="?utf-8"?/
         
     | 
| 
      
 61 
     | 
    
         
            +
                response[1].should =~ /username="?chris"?/
         
     | 
| 
      
 62 
     | 
    
         
            +
                response[1].should =~ /realm="?elwood.innosoft.com"?/
         
     | 
| 
      
 63 
     | 
    
         
            +
                response[1].should =~ /nonce="?OA9BSXrbuRhWay"?/
         
     | 
| 
      
 64 
     | 
    
         
            +
                response[1].should =~ /nc="?00000001"?/
         
     | 
| 
      
 65 
     | 
    
         
            +
                response[1].should =~ /cnonce="?OA9BSuZWMSpW8m"?/
         
     | 
| 
      
 66 
     | 
    
         
            +
                response[1].should =~ /digest-uri="?acap\/elwood.innosoft.com"?/
         
     | 
| 
      
 67 
     | 
    
         
            +
                response[1].should =~ /response=6084c6db3fede7352c551284490fd0fc"?/
         
     | 
| 
      
 68 
     | 
    
         
            +
                response[1].should =~ /"?qop=auth"?/
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                sasl.receive('challenge',
         
     | 
| 
      
 71 
     | 
    
         
            +
                             'rspauth=2f0b3d7c3c2e486600ef710726aa2eae').should ==
         
     | 
| 
      
 72 
     | 
    
         
            +
                  ['success', nil]
         
     | 
| 
      
 73 
     | 
    
         
            +
                sasl.success?.should == true
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              it 'should reauthenticate' do
         
     | 
| 
      
 77 
     | 
    
         
            +
                preferences.serv_type = 'imap'
         
     | 
| 
      
 78 
     | 
    
         
            +
                sasl = SASL::DigestMD5.new('DIGEST-MD5', preferences)
         
     | 
| 
      
 79 
     | 
    
         
            +
                sasl.start.should == ['auth', nil]
         
     | 
| 
      
 80 
     | 
    
         
            +
                sasl.cnonce = 'OA6MHXh6VqTrRk'
         
     | 
| 
      
 81 
     | 
    
         
            +
                sasl.receive('challenge',
         
     | 
| 
      
 82 
     | 
    
         
            +
                             'realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
         
     | 
| 
      
 83 
     | 
    
         
            +
                              algorithm=md5-sess,charset=utf-8')
         
     | 
| 
      
 84 
     | 
    
         
            +
                # reauth:
         
     | 
| 
      
 85 
     | 
    
         
            +
                response = sasl.start
         
     | 
| 
      
 86 
     | 
    
         
            +
                response[0].should == 'response'
         
     | 
| 
      
 87 
     | 
    
         
            +
                response[1].should =~ /charset="?utf-8"?/
         
     | 
| 
      
 88 
     | 
    
         
            +
                response[1].should =~ /username="?chris"?/
         
     | 
| 
      
 89 
     | 
    
         
            +
                response[1].should =~ /realm="?elwood.innosoft.com"?/
         
     | 
| 
      
 90 
     | 
    
         
            +
                response[1].should =~ /nonce="?OA6MG9tEQGm2hh"?/
         
     | 
| 
      
 91 
     | 
    
         
            +
                response[1].should =~ /nc="?00000002"?/
         
     | 
| 
      
 92 
     | 
    
         
            +
                response[1].should =~ /cnonce="?OA6MHXh6VqTrRk"?/
         
     | 
| 
      
 93 
     | 
    
         
            +
                response[1].should =~ /digest-uri="?imap\/elwood.innosoft.com"?/
         
     | 
| 
      
 94 
     | 
    
         
            +
                response[1].should =~ /response=b0b5d72a400655b8306e434566b10efb"?/ # my own result
         
     | 
| 
      
 95 
     | 
    
         
            +
                response[1].should =~ /"?qop=auth"?/
         
     | 
| 
      
 96 
     | 
    
         
            +
              end
         
     | 
| 
      
 97 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,56 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'sasl'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'spec'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe SASL do
         
     | 
| 
      
 5 
     | 
    
         
            +
              it 'should know DIGEST-MD5' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                sasl = SASL.new_mechanism('DIGEST-MD5', SASL::Preferences.new)
         
     | 
| 
      
 7 
     | 
    
         
            +
                sasl.should be_an_instance_of SASL::DigestMD5
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
              it 'should know PLAIN' do
         
     | 
| 
      
 10 
     | 
    
         
            +
                sasl = SASL.new_mechanism('PLAIN', SASL::Preferences.new)
         
     | 
| 
      
 11 
     | 
    
         
            +
                sasl.should be_an_instance_of SASL::Plain
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
              it 'should know ANONYMOUS' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                sasl = SASL.new_mechanism('ANONYMOUS', SASL::Preferences.new)
         
     | 
| 
      
 15 
     | 
    
         
            +
                sasl.should be_an_instance_of SASL::Anonymous
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
              it 'should choose ANONYMOUS' do
         
     | 
| 
      
 18 
     | 
    
         
            +
                preferences = SASL::Preferences.new
         
     | 
| 
      
 19 
     | 
    
         
            +
                class << preferences
         
     | 
| 
      
 20 
     | 
    
         
            +
                  def want_anonymous?
         
     | 
| 
      
 21 
     | 
    
         
            +
                    true
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
                SASL.new(%w(PLAIN DIGEST-MD5 ANONYMOUS), preferences).should be_an_instance_of SASL::Anonymous
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
              it 'should choose DIGEST-MD5' do
         
     | 
| 
      
 27 
     | 
    
         
            +
                preferences = SASL::Preferences.new
         
     | 
| 
      
 28 
     | 
    
         
            +
                class << preferences
         
     | 
| 
      
 29 
     | 
    
         
            +
                  def has_password?
         
     | 
| 
      
 30 
     | 
    
         
            +
                    true
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
                SASL.new(%w(PLAIN DIGEST-MD5 ANONYMOUS), preferences).should be_an_instance_of SASL::DigestMD5
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
              it 'should choose PLAIN' do
         
     | 
| 
      
 36 
     | 
    
         
            +
                preferences = SASL::Preferences.new
         
     | 
| 
      
 37 
     | 
    
         
            +
                class << preferences
         
     | 
| 
      
 38 
     | 
    
         
            +
                  def has_password?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    true
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  def allow_plaintext?
         
     | 
| 
      
 42 
     | 
    
         
            +
                    true
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
                SASL.new(%w(PLAIN ANONYMOUS), preferences).should be_an_instance_of SASL::Plain
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
              it 'should disallow PLAIN by default' do
         
     | 
| 
      
 48 
     | 
    
         
            +
                preferences = SASL::Preferences.new
         
     | 
| 
      
 49 
     | 
    
         
            +
                class << preferences
         
     | 
| 
      
 50 
     | 
    
         
            +
                  def has_password?
         
     | 
| 
      
 51 
     | 
    
         
            +
                    true
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
                lambda { SASL.new(%w(PLAIN ANONYMOUS), preferences) }.should raise_error(SASL::UnknownMechanism)
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/plain_spec.rb
    ADDED
    
    | 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'sasl'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'spec'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe SASL::Plain do
         
     | 
| 
      
 5 
     | 
    
         
            +
              class MyPlainPreferences < SASL::Preferences
         
     | 
| 
      
 6 
     | 
    
         
            +
                def authzid
         
     | 
| 
      
 7 
     | 
    
         
            +
                  'bob@example.com'
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
                def username
         
     | 
| 
      
 10 
     | 
    
         
            +
                  'bob'
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
                def has_password?
         
     | 
| 
      
 13 
     | 
    
         
            +
                  true
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
                def password
         
     | 
| 
      
 16 
     | 
    
         
            +
                  's3cr3t'
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
              preferences = MyPlainPreferences.new
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              it 'should authenticate' do
         
     | 
| 
      
 22 
     | 
    
         
            +
                sasl = SASL::Plain.new('PLAIN', preferences)
         
     | 
| 
      
 23 
     | 
    
         
            +
                sasl.start.should == ['auth', "bob@example.com\000bob\000s3cr3t"]
         
     | 
| 
      
 24 
     | 
    
         
            +
                sasl.success?.should == false
         
     | 
| 
      
 25 
     | 
    
         
            +
                sasl.receive('success', nil).should == nil
         
     | 
| 
      
 26 
     | 
    
         
            +
                sasl.failure?.should == false
         
     | 
| 
      
 27 
     | 
    
         
            +
                sasl.success?.should == true
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              it 'should recognize failure' do
         
     | 
| 
      
 31 
     | 
    
         
            +
                sasl = SASL::Plain.new('PLAIN', preferences)
         
     | 
| 
      
 32 
     | 
    
         
            +
                sasl.start.should == ['auth', "bob@example.com\000bob\000s3cr3t"]
         
     | 
| 
      
 33 
     | 
    
         
            +
                sasl.success?.should == false
         
     | 
| 
      
 34 
     | 
    
         
            +
                sasl.failure?.should == false
         
     | 
| 
      
 35 
     | 
    
         
            +
                sasl.receive('failure', 'keep-idiots-out').should == nil
         
     | 
| 
      
 36 
     | 
    
         
            +
                sasl.failure?.should == true
         
     | 
| 
      
 37 
     | 
    
         
            +
                sasl.success?.should == false
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,66 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: astro-ruby-sasl
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.2
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Stephan Maka
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2009-02-06 00:00:00 -08:00
         
     | 
| 
      
 13 
     | 
    
         
            +
            default_executable: 
         
     | 
| 
      
 14 
     | 
    
         
            +
            dependencies: []
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            description: Simple Authentication and Security Layer (RFC 4422)
         
     | 
| 
      
 17 
     | 
    
         
            +
            email: stephan@spaceboyz.net
         
     | 
| 
      
 18 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 25 
     | 
    
         
            +
            - spec/mechanism_spec.rb
         
     | 
| 
      
 26 
     | 
    
         
            +
            - spec/anonymous_spec.rb
         
     | 
| 
      
 27 
     | 
    
         
            +
            - spec/plain_spec.rb
         
     | 
| 
      
 28 
     | 
    
         
            +
            - spec/digest_md5_spec.rb
         
     | 
| 
      
 29 
     | 
    
         
            +
            - lib/sasl/base.rb
         
     | 
| 
      
 30 
     | 
    
         
            +
            - lib/sasl/digest_md5.rb
         
     | 
| 
      
 31 
     | 
    
         
            +
            - lib/sasl/anonymous.rb
         
     | 
| 
      
 32 
     | 
    
         
            +
            - lib/sasl/plain.rb
         
     | 
| 
      
 33 
     | 
    
         
            +
            - lib/sasl/base64.rb
         
     | 
| 
      
 34 
     | 
    
         
            +
            - lib/sasl.rb
         
     | 
| 
      
 35 
     | 
    
         
            +
            - README.markdown
         
     | 
| 
      
 36 
     | 
    
         
            +
            has_rdoc: false
         
     | 
| 
      
 37 
     | 
    
         
            +
            homepage: http://github.com/astro/ruby-sasl/
         
     | 
| 
      
 38 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 39 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 42 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 43 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 44 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 45 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 46 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 47 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 48 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 49 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 50 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 51 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 52 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 53 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 54 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 55 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 58 
     | 
    
         
            +
            rubygems_version: 1.2.0
         
     | 
| 
      
 59 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 60 
     | 
    
         
            +
            specification_version: 2
         
     | 
| 
      
 61 
     | 
    
         
            +
            summary: SASL client library
         
     | 
| 
      
 62 
     | 
    
         
            +
            test_files: 
         
     | 
| 
      
 63 
     | 
    
         
            +
            - spec/mechanism_spec.rb
         
     | 
| 
      
 64 
     | 
    
         
            +
            - spec/anonymous_spec.rb
         
     | 
| 
      
 65 
     | 
    
         
            +
            - spec/plain_spec.rb
         
     | 
| 
      
 66 
     | 
    
         
            +
            - spec/digest_md5_spec.rb
         
     |