pop3d.tcl

Go to the documentation of this file.
00001 /*  pop3d.tcl --*/
00002 /* */
00003 /*  Implementation of a pop3 server for Tcl.*/
00004 /* */
00005 /*  Copyright (c) 2002 by Andreas Kupries*/
00006 /* */
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 /*  */
00010 /*  RCS: @(#) $Id: pop3d.tcl,v 1.22 2006/01/10 23:05:19 andreas_kupries Exp $*/
00011 
00012 package require md5  ; /*  tcllib | APOP*/
00013 package require mime ; /*  tcllib | storage callback*/
00014 package require log  ; /*  tcllib | tracing*/
00015 
00016 namespace ::pop3d {
00017     /*  Data storage in the pop3d module*/
00018     /*  -------------------------------*/
00019     /* */
00020     /*  There's a number of bits to keep track of for each server and*/
00021     /*  connection managed by it.*/
00022     /* */
00023     /*    port*/
00024     /*  callbacks*/
00025     /*  connections*/
00026     /*  connection state*/
00027     /*    server state*/
00028     /* */
00029     /*  It would quickly become unwieldy to try to keep these in arrays or lists*/
00030     /*  within the pop3d namespace itself.  Instead, each pop3 server will*/
00031     /*  get its own namespace.  Each namespace contains:*/
00032     /* */
00033     /*  port    - port to listen on*/
00034     /*  sock    - listening socket*/
00035     /*  authCmd - authentication callback*/
00036     /*  storCmd - storage callback*/
00037     /*  sockCmd - command prefix for openin the server socket*/
00038     /*  state   - state of the server (up, down, exiting)*/
00039     /*  conn    - map : sock -> state array*/
00040     /*  counter - counter for state arrays*/
00041     /* */
00042     /*  Per connection in a server its own state array 'connXXX'.*/
00043     /* */
00044     /*  id         - unique id for the connection (APOP)*/
00045     /*  state      - state of connection       (auth, trans, update, fail)*/
00046     /*  name       - user for that connection*/
00047     /*  storage    - storage ref for that user*/
00048     /*  logon      - authentication method     (empty, apop, user)*/
00049     /*  deleted    - list of deleted messages*/
00050     /*  msg        - number of messages in storage*/
00051     /*  remotehost - name of remote host for connection*/
00052     /*  remoteport - remote port for connection*/
00053 
00054     /*  counter is used to give a unique name for unnamed server*/
00055     variable counter 0
00056 
00057     /*  commands is the list of subcommands recognized by the server*/
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     /*  Capabilities to be reported by the CAPA command. The list*/
00085     /*  contains pairs of capability strings and the connection state in*/
00086     /*  which they are reported. The state can be "auth", "trans", or*/
00087     /*  "both".*/
00088     variable capabilities \
00089     [list \
00090          USER           both \
00091          PIPELINING         both \
00092          "IMPLEMENTATION $server"   trans \
00093         ]
00094     
00095     /*  -- UIDL -- not implemented --*/
00096 
00097     /*  Only export one command, the one used to instantiate a new server*/
00098     namespace export new
00099 }
00100 
00101 /*  ::pop3d::new --*/
00102 /* */
00103 /*  Create a new pop3 server with a given name; if no name is given, use*/
00104 /*  pop3dX, where X is a number.*/
00105 /* */
00106 /*  Arguments:*/
00107 /*  name    name of the pop3 server; if null, generate one.*/
00108 /* */
00109 /*  Results:*/
00110 /*  name    name of the pop3 server created*/
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 /*  Private functions follow*/
00145 
00146 /*  ::pop3d::Pop3dProc --*/
00147 /* */
00148 /*  Command that processes all pop3 server object commands.*/
00149 /* */
00150 /*  Arguments:*/
00151 /*  name    name of the pop3 server object to manipulate.*/
00152 /*  args    command name and args for the command*/
00153 /* */
00154 /*  Results:*/
00155 /*  Varies based on command to perform*/
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 /*  ::pop3d::_up --*/
00174 /* */
00175 /*  Start listening on the configured port.*/
00176 /* */
00177 /*  Arguments:*/
00178 /*  name    name of the pop3 server.*/
00179 /* */
00180 /*  Results:*/
00181 /*  None.*/
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 /*  ::pop3d::_down --*/
00209 /* */
00210 /*  Stop listening on the configured port.*/
00211 /* */
00212 /*  Arguments:*/
00213 /*  name    name of the pop3 server.*/
00214 /* */
00215 /*  Results:*/
00216 /*  None.*/
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 /*  ::pop3d::_destroy --*/
00236 /* */
00237 /*  Destroy a pop3 server.*/
00238 /* */
00239 /*  Arguments:*/
00240 /*  name    name of the pop3 server.*/
00241 /*  mode    destruction mode*/
00242 /* */
00243 /*  Results:*/
00244 /*  None.*/
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 /*  ::pop3d::_cget --*/
00280 /* */
00281 /*  Query option value*/
00282 /* */
00283 /*  Arguments:*/
00284 /*  name    name of the pop3 server.*/
00285 /* */
00286 /*  Results:*/
00287 /*  None.*/
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 /*  ::pop3d::_configure --*/
00321 /* */
00322 /*  Query and set option values*/
00323 /* */
00324 /*  Arguments:*/
00325 /*  name    name of the pop3 server.*/
00326 /*  args    options and option values*/
00327 /* */
00328 /*  Results:*/
00329 /*  None.*/
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 /*  ::pop3d::_conn --*/
00392 /* */
00393 /*  Query connection state.*/
00394 /* */
00395 /*  Arguments:*/
00396 /*  name    name of the pop3 server.*/
00397 /*  cmd subcommand to perform*/
00398 /*  args    arguments for subcommand*/
00399 /* */
00400 /*  Results:*/
00401 /*  Specific to subcommand*/
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 /*  Server implementation.*/
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 /*  Command implementations.*/
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 /*  Command helper commands.*/
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 /*  Module initialization*/
01146 
01147 package provide pop3d $::pop3d::version
01148 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1