Project

General

Profile

RE: Problems setting up email notifications » ntlm.rb

Sam Chen, 2011-12-12 11:41

 
1
#
2
# = net/ntlm.rb
3
#
4
# An NTLM Authentication Library for Ruby
5
#
6
# This code is a derivative of "dbf2.rb" written by yrock
7
# and Minero Aoki. You can find original code here:
8
# http://jp.rubyist.net/magazine/?0013-CodeReview
9
# -------------------------------------------------------------
10
# Copyright (c) 2005,2006 yrock
11
# 
12
# This program is free software.
13
# You can distribute/modify this program under the terms of the
14
# Ruby License.
15
#
16
# 2006-02-11 refactored by Minero Aoki
17
# -------------------------------------------------------------
18
#
19
# All protocol information used to write this code stems from
20
# "The NTLM Authentication Protocol" by Eric Glass. The author 
21
# would thank to him for this tremendous work and making it 
22
# available on the net.
23
# http://davenport.sourceforge.net/ntlm.html
24
# -------------------------------------------------------------
25
# Copyright (c) 2003 Eric Glass
26
#
27
# Permission to use, copy, modify, and distribute this document
28
# for any purpose and without any fee is hereby granted,
29
# provided that the above copyright notice and this list of
30
# conditions appear in all copies. 
31
# -------------------------------------------------------------
32
#
33
# The author also looked Mozilla-Firefox-1.0.7 source code,
34
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
35
# Jonathan Bastien-Filiatrault's libntlm-ruby.
36
# "http://x2a.org/websvn/filedetails.php?
37
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
38
# The latter has a minor bug in its separate_keys function.
39
# The third key has to begin from the 14th character of the 
40
# input string instead of 13th:)
41
#--
42
# $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
43
#++
44

    
45
require 'base64'
46
require 'openssl'
47
require 'openssl/digest'
48

    
49
module Net  #:nodoc:
50
  module NTLM
51

    
52
    module VERSION #:nodoc:
53
      MAJOR = 0
54
      MINOR = 1
55
      TINY  = 1
56
      STRING = [MAJOR, MINOR, TINY].join('.')
57
    end
58

    
59
    SSP_SIGN = "NTLMSSP\0"
60
    BLOB_SIGN = 0x00000101
61
    LM_MAGIC = "KGS!@\#$%"
62
    TIME_OFFSET = 11644473600
63
    MAX64 = 0xffffffffffffffff
64
    
65
    FLAGS = {
66
      :UNICODE              => 0x00000001,
67
      :OEM                  => 0x00000002,
68
      :REQUEST_TARGET       => 0x00000004,
69
  #   :UNKNOWN              => 0x00000008,
70
      :SIGN                 => 0x00000010,
71
      :SEAL                 => 0x00000020,
72
  #   :UNKNOWN              => 0x00000040,
73
      :NETWARE              => 0x00000100,
74
      :NTLM                 => 0x00000200,
75
  #   :UNKNOWN              => 0x00000400,
76
  #   :UNKNOWN              => 0x00000800,
77
      :DOMAIN_SUPPLIED      => 0x00001000,
78
      :WORKSTATION_SUPPLIED => 0x00002000,
79
      :LOCAL_CALL           => 0x00004000,
80
      :ALWAYS_SIGN          => 0x00008000,
81
      :TARGET_TYPE_DOMAIN   => 0x00010000,
82
      :TARGET_INFO          => 0x00800000,
83
      :NTLM2_KEY            => 0x00080000,
84
      :KEY128               => 0x20000000,
85
      :KEY56                => 0x80000000
86
    }
87
    
88
    FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
89

    
90
    DEFAULT_FLAGS = {
91
      :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
92
      :TYPE2 => FLAGS[:UNICODE],
93
      :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
94
    }
95

    
96
  # module functions
97
    class << self
98
      def decode_utf16le(str)
99
        Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
100
      end
101

    
102
      def encode_utf16le(str)
103
        swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
104
      end
105
    
106
      def pack_int64le(val)
107
          [val & 0x00000000ffffffff, val >> 32].pack("V2")
108
      end
109
      
110
      def swap16(str)
111
        str.unpack("v*").pack("n*")
112
      end
113

    
114
      def split7(str)
115
        s = str.dup
116
        until s.empty?
117
          (ret ||= []).push s.slice!(0, 7)
118
        end
119
        ret
120
      end
121
    
122
      def gen_keys(str)
