00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 package require md5 ;
00013 package require mime ;
00014 package require log ;
00015
00016 namespace ::pop3d {
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055 variable counter 0
00056
00057
00058 variable commands [list \
00059 "cget" \
00060 "configure" \
00061 "destroy" \
00062 "down" \
00063 "up" \
00064 ]
00065
00066 variable version ; version = 1.1.0
00067 variable server "tcllib/pop3d-$version"
00068
00069 variable cmdMap ; array cmdMap = {
00070 CAPA H_capa
00071 USER H_user
00072 PASS H_pass
00073 APOP H_apop
00074 STAT H_stat
00075 DELE H_dele
00076 RETR H_retr
00077 TOP H_top
00078 QUIT H_quit
00079 NOOP H_noop
00080 RSET H_r
00081 LIST = H_list
00082 }
00083
00084
00085
00086
00087
00088 variable capabilities \
00089 [list \
00090 USER both \
00091 PIPELINING both \
00092 "IMPLEMENTATION $server" trans \
00093 ]
00094
00095
00096
00097
00098 namespace export new
00099 }
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112 ret ::pop3d::new (optional name ="") {
00113 variable counter
00114
00115 if { [llength [info level 0]] == 1 } {
00116 incr counter
00117 set name "pop3d${counter}"
00118 }
00119
00120 if { ![string equal [info commands ::$name] ""] } {
00121 return -code error "command \"$name\" already exists, unable to create pop3 server"
00122 }
00123
00124 # Set up the namespace
00125 namespace eval ::pop3d::pop3d::$name {
00126 variable port 110
00127 variable trueport 110
00128 variable sock {}
00129 variable sockCmd socket
00130 variable authCmd {}
00131 variable storCmd {}
00132 variable state down
00133 variable conn ; array set conn {}
00134 variable counter 0
00135 }
00136
00137 # Create the command to manipulate the pop3 server
00138 interp alias {} ::$name {} ::pop3d::Pop3dProc $name
00139
00140 return $name
00141 }
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157 ret ::pop3d::Pop3dProc (type name , optional cmd ="" , type args) {
00158 # Do minimal args checks here
00159 if { [llength [info level 0]] == 2 } {
00160 return -code error "wrong # args: should be \"$name option ?arg arg ...?\""
00161 }
00162
00163 # Split the args into command and args components
00164 if { [llength [info commands ::pop3d::_$cmd]] == 0 } {
00165 variable commands
00166 set optlist [join $commands ", "]
00167 set optlist [linsert $optlist "end-1" "or"]
00168 return -code error "bad option \"$cmd\": must be $optlist"
00169 }
00170 eval [list ::pop3d::_$cmd $name] $args
00171 }
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183 ret ::pop3d::_up (type name) {
00184 upvar ::pop3d::pop3d::${name}::port port
00185 upvar ::pop3d::pop3d::${name}::trueport trueport
00186 upvar ::pop3d::pop3d::${name}::state state
00187 upvar ::pop3d::pop3d::${name}::sockCmd sockCmd
00188 upvar ::pop3d::pop3d::${name}::sock sock
00189
00190 log::log debug "pop3d $name up"
00191 if {[string equal $state up]} {return}
00192
00193 log::log debug "pop3d $name listening, requested port $port"
00194
00195 set cmd $sockCmd
00196 lappend cmd -server [list ::pop3d::HandleNewConnection $name] $port
00197 #puts $cmd
00198 set s [eval $cmd]
00199 set trueport [lindex [fconfigure $s -sockname] 2]
00200
00201 ::log::log debug "pop3d $name listening on $trueport, socket $s ([fconfigure $s -sockname])"
00202
00203 set state up
00204 set sock $s
00205 return
00206 }
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218 ret ::pop3d::_down (type name) {
00219 upvar ::pop3d::pop3d::${name}::state state
00220 upvar ::pop3d::pop3d::${name}::sock sock
00221 upvar ::pop3d::pop3d::${name}::trueport trueport
00222 upvar ::pop3d::pop3d::${name}::port port
00223
00224 # Ignore if server is down or exiting
00225 if {![string equal $state up]} {return}
00226
00227 close $sock
00228 set state down
00229 set sock {}
00230
00231 set trueport $port
00232 return
00233 }
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246 ret ::pop3d::_destroy (type name , optional mode =kill) {
00247 upvar ::pop3d::pop3d::${name}::conn conn
00248
00249 switch -exact -- $mode {
00250 kill {
00251 _down $name
00252 foreach c [array names conn] {
00253 CloseConnection $name $c
00254 }
00255
00256 namespace delete ::pop3d::pop3d::$name
00257 interp alias {} ::$name {}
00258 }
00259 defer {
00260 if {[array size conn] > 0} {
00261 upvar ::pop3d::pop3d::${name}::state state
00262
00263 _down $name
00264 set state exiting
00265 return
00266 }
00267 _destroy $name kill
00268 return
00269 }
00270 default {
00271 return -code error \
00272 "Illegal destruction mode \"$mode\":\
00273 Expected \"kill\", or \"defer\""
00274 }
00275 }
00276 return
00277 }
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289 ret ::pop3d::_cget (type name , type anoption) {
00290 switch -exact -- $anoption {
00291 -state {
00292 upvar ::pop3d::pop3d::${name}::state state
00293 return $state
00294 }
00295 -port {
00296 upvar ::pop3d::pop3d::${name}::trueport trueport
00297 return $trueport
00298 }
00299 -auth {
00300 upvar ::pop3d::pop3d::${name}::authCmd authCmd
00301 return $authCmd
00302 }
00303 -storage {
00304 upvar ::pop3d::pop3d::${name}::storCmd storCmd
00305 return $storCmd
00306 }
00307 -socket {
00308 upvar ::pop3d::pop3d::${name}::sockCmd sockCmd
00309 return $sockCmd
00310 }
00311 default {
00312 return -code error \
00313 "Unknown option \"$anoption\":\
00314 Expected \"-state\", \"-port\", \"-auth\", \"-socket\", or \"-storage\""
00315 }
00316 }
00317 # return - in all branches
00318 }
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331 ret ::pop3d::_configure (type name , type args) {
00332 set argc [llength $args]
00333 if {($argc > 1) && (($argc % 2) == 1)} {
00334 return -code error \
00335 "wrong # args, expected: -option | (-option value)..."
00336 }
00337 if {$argc == 1} {
00338 return [_cget $name [lindex $args 0]]
00339 }
00340
00341 upvar ::pop3d::pop3d::${name}::trueport trueport
00342 upvar ::pop3d::pop3d::${name}::port port
00343 upvar ::pop3d::pop3d::${name}::authCmd authCmd
00344 upvar ::pop3d::pop3d::${name}::storCmd storCmd
00345 upvar ::pop3d::pop3d::${name}::sockCmd sockCmd
00346 upvar ::pop3d::pop3d::${name}::state state
00347
00348 if {$argc == 0} {
00349 # Return the full configuration.
00350 return [list \
00351 -port $trueport \
00352 -auth $authCmd \
00353 -storage $storCmd \
00354 -socket $sockCmd \
00355 -state $state \
00356 ]
00357 }
00358
00359 while {[llength $args] > 0} {
00360 set option [lindex $args 0]
00361 set value [lindex $args 1]
00362 switch -exact -- $option {
00363 -auth {set authCmd $value}
00364 -storage {set storCmd $value}
00365 -socket {set sockCmd $value}
00366 -port {
00367 set port $value
00368
00369 # Propagate to the queried value if the server is down
00370 # and thus has no real true port.
00371
00372 if {[string equal $state down]} {
00373 set trueport $value
00374 }
00375 }
00376 -state {
00377 return -code error "Option -state is read-only"
00378 }
00379 default {
00380 return -code error \
00381 "Unknown option \"$option\":\
00382 Expected \"-port\", \"-auth\", \"-socket\", or \"-storage\""
00383 }
00384 }
00385 set args [lrange $args 2 end]
00386 }
00387 return ""
00388 }
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403 ret ::pop3d::_conn (type name , type cmd , type args) {
00404 upvar ::pop3d::pop3d::${name}::conn conn
00405 switch -exact -- $cmd {
00406 list {
00407 if {[llength $args] > 0} {
00408 return -code error "wrong # args: should be \"$name conn list\""
00409 }
00410 return [array names conn]
00411 }
00412 state {
00413 if {[llength $args] != 1} {
00414 return -code error "wrong # args: should be \"$name conn state connId\""
00415 }
00416 set sock [lindex $args 0]
00417 upvar $conn($sock) cstate
00418 return [array get cstate]
00419 }
00420 default {
00421 return -code error "bad option \"$cmd\": must be list, or state"
00422 }
00423 }
00424 }
00425
00426
00427
00428
00429
00430 ret ::pop3d::HandleNewConnection (type name , type sock , type rHost , type rPort) {
00431 upvar ::pop3d::pop3d::${name}::conn conn
00432 upvar ::pop3d::pop3d::${name}::counter counter
00433
00434 set csa ::pop3d::pop3d::${name}::conn[incr counter]
00435 set conn($sock) $csa
00436 upvar $csa cstate
00437
00438 set cstate(remotehost) $rHost
00439 set cstate(remoteport) $rPort
00440 set cstate(server) $name
00441 set cstate(id) "<[string map {- {}} [clock clicks]]_${name}_[pid]@[::info hostname]>"
00442 set cstate(state) "auth"
00443 set cstate(name) ""
00444 set cstate(logon) ""
00445 set cstate(storage) ""
00446 set cstate(deleted) ""
00447 set cstate(msg) 0
00448 set cstate(size) 0
00449
00450 ::log::log notice "pop3d $name $sock state auth, waiting for logon"
00451
00452 fconfigure $sock -buffering line -translation crlf -blocking 0
00453
00454 if {[catch {::pop3d::GreetPeer $name $sock} errmsg]} {
00455 close $sock
00456 log::log error "pop3d $name $sock greeting $errmsg"
00457 unset cstate
00458 unset conn($sock)
00459 return
00460 }
00461
00462 fileevent $sock readable [list ::pop3d::HandleCommand $name $sock]
00463 return
00464 }
00465
00466 ret ::pop3d::CloseConnection (type name , type sock) {
00467 upvar ::pop3d::pop3d::${name}::storCmd storCmd
00468 upvar ::pop3d::pop3d::${name}::state state
00469 upvar ::pop3d::pop3d::${name}::conn conn
00470
00471 upvar $conn($sock) cstate
00472
00473 # Kill a pending idle event for CloseConnection, we are closing now.
00474 catch {after cancel $cstate(idlepending)}
00475
00476 ::log::log debug "pop3d $name $sock closing connection"
00477
00478 if {[catch {close $sock} msg]} {
00479 ::log::log error "pop3d $name $sock close: $msg"
00480 }
00481 if {$storCmd != {}} {
00482 # remove possible lock set in storage facility.
00483 if {[catch {
00484 uplevel #0 [linsert $storCmd end unlock $cstate(storage)]
00485 } msg]} {
00486 ::log::log error "pop3d $name $sock storage unlock: $msg"
00487 # -W- future ? kill all connections, execute clean up of storage
00488 # -W- facility.
00489 }
00490 }
00491
00492 unset cstate
00493 unset conn($sock)
00494
00495 ::log::log notice "pop3d $name $sock closed"
00496
00497 if {[string equal $state existing] && ([array size conn] == 0)} {
00498 _destroy $name
00499 }
00500 return
00501 }
00502
00503 ret ::pop3d::HandleCommand (type name , type sock) {
00504 # @c Called by the event system after arrival of a new command for
00505 # @c connection.
00506
00507 # @a sock: Direct access to the channel representing the connection.
00508
00509 # Client closed connection, bye bye
00510 if {[eof $sock]} {
00511 CloseConnection $name $sock
00512 return
00513 }
00514
00515 # line was incomplete, wait for more
00516 if {[gets $sock line] < 0} {
00517 return
00518 }
00519
00520 upvar ::pop3d::pop3d::${name}::conn conn
00521 upvar $conn($sock) cstate
00522 variable cmdMap
00523
00524 ::log::log info "pop3d $name $sock < $line"
00525
00526 set fail [catch {
00527 set cmd [string toupper [lindex $line 0]]
00528
00529 if {![::info exists cmdMap($cmd)]} {
00530 # unknown command, use unknown handler
00531
00532 HandleUnknownCmd $name $sock $cmd $line
00533 } else {
00534 $cmdMap($cmd) $name $sock $cmd $line
00535 }
00536 } errmsg] ;#{}
00537
00538 if {$fail} {
00539 # Had an error during handling of 'cmd'.
00540 # Handled by closing the connection.
00541 # (We do not know how to relay the internal error to the client)
00542
00543 ::log::log error "pop3d $name $sock $cmd: $errmsg"
00544 CloseConnection $name $sock
00545 }
00546 return
00547 }
00548
00549 ret ::pop3d::GreetPeer (type name , type sock) {
00550 # @c Called after the initialization of a new connection. Writes the
00551 # @c greeting to the new client. Overides the baseclass definition
00552 # @c (<m server:GreetPeer>).
00553 #
00554 # @a conn: Descriptor of connection to write to.
00555
00556 upvar cstate cstate
00557 variable server
00558
00559 log::log debug "pop3d $name $sock _ Greeting"
00560
00561 Respond2Client $name $sock +OK \
00562 "[::info hostname] $server ready $cstate(id)"
00563 return
00564 }
00565
00566 ret ::pop3d::HandleUnknownCmd (type name , type sock , type cmd , type line) {
00567 Respond2Client $name $sock -ERR "unknown command '$cmd'"
00568 return
00569 }
00570
00571 ret ::pop3d::Respond2Client (type name , type sock , type ok , type wtext) {
00572 ::log::log info "pop3d $name $sock > $ok $wtext"
00573 puts $sock "$ok $wtext"
00574 return
00575 }
00576
00577
00578
00579
00580
00581 ret ::pop3d::H_capa (type name , type sock , type cmd , type line) {
00582 # @c Handle CAPA command.
00583
00584 # Capabilities should better be configurable and handled per
00585 # server object, so that e.g. USER/PASS authentication can be
00586 # turned off.
00587
00588 upvar cstate cstate
00589 variable capabilities
00590
00591 Respond2Client $name $sock +OK "Capability list follows"
00592 foreach {capability state} $capabilities {
00593 if {
00594 [string equal $state "both"] ||
00595 [string equal $state $cstate(state)]
00596 } {
00597 puts $sock $capability
00598 }
00599 }
00600 puts $sock .
00601 return
00602 }
00603
00604 ret ::pop3d::H_user (type name , type sock , type cmd , type line) {
00605 # @c Handle USER command.
00606 #
00607 # @a conn: Descriptor of connection to write to.
00608 # @a cmd: The sent command
00609 # @a line: The sent line, with <a cmd> as first word.
00610
00611 # Called only in places where cstate is known!
00612 upvar cstate cstate
00613
00614 if {[string equal $cstate(logon) apop]} {
00615 Respond2Client $name $sock -ERR "login mechanism APOP was chosen"
00616 } elseif {[string equal $cstate(state) trans]} {
00617 Respond2Client $name $sock -ERR "client already authenticated"
00618 } else {
00619 # The user name is the first argument to the command
00620
00621 set cstate(name) [lindex [split $line] 1]
00622 set cstate(logon) user
00623
00624 Respond2Client $name $sock +OK "please send PASS command"
00625 }
00626 return
00627 }
00628
00629
00630 ret ::pop3d::H_pass (type name , type sock , type cmd , type line) {
00631 # @c Handle PASS command.
00632 #
00633 # @a conn: Descriptor of connection to write to.
00634 # @a cmd: The sent command
00635 # @a line: The sent line, with <a cmd> as first word.
00636
00637 # Called only in places where cstate is known!
00638 upvar cstate cstate
00639
00640 if {[string equal $cstate(logon) apop]} {
00641 Respond2Client $name $sock -ERR "login mechanism APOP was chosen"
00642 } elseif {[string equal $cstate(state) trans]} {
00643 Respond2Client $name $sock -ERR "client already authenticated"
00644 } else {
00645 upvar ::pop3d::pop3d::${name}::authCmd authCmd
00646
00647 if {$authCmd == {}} {
00648 # No authentication is possible. Reject all users.
00649 CheckLogin $name $sock "" "" ""
00650 return
00651 }
00652
00653 # The password is given as the first argument of the command
00654
00655 set pwd [lindex [split $line] 1]
00656
00657 if {![uplevel #0 [linsert $authCmd end exists $cstate(name)]]} {
00658 ::log::log warning "pop3d $name $sock $authCmd lookup $cstate(name) : user does not exist"
00659 CheckLogin $name $sock "" "" ""
00660 return
00661 }
00662 if {[catch {
00663 set info [uplevel #0 [linsert $authCmd end lookup $cstate(name)]]
00664 } msg]} {
00665 ::log::log error "pop3d $name $sock $authCmd lookup $cstate(name) : $msg"
00666 CheckLogin $name $sock "" "" ""
00667 return
00668 }
00669 CheckLogin $name $sock $pwd [lindex $info 0] [lindex $info 1]
00670 }
00671 return
00672 }
00673
00674
00675 ret ::pop3d::H_apop (type name , type sock , type cmd , type line) {
00676 # @c Handle APOP command.
00677 #
00678 # @a conn: Descriptor of connection to write to.
00679 # @a cmd: The sent command
00680 # @a line: The sent line, with <a cmd> as first word.
00681
00682 # Called only in places where cstate is known!
00683 upvar cstate cstate
00684
00685 if {[string equal $cstate(logon) user]} {
00686 Respond2Client $name $sock -ERR "login mechanism USER/PASS was chosen"
00687 return
00688 } elseif {[string equal $cstate(state) trans]} {
00689 Respond2Client $name $sock -ERR "client already authenticated"
00690 return
00691 }
00692
00693 # The first two arguments to the command are user name and its
00694 # response to the challenge set by the server.
00695
00696 set cstate(name) [lindex $line 1]
00697 set cstate(logon) apop
00698
00699 upvar ::pop3d::pop3d::${name}::authCmd authCmd
00700
00701 #log::log debug "authCmd|$authCmd|"
00702
00703 if {$authCmd == {}} {
00704 # No authentication is possible. Reject all users.
00705 CheckLogin $name $sock "" "" ""
00706 return
00707 }
00708
00709 set digest [lindex $line 2]
00710
00711 if {![uplevel #0 [linsert $authCmd end exists $cstate(name)]]} {
00712 ::log::log warning "pop3d $name $sock $authCmd lookup $cstate(name) : user does not exist"
00713 CheckLogin $name $sock "" "" ""
00714 return
00715 }
00716 if {[catch {
00717 set info [uplevel #0 [linsert $authCmd end lookup $cstate(name)]]
00718 } msg]} {
00719 ::log::log error "pop3d $name $sock $authCmd lookup $cstate(name) : $msg"
00720 CheckLogin $name $sock "" "" ""
00721 return
00722 }
00723
00724 set pwd [lindex $info 0]
00725 set storage [lindex $info 1]
00726
00727 ::log::log debug "pop3d $name $sock info = <$info>"
00728
00729 if {$storage == {}} {
00730 # user does not exist, skip over digest computation
00731 CheckLogin $name $sock "" "" $storage
00732 return
00733 }
00734
00735 # Do the same algorithm as the client to generate a digest, then
00736 # compare our data with information sent by the client. As we are
00737 # using tcl 8.x there is need to use channels, an immediate
00738 # computation is possible.
00739
00740 set ourDigest [Md5 "$cstate(id)$pwd"]
00741
00742 ::log::log debug "pop3d $name $sock digest input <$cstate(id)$pwd>"
00743 ::log::log debug "pop3d $name $sock digest outpt <$ourDigest>"
00744 ::log::log debug "pop3d $name $sock digest given <$digest>"
00745
00746 CheckLogin $name $sock $digest $ourDigest $storage
00747 return
00748 }
00749
00750
00751 ret ::pop3d::H_stat (type name , type sock , type cmd , type line) {
00752 # @c Handle STAT command.
00753 #
00754 # @a conn: Descriptor of connection to write to.
00755 # @a cmd: The sent command
00756 # @a line: The sent line, with <a cmd> as first word.
00757
00758 # Called only in places where cstate is known!
00759 upvar cstate cstate
00760
00761 if {[string equal $cstate(state) auth]} {
00762 Respond2Client $name $sock -ERR "client not authenticated"
00763 } else {
00764 # Return number of messages waiting and size of the contents
00765 # of the chosen maildrop in octects.
00766 Respond2Client $name $sock +OK "$cstate(msg) $cstate(size)"
00767 }
00768
00769 return
00770 }
00771
00772
00773 ret ::pop3d::H_dele (type name , type sock , type cmd , type line) {
00774 # @c Handle DELE command.
00775 #
00776 # @a conn: Descriptor of connection to write to.
00777 # @a cmd: The sent command
00778 # @a line: The sent line, with <a cmd> as first word.
00779
00780 # Called only in places where cstate is known!
00781 upvar cstate cstate
00782
00783 if {[string equal $cstate(state) auth]} {
00784 Respond2Client $name $sock -ERR "client not authenticated"
00785 return
00786 }
00787
00788 set msgid [lindex $line 1]
00789
00790 if {
00791 ($msgid < 1) ||
00792 ($msgid > $cstate(msg)) ||
00793 ([lsearch $msgid $cstate(deleted)] >= 0)
00794 } {
00795 Respond2Client $name $sock -ERR "no such message"
00796 } else {
00797 lappend cstate(deleted) $msgid
00798 Respond2Client $name $sock +OK "message $msgid deleted"
00799 }
00800 return
00801 }
00802
00803
00804 ret ::pop3d::H_retr (type name , type sock , type cmd , type line) {
00805 # @c Handle RETR command.
00806 #
00807 # @a conn: Descriptor of connection to write to.
00808 # @a cmd: The sent command
00809 # @a line: The sent line, with <a cmd> as first word.
00810
00811 # Called only in places where cstate is known!
00812 upvar cstate cstate
00813
00814 if {[string equal $cstate(state) auth]} {
00815 Respond2Client $name $sock -ERR "client not authenticated"
00816 return
00817 }
00818
00819 set msgid [lindex $line 1]
00820
00821 if {
00822 ($msgid > $cstate(msg)) ||
00823 ([lsearch $msgid $cstate(deleted)] >= 0)
00824 } {
00825 Respond2Client $name $sock -ERR "no such message"
00826 } else {
00827 Transfer $name $sock $msgid
00828 }
00829 return
00830 }
00831
00832
00833 ret ::pop3d::H_top (type name , type sock , type cmd , type line) {
00834 # @c Handle RETR command.
00835 #
00836 # @a conn: Descriptor of connection to write to.
00837 # @a cmd: The sent command
00838 # @a line: The sent line, with <a cmd> as first word.
00839
00840 # Called only in places where cstate is known!
00841 upvar cstate cstate
00842
00843 if {[string equal $cstate(state) auth]} {
00844 Respond2Client $name $sock -ERR "client not authenticated"
00845 return
00846 }
00847
00848 set msgid [lindex $line 1]
00849 set nlines [lindex $line 2]
00850
00851 if {
00852 ($msgid > $cstate(msg)) ||
00853 ([lsearch $msgid $cstate(deleted)] >= 0)
00854 } {
00855 Respond2Client $name $sock -ERR "no such message"
00856 } elseif {$nlines == {}} {
00857 Respond2Client $name $sock -ERR "missing argument: #lines to read"
00858 } elseif {$nlines < 0} {
00859 Respond2Client $name $sock -ERR \
00860 "number of lines has to be greater than or equal to zero."
00861 } elseif {$nlines == 0} {
00862 # nlines == 0, no limit, same as H_retr
00863 Transfer $name $sock $msgid
00864 } else {
00865 # nlines > 0
00866 Transfer $name $sock $msgid $nlines
00867 }
00868 return
00869 }
00870
00871
00872 ret ::pop3d::H_quit (type name , type sock , type cmd , type line) {
00873 # @c Handle QUIT command.
00874 #
00875 # @a conn: Descriptor of connection to write to.
00876 # @a cmd: The sent command
00877 # @a line: The sent line, with <a cmd> as first word.
00878
00879 # Called only in places where cstate is known!
00880 upvar cstate cstate
00881 variable server
00882
00883 set cstate(state) update
00884
00885 if {$cstate(deleted) != {}} {
00886 upvar ::pop3d::pop3d::${name}::storCmd storCmd
00887 if {$storCmd != {}} {
00888 uplevel #0 [linsert $storCmd end \
00889 dele $cstate(storage) $cstate(deleted)]
00890 }
00891 }
00892
00893 set cstate(idlepending) [after idle [list ::pop3d::CloseConnection $name $sock]]
00894
00895 Respond2Client $name $sock +OK \
00896 "[::info hostname] $server shutting down"
00897 return
00898 }
00899
00900
00901 ret ::pop3d::H_noop (type name , type sock , type cmd , type line) {
00902 # @c Handle NOOP command.
00903 #
00904 # @a conn: Descriptor of connection to write to.
00905 # @a cmd: The sent command
00906 # @a line: The sent line, with <a cmd> as first word.
00907
00908 # Called only in places where cstate is known!
00909 upvar cstate cstate
00910
00911 if {[string equal $cstate(state) fail]} {
00912 Respond2Client $name $sock -ERR "login failed, no actions possible"
00913 } elseif {[string equal $cstate(state) auth]} {
00914 Respond2Client $name $sock -ERR "client not authenticated"
00915 } else {
00916 Respond2Client $name $sock +OK ""
00917 }
00918 return
00919 }
00920
00921
00922 ret ::pop3d::H_rset (type name , type sock , type cmd , type line) {
00923 # @c Handle RSET command.
00924 #
00925 # @a conn: Descriptor of connection to write to.
00926 # @a cmd: The sent command
00927 # @a line: The sent line, with <a cmd> as first word.
00928
00929 # Called only in places where cstate is known!
00930 upvar cstate cstate
00931
00932 if {[string equal $cstate(state) fail]} {
00933 Respond2Client $name $sock -ERR "login failed, no actions possible"
00934 } elseif {[string equal $cstate(state) auth]} {
00935 Respond2Client $name $sock -ERR "client not authenticated"
00936 } else {
00937 set cstate(deleted) ""
00938
00939 Respond2Client $name $sock +OK "$cstate(msg) messages waiting"
00940 }
00941 return
00942 }
00943
00944
00945 ret ::pop3d::H_list (type name , type sock , type cmd , type line) {
00946 # @c Handle LIST command. Generates scan listing
00947 #
00948 # @a conn: Descriptor of connection to write to.
00949 # @a cmd: The sent command
00950 # @a line: The sent line, with <a cmd> as first word.
00951
00952 # Called only in places where cstate is known!
00953 upvar cstate cstate
00954
00955 if {[string equal $cstate(state) fail]} {
00956 Respond2Client $name $sock -ERR "login failed, no actions possible"
00957 return
00958 } elseif {[string equal $cstate(state) auth]} {
00959 Respond2Client $name $sock -ERR "client not authenticated"
00960 return
00961 }
00962
00963 set msgid [lindex $line 1]
00964
00965 upvar ::pop3d::pop3d::${name}::storCmd storCmd
00966
00967 if {$msgid == {}} {
00968 # full listing
00969 Respond2Client $name $sock +OK "$cstate(msg) messages"
00970
00971 set n $cstate(msg)
00972
00973 for {set i 1} {$i <= $n} {incr i} {
00974 Respond2Client $name $sock $i \
00975 [uplevel #0 [linsert $storCmd end \
00976 size $cstate(storage) $i]]
00977 }
00978 puts $sock "."
00979
00980 } else {
00981 # listing for specified message
00982
00983 if {
00984 ($msgid < 1) ||
00985 ($msgid > $cstate(msg)) ||
00986 ([lsearch $msgid $cstate(deleted)] >= 0)
00987 } {
00988 Respond2Client $name $sock -ERR "no such message"
00989 return
00990 }
00991
00992 Respond2Client $name $sock +OK \
00993 "$msgid [uplevel #0 [linsert $storCmd end \
00994 size $cstate(storage) $msgid]]"
00995 return
00996 }
00997 }
00998
00999
01000
01001
01002
01003 ret ::pop3d::CheckLogin (type name , type sock , type clientid , type serverid , type storage) {
01004 # @c Internal procedure. General code used by USER/PASS and
01005 # @c APOP login mechanisms to verify the given user-id.
01006 # @c Locks the mailbox in case of a match.
01007 #
01008 # @a conn: Descriptor of connection to write to.
01009 # @a clientid: Authentication code transmitted by client
01010 # @a serverid: Authentication code calculated here.
01011 # @a storage: Handle of mailbox requested by client.
01012
01013 #log::log debug "CheckLogin|$name|$sock|$clientid|$serverid|$storage|"
01014
01015 upvar cstate cstate
01016 upvar ::pop3d::pop3d::${name}::storCmd storCmd
01017
01018 set noStorage [expr {$storCmd == {}}]
01019
01020 if {$storage == {}} {
01021 # The user given by the client has no storage, therefore it does
01022 # not exist. React as if wrong password was given.
01023
01024 set cstate(state) auth
01025 set cstate(logon) ""
01026
01027 ::log::log notice "pop3d $name $sock state auth, no maildrop"
01028 Respond2Client $name $sock -ERR "authentication failed, sorry"
01029
01030 } elseif {[string compare $clientid $serverid] != 0} {
01031 # password/digest given by client dos not match
01032
01033 set cstate(state) auth
01034 set cstate(logon) ""
01035
01036 ::log::log notice "pop3d $name $sock state auth, secret does not match"
01037 Respond2Client $name $sock -ERR "authentication failed, sorry"
01038
01039 } elseif {
01040 !$noStorage &&
01041 ! [uplevel #0 [linsert $storCmd end lock $storage]]
01042 } {
01043 # maildrop is locked already (by someone else).
01044
01045 set cstate(state) auth
01046 set cstate(logon) ""
01047
01048 ::log::log notice "pop3d $name $sock state auth, maildrop already locked"
01049 Respond2Client $name $sock -ERR \
01050 "could not aquire lock for maildrop $cstate(name)"
01051 } else {
01052 # everything went fine. allow to proceed in session.
01053
01054 set cstate(storage) $storage
01055 set cstate(state) trans
01056 set cstate(logon) ""
01057
01058 set cstate(msg) 0
01059 if {!$noStorage} {
01060 set cstate(msg) [uplevel #0 [linsert $storCmd end \
01061 stat $cstate(storage)]]
01062 set cstate(size) [uplevel #0 [linsert $storCmd end \
01063 size $cstate(storage)]]
01064 }
01065
01066 ::log::log notice \
01067 "pop3d $name $sock login $cstate(name) $storage $cstate(msg)"
01068 ::log::log notice "pop3d $name $sock state trans"
01069
01070 Respond2Client $name $sock +OK "congratulations"
01071 }
01072 return
01073 }
01074
01075 ret ::pop3d::Transfer (type name , type sock , type msgid , optional limit =-1) {
01076 # We ask the storage for the mime token of the mail and use
01077 # that to generate and copy the mail to the requestor.
01078
01079 upvar cstate cstate
01080 upvar ::pop3d::pop3d::${name}::storCmd storCmd
01081
01082 if {$limit < 0} {
01083 Respond2Client $name $sock +OK \
01084 "[uplevel #0 [linsert $storCmd end \
01085 size $cstate(storage) $msgid]] octets"
01086 } else {
01087 Respond2Client $name $sock +OK ""
01088 }
01089
01090 set token [uplevel #0 [linsert $storCmd end get $cstate(storage) $msgid]]
01091
01092 ::log::log debug "pop3d $name $sock transfering data ($token)"
01093
01094 if {$limit < 0} {
01095 # Full transfer, we can use "copymessage" and avoid
01096 # construction in memory (depending on source of token).
01097
01098 log::log debug "pop3d $name Transfer $msgid /full"
01099
01100 # We do "."-stuffing here. This is not in the scope of the
01101 # MIME library we use, but a transport dependent thing.
01102
01103 set msg [string trimright [string map [list "\n." "\n.."] \
01104 [mime::buildmessage $token]] \n]
01105 log::log debug "($msg)"
01106 puts $sock $msg
01107 puts $sock .
01108
01109 } else {
01110 # As long as FR #531541 is not implemented we have to build
01111 # the entire message in memory and then cut it down to the
01112 # requested size. If limit was greater than the number of
01113 # lines in the message we will get the terminating "."
01114 # too. Using regsub we make sure that it is not present and
01115 # reattach during the transfer. Otherwise we would have to use
01116 # a regexp/if combo to decide wether to attach the terminator
01117 # not.
01118
01119 set msg [split [mime::buildmessage $token] \n]
01120 set i 0
01121 incr limit -1
01122 while {[lindex $msg $i] != {}} {
01123 incr i
01124 incr limit
01125 }
01126 # i now refers to the line separating header and body
01127
01128 regsub -- "\n\\.\n$" [string map [list "\n." "\n.."] [join [lrange $msg 0 $limit] \n]] {} data
01129 puts $sock ${data}\n.
01130 }
01131 ::log::log debug "pop3d $name $sock transfer complete"
01132 # response already sent.
01133 return
01134 }
01135
01136 major = [lindex [split [package require md5] .] 0]
01137 if {$::major < 2} {
01138 ret ::pop3d::Md5 (type text) {md5::md5 $text}
01139 } else {
01140 ret ::pop3d::Md5 (type text) {string tolower [md5::md5 -hex $text]}
01141 }
01142 un major =
01143
01144
01145
01146
01147 package provide pop3d $::pop3d::version
01148