# small 9p implementation in tcl by Axel.Belinfante@cs.utwente.nl
# it needs tclDES from http://tcldes.sourceforge.net/
# I have used version 0.8 with good success
# this is based on the python 9P implementation by Tim Newsham.
# http://lava.net/~newsham/plan9/
package require tclDES
namespace eval p9sk1 {
variable TickReqLen 141
variable TickLen 72
variable AuthLen 13
variable AuthTreq 1
variable AuthChal 2
variable AuthPass 3
variable AuthOK 4
variable AuthErr 5
variable AuthMod 6
variable AuthTs 64
variable AuthTc 65
variable AuthAs 66
variable AuthAc 67
variable AuthTp 68
variable AuthHr 69
variable AUTHPORT 567
variable _par [list \
0x01 0x02 0x04 0x07 0x08 0x0b 0x0d 0x0e \
0x10 0x13 0x15 0x16 0x19 0x1a 0x1c 0x1f \
0x20 0x23 0x25 0x26 0x29 0x2a 0x2c 0x2f \
0x31 0x32 0x34 0x37 0x38 0x3b 0x3d 0x3e \
0x40 0x43 0x45 0x46 0x49 0x4a 0x4c 0x4f \
0x51 0x52 0x54 0x57 0x58 0x5b 0x5d 0x5e \
0x61 0x62 0x64 0x67 0x68 0x6b 0x6d 0x6e \
0x70 0x73 0x75 0x76 0x79 0x7a 0x7c 0x7f \
0x80 0x83 0x85 0x86 0x89 0x8a 0x8c 0x8f \
0x91 0x92 0x94 0x97 0x98 0x9b 0x9d 0x9e \
0xa1 0xa2 0xa4 0xa7 0xa8 0xab 0xad 0xae \
0xb0 0xb3 0xb5 0xb6 0xb9 0xba 0xbc 0xbf \
0xc1 0xc2 0xc4 0xc7 0xc8 0xcb 0xcd 0xce \
0xd0 0xd3 0xd5 0xd6 0xd9 0xda 0xdc 0xdf \
0xe0 0xe3 0xe5 0xe6 0xe9 0xea 0xec 0xef \
0xf1 0xf2 0xf4 0xf7 0xf8 0xfb 0xfd 0xfe \
]
}
proc p9sk1::pad {str l {padch {}}} {
set n [expr $l - [string length $str]]
set s $str
if {$padch == {}} {
set padch [binary format x]
}
append s [string repeat $padch $n]
return $s
}
# Expand a 7-byte DES key into an 8-byte DES key
proc p9sk1::expandKey {key} {
variable _par
binary scan $key c* tbuf
set k {}
foreach x $tbuf {
lappend k [expr $x & 0xff]
}
lappend k64 [expr [lindex $k 0] >> 1]
lappend k64 [expr ([lindex $k 1] >> 2) | ([lindex $k 0] << 6)]
lappend k64 [expr ([lindex $k 2] >> 3) | ([lindex $k 1] << 5)]
lappend k64 [expr ([lindex $k 3] >> 4) | ([lindex $k 2] << 4)]
lappend k64 [expr ([lindex $k 4] >> 5) | ([lindex $k 3] << 3)]
lappend k64 [expr ([lindex $k 5] >> 6) | ([lindex $k 4] << 2)]
lappend k64 [expr ([lindex $k 6] >> 7) | ([lindex $k 5] << 1)]
lappend k64 [expr [lindex $k 6] << 0]
foreach x $k64 {
lappend r [lindex $_par [expr $x & 0x7f]]
}
return [binary format c* $r]
}
proc p9sk1::newKey {key} {
set e [expandKey $key]
return [::des::keyset create $e]
}
proc p9sk1::encrypt {key msg} {
set r [::des::encrypt $key $msg]
return $r
}
proc p9sk1::decrypt {key msg} {
set r [::des::decrypt $key $msg]
return $r
}
proc p9sk1::makeKey {password} {
set password [string range $password 0 26]
append password [binary format x]
set n [expr [string length $password] - 1]
set password [p9sk1::pad $password 28 { }]
set buf $password
while {1} {
set ts [string range $buf 0 7]
binary scan $ts c* tl
set t {}
foreach x $tl {
lappend t [expr $x & 0xff]
}
set i 0
set k {}
while {$i < 7} {
lappend k [expr ([lindex $t $i] >> $i) + ([lindex $t [expr $i + 1]] << (8-($i + 1))) & 0xff]
incr i
}
set key [binary format c* $k]
if {$n <= 8} {
return $key
}
incr n -8
if {$n < 8} {
set buf [string range $buf $n end]
} else {
set buf [string range $buf 8 end]
}
set buf [string replace $buf 0 7 [::p9sk1::encrypt [newKey $key] [string range $buf 0 7]]]
}
}
# XXX This is *NOT* a secure way to generate random strings!
# This should be fixed if this code is ever used in a serious manner.
proc p9sk1::randChars {n} {
set i 0
while {$i < $n} {
lappend r [expr int(rand()*255)]
incr i
}
return [binary format c* $r]
}
namespace eval 9p::marshal {
# upvar #0 C_[set self](ks) ks
# upvar #0 C_[set self](kn) kn
# set ks None
# set kn None
}
proc 9p::marshal::setKs {self k} {
upvar #0 C_[set self](ks) ks
set ks [::p9sk1::newKey $k]
}
proc 9p::marshal::setKn {self k} {
upvar #0 C_[set self](kn) kn
set kn [::p9sk1::newKey $k]
}
proc 9p::marshal::encrypt {self n key} {
set idx [expr [lenBuf $self] - $n]
incr n -1
set dummy 0
while {$dummy < [expr $n / 7]} {
set end [expr $idx + 8 -1]
replaceBuf $self $idx $end [::p9sk1::encrypt $key [rangeBuf $self $idx $end]]
incr idx 7
incr dummy
}
if {$n % 7} {
set end [expr [lenBuf $self] - 1]
set start [expr $end - 8 + 1]
replaceBuf $self $start $end [::p9sk1::encrypt $key [rangeBuf $self $start $end]]
}
}
proc 9p::marshal::decrypt {self n key} {
upvar #0 C_[set self](kn) kn
if {[string compare $key None] == 0} {
return
}
set m [expr $n -1 ]
if {$m % 7} {
set start [expr $n - 8]
set end [expr $n - 1]
replaceBuf $self $start $end [::p9sk1::decrypt $key [rangeBuf $self $start $end]]
}
set idx [expr $m - ($m % 7)]
set dummy 0
while {$dummy < [expr $m / 7]} {
incr idx -7
set end [expr $idx + 8 - 1]
replaceBuf $self $idx $end [::p9sk1::decrypt $key [rangeBuf $self $idx $end]]
incr dummy
}
}
proc 9p::marshal::encPad {self x l} {
encX $self [::p9sk1::pad $x $l]
}
proc 9p::marshal::decPad {self l} {
set x [decX $self $l]
set z [binary format x]
set idx [string first $z $x]
if {$idx >= 0} {
set x [string range $x 0 [expr $idx - 1]]
}
return $x
}
proc 9p::marshal::encChal {self x} {
checkLen $x 8
encX $self $x
}
proc 9p::marshal::decChal {self} {
set r [decX $self 8]
return $r
}
proc 9p::marshal::encTicketReq {self x} {
enc1 $self [lindex $x 0] ;# type
encPad $self [lindex $x 1] 28 ;# authid
encPad $self [lindex $x 2] 48 ;# authdom
encChal $self [lindex $x 3] ;# chal
encPad $self [lindex $x 4] 28 ;# hostid
encPad $self [lindex $x 5] 28 ;# uid
}
proc 9p::marshal::decTicketReq {self} {
set r [list \
[dec1 $self] \
[decPad $self 28] \
[decPad $self 48] \
[decChal $self] \
[decPad $self 28] \
[decPad $self 28] \
]
return $r
}
proc 9p::marshal::encTicket {self x} {
upvar #0 C_[set self](ks) ks
set num [lindex $x 0]
set chal [lindex $x 1]
set cuid [lindex $x 2]
set suid [lindex $x 3]
set key [lindex $x 4]
checkLen $key 7
enc1 $self $num
encChal $self $chal
encPad $self $cuid 28
encPad $self $suid 28
encX $sel $key
encrypt $self [expr 1 + 8 + 28 + 28 + 7] $ks
}
proc 9p::marshal::decTicket {self} {
upvar #0 C_[set self](ks) ks
decrypt $self [expr 1 + 8 + 28 + 28 + 7] $ks
set r [list \
[dec1 $self] \
[decChal $self] \
[decPad $self 28] \
[decPad $self 28] \
[decX $self 7] \
]
return $r
}
proc 9p::marshal::encAuth {self x} {
upvar #0 C_[set self](kn) kn
set num [lindex $x 0]
set chal [lindex $x 1]
set id [lindex $x 2]
enc1 $self $num
encChal $self $chal
enc4 $self $id
encrypt $self [expr 1 + 8 + 4] $kn
}
proc 9p::marshal::decAuth {self} {
upvar #0 C_[set self](kn) kn
decrypt $self [expr 1 + 8 + 4] $kn
set r [list \
[dec1 $self] \
[decChal $self] \
[dec4 $self] \
]
return $r
}
proc 9p::marshal::encTattach {self x} {
set tick [lindex $x 0]
set auth [lindex $x 1]
checkLen $tick 72
encX $self $tick
encAuth $self $auth
}
proc 9p::marshal::decTattach {self} {
set r [list \
[decX $self 72] \
[decAuth $self] \
]
return $r
}
# Connect to the auth server and request a set of tickets.
# Con is an open handle to the auth server, sk1 is a handle
# to a P9sk1 marshaller with Kc set and treq is a ticket request.
# Return the (opaque) server ticket and the (decoded) client ticket.
proc p9sk1::getTicket {con sk1 treq} {
variable AuthOK
variable AuthErr
::9p::marshal::setBuf $sk1 ""
::9p::marshal::encTicketReq $sk1 $treq
set x [::9p::marshal::getBuf $sk1]
::9p::marshal::swrite $con $x
set ch [::9p::marshal::sread $con 1]
if {$ch == [binary format c $AuthErr]} {
set err [::9p::marshal::sread $con 64]
error "AuthsrvError $err"
} elseif {$ch != [binary format c $AuthOK]} {
error "AuthsrvError invalid reply type [::9p::_dump $ch]"
}
set ctick [::9p::marshal::sread $con 72]
set stick [::9p::marshal::sread $con 72]
if {[expr [string length $ctick] + [string length $stick]] != [expr 2*72]} {
error "AuthsrvError short auth reply"
}
::9p::marshal::setBuf $sk1 $ctick
set ctl [::9p::marshal::decTicket $sk1]
set r [list $ctl $stick]
return $r
}
# Authenticate ourselves to the server.
# Cl is a P9 RpcClient, afid is the fid to use, user is the
# user name, Kc is the user's key, authsrv and authport specify
# the auth server to use for requesting tickets.
#
# XXX perhaps better if the auth server can be prompted for
# based on the domain in the negotiation.
proc p9sk1::clientAuth {cl afid user Kc authsrv {authport 567}} {
variable TickReqLen
variable AuthLen
variable AuthTreq
variable AuthTc
variable AuthAc
variable AuthAs
set CHc [randChars 8]
set sk1 aapje
::9p::marshal::setKs $sk1 $Kc
::9p::afidopen $cl $afid
set gen 0
# negotiate
set proto [::9p::read $cl $afid 128]
set v2 0
if {[string compare [string range $proto 0 9] {v.2 p9sk1@}] == 0} {
set v2 1
set proto [string range $proto 4 end]
}
if {[string compare [string range $proto 0 5] {p9sk1@}] != 0} {
error "AuthError unknown protocol $proto"
}
set idx [string first @ $proto]
::9p::write $cl $afid [string replace $proto $idx $idx { }]
if {$v2} {
set ok [::9p::read $cl $afid 3]
if {[string compare $ok OK[binary format x]] != 0} {
error "AuthError v.2 protocol botch"
}
}
# Tsession
::9p::marshal::setBuf $sk1 ""
::9p::marshal::encChal $sk1 $CHc
::9p::write $cl $afid [::9p::marshal::getBuf $sk1]
# Rsession
::9p::marshal::setBuf $sk1 [::9p::read $cl $afid $TickReqLen]
set treq [::9p::marshal::decTicketReq $sk1]
if {$v2 && [lindex $treq 0] == 0} {
# kenfs is fast and loose with auth formats
set treq [lreplace $treq 0 0 $AuthTreq]
}
if {[lindex $treq 0] != $AuthTreq} {
error "AuthError bad server"
}
set CHs [lindex $treq 3]
# request ticket from authsrv
set treq [lreplace $treq end-1 end $user $user]
puts stderr "connecting to tcp!$authsrv!$authport"
set asock [socket $authsrv $authport]
fconfigure $asock -translation binary
set ticket [getTicket $asock $sk1 $treq] ;# XXX catch
set ctick [lindex $ticket 0]
set stick [lindex $ticket 1]
set num [lindex $ctick 0]
set CHs2 [lindex $ctick 1]
set cuid [lindex $ctick 2]
set suid [lindex $ctick 3]
set Kn [lindex $ctick 4]
close $asock
if {$num != $AuthTc || $CHs != $CHs2} {
error "AuthError bad password for $user or bad auth server"
} elseif {$num != $AuthTc} {
error "AuthError bad password for $user"
} elseif {$CHs != $CHs2} {
error "bad auth server"
}
::9p::marshal::setKn $sk1 $Kn
# Tattach
::9p::marshal::setBuf $sk1 ""
::9p::marshal::encTattach $sk1 [list $stick [list $AuthAc $CHs $gen]]
set b [::9p::marshal::getBuf $sk1]
::9p::write $cl $afid $b
set a [::9p::read $cl $afid $AuthLen]
::9p::marshal::setBuf $sk1 $a
set r [::9p::marshal::decAuth $sk1]
set num [lindex $r 0]
set CHc2 [lindex $r 1]
set gen2 [lindex $r 2]
if {$num != $AuthAs || $CHc2 != $CHc} {
# XXX check gen2 for replay
error "AuthError bad server"
}
return
}
|