pop3.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 package require Tcl 8.2
00016 package require cmdline
00017 package require log
00018 package provide pop3 1.6.3
00019
00020 namespace ::pop3 {
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037 variable state
00038 array state = {}
00039
00040 }
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052 ret ::pop3::config (type chan) {
00053 variable state
00054 return $state($chan)
00055 }
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067 ret ::pop3::close (type chan) {
00068 variable state
00069 catch {::pop3::send $chan "QUIT"}
00070 unset state($chan)
00071 ::close $chan
00072 }
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096 ret ::pop3::delete (type chan , type start , optional end =-1) {
00097
00098 variable state
00099 array set cstate $state($chan)
00100 set count $cstate(limit)
00101 set last 0
00102 catch {set last [::pop3::last $chan]}
00103
00104 if {![string is integer $start]} {
00105 if {[string match $start "next"]} {
00106 set start $last
00107 incr start
00108 } elseif {$start == "start"} {
00109 set start 1
00110 } elseif {$start == "end"} {
00111 set start $count
00112 } else {
00113 error "POP3 Deletion error: Bad start index $start"
00114 }
00115 }
00116 if {$start == 0} {
00117 set start 1
00118 }
00119
00120 if {![string is integer $end]} {
00121 if {$end == "end"} {
00122 set end $count
00123 } elseif {$end == "last"} {
00124 set end $last
00125 } else {
00126 error "POP3 Deletion error: Bad end index $end"
00127 }
00128 } elseif {$end < 0} {
00129 set end $start
00130 }
00131
00132 if {$end > $count} {
00133 set end $count
00134 }
00135
00136 for {set index $start} {$index <= $end} {incr index} {
00137 if {[catch {::pop3::send $chan "DELE $index"} errorStr]} {
00138 error "POP3 DELETE ERROR: $errorStr"
00139 }
00140 }
00141 return {}
00142 }
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161 ret ::pop3::last (type chan) {
00162
00163 if {[catch {
00164 set resultStr [::pop3::send $chan "LAST"]
00165 } errorStr]} {
00166 error "POP3 LAST ERROR: $errorStr"
00167 }
00168
00169 return [string trim $resultStr]
00170 }
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188 ret ::pop3::list (type chan , optional msg ="") {
00189 global PopErrorNm PopErrorStr debug
00190
00191 if {$msg == ""} {
00192 if {[catch {::pop3::send $chan "LIST"} errorStr]} {
00193 error "POP3 LIST ERROR: $errorStr"
00194 }
00195 set msgBuffer [RetrSlow $chan]
00196 } else {
00197 # argument msg given, single-line response expected
00198
00199 if {[catch {expr {0 + $msg}}]} {
00200 error "POP3 LIST ERROR: malformed message number '$msg'"
00201 } else {
00202 set msgBuffer [string trim [::pop3::send $chan "LIST $msg"]]
00203 }
00204 }
00205 return $msgBuffer
00206 }
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227 ret ::pop3::open (type args) {
00228 variable state
00229 array set cstate {msex 0 retr_mode retr limit {}}
00230
00231 log::log debug "pop3::open | [join $args]"
00232
00233 while {[set err [cmdline::getopt args {msex.arg retr-mode.arg} opt arg]]} {
00234 if {$err < 0} {
00235 return -code error "::pop3::open : $arg"
00236 }
00237 switch -exact -- $opt {
00238 msex {
00239 if {![string is boolean $arg]} {
00240 return -code error \
00241 ":pop3::open : Argument to -msex has to be boolean"
00242 }
00243 set cstate(msex) $arg
00244 }
00245 retr-mode {
00246 switch -exact -- $arg {
00247 retr - list - slow {
00248 set cstate(retr_mode) $arg
00249 }
00250 default {
00251 return -code error \
00252 ":pop3::open : Argument to -retr-mode has to be one of retr, list or slow"
00253 }
00254 }
00255 }
00256 default {# Can't happen}
00257 }
00258 }
00259
00260 if {[llength $args] > 4} {
00261 return -code error "To many arguments to ::pop3::open"
00262 }
00263 if {[llength $args] < 3} {
00264 return -code error "Not enough arguments to ::pop3::open"
00265 }
00266 foreach {host user password port} $args break
00267 if {$port == {}} {
00268 set port 110
00269 }
00270
00271 log::log debug "pop3::open | protocol, connect to $host $port"
00272
00273 # Argument processing is finally complete, now open the channel
00274
00275 set chan [socket $host $port]
00276 fconfigure $chan -buffering none
00277
00278 log::log debug "pop3::open | connect on $chan"
00279
00280 if {$cstate(msex)} {
00281 # We are talking to MS Exchange. Work around its quirks.
00282 fconfigure $chan -translation binary
00283 } else {
00284 fconfigure $chan -translation {binary crlf}
00285 }
00286
00287 log::log debug "pop3::open | wait for greeting"
00288
00289 if {[catch {::pop3::send $chan {}} errorStr]} {
00290 ::close $chan
00291 error "POP3 CONNECT ERROR: $errorStr"
00292 }
00293
00294 if {0} {
00295 # -FUTURE- Identify MS Exchange servers
00296 set cstate(msex) 1
00297
00298 # We are talking to MS Exchange. Work around its quirks.
00299 fconfigure $chan -translation binary
00300 }
00301
00302 log::log debug "pop3::open | authenticate $user (*password not shown*)"
00303
00304 if {[catch {
00305 ::pop3::send $chan "USER $user"
00306 ::pop3::send $chan "PASS $password"
00307 } errorStr]} {
00308 ::close $chan
00309 error "POP3 LOGIN ERROR: $errorStr"
00310 }
00311
00312 # [ 833486 ] Can't delete messages one at a time ...
00313 # Remember the number of messages in the maildrop at the beginning
00314 # of the session. This gives us the highest possible number for
00315 # message ids later. Note that this number must not be affected
00316 # when deleting mails later. While the number of messages drops
00317 # down the limit for the message id's stays the same. The messages
00318 # are not renumbered before the session actually closed.
00319
00320 set cstate(limit) [lindex [::pop3::status $chan] 0]
00321
00322 # Remember the state.
00323
00324 set state($chan) [array get cstate]
00325
00326 log::log debug "pop3::open | ok ($chan)"
00327 return $chan
00328 }
00329
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352 ret ::pop3::retrieve (type chan , type start , optional end =-1) {
00353 variable state
00354 array set cstate $state($chan)
00355
00356 set count $cstate(limit)
00357 set last 0
00358 catch {set last [::pop3::last $chan]}
00359
00360 if {![string is integer $start]} {
00361 if {[string match $start "next"]} {
00362 set start $last
00363 incr start
00364 } elseif {$start == "start"} {
00365 set start 1
00366 } elseif {$start == "end"} {
00367 set start $count
00368 } else {
00369 error "POP3 Retrieval error: Bad start index $start"
00370 }
00371 }
00372 if {$start == 0} {
00373 set start 1
00374 }
00375
00376 if {![string is integer $end]} {
00377 if {$end == "end"} {
00378 set end $count
00379 } elseif {$end == "last"} {
00380 set end $last
00381 } else {
00382 error "POP3 Retrieval error: Bad end index $end"
00383 }
00384 } elseif {$end < 0} {
00385 set end $start
00386 }
00387
00388 if {$end > $count} {
00389 set end $count
00390 }
00391
00392 set result {}
00393
00394 ::log::log debug "pop3 $chan retrieve $start -- $end"
00395
00396 for {set index $start} {$index <= $end} {incr index} {
00397 switch -exact -- $cstate(retr_mode) {
00398 retr {
00399 set sizeStr [::pop3::send $chan "RETR $index"]
00400
00401 ::log::log debug "pop3 $chan retrieve ($sizeStr)"
00402
00403 if {[scan $sizeStr {%d %s} size dummy] < 1} {
00404 # The server did not deliver the size information.
00405 # Switch our mode to "list" and use the slow
00406 # method this time. The next call will use LIST before
00407 # RETR to get the size information. If even that fails
00408 # the system will fall back to slow mode all the time.
00409
00410 ::log::log debug "pop3 $chan retrieve - no size information, go slow"
00411
00412 set cstate(retr_mode) list
00413 set state($chan) [array get cstate]
00414
00415 # Retrieve in slow motion.
00416 set msgBuffer [RetrSlow $chan]
00417 } else {
00418 ::log::log debug "pop3 $chan retrieve - size information present, use fast mode"
00419
00420 set msgBuffer [RetrFast $chan $size]
00421 }
00422 }
00423 list {
00424 set sizeStr [::pop3::send $chan "LIST $index"]
00425
00426 if {[scan $sizeStr {%d %d %s} dummy size dummy] < 2} {
00427 # Not even LIST generates the necessary size information.
00428 # Switch to full slow mode and don't bother anymore.
00429
00430 set cstate(retr_mode) slow
00431 set state($chan) [array get cstate]
00432
00433 ::pop3::send $chan "RETR $index"
00434
00435 # Retrieve in slow motion.
00436 set msgBuffer [RetrSlow $chan]
00437 } else {
00438 # Ignore response of RETR, already know the size
00439 # through LIST
00440
00441 ::pop3::send $chan "RETR $index"
00442 set msgBuffer [RetrFast $chan $size]
00443 }
00444 }
00445 slow {
00446 # Retrieve in slow motion.
00447
00448 ::pop3::send $chan "RETR $index"
00449 set msgBuffer [RetrSlow $chan]
00450 }
00451 }
00452 lappend result $msgBuffer
00453 }
00454 return $result
00455 }
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466
00467
00468 ret ::pop3::RetrFast (type chan , type size) {
00469 set msgBuffer [read $chan $size]
00470
00471 foreach line [split $msgBuffer \n] {
00472 ::log::log debug "pop3 $chan fast <$line>"
00473 }
00474
00475 # There is a small discrepance in counting octets we have to be
00476 # aware of. 'size' is #octets before transmission, i.e. can be
00477 # with one eol character, CR or LF. The channel system in binary
00478 # mode counts every character, and the protocol specified CRLF as
00479 # eol, so for every line in the message we read that many
00480 # characters _less_. Another factor which can cause a miscount is
00481 # the ".-stuffing performed by the sender. I.e. what we got now is
00482 # not necessarily the complete message. We have to perform slow
00483 # reads to get the remainder of the message. This has another
00484 # complication. We cannot simply check for a line containing the
00485 # terminating signature, simply because the point where the
00486 # message was broken in two might just be in between the dots of a
00487 # "\r\n..\r\n" sequence. We have to make sure that we do not
00488 # misinterpret the second part of this sequence as terminator.
00489 # Another possibility: "\r\n.\r\n" is broken just after the dot.
00490 # Then we have to ensure to not to miss the terminator entirely.
00491
00492 # Sometimes the gets returns nothing, need to get the real
00493 # terminating "." / "
00494
00495 if {[string equal [string range $msgBuffer end-3 end] "\n.\r\n"]} {
00496 # Complete terminator found. Remove it from the message buffer.
00497
00498 ::log::log debug "pop3 $chan /5__"
00499 set msgBuffer [string range $msgBuffer 0 end-3]
00500
00501 } elseif {[string equal [string range $msgBuffer end-2 end] "\n.\r"]} {
00502 # Complete terminator found. Remove it from the message buffer.
00503 # Also perform an empty read to remove the missing '\n' from
00504 # the channel. If we don't do this all following commands will
00505 # run into off-by-one (character) problems.
00506
00507 ::log::log debug "pop3 $chan /4__"
00508 set msgBuffer [string range $msgBuffer 0 end-2]
00509 while {[read $chan 1] != "\n"} {}
00510
00511 } elseif {[string equal [string range $msgBuffer end-1 end] "\n."]} {
00512 # \n. at the end of the fast buffer.
00513 # Can be \n.\r\n = Terminator
00514 # or \n..\r\n = dot-stuffed single .
00515
00516 log::log debug "pop3 $chan /check for cut .. or terminator sequence"
00517
00518 # Idle until non-empty line encountered.
00519 while {[set line [gets $chan]] == ""} {}
00520 if {"$line" == "\r"} {
00521 # Terminator already found. Note that we have to
00522 # remove the partial terminator sequence from the
00523 # message buffer.
00524 ::log::log debug "pop3 $chan /3__ <$line>"
00525 set msgBuffer [string range $msgBuffer 0 end-1]
00526 } else {
00527 # Append line and look for the real terminator
00528 append msgBuffer $line
00529 ::log::log debug "pop3 $chan ____ <$line>"
00530 while {[set line [gets $chan]] != ".\r"} {
00531 ::log::log debug "pop3 $chan ____ <$line>"
00532 append msgBuffer $line
00533 }
00534 ::log::log debug "pop3 $chan /2__ <$line>"
00535 }
00536 } elseif {[string equal [string index $msgBuffer end] \n]} {
00537 # Line terminator (\n) found. The remainder of the mail has to
00538 # consist of true lines we can read directly.
00539
00540 while {![string equal [set line [gets $chan]] ".\r"]} {
00541 ::log::log debug "pop3 $chan ____ <$line>"
00542 append msgBuffer $line
00543 }
00544 ::log::log debug "pop3 $chan /1__ <$line>"
00545 } else {
00546 # Incomplete line at the end of the buffer. We complete it in
00547 # a single read, and then handle the remainder like the case
00548 # before, where we had a complete line at the end of the
00549 # buffer.
00550
00551 set line [gets $chan]
00552 ::log::log debug "pop3 $chan /1a_ <$line>"
00553 append msgBuffer $line
00554
00555 ::log::log debug "pop3 $chan /1b_"
00556
00557 while {![string equal [set line [gets $chan]] ".\r"]} {
00558 ::log::log debug "pop3 $chan ____ <$line>"
00559 append msgBuffer $line
00560 }
00561 ::log::log debug "pop3 $chan /1c_ <$line>"
00562 }
00563
00564 ::log::log debug "pop3 $chan done"
00565
00566 # Map both cr+lf and cr to lf to simulate auto EOL translation, then
00567 # unstuff .-stuffed lines.
00568
00569 return [string map [::list \n.. \n.] [string map [::list \r \n] [string map [::list \r\n \n] $msgBuffer]]]
00570 }
00571
00572
00573
00574
00575
00576
00577
00578
00579
00580
00581
00582
00583 ret ::pop3::RetrSlow (type chan) {
00584
00585 set msgBuffer ""
00586
00587 while {1} {
00588 set line [string trimright [gets $chan] \r]
00589 ::log::log debug "pop3 $chan slow $line"
00590
00591 # End of the message is a line with just "."
00592 if {$line == "."} {
00593 break
00594 } elseif {[string index $line 0] == "."} {
00595 set line [string range $line 1 end]
00596 }
00597
00598 append msgBuffer $line "\n"
00599 }
00600
00601 return $msgBuffer
00602 }
00603
00604
00605
00606
00607
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617 ret ::pop3::send (type chan , type cmdstring) {
00618 global PopErrorNm PopErrorStr debug
00619
00620 if {$cmdstring != {}} {
00621 ::log::log debug "pop3 $chan >>> $cmdstring"
00622 puts $chan $cmdstring
00623 }
00624
00625 set popRet [string trim [gets $chan]]
00626 ::log::log debug "pop3 $chan <<< $popRet"
00627
00628 if {[string first "+OK" $popRet] == -1} {
00629 error [string range $popRet 4 end]
00630 }
00631
00632 return [string range $popRet 3 end]
00633 }
00634
00635
00636
00637
00638
00639
00640
00641
00642
00643
00644
00645
00646
00647
00648 ret ::pop3::status (type chan) {
00649
00650 if {[catch {set statusStr [::pop3::send $chan "STAT"]} errorStr]} {
00651 error "POP3 STAT ERROR: $errorStr"
00652 }
00653
00654 # Dig the sent size and count info out.
00655 set rawStatus [split [string trim $statusStr]]
00656
00657 return [::list [lindex $rawStatus 0] [lindex $rawStatus 1]]
00658 }
00659
00660
00661
00662
00663
00664
00665
00666
00667
00668
00669
00670
00671
00672
00673
00674 ret ::pop3::top (type chan , type msg , type n) {
00675 global PopErrorNm PopErrorStr debug
00676
00677 if {[catch {::pop3::send $chan "TOP $msg $n"} errorStr]} {
00678 error "POP3 TOP ERROR: $errorStr"
00679 }
00680
00681 return [RetrSlow $chan]
00682 }
00683
00684
00685
00686
00687
00688
00689
00690
00691
00692
00693
00694
00695
00696
00697
00698
00699
00700 ret ::pop3::uidl (type chan , optional msg ="") {
00701 if {$msg == ""} {
00702 if {[catch {::pop3::send $chan "UIDL"} errorStr]} {
00703 error "POP3 UIDL ERROR: $errorStr"
00704 }
00705 set msgBuffer [RetrSlow $chan]
00706 } else {
00707 # argument msg given, single-line response expected
00708
00709 if {[catch {expr {0 + $msg}}]} {
00710 error "POP3 UIDL ERROR: malformed message number '$msg'"
00711 } else {
00712 set msgBuffer [string trim [::pop3::send $chan "UIDL $msg"]]
00713 }
00714 }
00715
00716 return $msgBuffer
00717 }
00718