~ubuntu-branches/ubuntu/saucy/amsn/saucy

« back to all changes in this revision

Viewing changes to utils/sasl/sasl.tcl

  • Committer: Bazaar Package Importer
  • Author(s): Devid Antonio Filoni
  • Date: 2010-04-13 23:21:29 UTC
  • mfrom: (1.1.11 upstream) (3.1.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100413232129-vgpx20brdd2qavs7
Tags: 0.98.3-0ubuntu1
* Merge from Debian unstable (LP: #449072), remaining Ubuntu changes:
  - add 08_use_aplay_for_sound.dpatch patch by Festor Wailon Dacoba to use
    aplay to play sounds
  + debian/control:
    - modify iceweasel to firefox | abrowser in amsn Suggests field
    - add xdg-utils and gstreamer0.10-nice to amsn Depends field
    - mofify sox to alsa-utils in amsn Suggests field as we are now using
      aplay
* New upstream release (LP: #562619), tarball repacked according to
  debian/README.source.
* Fix missing-debian-source-format lintian warning.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# sasl.tcl - Copyright (C) 2005 Pat Thoyts <patthoyts@users.sourceforge.net>
 
2
#
 
3
# This is an implementation of a general purpose SASL library for use in
 
4
# Tcl scripts. 
 
5
#
 
6
# References:
 
7
#    Myers, J., "Simple Authentication and Security Layer (SASL)", 
 
8
#      RFC 2222, October 1997.
 
9
#    Rose, M.T., "TclSASL", "http://beepcore-tcl.sourceforge.net/tclsasl.html"
 
10
#
 
11
# -------------------------------------------------------------------------
 
12
# See the file "license.terms" for information on usage and redistribution
 
13
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 
14
# -------------------------------------------------------------------------
 
15
 
 
16
package require Tcl 8.2
 
17
 
 
18
namespace eval ::SASL {
 
19
    variable version 1.3.2
 
20
    variable rcsid {$Id: sasl.tcl,v 1.12 2008/01/29 00:51:39 patthoyts Exp $}
 
21
 
 
22
    variable uid
 
23
    if {![info exists uid]} { set uid 0 }
 
24
 
 
25
    variable mechanisms
 
26
    if {![info exists mechanisms]} {
 
27
        set mechanisms [list]
 
28
    }
 
29
}
 
30
 
 
31
# SASL::mechanisms --
 
32
#
 
33
#       Return a list of available SASL mechanisms. By default only the
 
34
#       client implementations are given but if type is set to server then
 
35
#       the list of available server mechanisms is returned.
 
36
#       No mechanism with a preference value less than 'minimum' will be
 
37
#       returned.
 
38
#       The list is sorted by the security preference with the most secure
 
39
#       mechanisms given first.
 
40
#
 
41
proc ::SASL::mechanisms {{type client} {minimum 0}} {
 
42
    variable mechanisms
 
43
    set r [list]
 
44
    foreach mech $mechanisms {
 
45
        if {[lindex $mech 0] < $minimum} { continue }
 
46
        switch -exact -- $type {
 
47
            client {
 
48
                if {[string length [lindex $mech 2]] > 0} {
 
49
                    lappend r [lindex $mech 1]
 
50
                }
 
51
            }
 
52
            server {
 
53
                if {[string length [lindex $mech 3]] > 0} {
 
54
                    lappend r [lindex $mech 1]
 
55
                }
 
56
            }
 
57
            default {
 
58
                return -code error "invalid type \"$type\":\
 
59
                    must be either client or server"
 
60
            }
 
61
        }
 
62
    }
 
63
    return $r
 
64
}
 
65
 
 
66
# SASL::register --
 
67
#
 
68
#       Register a new SASL mechanism with a security preference. Higher
 
69
#       preference values are chosen before lower valued mechanisms.
 
70
#       If no server implementation is available then an empty string 
 
71
#       should be provided for the serverproc parameter.
 
72
#
 
73
proc ::SASL::register {mechanism preference clientproc {serverproc {}}} {
 
74
    variable mechanisms
 
75
    set ndx [lsearch -regexp $mechanisms $mechanism]
 
76
    set mech [list $preference $mechanism $clientproc $serverproc]
 
77
    if {$ndx == -1} {
 
78
        lappend mechanisms $mech
 
79
    } else {
 
80
        set mechanisms [lreplace $mechanisms $ndx $ndx $mech]
 
81
    }
 
82
    set mechanisms [lsort -index 0 -decreasing -integer $mechanisms]
 
83
    return
 
84
}
 
85
 
 
86
# SASL::uid --
 
87
#
 
88
#       Return a unique integer.
 
89
#
 
90
proc ::SASL::uid {} {
 
91
    variable uid
 
92
    return [incr uid]
 
93
}
 
94
 
 
95
# SASL::response --
 
96
#
 
97
#       Get the reponse string from the SASL state.
 
98
#
 
99
proc ::SASL::response {context} {
 
100
    upvar #0 $context ctx
 
101
    return $ctx(response)
 
102
}
 
103
 
 
104
# SASL::reset --
 
105
#
 
106
#       Reset the SASL state. This permits the same instance to be reused
 
107
#       for a new round of authentication.
 
108
#
 
109
proc ::SASL::reset {context {step 0}} {
 
110
    upvar #0 $context ctx
 
111
    array set ctx [list step $step response "" valid false count 0]
 
112
    return $context
 
113
}
 
114
 
 
115
# SASL::cleanup --
 
116
#
 
117
#       Free any resources used with the SASL state.
 
118
#
 
119
proc ::SASL::cleanup {context} {
 
120
    if {[info exists $context]} {
 
121
        unset $context
 
122
    }
 
123
    return
 
124
}
 
125
 
 
126
# SASL::new --
 
127
#
 
128
#       Create a new SASL instance. 
 
129
#
 
130
proc ::SASL::new {args} {
 
131
    set context [namespace current]::[uid]
 
132
    upvar #0 $context ctx
 
133
    array set ctx [list mech {} callback {} proc {} service smtp server {} \
 
134
                       step 0 response "" valid false type client count 0]
 
135
    eval [linsert $args 0 [namespace origin configure] $context]
 
136
    return $context
 
137
}
 
138
 
 
139
# SASL::configure --
 
140
#
 
141
#       Configure the SASL state.
 
142
#
 
143
proc ::SASL::configure {context args} {
 
144
    variable mechanisms
 
145
    upvar #0 $context ctx
 
146
    while {[string match -* [set option [lindex $args 0]]]} {
 
147
        switch -exact -- $option {
 
148
            -service {
 
149
                set ctx(service) [Pop args 1]
 
150
            }
 
151
            -server - -serverFQDN {
 
152
                set ctx(server) [Pop args 1]
 
153
            }
 
154
            -mech - -mechanism {
 
155
                set mech [string toupper [Pop args 1]]
 
156
                set ctx(proc) {}
 
157
                foreach m $mechanisms {
 
158
                    if {[string equal [lindex $m 1] $mech]} {
 
159
                        set ctx(mech) $mech
 
160
                        if {[string equal $ctx(type) "server"]} {
 
161
                            set ctx(proc) [lindex $m 3]
 
162
                        } else {
 
163
                            set ctx(proc) [lindex $m 2]
 
164
                        }
 
165
                        break
 
166
                    }
 
167
                }
 
168
                if {[string equal $ctx(proc) {}]} {
 
169
                    return -code error "mechanism \"$mech\" not available:\
 
170
                        must be one of those given by \[sasl::mechanisms\]"
 
171
                }
 
172
            }
 
173
            -callback - -callbacks {
 
174
                set ctx(callback) [Pop args 1]
 
175
            }
 
176
            -type {
 
177
                set type [Pop args 1]
 
178
                if {[lsearch -exact {server client} $type] != -1} {
 
179
                    set ctx(type) $type
 
180
                    if {![string equal $ctx(mech) ""]} {
 
181
                        configure $context -mechanism $ctx(mech)
 
182
                    }
 
183
                } else {
 
184
                    return -code error "bad value \"$type\":\
 
185
                        must be either client or server"
 
186
                }
 
187
            }
 
188
            default {
 
189
                return -code error "bad option \"$option\":\
 
190
                    must be one of -mechanism, -service, -server -type\
 
191
                    or -callbacks"
 
192
            }
 
193
        }
 
194
        Pop args
 
195
    }
 
196
        
 
197
}
 
198
 
 
199
proc ::SASL::step {context challenge args} {
 
200
    upvar #0 $context ctx
 
201
    incr ctx(count)
 
202
    return [eval [linsert $args 0 $ctx(proc) $context $challenge]]
 
203
}
 
204
 
 
205
 
 
206
proc ::SASL::Pop {varname {nth 0}} {
 
207
    upvar $varname args
 
208
    set r [lindex $args $nth]
 
209
    set args [lreplace $args $nth $nth]
 
210
    return $r
 
211
}
 
212
 
 
213
proc ::SASL::md5_init {} {
 
214
    variable md5_inited
 
215
    if {[info exists md5_inited]} {return} else {set md5_inited 1}
 
216
    # Deal with either version of md5. We'd like version 2 but someone
 
217
    # may have already loaded version 1.
 
218
    set md5major [lindex [split [package require md5] .] 0]
 
219
    if {$md5major < 2} {
 
220
        # md5 v1, no options, and returns a hex string ready for us.
 
221
        proc ::SASL::md5_hex {data} { return [::md5::md5 $data] }
 
222
        proc ::SASL::md5_bin {data} { return [binary format H* [::md5::md5 $data]] }
 
223
        proc ::SASL::hmac_hex {pass data} { return [::md5::hmac $pass $data] }
 
224
        proc ::SASL::hmac_bin {pass data} { return [binary format H* [::md5::hmac $pass $data]] }
 
225
    } else {
 
226
        # md5 v2 requires -hex to return hash as hex-encoded non-binary string.
 
227
        proc ::SASL::md5_hex {data} { return [string tolower [::md5::md5 -hex $data]] }
 
228
        proc ::SASL::md5_bin {data} { return [::md5::md5 $data] }
 
229
        proc ::SASL::hmac_hex {pass data} { return [::md5::hmac -hex -key $pass $data] }
 
230
        proc ::SASL::hmac_bin {pass data} { return [::md5::hmac -key $pass $data] }
 
231
    }
 
232
}
 
233
 
 
234
# -------------------------------------------------------------------------
 
235
 
 
236
# CRAM-MD5 SASL MECHANISM
 
237
#
 
238
#       Implementation of the Challenge-Response Authentication Mechanism
 
239
#       (RFC2195).
 
240
#
 
241
# Comments:
 
242
#       This mechanism passes a server generated string containing
 
243
#       a timestamp and has the client generate an MD5 HMAC using the
 
244
#       shared secret as the key and the server string as the data.
 
245
#       The downside of this protocol is that the server must have access
 
246
#       to the plaintext password.
 
247
#
 
248
proc ::SASL::CRAM-MD5:client {context challenge args} {
 
249
    upvar #0 $context ctx
 
250
    md5_init
 
251
    if {$ctx(step) != 0} {
 
252
        return -code error "unexpected state: CRAM-MD5 has only 1 step"
 
253
    }
 
254
    if {[string length $challenge] == 0} {
 
255
        set ctx(response) ""
 
256
        return 1
 
257
    }
 
258
    set password [eval $ctx(callback) [list $context password]]
 
259
    set username [eval $ctx(callback) [list $context username]]
 
260
    set reply [hmac_hex $password $challenge]
 
261
    set reply "$username [string tolower $reply]"
 
262
    set ctx(response) $reply
 
263
    incr ctx(step)
 
264
    return 0
 
265
}
 
266
 
 
267
proc ::SASL::CRAM-MD5:server {context clientrsp args} {
 
268
    upvar #0 $context ctx
 
269
    md5_init
 
270
    incr ctx(step)
 
271
    switch -exact -- $ctx(step) {
 
272
        1 {
 
273
            set ctx(realm) [eval $ctx(callback) [list $context realm]]
 
274
            set ctx(response) "<[pid].[clock seconds]@$ctx(realm)>"
 
275
            return 1
 
276
        }
 
277
        2 {
 
278
            foreach {user hash} $clientrsp break
 
279
            set hash [string tolower $hash]
 
280
            set pass [eval $ctx(callback) [list $context password $user $ctx(realm)]]
 
281
            set check [hmac_bin $pass $ctx(response)]
 
282
            binary scan $check H* cx
 
283
            if {[string equal $cx $hash]} {
 
284
                return 0
 
285
            } else {
 
286
                return -code error "authentication failed"
 
287
            }
 
288
        }
 
289
        default {
 
290
            return -code error "invalid state"
 
291
        }
 
292
    }
 
293
}
 
294
 
 
295
::SASL::register CRAM-MD5 30 ::SASL::CRAM-MD5:client ::SASL::CRAM-MD5:server
 
296
 
 
297
# -------------------------------------------------------------------------
 
298
# PLAIN SASL MECHANISM
 
299
#
 
300
#       Implementation of the single step login SASL mechanism (RFC2595).
 
301
#
 
302
# Comments:
 
303
#       A single step mechanism in which the authorization ID, the
 
304
#       authentication ID and password are all transmitted in plain
 
305
#       text. This should not be used unless the channel is secured by
 
306
#       some other means (such as SSL/TLS).
 
307
#
 
308
proc ::SASL::PLAIN:client {context challenge args} {
 
309
    upvar #0 $context ctx
 
310
    incr ctx(step)
 
311
    set authzid  [eval $ctx(callback) [list $context login]]
 
312
    set username [eval $ctx(callback) [list $context username]]
 
313
    set password [eval $ctx(callback) [list $context password]]
 
314
    set ctx(response) "$authzid\x00$username\x00$password"
 
315
    return 0
 
316
}
 
317
 
 
318
proc ::SASL::PLAIN:server {context clientrsp args} {
 
319
    upvar \#0 $context ctx
 
320
    if {[string length $clientrsp] < 1} {
 
321
        set ctx(response) ""
 
322
        return 1
 
323
    } else {
 
324
        foreach {authzid authid pass} [split $clientrsp \0] break
 
325
        set realm [eval $ctx(callback) [list $context realm]]
 
326
        set check [eval $ctx(callback) [list $context password $authid $realm]]
 
327
        if {[string equal $pass $check]} {
 
328
            return 0
 
329
        } else {
 
330
            return -code error "authentication failed"
 
331
        }
 
332
    }
 
333
}
 
334
 
 
335
::SASL::register PLAIN 10 ::SASL::PLAIN:client ::SASL::PLAIN:server
 
336
 
 
337
# -------------------------------------------------------------------------
 
338
# LOGIN SASL MECHANISM
 
339
#
 
340
#       Implementation of the two step login SASL mechanism.
 
341
#
 
342
# Comments:
 
343
#       This is an unofficial but widely deployed SASL mechanism somewhat
 
344
#       akin to the PLAIN mechanism. Both the authentication ID and password
 
345
#       are transmitted in plain text in response to server prompts.
 
346
#
 
347
#       NOT RECOMMENDED for use in new protocol implementations.
 
348
#
 
349
proc ::SASL::LOGIN:client {context challenge args} {
 
350
    upvar #0 $context ctx
 
351
    if {$ctx(step) == 0 && [string length $challenge] == 0} {
 
352
        set ctx(response) ""
 
353
        return 1
 
354
    }
 
355
    incr ctx(step)
 
356
    switch -exact -- $ctx(step) {
 
357
        1 {
 
358
            set ctx(response) [eval $ctx(callback) [list $context username]]
 
359
            set r 1
 
360
        }
 
361
        2 {
 
362
            set ctx(response) [eval $ctx(callback) [list $context password]]
 
363
            set r 0
 
364
        }
 
365
        default {
 
366
            return -code error "unexpected state \"$ctx(step)\":\
 
367
                LOGIN has only 2 steps"
 
368
        }
 
369
    }
 
370
    return $r
 
371
}
 
372
 
 
373
proc ::SASL::LOGIN:server {context clientrsp args} {
 
374
    upvar #0 $context ctx
 
375
    incr ctx(step)
 
376
    switch -exact -- $ctx(step) {
 
377
        1 {
 
378
            set ctx(response) "Username:"
 
379
            return 1
 
380
        }
 
381
        2 {
 
382
            set ctx(username) $clientrsp
 
383
            set ctx(response) "Password:"
 
384
            return 1
 
385
        }
 
386
        3 {
 
387
            set user $ctx(username)
 
388
            set realm [eval $ctx(callback) [list $context realm]]
 
389
            set pass [eval $ctx(callback) [list $context password $user $realm]]
 
390
            if {[string equal $clientrsp $pass]} {
 
391
                return 0
 
392
            } else {
 
393
                return -code error "authentication failed"
 
394
            }
 
395
        }
 
396
        default {
 
397
            return -code error "invalid state"
 
398
        }
 
399
    }
 
400
}
 
401
 
 
402
::SASL::register LOGIN 20 ::SASL::LOGIN:client ::SASL::LOGIN:server
 
403
 
 
404
# -------------------------------------------------------------------------
 
405
# ANONYMOUS SASL MECHANISM
 
406
#
 
407
#       Implementation of the ANONYMOUS SASL mechanism (RFC2245).
 
408
#
 
409
# Comments:
 
410
#
 
411
 
412
proc ::SASL::ANONYMOUS:client {context challenge args} {
 
413
    upvar #0 $context ctx
 
414
    set user  [eval $ctx(callback) [list $context username]]
 
415
    set realm [eval $ctx(callback) [list $context realm]]
 
416
    set ctx(response) $user@$realm
 
417
    return 0
 
418
}
 
419
 
 
420
proc ::SASL::ANONYMOUS:server {context clientrsp args} {
 
421
    upvar #0 $context ctx
 
422
    set ctx(response) ""
 
423
    if {[string length $clientrsp] < 1} {
 
424
        if {$ctx(count) > 2} {
 
425
            return -code error "authentication failed"
 
426
        }
 
427
        return 1
 
428
    } else {
 
429
        set ctx(trace) $clientrsp
 
430
        return 0
 
431
    }
 
432
}
 
433
 
 
434
::SASL::register ANONYMOUS 5 ::SASL::ANONYMOUS:client ::SASL::ANONYMOUS:server
 
435
 
 
436
# -------------------------------------------------------------------------
 
437
 
 
438
# DIGEST-MD5 SASL MECHANISM
 
439
#
 
440
#       Implementation of the DIGEST-MD5 SASL mechanism (RFC2831).
 
441
#
 
442
# Comments:
 
443
#
 
444
proc ::SASL::DIGEST-MD5:client {context challenge args} {
 
445
    upvar #0 $context ctx
 
446
    md5_init
 
447
    if {$ctx(step) == 0 && [string length $challenge] == 0} {
 
448
        if {[info exists ctx(challenge)]} {
 
449
            set challenge $ctx(challenge)
 
450
        } else {
 
451
            set ctx(response) ""
 
452
            return 1
 
453
        }
 
454
    }
 
455
    incr ctx(step)
 
456
    set result 0
 
457
    switch -exact -- $ctx(step) {
 
458
        1 {
 
459
            set ctx(challenge) $challenge
 
460
            array set params [DigestParameters $challenge]
 
461
            
 
462
            if {![info exists ctx(noncecount)]} {
 
463
                set ctx(noncecount) 0
 
464
            }
 
465
            set nonce $params(nonce)
 
466
            set cnonce [CreateNonce]
 
467
            set noncecount [format %08u [incr ctx(noncecount)]]
 
468
            set qop auth
 
469
            
 
470
            # support the 'charset' parameter.
 
471
            set username [eval $ctx(callback) [list $context username]]
 
472
            set password [eval $ctx(callback) [list $context password]]
 
473
            set encoding iso8859-1
 
474
            if {[info exists params(charset)]} {
 
475
                set encoding $params(charset)
 
476
            }
 
477
            set username [encoding convertto $encoding $username]
 
478
            set password [encoding convertto $encoding $password]
 
479
 
 
480
            if {[info exists params(realm)]} {
 
481
                set realm $params(realm)
 
482
            } else {
 
483
                set realm [eval $ctx(callback) [list $context realm]]
 
484
            }
 
485
            
 
486
            set uri "$ctx(service)/$realm"
 
487
            set R [DigestResponse $username $realm $password $uri \
 
488
                       $qop $nonce $noncecount $cnonce]
 
489
            
 
490
            set ctx(response) "username=\"$username\",realm=\"$realm\",nonce=\"$nonce\",nc=\"$noncecount\",cnonce=\"$cnonce\",digest-uri=\"$uri\",response=\"$R\",qop=$qop"
 
491
            if {[info exists params(charset)]} {
 
492
                append ctx(response) ",charset=$params(charset)"
 
493
            }
 
494
            set result 1
 
495
        }
 
496
        
 
497
        2 {
 
498
            set ctx(response) ""
 
499
            set result 0
 
500
        }
 
501
        default {
 
502
            return -code error "invalid state"
 
503
        }
 
504
    }
 
505
    return $result
 
506
}
 
507
 
 
508
proc ::SASL::DIGEST-MD5:server {context challenge args} {
 
509
    upvar #0 $context ctx
 
510
    md5_init
 
511
    incr ctx(step)
 
512
    set result 0
 
513
    switch -exact -- $ctx(step) {
 
514
        1 {
 
515
            set realm [eval $ctx(callback) [list $context realm]]
 
516
            set ctx(nonce) [CreateNonce]
 
517
            set ctx(nc) 0
 
518
            set ctx(response) "realm=\"$realm\",nonce=\"$ctx(nonce)\",qop=\"auth\",charset=utf-8,algorithm=md5-sess"
 
519
            set result 1
 
520
        }
 
521
        2 {
 
522
            array set params [DigestParameters $challenge]
 
523
            set realm [eval $ctx(callback) [list $context realm]]
 
524
            set password [eval $ctx(callback)\
 
525
                              [list $context password $params(username) $realm]]
 
526
            set uri "$ctx(service)/$realm"
 
527
            set nc [format %08u [expr {$ctx(nc) + 1}]]
 
528
            set R [DigestResponse $params(username) $realm $password \
 
529
                       $uri auth $ctx(nonce) $nc $params(cnonce)]
 
530
            if {[string equal $R $params(response)]} {
 
531
                set R2 [DigestResponse $params(username) $realm $password \
 
532
                        $uri auth $ctx(nonce) $nc $params(cnonce)]
 
533
                set ctx(response) "rspauth=$R2"
 
534
                incr ctx(nc)
 
535
                set result 1
 
536
            } else {
 
537
                return -code error "authentication failed"
 
538
            }
 
539
        }
 
540
        3 {
 
541
            set ctx(response) ""
 
542
            set result 0
 
543
        }
 
544
        default {
 
545
            return -code error "invalid state"
 
546
        }
 
547
    }
 
548
    return $result
 
549
}
 
550
 
 
551
# RFC 2831 2.1
 
552
# Char categories as per spec...
 
553
# Build up a regexp for splitting the challenge into key value pairs.
 
554
proc ::SASL::DigestParameters {challenge} {
 
555
    set sep "\\\]\\\[\\\\()<>@,;:\\\"\\\?=\\\{\\\} \t"
 
556
    set tok {0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\-\|\~\!\#\$\%\&\*\+\.\^\_\`}
 
557
    set sqot {(?:\'(?:\\.|[^\'\\])*\')}
 
558
    set dqot {(?:\"(?:\\.|[^\"\\])*\")}
 
559
    set parameters {}
 
560
    regsub -all "(\[${tok}\]+)=(${dqot}|(?:\[${tok}\]+))(?:\[${sep}\]+|$)" $challenge {\1 \2 } parameters
 
561
    return $parameters
 
562
}
 
563
 
 
564
# RFC 2831 2.1.2.1
 
565
#
 
566
proc ::SASL::DigestResponse {user realm pass uri qop nonce noncecount cnonce} {
 
567
    set A1 [md5_bin "$user:$realm:$pass"]
 
568
    set A2 "AUTHENTICATE:$uri"
 
569
    if {![string equal $qop "auth"]} {
 
570
        append A2 :[string repeat 0 32]
 
571
    }
 
572
    set A1h [md5_hex "${A1}:$nonce:$cnonce"]
 
573
    set A2h [md5_hex $A2]
 
574
    set R   [md5_hex $A1h:$nonce:$noncecount:$cnonce:$qop:$A2h]
 
575
    return $R
 
576
}
 
577
 
 
578
# RFC 2831 2.1.2.2
 
579
#
 
580
proc ::SASL::DigestResponse2 {user realm pass uri qop nonce noncecount cnonce} {
 
581
    set A1 [md5_bin "$user:$realm:$pass"]
 
582
    set A2 ":$uri"
 
583
    if {![string equal $qop "auth"]} {
 
584
        append A2 :[string repeat 0 32]
 
585
    }
 
586
    set A1h [md5_hex "${A1}:$nonce:$cnonce"]
 
587
    set A2h [md5_hex $A2]
 
588
    set R   [md5_hex $A1h:$nonce:$noncecount:$cnonce:$qop:$A2h]
 
589
    return $R
 
590
}
 
591
 
 
592
# Get 16 random bytes for a nonce value. If we can use /dev/random, do so
 
593
# otherwise we hash some values.
 
594
#
 
595
proc ::SASL::CreateNonce {} {
 
596
    set bytes {}
 
597
    if {[file readable /dev/urandom]} {
 
598
        catch {
 
599
            set f [open /dev/urandom r]
 
600
            fconfigure $f -translation binary -buffering none
 
601
            set bytes [read $f 16]
 
602
            close $f
 
603
        }
 
604
    }
 
605
    if {[string length $bytes] < 1} {
 
606
        set bytes [md5_bin [clock seconds]:[pid]:[expr {rand()}]]
 
607
    }
 
608
    return [binary scan $bytes h* r; set r]
 
609
}
 
610
 
 
611
::SASL::register DIGEST-MD5 40 \
 
612
    ::SASL::DIGEST-MD5:client ::SASL::DIGEST-MD5:server
 
613
 
 
614
# -------------------------------------------------------------------------
 
615
 
 
616
# OTP SASL MECHANISM
 
617
#
 
618
#       Implementation of the OTP SASL mechanism (RFC2444).
 
619
#
 
620
# Comments:
 
621
#
 
622
#       RFC 2289: A One-Time Password System
 
623
#       RFC 2444: OTP SASL Mechanism
 
624
#       RFC 2243: OTP Extended Responses
 
625
#       Client initializes with authid\0authzid
 
626
#       Server responds with extended OTP responses 
 
627
#       eg: otp-md5 498 bi32123 ext
 
628
#       Client responds with otp result as:
 
629
#        hex:xxxxxxxxxxxxxxxx
 
630
#       or
 
631
#        word:WWWW WWW WWWW WWWW WWWW
 
632
#
 
633
#       To support changing the otp sequence the extended commands have:
 
634
#         init-hex:<current>:<new params>:<new>
 
635
#       eg: init-hex:xxxxxxxxxxxx:md5 499 seed987:xxxxxxxxxxxxxx
 
636
#       or init-word
 
637
 
 
638
proc ::SASL::OTP:client {context challenge args} {
 
639
    upvar #0 $context ctx
 
640
    package require otp
 
641
    incr ctx(step)
 
642
    switch -exact -- $ctx(step) {
 
643
        1 {
 
644
            set authzid  [eval $ctx(callback) [list $context login]]
 
645
            set username [eval $ctx(callback) [list $context username]]
 
646
            set ctx(response) "$authzid\x00$username"
 
647
            set cont 1
 
648
        }
 
649
        2 {
 
650
            foreach {type count seed ext} $challenge break
 
651
            set type [lindex [split $type -] 1]
 
652
            if {[lsearch -exact {md4 md5 sha1 rmd160} $type] == -1} {
 
653
                return -code error "unsupported digest algorithm \"$type\":\
 
654
                    must be one of md4, md5, sha1 or rmd160"
 
655
            }
 
656
            set challenge [lrange $challenge 3 end]
 
657
            set password [eval $ctx(callback) [list $context password]]
 
658
            set otp [::otp::otp-$type -word -seed $seed \
 
659
                         -count $count $password]
 
660
            if {[string match "ext*" $ext]} {
 
661
                set otp word:$otp
 
662
            }
 
663
            set ctx(response) $otp
 
664
            set cont 0
 
665
        }
 
666
        default {
 
667
            return -code error "unexpected state \"$ctx(step)\":\
 
668
               the SASL OTP mechanism only has 2 steps"
 
669
        }
 
670
    }
 
671
    return $cont
 
672
}
 
673
 
 
674
::SASL::register OTP 45 ::SASL::OTP:client
 
675
 
 
676
# -------------------------------------------------------------------------
 
677
 
 
678
package provide SASL $::SASL::version
 
679
 
 
680
# -------------------------------------------------------------------------
 
681
#
 
682
# Local variables:
 
683
#   indent-tabs-mode: nil
 
684
# End: