home *** CD-ROM | disk | FTP | other *** search
/ H4CK3R 5 / hacker05 / 05_HACK_05.ISO / programacao / freewrap / TCLLIBsampleApp.exe / sample / tcllib / tcllib1.0 / pop3 / pop3.tcl < prev   
Encoding:
Text File  |  2001-08-17  |  14.8 KB  |  575 lines

  1. # pop3.tcl --
  2. #
  3. #    POP3 mail client package, written in pure Tcl.
  4. #    Some concepts borrowed from "frenchie", a POP3
  5. #    mail client utility written by Scott Beasley.
  6. #
  7. # Copyright (c) 2000 by Ajuba Solutions.
  8. # portions Copyright (c) 2000 by Scott Beasley
  9. #
  10. # See the file "license.terms" for information on usage and redistribution
  11. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  12. # RCS: @(#) $Id: pop3.tcl,v 1.13 2001/08/02 17:22:29 andreas_kupries Exp $
  13.  
  14. package require Tcl 8.2
  15. package require cmdline
  16. package provide pop3 1.3
  17.  
  18. namespace eval ::pop3 {
  19.  
  20.     # The state variable remembers information about the open pop3
  21.     # connection. It is indexed by channel id. The information is
  22.     # a keyed list, with keys "msex" and "retr_mode". The value
  23.     # associated with "msex" is boolean, a true value signals that the
  24.     # server at the other end is MS Exchange. The value associated
  25.     # with "retr_mode" is one of {retr, list, slow}.
  26.  
  27.     # The value of "msex" influences how the translation for the
  28.     # channel is set and is determined by the contents of the received
  29.     # greeting. The value of "retr_mode" is initially "retr" and
  30.     # completely determined by the first call to [retr]. For "list" the
  31.     # system will use LIST before RETR to retrieve the message size.
  32.  
  33.     # The state can be influenced by options given to "open".
  34.  
  35.     variable  state
  36.     array set state {}
  37.  
  38. }
  39.  
  40. # pop3::open --
  41. #
  42. #    Opens a connection to a POP3 mail server.
  43. #
  44. # Arguments:
  45. #       args     A list of options and values, possibly empty,
  46. #         followed by the regular arguments, i.e. host, user,
  47. #         passwd and port. The latter is optional.
  48. #
  49. #    host     The name or IP address of the POP3 server host.
  50. #       user     The username to use when logging into the server.
  51. #       passwd   The password to use when logging into the server.
  52. #       port     (optional) The socket port to connect to, defaults
  53. #                to port 110, the POP standard port address.
  54. #
  55. # Results:
  56. #    The connection channel (a socket).
  57. #       May throw errors from the server.
  58.  
  59. proc ::pop3::open {args} {
  60.     variable state
  61.     array set cstate {msex 0 retr_mode retr}
  62.  
  63.     while {[set err [cmdline::getopt args {msex.arg retr-mode.arg} opt arg]]} {
  64.     if {$err < 0} {
  65.         return -code error "::pop3::open : $arg"
  66.     }
  67.     switch -exact -- $opt {
  68.         msex {
  69.         if {![string is boolean $arg]} {
  70.             return -code error \
  71.                 ":pop3::open : Argument to -msex has to be boolean"
  72.         }
  73.         set cstate(msex) $arg
  74.         }
  75.         retr-mode {
  76.         switch -exact -- $arg {
  77.             retr - list - slow {
  78.             set cstate(retr_mode) $arg
  79.             }
  80.             default {
  81.             return -code error \
  82.                 ":pop3::open : Argument to -retr-mode has to be one of retr, list or slow"
  83.             }
  84.         }
  85.         }
  86.         default {# Can't happen}
  87.     }
  88.     }
  89.  
  90.     if {[llength $args] > 4} {
  91.     return -code error "To many arguments to ::pop3::open"
  92.     }
  93.     if {[llength $args] < 3} {
  94.     return -code error "Not enough arguments to ::pop3::open"
  95.     }
  96.     foreach {host user password port} $args break
  97.     if {$port == {}} {
  98.     set port 110
  99.     }
  100.  
  101.     # Argument processing is finally complete, now open the channel
  102.  
  103.     set chan [socket $host $port]
  104.     fconfigure $chan -buffering none
  105.  
  106.     if {$cstate(msex)} {
  107.     # We are talking to MS Exchange. Work around its quirks.
  108.     fconfigure $chan -translation binary
  109.     } else {
  110.     fconfigure $chan -translation {binary crlf}
  111.     }
  112.  
  113.     if {[catch {::pop3::send $chan {}} errorStr]} {
  114.     error "POP3 CONNECT ERROR: $errorStr"
  115.     }
  116.  
  117.     if {0} {
  118.     # -FUTURE- Identify MS Exchange servers
  119.     set cstate(msex) 1
  120.  
  121.     # We are talking to MS Exchange. Work around its quirks.
  122.     fconfigure $chan -translation binary
  123.     }
  124.  
  125.     if {[catch {
  126.         ::pop3::send $chan "user $user"
  127.         ::pop3::send $chan "pass $password"
  128.         } errorStr]} {
  129.     error "POP3 LOGIN ERROR: $errorStr"
  130.     }
  131.  
  132.     # Remember the state.
  133.  
  134.     set state($chan) [array get cstate]
  135.     return $chan
  136. }
  137.  
  138. # ::pop3::status --
  139. #
  140. #    Get the status of the mail spool on the POP3 server.
  141. #
  142. # Arguments:
  143. #    chan      The channel, returned by ::pop3::open
  144. #
  145. # Results:
  146. #    A list containing two elements, {msgCount octetSize},
  147. #       where msgCount is the number of messages in the spool
  148. #       and octetSize is the size (in octets, or 8 bytes) of
  149. #       the entire spool.
  150.  
  151. proc ::pop3::status {chan} {
  152.  
  153.     if {[catch {set statusStr [::pop3::send $chan "STAT"]} errorStr]} {
  154.     error "POP3 STAT ERROR: $errorStr"
  155.     }
  156.  
  157.     # Dig the sent size and count info out.
  158.     set rawStatus [split [string trim $statusStr]]
  159.     
  160.     return [::list [lindex $rawStatus 0] [lindex $rawStatus 1]]
  161. }
  162.  
  163. # ::pop3::last --
  164. #
  165. #    Gets the index of the last email read from the server.
  166. #       Note, some POP3 servers do not support this feature,
  167. #       in which case the value returned may always be zero,
  168. #       or an error may be thrown.
  169. #
  170. # Arguments:
  171. #    chan      The channel, returned by ::pop3::open
  172. #
  173. # Results:
  174. #    The index of the last email message read, which may
  175. #       be zero if none have been read or if the server does
  176. #       not support this feature.
  177. #       Server errors may be thrown, including some cases
  178. #       when the LAST command is not supported.
  179.  
  180. proc ::pop3::last {chan} {
  181.  
  182.     if {[catch {
  183.         set resultStr [::pop3::send $chan "LAST"]
  184.         } errorStr]} {
  185.     error "POP3 LAST ERROR: $errorStr"
  186.     }
  187.     
  188.     return [string trim $resultStr]
  189. }
  190.  
  191. # ::pop3::retrieve --
  192. #
  193. #    Retrieve email message(s) from the server.
  194. #
  195. # Arguments:
  196. #    chan      The channel, returned by ::pop3::open
  197. #       start     The first message to retrieve in the range.
  198. #                 May be "next" (the next message after the last
  199. #                 one seen, see ::pop3::last), "start" (aka 1),
  200. #                 "end" (the last message in the spool, for 
  201. #                 retriving only the last message).
  202. #       end       (optional, defaults to -1) The last message
  203. #                 to retrieve in the range. May be "last"
  204. #                 (the last message viewed), "end" (the last
  205. #                 message in the spool), or "-1" (the default,
  206. #                 any negative number means retrieve only
  207. #                 one message).
  208. #
  209. # Results:
  210. #    A list containing all of the messages retrieved.
  211. #       May throw errors from the server.
  212.  
  213. proc ::pop3::retrieve {chan start {end -1}} {
  214.     variable state
  215.     array set cstate $state($chan)
  216.     
  217.     set count [lindex [::pop3::status $chan] 0]
  218.     set last 0
  219.     catch {set last [::pop3::last $chan]}
  220.  
  221.     if {![string is integer $start]} {
  222.     if {[string match $start "next"]} {
  223.         set start $last
  224.         incr start
  225.     } elseif {$start == "start"} {
  226.         set start 1
  227.     } elseif {$start == "end"} {
  228.         set start $count
  229.     } else {
  230.         error "POP3 Retrieval error: Bad start index $start"
  231.     }
  232.     } 
  233.     if {$start == 0} {
  234.     set start 1
  235.     }
  236.     
  237.     if {![string is integer $end]} {
  238.     if {$end == "end"} {
  239.         set end $count
  240.     } elseif {$end == "last"} {
  241.         set end $last
  242.     } else {
  243.         error "POP3 Retrieval error: Bad end index $end"
  244.     }
  245.     } elseif {$end < 0} {
  246.     set end $start
  247.     }
  248.  
  249.     if {$end > $count} {
  250.     set end $count
  251.     }
  252.     
  253.     set result {}
  254.  
  255.     for {set index $start} {$index <= $end} {incr index} {
  256.     switch -exact -- $cstate(retr_mode) {
  257.         retr {
  258.         set sizeStr [::pop3::send $chan "RETR $index"]
  259.  
  260.         if {[scan $sizeStr {%d %s} size dummy] < 0} {
  261.             # The server did not deliver the size information.
  262.             # Switch our mode to "list" and use the slow
  263.             # method this time. The next call will use LIST before
  264.             # RETR to get the size information. If even that fails
  265.             # the system will fall back to slow mode all the time.
  266.  
  267.             set cstate(retr_mode) list
  268.             set state($chan) [array get cstate]
  269.  
  270.             # Retrieve in slow motion.
  271.             set msgBuffer [RetrSlow $chan]
  272.         } else {
  273.             set msgBuffer [RetrFast $chan $size]
  274.         }
  275.         }
  276.         list {
  277.         set sizeStr [::pop3::send $chan "LIST $index"]
  278.  
  279.         if {[scan $sizeStr {%d %d %s} dummy size dummy] < 0} {
  280.             # Not even LIST generates the necessary size information.
  281.             # Switch to full slow mode and don't bother anymore.
  282.  
  283.             set cstate(retr_mode) slow
  284.             set state($chan) [array get cstate]
  285.  
  286.             # Retrieve in slow motion.
  287.             set msgBuffer [RetrSlow $chan]
  288.         } else {
  289.             # Ignore response of RETR, already know the size
  290.             # through LIST
  291.  
  292.             ::pop3::send $chan "RETR $index"
  293.  
  294.             set msgBuffer [RetrFast $chan $size]
  295.         }
  296.         }
  297.         slow {
  298.         # Retrieve in slow motion.
  299.  
  300.         set msgBuffer [RetrSlow $chan]
  301.         }
  302.     }
  303.     lappend result $msgBuffer
  304.     }
  305.     return $result
  306. }
  307.  
  308. # ::pop3::RetrFast --
  309. #
  310. #    Fast retrieval of a message from the pop3 server.
  311. #    Internal helper to prevent code bloat in "pop3::retrieve"
  312. #
  313. # Arguments:
  314. #    chan    The channel to read the message from.
  315. #
  316. # Results:
  317. #    The text of the retrieved message.
  318.  
  319. proc ::pop3::RetrFast {chan size} {
  320.     set msgBuffer [read $chan $size]
  321.  
  322.     # We might have read not enough because of .-stuffed lines.
  323.     # Read the possible remainder in line by line fashion!
  324.     #            
  325.     # get the terminating "."
  326.     # sometimes the gets returns nothing, 
  327.     # need to get the real terminating "."
  328.  
  329.     while {[set line [gets $chan]] != ".\r"} {
  330.     append msgBuffer $line
  331.     }
  332.  
  333.     # Map both cr+lf and cr to lf to simulate auto EOL translation, then
  334.     # unstuff .-stuffed lines.
  335.  
  336.     return [string map [::list \n.. \n.] [string map [::list \r \n] [string map [::list \r\n \n] $msgBuffer]]]
  337. }
  338.  
  339. # ::pop3::RetrSlow --
  340. #
  341. #    Slow retrieval of a message from the pop3 server.
  342. #    Internal helper to prevent code bloat in "pop3::retrieve"
  343. #
  344. # Arguments:
  345. #    chan    The channel to read the message from.
  346. #
  347. # Results:
  348. #    The text of the retrieved message.
  349.  
  350. proc ::pop3::RetrSlow {chan} {
  351.     set msgBuffer ""
  352.     
  353.     while {1} {
  354.     set line [string trimright [gets $chan] \r]
  355.         
  356.     # End of the message is a line with just "."
  357.     if {$line == "."} {
  358.         break
  359.     } elseif {[string index $line 0] == "."} {
  360.         set line [string range $line 1 end]
  361.     }
  362.         
  363.     append msgBuffer $line "\n"
  364.     }
  365.  
  366.     return $msgBuffer
  367. }
  368.  
  369. # ::pop3::delete --
  370. #
  371. #    Delete messages on the POP3 server.
  372. #
  373. # Arguments:
  374. #    chan      The channel, returned by ::pop3::open
  375. #       start     The first message to delete in the range.
  376. #                 May be "next" (the next message after the last
  377. #                 one seen, see ::pop3::last), "start" (aka 1),
  378. #                 "end" (the last message in the spool, for 
  379. #                 deleting only the last message).
  380. #       end       (optional, defaults to -1) The last message
  381. #                 to delete in the range. May be "last"
  382. #                 (the last message viewed), "end" (the last
  383. #                 message in the spool), or "-1" (the default,
  384. #                 any negative number means delete only
  385. #                 one message).
  386. #
  387. # Results:
  388. #    None.
  389. #       May throw errors from the server.
  390.  
  391. proc ::pop3::delete {chan start {end -1}} {
  392.     
  393.     set count [lindex [::pop3::status $chan] 0]
  394.     set last 0
  395.     catch {set last [::pop3::last $chan]}
  396.  
  397.     if {![string is integer $start]} {
  398.     if {[string match $start "next"]} {
  399.         set start $last
  400.         incr start
  401.     } elseif {$start == "start"} {
  402.         set start 1
  403.     } elseif {$start == "end"} {
  404.         set start $count
  405.     } else {
  406.         error "POP3 Deletion error: Bad start index $start"
  407.     }
  408.     } 
  409.     if {$start == 0} {
  410.     set start 1
  411.     }
  412.     
  413.     
  414.     if {![string is integer $end]} {
  415.     if {$end == "end"} {
  416.         set end $count
  417.     } elseif {$end == "last"} {
  418.         set end $last
  419.     } else {
  420.         error "POP3 Deletion error: Bad end index $end"
  421.     }
  422.     } elseif {$end < 0} {
  423.     set end $start
  424.     }
  425.  
  426.     if {$end > $count} {
  427.     set end $count
  428.     }
  429.     
  430.     for {set index $start} {$index <= $end} {incr index} {
  431.     if {[catch {::pop3::send $chan "DELE $index"} errorStr]} {
  432.         error "POP3 DELETE ERROR: $errorStr"
  433.     }
  434.     }
  435.     return {}
  436. }
  437.  
  438. # ::pop3::close --
  439. #
  440. #    Close the connection to the POP3 server.
  441. #
  442. # Arguments:
  443. #    chan      The channel, returned by ::pop3::open
  444. #
  445. # Results:
  446. #    None.
  447.  
  448. proc ::pop3::close {chan} {
  449.     variable state
  450.     catch {::pop3::send $chan "QUIT"}
  451.     unset state($chan)
  452.     ::close $chan
  453. }
  454.  
  455.         
  456.  
  457. # ::pop3::send --
  458. #
  459. #    Send a command string to the POP3 server.  This is an
  460. #       internal function, but may be used in rare cases.
  461. #
  462. # Arguments:
  463. #    chan        The channel open to the POP3 server.
  464. #       cmdstring   POP3 command string
  465. #
  466. # Results:
  467. #    Result string from the POP3 server, except for the +OK tag.
  468. #       Errors from the POP3 server are thrown.
  469.  
  470. proc ::pop3::send {chan cmdstring} {
  471.    global PopErrorNm PopErrorStr debug
  472.  
  473.    if {$cmdstring != {}} {
  474.       puts $chan $cmdstring
  475.    }
  476.    
  477.    set popRet [string trim [gets $chan]]
  478.  
  479.    if {[string first "+OK" $popRet] == -1} {
  480.        error [string range $popRet 4 end]
  481.    }
  482.  
  483.    return [string range $popRet 3 end]
  484. }
  485.  
  486. # ::pop3::list --
  487. #
  488. #    Returns "scan listing" of the mailbox. If parameter msg
  489. #       is defined, then the listing only for the given message 
  490. #       is returned.
  491. #
  492. # Arguments:
  493. #    chan        The channel open to the POP3 server.
  494. #       msg         The message number (optional).
  495. #
  496. # Results:
  497. #    If msg parameter is not given, Tcl list of scan listings in 
  498. #       the maildrop is returned. In case msg parameter is given,
  499. #       a list of length one containing the specified message listing
  500. #       is returned.
  501.  
  502. proc ::pop3::list {chan {msg ""}} {
  503.     global PopErrorNm PopErrorStr debug
  504.  
  505.     if {$msg == ""} {
  506.     if {[catch {::pop3::send $chan "LIST"} errorStr]} {
  507.         error "POP3 LIST ERROR: $errorStr"
  508.     }
  509.     set msgBuffer {}
  510.     while {1} {
  511.         set line [gets $chan]
  512.  
  513.         # End of the message is a line with just "."
  514.  
  515.         set line [string trimright $line]
  516.  
  517.         if {$line == "."} {
  518.         break
  519.         } elseif {[string index $line 0] == "."} {
  520.         # Use trimright to ge rid of superfluous \r's
  521.         # (we get them due to binary mode)
  522.  
  523.         set line [string range $line 1 end]
  524.         }
  525.  
  526.         lappend msgBuffer $line
  527.     }
  528.     } else {
  529.     # argument msg given, single-line response expected
  530.  
  531.     if {[catch {expr {0 + $msg}}]} {
  532.         error "POP3 LIST ERROR: malformed message number '$msg'"
  533.     } else {
  534.         lappend msgBuffer [string trim [::pop3::send $chan "LIST $msg"]]
  535.     }
  536.     }
  537.     return $msgBuffer
  538. }
  539.  
  540. # ::pop3::top --
  541. #
  542. #       Optional POP3 command (see RFC1939). Retrieves message header
  543. #       and given number of lines from the message body.
  544. #
  545. # Arguments:
  546. #    chan        The channel open to the POP3 server.
  547. #       msg         The message number to be retrieved.
  548. #       n           Number of lines returned from the message body.
  549. #
  550. # Results:
  551. #    Text (with newlines) from the server.
  552. #       Errors from the POP3 server are thrown.
  553.  
  554. proc ::pop3::top {chan msg n} {
  555.     global PopErrorNm PopErrorStr debug
  556.     
  557.     if {[catch {::pop3::send $chan "TOP $msg $n"} errorStr]} {
  558.     error "POP3 TOP ERROR: $errorStr"
  559.     }
  560.  
  561.     while {1} {
  562.     set line [gets $chan]
  563.     # End of the message is a line with just "."
  564.     if {[string trimright $line] == "."} {
  565.         break
  566.     } elseif {[string index $line 0] == "."} {
  567.         # Get rid of traling \r's. We get them due to binary mode.
  568.         set line [string trimright [string range $line 1 end]]
  569.     }
  570.     append msgBuffer "$line\n"
  571.     }
  572.     return $msgBuffer
  573. }
  574.