123
        split7(str).map{ |str7| 
124
          bits = split7(str7.unpack("B*")[0]).inject('')\
125
            {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
126
          [bits].pack("B*")
127
        }
128
      end
129
      
130
      def apply_des(plain, keys)
131
        dec = OpenSSL::Cipher::DES.new
132
        keys.map {|k|
133
          dec.key = k
134
          dec.encrypt.update(plain)
135
        }
136
      end
137
      
138
      def lm_hash(password)
139
        keys = gen_keys password.upcase.ljust(14, "\0")
140
        apply_des(LM_MAGIC, keys).join
141
      end   
142
      
143
      def ntlm_hash(password, opt = {})
144
        pwd = password.dup
145
        unless opt[:unicode]
146
          pwd = encode_utf16le(pwd)
147
        end
148
        OpenSSL::Digest::MD4.digest pwd
149
      end
150

    
151
      def ntlmv2_hash(user, password, target, opt={})
152
        ntlmhash = ntlm_hash(password, opt)
153
        userdomain = (user + target).upcase
154
        unless opt[:unicode]
155
          userdomain = encode_utf16le(userdomain)
156
        end
157
        OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
158
      end
159

    
160
      # responses
161
      def lm_response(arg)
162
        begin
163
          hash = arg[:lm_hash]
164
          chal = arg[:challenge]
165
        rescue
166
          raise ArgumentError
167
        end
168
        chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
169
        keys = gen_keys hash.ljust(21, "\0")
170
        apply_des(chal, keys).join
171
      end
172
      
173
      def ntlm_response(arg)
174
        hash = arg[:ntlm_hash]
175
        chal = arg[:challenge]
176
        chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
177
        keys = gen_keys hash.ljust(21, "\0")
178
        apply_des(chal, keys).join
179
      end
180

    
181
      def ntlmv2_response(arg, opt = {})
182
        begin
183
          key = arg[:ntlmv2_hash]
184
          chal = arg[:challenge]
185
          ti = arg[:target_info]
186
        rescue
187
          raise ArgumentError
188
        end
189
        chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
190
        
191
        if opt[:client_challenge]
192
          cc  = opt[:client_challenge]
193
        else
194
          cc = rand(MAX64)
195
        end
196
        cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
197

    
198
        if opt[:timestamp]
199
          ts = opt[:timestamp]
200
        else
201
          ts = Time.now.to_i
202
        end
203
        # epoch -> milsec from Jan 1, 1601
204
        ts = 10000000 * (ts + TIME_OFFSET)
205

    
206
        blob = Blob.new
207
        blob.timestamp = ts
208
        blob.challenge = cc
209
        blob.target_info = ti
210
        
211
        bb = blob.serialize
212
        OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
213
      end
214
      
215
      def lmv2_response(arg, opt = {})
216
        key = arg[:ntlmv2_hash]
217
        chal = arg[:challenge]
218
        
219
        chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
220

    
221
        if opt[:client_challenge]
222
          cc  = opt[:client_challenge]
223
        else
224
          cc = rand(MAX64)
225
        end
226
        cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
227

    
228
        OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
229
      end
230
      
231
      def ntlm2_session(arg, opt = {})
232
        begin
233
          passwd_hash = arg[:ntlm_hash]
234
          chal = arg[:challenge]
235
        rescue
236
          raise ArgumentError
237
        end
238

    
239
        if opt[:client_challenge]
240
          cc  = opt[:client_challenge]
241
        else
242
          cc = rand(MAX64)
243
        end
244
        cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
245

    
246
        keys = gen_keys passwd_hash.ljust(21, "\0")
247
        session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
248
        response = apply_des(session_hash, keys).join
249
        [cc.ljust(24, "\0"), response]
250
      end
251
    end
252

    
253

    
254
    # base classes for primitives
255
    class Field
256
      attr_accessor :active, :value
257

    
258
      def initialize(opts)
259
        @value  = opts[:value]
260
        @active = opts[:active].nil? ? true : opts[:active]
261
      end
262
      
263
      def size
264
        @active ? @size : 0
265
      end
266
    end
267

    
268
    class String < Field
269
      def initialize(opts)
270
        super(opts)
271
        @size = opts[:size]
272
      end
273
      
274
      def parse(str, offset=0)
275
        if @active and str.size >= offset + @size
276
          @value = str[offset, @size]
277
          @size
278
        else
279
          0
280
        end
281
      end
282
      
283
      def serialize
284
        if @active
285
          @value
286
        else
287
          ""
288
        end
289
      end
290
      
291
      def value=(val)
292
        @value = val
293
        @size = @value.nil? ? 0 : @value.size
294
        @active = (@size > 0)
295
      end
296
    end
297

    
298

    
299
    class Int16LE < Field
300
      def initialize(opt)
301
        super(opt)
302
        @size = 2
303
      end
304
      def parse(str, offset=0)
305
        if @active and str.size >= offset + @size
306
          @value = str[offset, @size].unpack("v")[0]
307
          @size
308
        else
309
          0
310
        end
311
      end
312
      
313
      def serialize
314
        [@value].pack("v")
315
      end
316
    end
317

    
318
    class Int32LE < Field
319
      def initialize(opt)
320
        super(opt)
321
        @size = 4
322
      end
323

    
324
      def parse(str, offset=0)
325
        if @active and str.size >= offset + @size
326
          @value = str.slice(offset, @size).unpack("V")[0]
327
          @size
328
        else
329
          0
330
        end
331
      end
332

    
333
      def serialize
334
        [@value].pack("V") if @active
335
      end
336
    end
337

    
338
    class Int64LE < Field
339
      def initialize(opt)
340
        super(opt)
341
        @size = 8
342
      end
343

    
344
      def parse(str, offset=0)
345
        if @active and str.size >= offset + @size
346
          d, u = str.slice(offset, @size).unpack("V2")
347
          @value = (u * 0x100000000 + d)
348
          @size
349
        else
350
          0
351
        end
352
      end
353
      
354
      def serialize
355
        [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
356
      end
357
    end
358

    
359
    # base class of data structure
360
    class FieldSet
361
      class << FieldSet
362
        def define(&block)
363
          c = Class.new(self)
364
          def c.inherited(subclass)
365
            proto = @proto
366
            subclass.instance_eval {
367
              @proto = proto
368
            }
369
          end
370
          c.module_eval(&block)
371
          c
372
        end
373
        
374
        def string(name, opts)
375
          add_field(name, String, opts)
376
        end
377
        
378
        def int16LE(name, opts)
379
          add_field(name, Int16LE, opts)
380
        end
381

    
382
        def int32LE(name, opts)
383
          add_field(name, Int32LE, opts)
384
        end
385

    
386
        def int64LE(name, opts)
387
          add_field(name, Int64LE, opts)
388
        end
389
        
390
        def security_buffer(name, opts)
391
          add_field(name, SecurityBuffer, opts)
392
        end
393

    
394
        def prototypes
395
          @proto
396
        end
397
        
398
        def names
399
          @proto.map{|n, t, o| n}
400
        end
401

    
402
        def types
403
          @proto.map{|n, t, o| t}
404
        end
405
        
406
        def opts
407
          @proto.map{|n, t, o| o}
408
        end
409
        
410
        private
411
        
412
        def add_field(name, type, opts)
413
          (@proto ||= []).push [name, type, opts]
414
          define_accessor name
415
        end
416
        
417
        def define_accessor(name)
418
          module_eval(<<-End, __FILE__, __LINE__ + 1)
419
          def #{name}
420
            self['#{name}'].value
421
          end
422
            
423
          def #{name}=(val)
424
            self['#{name}'].value = val
425
          end
426
          End
427
        end 
428
      end
429
      
430
      def initialize
431
        @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
432
      end
433
      
434
      def serialize
435
        @alist.map{|n, f| f.serialize }.join
436
      end
437
      
438
      def parse(str, offset=0)
439
        @alist.inject(offset){|cur, a|  cur += a[1].parse(str, cur)}
440
      end
441

    
442
      def size
443
        @alist.inject(0){|sum, a| sum += a[1].size}
444
      end
445

    
446
      def [](name)
447
        a = @alist.assoc(name.to_s.intern)
448
        raise ArgumentError, "no such field: #{name}" unless a
449
        a[1]
450
      end
451
      
452
      def []=(name, val)
453
        a = @alist.assoc(name.to_s.intern)
454
        raise ArgumentError, "no such field: #{name}" unless a
455
        a[1] = val
456
      end
457
      
458
      def enable(name)
459
        self[name].active = true
460
      end
461
      
462
      def disable(name)
463
        self[name].active = false
464
      end
465
    end
466

    
467

    
468
    Blob = FieldSet.define {
469
      int32LE    :blob_signature,   {:value => BLOB_SIGN}
470
      int32LE    :reserved,         {:value => 0}
471
      int64LE    :timestamp,      {:value => 0}
472
      string     :challenge,      {:value => "", :size => 8}
473
      int32LE    :unknown1,     {:value => 0}
474
      string     :target_info,      {:value => "", :size => 0}
475
      int32LE    :unknown2,         {:value => 0}
476
    }
477

    
478
    SecurityBuffer = FieldSet.define {
479
      int16LE   :length,        {:value => 0}
480
      int16LE   :allocated,     {:value => 0}
481
      int32LE   :offset,        {:value => 0}
482
    }
483

    
484
    class SecurityBuffer
485
      attr_accessor :active
486
      def initialize(opts)
487
        super()
488
        @value  = opts[:value]
489
        @active = opts[:active].nil? ? true : opts[:active]
490
        @size = 8
491
      end
492
      
493
      def parse(str, offset=0)
494
        if @active and str.size >= offset + @size
495
          super(str, offset)
496
          @value = str[self.offset, self.length]
497
          @size
498
        else
499
          0
500
        end
501
      end
502
      
503
      def serialize
504
        super if @active
505
      end
506
      
507
      def value
508
        @value
509
      end
510
      
511
      def value=(val)
512
        @value = val
513
        self.length = self.allocated = val.size
514
      end
515
      
516
      def data_size
517
        @active ? @value.size : 0
518
      end
519
    end
520
    
521
    class Message < FieldSet
522
      class << Message
523
        def parse(str)
524
          m = Type0.new
525
          m.parse(str)
526
          case m.type
527
          when 1
528
            t = Type1.parse(str)
529
          when 2
530
            t = Type2.parse(str)
531
          when 3
532
            t = Type3.parse(str)
533
          else
534
            raise ArgumentError, "unknown type: #{m.type}"
535
          end
536
          t
537
        end
538
        
539
        def decode64(str)
540
          parse(Base64.decode64(str))
541
        end
542
      end
543
      
544
      def has_flag?(flag)
545
        (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
546
      end
547
      
548
      def set_flag(flag)
549
        self[:flag].value  |= FLAGS[flag]
550
      end
551
      
552
      def dump_flags
553
        FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
554
      end
555
      
556
      def serialize
557
        deflag
558
        super + security_buffers.map{|n, f| f.value}.join
559
      end
560
      
561
      def encode64
562
        Base64.encode64(serialize).gsub(/\n/, '')
563
      end
564
      
565
      def decode64(str)
566
        parse(Base64.decode64(str))
567
      end
568
      
569
      alias head_size size
570

    
571
      def data_size
572
        security_buffers.inject(0){|sum, a| sum += a[1].data_size}
573
      end
574

    
575
      def size
576
        head_size + data_size
577
      end
578
      
579

    
580
      private
581

    
582
      def security_buffers
583
        @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
584
      end
585
      
586
      def deflag
587
        security_buffers.inject(head_size){|cur, a|
588
          a[1].offset = cur
589
          cur += a[1].data_size
590
        }
591
      end
592
      
593
      def data_edge
594
        security_buffers.map{ |n, f| f.active ? f.offset : size}.min
595
      end
596

    
597
      # sub class definitions
598
      
599
      Type0 = Message.define {
600
        string        :sign,      {:size => 8, :value => SSP_SIGN}
601
        int32LE       :type,      {:value => 0}
602
      }
603
      
604
      Type1 = Message.define {
605
        string          :sign,         {:size => 8, :value => SSP_SIGN}
606
        int32LE         :type,         {:value => 1}
607
        int32LE         :flag,         {:value => DEFAULT_FLAGS[:TYPE1] }
608
        security_buffer :domain,       {:value => "", :active => false}
609
        security_buffer :workstation,  {:value => "", :active => false}
610
        string          :padding,      {:size => 0, :value => "", :active => false }
611
      }
612

    
613
      class Type1
614
        class << Type1
615
          def parse(str)
616
            t = new
617
            t.parse(str)
618
            t
619
          end
620
        end
621
        
622
        def parse(str)
623
          super(str)
624
          enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
625
          enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
626
          super(str)
627
          if ( (len = data_edge - head_size) > 0)
628
            self.padding = "\0" * len
629
            super(str)
630
          end
631
        end
632
      end
633
      
634
      Type2 = Message.define{
635
        string        :sign,         {:size => 8, :value => SSP_SIGN}
636
        int32LE       :type,      {:value => 2}
637
        security_buffer   :target_name,  {:size => 0, :value => ""}
638
        int32LE       :flag,         {:value => DEFAULT_FLAGS[:TYPE2]}
639
        int64LE           :challenge,    {:value => 0}
640
        int64LE           :context,      {:value => 0, :active => false}
641
        security_buffer   :target_info,  {:value => "", :active => false}
642
        string        :padding,   {:size => 0, :value => "", :active => false }
643
      }
644
      
645
      class Type2
646
        class << Type2
647
          def parse(str)
648
            t = new
649
            t.parse(str)
650
            t
651
          end
652
        end
653
        
654
        def parse(str)
655
          super(str)
656
          if has_flag?(:TARGET_INFO)
657
            enable(:context)
658
            enable(:target_info)
659
            super(str)
660
          end
661
          if ( (len = data_edge - head_size) > 0)
662
            self.padding = "\0" * len
663
            super(str)
664
          end
665
        end
666
        
667
        def response(arg, opt = {})
668
          usr = arg[:user]
669
          pwd = arg[:password]
670
          if usr.nil? or pwd.nil?
671
            raise ArgumentError, "user and password have to be supplied"
672
          end
673
          
674
          if opt[:workstation]
675
            ws = opt[:workstation]
676
          else
677
            ws = ""
678
          end
679
          
680
          if opt[:client_challenge]
681
            cc  = opt[:client_challenge]
682
          else
683
            cc = rand(MAX64)
684
          end
685
          cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
686
          opt[:client_challenge] = cc
687

    
688
          if has_flag?(:OEM) and opt[:unicode]
689
            usr = NTLM::decode_utf16le(usr)
690
            pwd = NTLM::decode_utf16le(pwd)
691
            ws  = NTLM::decode_utf16le(ws)
692
            opt[:unicode] = false
693
          end
694

    
695
          if has_flag?(:UNICODE) and !opt[:unicode]
696
            usr = NTLM::encode_utf16le(usr)
697
            pwd = NTLM::encode_utf16le(pwd)
698
            ws  = NTLM::encode_utf16le(ws)
699
            opt[:unicode] = true
700
          end
701

    
702
          tgt = self.target_name
703
          ti = self.target_info
704

    
705
          chal = self[:challenge].serialize
706
          
707
          if opt[:ntlmv2]
708
            ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, tgt, opt), :challenge => chal, :target_info => ti}
709
            lm_res = NTLM::lmv2_response(ar, opt)
710
            ntlm_res = NTLM::ntlmv2_response(ar, opt)
711
          elsif has_flag?(:NTLM2_KEY)
712
            ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
713
            lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
714
          else
715
            lm_res = NTLM::lm_response(pwd, chal)
716
            ntlm_res = NTLM::ntlm_response(pwd, chal)
717
          end
718
          
719
          Type3.create({
720
          	:lm_response => lm_res,
721
          	:ntlm_response => ntlm_res,
722
          	:domain => tgt,
723
            :user => usr,
724
            :workstation => ws,
725
            :flag => self.flag
726
          })
727
        end
728
      end
729
      
730
            
731
      Type3 = Message.define{
732
        string          :sign,          {:size => 8, :value => SSP_SIGN}
733
        int32LE         :type,          {:value => 3}
734
        security_buffer :lm_response,   {:value => ""}
735
        security_buffer :ntlm_response, {:value => ""}
736
        security_buffer :domain,        {:value => ""}
737
        security_buffer :user,          {:value => ""}
738
        security_buffer :workstation,   {:value => ""}
739
        security_buffer :session_key,   {:value => "", :active => false }
740
        int64LE         :flag,          {:value => 0, :active => false }
741
      }
742
      
743
      class Type3
744
        class << Type3
745
          def parse(str)
746
            t = new
747
            t.parse(str)
748
            t
749
          end
750
        
751
          def create(arg, opt ={})
752
            t = new
753
            t.lm_response = arg[:lm_response]
754
            t.ntlm_response = arg[:ntlm_response]
755
            t.domain = arg[:domain]
756
            t.user = arg[:user]
757
            t.workstation = arg[:workstation]
758
            
759
            if arg[:session_key]
760
              t.enable(:session_key)
761
              t.session_key = arg[session_key]
762
            end
763
            if arg[:flag]
764
              t.enable(:session_key)
765
              t.enable(:flag)
766
              t.flag = arg[:flag]
767
            end
768
            t
769
          end
770
        end
771
      end
772
    end
773
  end
774
end
775

    
(1-1/2)