pop3d_dbox.tcl

Go to the documentation of this file.
00001 /*  -*- tcl -*-*/
00002 /*  pop3d_dbox.tcl --*/
00003 /* */
00004 /*  Implementation of a simple mailbox database for the pop3 server*/
00005 /*        Each mailbox is a a directory in a base directory, with each mail*/
00006 /*  a file in that directory. The mail file contains both headers and*/
00007 /*  body of the mail.*/
00008 /* */
00009 /*  Copyright (c) 2002 by Andreas Kupries*/
00010 /* */
00011 /*  See the file "license.terms" for information on usage and redistribution*/
00012 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00013 /*  */
00014 /*  RCS: @(#) $Id: pop3d_dbox.tcl,v 1.12 2005/09/28 04:51:23 andreas_kupries Exp $*/
00015 
00016 package require mime ; /*  tcllib | mime token is result of "get".*/
00017 package require log  ; /*  tcllib | Logging package*/
00018 
00019 namespace ::pop3d::dbox {
00020     /*  Data storage in the pop3d::dbox module*/
00021     /*  -------------------------------------*/
00022     /*  One array per object containing the db contents. Keyed by user name.*/
00023     /*  And the information about the last file data was read from.*/
00024 
00025     /*  counter is used to give a unique name for unnamed databases*/
00026     variable counter 0
00027 
00028     /*  commands is the list of subcommands recognized by the server*/
00029     variable commands [list \
00030         "add"   \
00031         "base"  \
00032         "dele"  \
00033         "destroy"   \
00034         "exists"    \
00035         "get"   \
00036         "list"  \
00037         "lock"  \
00038         "locked"    \
00039         "move"  \
00040         "remove"    \
00041         "size"  \
00042         "stat"  \
00043         "unlock"    \
00044         ]
00045 
00046     variable version ;  version =  1.0.2
00047 }
00048 
00049 
00050 /*  ::pop3d::dbox::new --*/
00051 /* */
00052 /*  Create a new mailbox database with a given name;*/
00053 /*  if no name is given, use*/
00054 /*  p3dboxX, where X is a number.*/
00055 /* */
00056 /*  Arguments:*/
00057 /*  name    name of the mailbox database; if null, generate one.*/
00058 /* */
00059 /*  Results:*/
00060 /*  name    name of the mailbox database created*/
00061 
00062 ret  ::pop3d::dbox::new (optional name ="") {
00063     variable counter
00064     
00065     if { [llength [info level 0]] == 1 } {
00066     incr counter
00067     set name "p3dbox${counter}"
00068     }
00069 
00070     if { ![string equal [info commands ::$name] ""] } {
00071     return -code error \
00072         "command \"$name\" already exists,\
00073         unable to create mailbox database"
00074     }
00075 
00076     # Set up the namespace
00077     namespace eval ::pop3d::dbox::dbox::$name {
00078     variable dir ""
00079     variable state    ; array set state  {}
00080     variable locked   ; array set locked {}
00081     variable transfer ; array set transfer {}
00082     }
00083 
00084     # Create the command to manipulate the mailbox database
00085     interp alias {} ::$name {} ::pop3d::dbox::DboxProc $name
00086 
00087     return $name
00088 }
00089 
00090 /* */
00091 /*  Private functions follow*/
00092 
00093 /*  ::pop3d::dbox::DboxProc --*/
00094 /* */
00095 /*  Command that processes all mailbox database object commands.*/
00096 /* */
00097 /*  Arguments:*/
00098 /*  name    name of the mailbox database object to manipulate.*/
00099 /*  args    command name and args for the command*/
00100 /* */
00101 /*  Results:*/
00102 /*  Varies based on command to perform*/
00103 
00104 ret  ::pop3d::dbox::DboxProc (type name , optional cmd ="" , type args) {
00105 
00106     # Do minimal args checks here
00107     if { [llength [info level 0]] == 2 } {
00108     return -code error \
00109         "wrong # args: should be \"$name option ?arg arg ...?\""
00110     }
00111     
00112     # Split the args into command and args components
00113     if { [llength [info commands ::pop3d::dbox::_$cmd]] == 0 } {
00114     variable commands
00115     set optlist [join $commands ", "]
00116     set optlist [linsert $optlist "end-1" "or"]
00117     return -code error "bad option \"$cmd\": must be $optlist"
00118     }
00119     eval [list ::pop3d::dbox::_$cmd $name] $args
00120 }
00121 
00122 
00123 ret  ::pop3d::dbox::_base (type name , type base) {
00124     # @c Constructor. Does some more checks on the given base directory.
00125 
00126     # sanity checks
00127     if {$base == {}} {
00128     return -code error "directory not specified"
00129     }
00130     if {! [file exists      $base]} {
00131     return -code error "base: \"$base\" does not exist"
00132     }
00133     if {! [file isdirectory $base]} {
00134     return -code error "base: \"$base\" not a directory"
00135     }
00136     if {! [file readable    $base]} {
00137     return -code error "base: \"$base\" not readable"
00138     }
00139     if {! [file writable    $base]} {
00140     return -code error "base: \"$base\" not writable"
00141     }
00142 
00143     upvar ::pop3d::dbox::dbox::${name}::dir dir
00144     set dir $base
00145     return
00146 }
00147 
00148 
00149 /*  ::pop3d::dbox::_destroy --*/
00150 /* */
00151 /*  Destroy a mail database, including its associated command and*/
00152 /*  data storage.*/
00153 /* */
00154 /*  Arguments:*/
00155 /*  name    Name of the database to destroy.*/
00156 /* */
00157 /*  Results:*/
00158 /*  None.*/
00159 
00160 ret  ::pop3d::dbox::_destroy (type name) {
00161     namespace delete ::pop3d::dbox::dbox::$name
00162     interp alias {} ::$name {}
00163     return
00164 }
00165 
00166 ret  ::pop3d::dbox::_add (type name , type mbox) {
00167     # @c Create a mailbox with handle <a mbox>. The handle is used as the
00168     # @c name of the directory to contain the mails too.
00169     #
00170     # @a mbox: Reference to the mailbox to be operated on.
00171 
00172     set dir      [CheckDir $name]
00173     set mboxpath [file join $dir $mbox]
00174 
00175     if {[file exists $mboxpath]} {
00176     return -code error "cannot add \"$mbox\", mailbox already in existence"
00177     }
00178 
00179     file mkdir $mboxpath
00180     return
00181 }
00182 
00183 
00184 ret  ::pop3d::dbox::_remove (type name , type mbox) {
00185     # @c Remove mailbox with handle <a mbox>. This will destroy all mails
00186     # @c contained in it too.
00187     #
00188     # @a mbox: Reference to the mailbox to be operated on.
00189 
00190     set dir      [CheckDir $name]
00191     set mboxpath [file join $dir $mbox]
00192 
00193     if {![file exists $mboxpath]} {
00194     return -code error "cannot remove \"$mbox\", mailbox does not exist"
00195     }
00196 
00197     if {[_locked $name $mbox]} {
00198     return -code error "cannot remove \"$mbox\", mailbox is locked"
00199     }
00200 
00201     file delete -force $mboxpath
00202     return
00203 }
00204 
00205 
00206 ret  ::pop3d::dbox::_move (type name , type old , type new) {
00207     # @c Change the handle of mailbox <a old> to <a new>.
00208     #
00209     # @a old: Reference to the mailbox to be operated on.
00210     # @a new: New reference to the mailbox
00211 
00212     set dir     [CheckDir $name]
00213     set oldpath [file join $dir $old]
00214     set newpath [file join $dir $new]
00215 
00216     if {![file exists $oldpath]} {
00217     return -code error "cannot move \"$old\", mailbox does not exist"
00218     }
00219     if {[file exists $newpath]} {
00220     return -code error \
00221         "cannot move \"$old\", destination \"$new\" already exists"
00222     }
00223 
00224     file rename -force $oldpath $newpath
00225     return
00226 }
00227 
00228 
00229 ret  ::pop3d::dbox::_list (type name) {
00230     # @c Lists known mailboxes in object.
00231     # @r List of mailbox names.
00232 
00233     set dir  [CheckDir $name]
00234     set here [pwd]
00235     cd $dir
00236     set files [glob -nocomplain *]
00237     cd $here
00238 
00239     set res [list]
00240     foreach f $files {
00241     set mboxpath [file join $dir $f]
00242     if {! [file isdirectory $mboxpath]} {continue}
00243     if {! [file readable    $mboxpath]} {continue}
00244     if {! [file writable    $mboxpath]} {continue}
00245     lappend res $f
00246     }
00247     return $res
00248 }
00249 
00250 
00251 ret  ::pop3d::dbox::_exists (type name , type mbox) {
00252     # @c Determines existence of mailbox <a mbox>.
00253     # @a mbox: Reference to the mailbox to check for.
00254     # @r 1 if the mailbox exists, 0 else.
00255 
00256     set dir  [CheckDir $name]
00257     set mbox [file join $dir $mbox]
00258     return   [file exists    $mbox]
00259 }
00260 
00261 
00262 ret  ::pop3d::dbox::_locked (type name , type mbox) {
00263     # @c Checks wether the specified mailbox is locked or not.
00264     # @a mbox: Reference to the mailbox to check.
00265     # @r 1 if the mailbox is locked, 0 else.
00266 
00267     set     dir  [CheckDir $name]
00268     set     mbox [file join $dir $mbox]
00269 
00270     upvar ::pop3d::dbox::dbox::${name}::locked locked
00271 
00272     return [::info exists locked($mbox)]
00273 }
00274 
00275 
00276 /*  -- interface to the pop server (storage callback) --*/
00277 
00278 ret  ::pop3d::dbox::_lock (type name , type mbox) {
00279     # @c Locks the given mailbox, additionally stores a list of the
00280     # @c available files in the manager state. All files (= messages)
00281     # @c added to the mailbox after this operation will be ignored
00282     # @c during the session.
00283     #
00284     # @a mbox: Reference to the mailbox to be locked.
00285     # @r 1 if mailbox was locked sucessfully, 0 else.
00286 
00287     # locked already ?
00288     if {[_locked $name $mbox]} {
00289     return 0
00290     }
00291 
00292     set dir [Check $name $mbox]
00293 
00294     # Compute a list of message files residing in the mailbox directory
00295 
00296     upvar ::pop3d::dbox::dbox::${name}::state  state
00297     upvar ::pop3d::dbox::dbox::${name}::locked locked
00298 
00299     set  state($dir)  [lsort [glob -nocomplain [file join $dir *]]]
00300     set locked($dir) 1
00301     return 1
00302 }
00303 
00304 
00305 ret  ::pop3d::dbox::_unlock (type name , type mbox) {
00306     # @c A locked mailbox is unlocked, thereby made available
00307     # @c to other sessions.
00308     #
00309     # @a mbox: Reference to the mailbox to be locked.
00310 
00311     # not locked ?
00312     if {![_locked $name $mbox]} {return}
00313     set dir [Check $name $mbox]
00314 
00315     upvar ::pop3d::dbox::dbox::${name}::state  state
00316     upvar ::pop3d::dbox::dbox::${name}::locked locked
00317 
00318     unset   state($dir)
00319     unset  locked($dir)
00320     return
00321 }
00322 
00323 
00324 ret  ::pop3d::dbox::_stat (type name , type mbox) {
00325     # @c Determines the number of messages picked up by <m lock>.
00326     # @c Will fail if the mailbox was not locked.
00327     #
00328     # @a mbox: Reference to the mailbox queried.
00329     # @r The number of messages in the mailbox
00330 
00331     set dir [Check $name $mbox]
00332 
00333     if {![_locked $name $mbox]} {
00334     return -code error "mailbox \"$mbox\" is not locked"
00335     }
00336 
00337     upvar ::pop3d::dbox::dbox::${name}::state  state
00338 
00339     return  [llength $state($dir)]
00340 }
00341 
00342 
00343 ret  ::pop3d::dbox::_size (type name , type mbox , optional msgId ={)} {
00344     # @c Determines the size of the specified message, in bytes.
00345     #
00346     # @a mbox: Reference to the mailbox to be operated on.
00347     # @a msgId: Numerical index of the message to look at.
00348     # @r size of the message in bytes.
00349 
00350     log::log debug "$name size $mbox ($msgId)"
00351 
00352     set dir [Check $name $mbox]
00353 
00354     log::log debug "$name mbox dir = $dir"
00355 
00356     upvar ::pop3d::dbox::dbox::${name}::state  state
00357 
00358     if {$msgId == {}} {
00359     log::log debug "$name size /full"
00360 
00361     /*  Full size of the maildrop requested.*/
00362     if {![info exists state($dir)]} {
00363         /*  No stat before size, assume that there are no messages*/
00364         /*  in the maildrop, which implies that the maildrop is*/
00365         /*  empty, i.e. of size 0.*/
00366         return 0
00367     }
00368 
00369      n =  0
00370      k =  [llength $state($dir)]
00371     for { id =  0} {$id < $k} {incr id} {
00372         incr n [file size [lindex $state($dir) $id]]
00373     }
00374     return $n
00375     }
00376 
00377     if {
00378     ($msgId < 1) ||
00379     (![info exists state($dir)]) ||
00380     ([llength $state($dir)] < $msgId)
00381     } {
00382     return -code error "id \"$msgId\" out of range"
00383     }
00384     incr msgId -1
00385 
00386     /*  log::log debug "$name msg mails = $state($dir)"*/
00387     log::log debug "$name msg file = [lindex $state($dir) $msgId]"
00388 
00389     return [file size [lindex $state($dir) $msgId]]
00390 }
00391 
00392 
00393 ret  ::pop3d::dbox::_dele (type name , type mbox , type msgList) {
00394     # @c Deletes the specified messages from the mailbox. This should
00395     # @c be followed by a <m unlock> as the state is not updated
00396     # @c accordingly.
00397     #
00398     # @a mbox: Reference to the mailbox to be operated on.
00399     # @a msgList: List of message ids.
00400 
00401     set dir [Check $name $mbox]
00402     if {[llength $msgList] == 0} {
00403     return -code error "nothing to delete"
00404     }
00405 
00406     # @d The code assumes that the id's in the list were already
00407     # @d checked against the maximal number of messages.
00408 
00409     upvar ::pop3d::dbox::dbox::${name}::state  state
00410 
00411     foreach msgId $msgList {
00412     if {
00413         ($msgId < 1) ||
00414         (![info exists state($dir)]) ||
00415         ([llength $state($dir)] < $msgId)
00416     } {
00417         return -code error "id \"$msgId\" out of range"
00418     }
00419     }
00420     foreach msgId $msgList {
00421     file delete [lindex $state($dir) [incr msgId -1]]
00422     }
00423 
00424     # the mailbox state is unusable now.
00425     return
00426 }
00427 
00428 ret  ::pop3d::dbox::_get (type name , type mbox , type msgId) {
00429     set dir [Check $name $mbox]
00430 
00431     upvar ::pop3d::dbox::dbox::${name}::state  state
00432 
00433     if {
00434     ($msgId < 1) ||
00435     (![info exists state($dir)]) ||
00436     ([llength $state($dir)] < $msgId)
00437     } {
00438     return -code error "id \"$msgId\" out of range"
00439     }
00440     incr msgId -1
00441 
00442     set mailfile [lindex $state($dir) $msgId]
00443 
00444     set token [::mime::initialize -file $mailfile]
00445     return $token
00446 }
00447 
00448 /* */
00449 /* */
00450 /*  Internal helper commands.*/
00451 
00452 ret  ::pop3d::dbox::Check (type name , type mbox) {
00453     # @c Internal procedure. Used to map a mailbox handle
00454     # @c to the directory containing the messages.
00455     # @a mbox: Reference to the mailbox to be operated on.
00456     # @r Path of directory holding the message files of the
00457     # @r specified mailbox.
00458 
00459     set dir      [CheckDir $name]
00460     set mboxpath [file join $dir $mbox]
00461 
00462     if {! [file exists      $mboxpath]} {
00463     return -code error "\"$mbox\" does not exist"
00464     }
00465     if {! [file isdirectory $mboxpath]} {
00466     return -code error "\"$mbox\" is not a directory"
00467     }
00468     if {! [file readable    $mboxpath]} {
00469     return -code error "\"$mbox\" is not readable"
00470     }
00471     if {! [file writable    $mboxpath]} {
00472     return -code error "\"$mbox\" is not writable"
00473     }
00474     return $mboxpath
00475 }
00476 
00477 ret  ::pop3d::dbox::CheckDir (type name) {
00478     upvar ::pop3d::dbox::dbox::${name}::dir dir
00479 
00480     if {$dir == {}} {
00481     return -code error "base directory not specified"
00482     }
00483     return $dir
00484 }
00485 
00486 /* */
00487 /*  Module initialization*/
00488 
00489 package provide pop3d::dbox $::pop3d::dbox::version
00490 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1