Project

General

Profile

RE: Problems setting up email notifications » ntlm.rb

Konstantin Strelkov, 2012-10-17 11:31

 
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 'kconv'
48
require 'openssl/digest'
49

    
50
module Net  #:nodoc:
51
  module NTLM
52

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
254

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

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

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

    
299

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

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

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

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

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

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

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

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

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

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

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

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

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

    
468

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

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

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

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

    
576
      def size
577
        head_size + data_size
578
      end
579
      
580

    
581
      private
582

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

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

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

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

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

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

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

    
(1-1/2)