home *** CD-ROM | disk | FTP | other *** search
/ PCNET 2006 August - Disc 1 / PCNET_CD_2006_08_1.iso / linux / puppy-barebones-2.01r2.iso / pup_201.sfs / usr / sbin / sockspy.tcl < prev    next >
Encoding:
Text File  |  2004-07-19  |  38.1 KB  |  1,175 lines

  1. #!/bin/sh
  2. # restart using wish    -*- mode: tcl; tab-width: 8; -*- \
  3. exec wish $0 ${1+"$@"}
  4.  
  5. # sockspy: copyright tom poindexter 1998
  6. # sockspy: copyright Keith Vetter 2002
  7. #        tpoindex@nyx.net
  8. # version 1.0 - december 10, 1998
  9. # version 2.0 - January, 2002 by Keith Vetter
  10. # KPV Nov 01, 2002 - added proxy mode
  11. # KPV Dec 21, 2002 - added extract window
  12. # version 2.5 - February, 2003 by Don Libes
  13. # DL Jan/Feb, 2003 - added date support, state save/restore, cmdline display
  14. # spy on conversation between a tcp client and server
  15. #
  16. # usage: sockspy clientPort serverHost serverPort
  17. #  -or-     sockspy -proxy proxyPort
  18. #       clientPort - port to which clients connect
  19. #       serverHost - machine where real server runs
  20. #       serverPort - port on which real server listens
  21. #
  22. #  e.g. to snoop on http connections to a web server:
  23. #    sockspy 8080 www.some.com 80
  24. #  then client web browser should use a url like:
  25. #     http://localhost:8080/index.html
  26. #     (or set your browser's proxy to use 8080 on the sockspy machine)
  27.  
  28. catch {package require uri}            ;# Error handled below
  29.  
  30. array set state {
  31.     version 2.5
  32.     extract 0
  33.     bbar 1
  34.     ascii 1
  35.     autoscroll 1
  36.     autowrap 1
  37.     capture 1
  38.     msg ""
  39.     fixed {}
  40.     fixedbold {}
  41.     fontSize 9
  42.     playback ""
  43.     gui 0
  44.     listen ""
  45.     title "not connected"
  46.     proxy 0
  47.     fname ""
  48.     time 1
  49.     timeFormat ""
  50.     timeFormatDefault "%H:%M:%S "
  51. }
  52.  
  53. # variables to save across runs
  54. set saveList {
  55.     state(extract)
  56.     state(bbar)
  57.     state(ascii)
  58.     state(autoscroll)
  59.     state(autowrap)
  60.     state(proxy)
  61.     state(fname)
  62.     state(time)
  63.     state(timeFormat)
  64.     state(fontSize)
  65.     extract(client)
  66.     extract(server)
  67.     extract(meta2)
  68.     SP(proxyPort)
  69.     SP(clntPort)
  70.     SP(servPort)
  71.     SP(servHost)
  72. }
  73.  
  74. array set colors {client green server cyan meta red meta2 yellow}
  75. array set SP {proxyPort 8080 clntPort 8080 servHost "" servPort 80}
  76.  
  77. set extract(client) {^(GET |POST |HEAD )}
  78. set extract(server) {^(HTTP/|Location: |Content-)}
  79. set extract(meta2) {.}
  80. #set extract(meta) {.}
  81.  
  82. ##+##########################################################################
  83. # createMain -- Creates the display
  84. proc createMain {} {
  85.     global state colors tcl_platform
  86.     
  87.     wm withdraw .
  88.  
  89.     set state(fixed) [font create -family courier -size $state(fontSize)]
  90.     set state(fixedbold) [font create -family courier -size $state(fontSize) \
  91.           -weight bold]
  92.  
  93.     wm title . "SockSpy -- $state(title)"
  94.     wm resizable .  1 1
  95.     wm protocol . WM_DELETE_WINDOW Shutdown    ;# So we shut down cleanly
  96.  
  97.     #
  98.     # Set up the menus
  99.     #
  100.     menu .m -tearoff 0
  101.     . configure -menu .m
  102.     .m add cascade -menu .m.file -label "File" -underline 0
  103.     .m add cascade -menu .m.view -label "View" -underline 0
  104.     .m add cascade -menu .m.help -label "Help" -underline 0
  105.     
  106.     menu .m.file
  107.     .m.file add command -label "Save ..." -underline 0 -command saveOutput
  108.     .m.file add command -label "Reconnect ..." -underline 0 -command GetSetup
  109.     .m.file add separator
  110.     .m.file add command -label "Exit" -underline 1 -command Shutdown
  111.     
  112.     menu .m.view
  113.     .m.view add command -label " Clear" -underline 1 -command clearOutput
  114.     .m.view add separator
  115.     .m.view add checkbutton -label " ButtonBar" -variable state(bbar) \
  116.         -underline 1 -command ButtonBar
  117.     .m.view add checkbutton -label " Extract Window" -variable state(extract) \
  118.         -underline 1 -command ToggleExtract
  119.     .m.view add separator
  120.     .m.view add command -label " + Font" -command [list doFont 1]
  121.     .m.view add command -label " - Font" -command [list doFont -1]
  122.     .m.view add separator
  123.     .m.view add radiobutton -label " Hex" -underline 1 \
  124.         -variable state(ascii) -value 0 -command redraw
  125.     .m.view add radiobutton -label " ASCII" -underline 1 \
  126.         -variable state(ascii) -value 1 -command redraw
  127.     .m.view add separator
  128.     .m.view add checkbutton -label " Autoscroll" -underline 5 \
  129.         -variable state(autoscroll)
  130.     .m.view add checkbutton -label " Autowrap" -underline 5 \
  131.         -variable state(autowrap) -command ToggleWrap
  132.     .m.view add checkbutton -label " Capture" -underline 5 \
  133.         -variable state(capture) -command ToggleCapture
  134.     .m.view add separator
  135.     .m.view add checkbutton -label " Time" \
  136.     -variable state(time) -command redraw
  137.     .m.view add command -label " Time Format ..." -command timestampWindow
  138.  
  139.     menu .m.help -tearoff 0
  140.     .m.help add command -label Help -underline 1 -command Help
  141.     .m.help add separator
  142.     .m.help add command -label About -command About
  143.     #
  144.     # Title and status window
  145.     #
  146.     frame .bbar
  147.     frame .cmd -relief sunken -bd 2
  148.     radiobutton .cmd.hex -text Hex -variable state(ascii) \
  149.         -value 0 -command redraw
  150.     radiobutton .cmd.ascii -text ASCII -variable state(ascii) \
  151.         -value 1 -command redraw
  152.     checkbutton .cmd.autos -text Autoscroll -variable state(autoscroll)
  153.     checkbutton .cmd.autow -text Autowrap -variable state(autowrap) \
  154.      -command ToggleWrap
  155.     checkbutton .cmd.capture -text Capture -variable state(capture) \
  156.      -command ToggleCapture
  157.     checkbutton .cmd.time -text Time -variable state(time) \
  158.      -command redraw
  159.     button .cmd.clear -text Clear -command clearOutput
  160.     #button .cmd.incr -text "+ Font" -command [list doFont 1]
  161.     #button .cmd.decr -text "- Font" -command [list doFont -1]
  162.     button .cmd.save -text Save -command saveOutput
  163.     button .cmd.kill -text Exit -command Shutdown
  164.     pack .cmd -side top -fill x -pady 5 -in .bbar
  165.     pack .cmd.kill .cmd.save .cmd.clear .cmd.autow .cmd.autos .cmd.capture \
  166.     .cmd.time \
  167.     -side right -padx 3 -pady 3
  168.     #label .title -relief ridge -textvariable state(title)
  169.     #.title config -font "[.title cget -font] bold"
  170.     label .stat -textvariable state(msg) -relief ridge -anchor w
  171.  
  172.     #
  173.     # Now for the output area of the display
  174.     #
  175.     scrollbar .yscroll -orient vertical -command {.out yview}
  176.     scrollbar .xscroll -orient horizontal -command {.out xview}
  177.     text .out -width 80 -height 50 -font $state(fixed) -bg white -setgrid 1 \
  178.         -yscrollcommand ".yscroll set" -xscrollcommand ".xscroll set"
  179.     foreach t [array names colors] {
  180.     .out tag configure $t -background $colors($t) -borderwidth 2 \
  181.         -relief raised -lmargin1 5 -lmargin2 5
  182.     .out tag configure time_$t -background $colors($t) -borderwidth 2 \
  183.         -relief raised -lmargin1 5 -lmargin2 5 -font $state(fixedbold)
  184.     }
  185.     .out tag configure client2 -font $state(fixedbold)
  186.     .out tag raise sel                ;# Selection is most prominent
  187.  
  188.     grid .bbar -       -row 0 -sticky ew
  189.     grid .out .yscroll -row 1 -sticky news
  190.     grid .xscroll      -row 2 -sticky ew
  191.     grid .stat -       -row 3 -sticky ew
  192.     grid rowconfigure     . 1 -weight 1
  193.     grid columnconfigure . 0 -weight 1
  194.  
  195.     bind .out <Control-l> clearOutput
  196.     bind all <Alt-c> {console show}
  197.     focus .out
  198.     wm geometry . +10+10
  199. }
  200. ##+##########################################################################
  201. # createExtract -- Creates the extract toplevel window.
  202. proc createExtract {} {
  203.     global state colors
  204.     
  205.     if {[winfo exists .extract]} {
  206.     wm deiconify .extract
  207.     return
  208.     }
  209.     set top ".extract"
  210.     toplevel $top
  211.     wm title $top "SockSpy Extract"
  212.     wm protocol $top WM_DELETE_WINDOW [list ToggleExtract -1]
  213.     if {[regexp {(\+[0-9]+)(\+[0-9]+)$} [wm geom .] => wx wy]} {
  214.     wm geom $top "+[expr {$wx+35+[winfo width .]}]+[expr {$wy+15}]"
  215.     }
  216.  
  217.     frame $top.top -bd 2 -relief ridge
  218.     label $top.top.c  -text "Client Filter" -anchor e
  219.     entry $top.top.ce -textvariable extract(client) -bg $colors(client)
  220.     label $top.top.s  -text "Server Filter" -anchor e
  221.     entry $top.top.se -textvariable extract(server) -bg $colors(server)
  222.     label $top.top.m  -text "Metadata Filter" -anchor e
  223.     entry $top.top.me -textvariable extract(meta2) -bg $colors(meta2)
  224.     
  225.     text $top.out -width 80 -height 20 -font $state(fixed) -bg beige \
  226.     -setgrid 1 -wrap none -yscrollcommand [list $top.yscroll set] \
  227.     -xscrollcommand [list $top.xscroll set]
  228.     foreach t [array names colors] {
  229.     $top.out tag configure $t -background $colors($t) -borderwidth 2 \
  230.         -relief raised -lmargin1 5 -lmargin2 5
  231.     $top.out tag configure time_$t -background $colors($t) -borderwidth 2 \
  232.         -relief raised -lmargin1 5 -lmargin2 5 -font $state(fixedbold)
  233.     }
  234.     $top.out tag raise sel            ;# Selection is most prominent
  235.  
  236.     scrollbar $top.yscroll -orient vertical -command [list $top.out yview]
  237.     scrollbar $top.xscroll -orient horizontal -command [list $top.out xview]
  238.     grid $top.top - -row 0     -sticky ew -ipady 10
  239.     grid $top.out $top.yscroll -sticky news
  240.     grid $top.xscroll           -sticky ew
  241.  
  242.     grid rowconfigure     $top 1 -weight 1
  243.     grid columnconfigure $top 0 -weight 1
  244.  
  245.     grid $top.top.c $top.top.ce -row 0 -sticky ew
  246.     grid $top.top.s $top.top.se           -sticky ew
  247.     grid $top.top.m $top.top.me           -sticky ew
  248.     grid columnconfigure $top.top 1 -weight 1
  249.     grid columnconfigure $top.top 2 -minsize 10
  250. }
  251. ##+##########################################################################
  252. # doFont -- Changes the size of the font used for the display text
  253. proc doFont {delta} {
  254.     global state
  255.  
  256.     incr state(fontSize) $delta
  257.     font configure $state(fixed) -size $state(fontSize)
  258.     font configure $state(fixedbold) -size $state(fontSize)
  259. }
  260. ##+##########################################################################
  261. # clearOutput -- Erases the content of the output window
  262. proc clearOutput {} {
  263.     global state
  264.     if {$state(gui)} {
  265.     .out delete 0.0 end
  266.     catch {.extract.out delete 0.0 end}
  267.     }
  268.     set state(playback) ""
  269. }
  270. ##+##########################################################################
  271. # redraw -- Redraws the contents of the output window.
  272. #
  273. # It does this by replaying the input stream.
  274. proc redraw {} {
  275.     global state 
  276.  
  277.     set save_as $state(autoscroll)        ;# Disable autoscrolling
  278.     set state(autoscroll) 0
  279.  
  280.     set p $state(playback)            ;# Save what gets displayed
  281.     clearOutput                    ;# Erase current screen
  282.     foreach {who data time} $p {        ;# Replay the input stream
  283.     insertData $who $data $time 1
  284.     }
  285.     set state(autoscroll) $save_as
  286. }
  287. ##+##########################################################################
  288. # saveOutput -- Saves the content of the output window. 
  289. # It uses the playback stream as its data source.
  290. proc saveOutput {} {
  291.     global state but
  292.  
  293.     set but -1
  294.     set but [tk_dialog .what "SockSpy Save" "Save which window?" \
  295.         questhead 2 server client both cancel]
  296.  
  297.     if {$but == -1 || $but == 3} {
  298.     return
  299.     }
  300.     set file [tk_getSaveFile -parent . -initialfile $state(fname)]
  301.     if {$file == ""} return
  302.  
  303.     set state(fname) $file
  304.     if {[catch {open $file w} fd]} {
  305.     tk_messageBox -message "file $file cannot be opened" -icon error \
  306.         -type ok
  307.     return
  308.     }
  309.     fconfigure $fd -translation binary
  310.     foreach {who data time} $state(playback) {
  311.     if {$who == "meta" || $who == "meta2"} continue
  312.     if {$but == 2 || ($but == 0 && $who == "server") || \
  313.         ($but == 1 && $who == "client")} {
  314.         if {$state(time)} {
  315.         puts $fd [timestamp $time]
  316.         }
  317.         puts $fd $data
  318.     }
  319.     }
  320.     close $fd
  321.     bell
  322. }
  323. ##+##########################################################################
  324. # printable -- Replaces all unprintable characters into dots.
  325. proc printable {s {spaces 0}} {
  326.     regsub -all {[^\x09\x20-\x7e]} $s "." n
  327.     if {$spaces} {
  328.     regsub -all { } $n "_" n
  329.     }
  330.     return $n;
  331. }
  332. ##+##########################################################################
  333. # insertData -- Inserts data into the output window. WHO tells us if it is
  334. # from the client, server or meta.
  335. proc insertData {who data {time {}} {force 0}} {
  336.     global state
  337.     array set prefix {meta = meta2 = client > server <}
  338.  
  339.     if {$time == ""} {                ;# If not set, then set to now
  340.     set time [clock seconds]
  341.     }
  342.     set timestamp [timestamp $time]
  343.  
  344.     DoExtract $who $data $timestamp        ;# Display any extracted data
  345.     if {! $force && ! $state(capture)} return    ;# No display w/o capture on
  346.     lappend state(playback) $who $data $time    ;# Save for redraw and saving
  347.     
  348.     if {$state(ascii) || [regexp {^meta2?$} $who] } {
  349.     regsub -all \r $data "" data
  350.     foreach line [split $data \n] {
  351.         set line [printable $line]
  352.         set tag $who
  353.         if {$tag == "client" && [regexp -nocase {^get |^post } $line]} {
  354.         lappend tag client2
  355.         }
  356.         if {$state(gui)} {
  357.         .out insert end "$timestamp" time_$tag "$line\n" $tag
  358.         } else {
  359.         puts "$timestamp$prefix($who)$line"
  360.         }
  361.     }
  362.     } else {                    ;# Hex output
  363.     while {[string length $data]} {
  364.         set line [string range $data 0 15]
  365.         set data [string range $data [string length $line] end]
  366.         binary scan $line H* hex
  367.         regsub -all {([0-9a-f][0-9a-f])} $hex {\1 } hex
  368.         set line [format "%-48.48s    %-16.16s\n" $hex [printable $line 1]] 
  369.         if {$state(gui)} {
  370.         .out insert end "$timestamp" time_$who "$line" $who
  371.         } else {
  372.         puts "$timestamp$prefix(who)$line"
  373.         }
  374.     }
  375.     }
  376.     if {$state(autoscroll) && $state(gui)} {
  377.     .out see end
  378.     }
  379. }
  380. ##+##########################################################################
  381. #
  382. # timestampInit -- Initialize timestamp support
  383. #
  384. proc timestampInit {} {
  385.     global state
  386.  
  387.     set state(timeFormat) $state(timeFormatDefault)
  388. }
  389. ##+##########################################################################
  390. #
  391. # timestamp -- Produce printable timestamps
  392. #
  393. # Note that it is the user's responsibility to make sure the
  394. # user-supplied format ends with a delimiter or separator such as a
  395. # space or colon. The timestamp code itself checks whether or not it
  396. # should do anything to simplify the many different places in the code
  397. # from which can be called.
  398.  
  399. proc timestamp {time} {
  400.     global state
  401.     
  402.     if {! $state(time)} { return "" }
  403.     return [clock format $time -format $state(timeFormat)]
  404. }
  405. ##+###########################################################################
  406. #
  407. # timestampWindow -- Dialog for the user to configure the timestamp format.
  408. #
  409. proc timestampWindow {} {
  410.     global state
  411.  
  412.     set state(oldTimeFormat) $state(timeFormat)
  413.  
  414.     set w .tf2
  415.     destroy .tf
  416.     toplevel .tf
  417.     wm title .tf "SockSpy Time Format"
  418.  
  419.     set txt "Edit the format used for timestamps. "
  420.     append txt "See Tcl's clock command documentation for a complete "
  421.     append txt "description of acceptable formats."
  422.     
  423.     frame .tf.top -bd 2 -relief raised -padx 5
  424.     
  425.     message .tf.t -aspect 500 -text $txt
  426.     label .tf.l -text "Format: "
  427.     entry .tf.e -textvariable state(timeFormat)
  428.     button .tf.default -text Default -width 10 -command {tfButton default}
  429.     button .tf.ok -text OK -width 10 -command {tfButton ok}
  430.     button .tf.cancel -text Cancel -width 10 -command {tfButton cancel}
  431.     
  432.     grid .tf.top -row 0 -column 0 -columnspan 4 -sticky ew -padx 10 -pady 10
  433.     grid columnconfigure .tf 0 -weight 1
  434.     grid x .tf.default .tf.ok .tf.cancel -padx 5 -sticky ew
  435.     grid rowconfigure .tf 2 -minsize 8
  436.  
  437.     grid .tf.t - -in .tf.top -row 0
  438.     grid .tf.l .tf.e -in .tf.top -row 1 -pady 10 -sticky ew
  439.     grid columnconfigure .tf.top 1 -weight 1
  440.     grid columnconfigure .tf.top 2 -minsize 10
  441.  
  442.     focus .tf.e
  443.     .tf.e icursor end
  444.     .tf.e select range 0 end
  445. }
  446. ##+##########################################################################
  447. # tfButton -- handles button clicks on the timestamp dialog
  448. proc tfButton {who} {
  449.     if {$who == "defaut"} {
  450.     set ::state(timeFormat) $::state(timeFormatDefault)
  451.     } elseif {$who == "ok"} {
  452.     destroy .tf
  453.     redraw
  454.     } elseif {$who == "cancel"} {
  455.     set ::state(timeFormat) $::state(oldTimeFormat)
  456.     destroy .tf
  457.     }
  458. }
  459. ##+##########################################################################
  460. # INFO
  461. # Puts up an informational message both in the output window and
  462. # in the status window.
  463. proc INFO {msg {who meta} {time {}} {display 0}} {
  464.     global state
  465.     set state(msg) $msg
  466.     insertData $who $msg $time $display
  467. }
  468. proc ERROR {emsg} {
  469.     if {$::state(gui)} {
  470.     tk_messageBox -title "SockSpy Error" -message $emsg -icon error
  471.     } else {
  472.     puts $emsg
  473.     }
  474. }
  475. ##+##########################################################################
  476. # sockReadable -- Called when there is data available on the fromSocket
  477. proc sockReadable {fromSock toSock who} {
  478.     global state
  479.     set data [read $fromSock]
  480.     if {[string length $data] == 0} {
  481.     close $fromSock
  482.     catch { close $toSock }
  483.     insertData meta "----- closed connection -----"
  484.     INFO "waiting for new connection..." 
  485.     return
  486.     }
  487.     if {$toSock == ""} {            ;# Not connected yet
  488.     ProxyConnect $fromSock $data        ;# Do proxy forwarding
  489.     } else {
  490.     catch { puts -nonewline $toSock $data } ;# Forward if we have a socket
  491.     }
  492.     insertData $who $data
  493.     update
  494. }
  495. ##+##########################################################################
  496. # ProxyConnect
  497. # Called from a new socket connection when we have to determing who
  498. # to forward to.
  499. proc ProxyConnect {fromSock data} {
  500.     set line1 [lindex [split $data \r] 0]
  501.     set bad [regexp -nocase {(http:[^ ]+)} $line1 => uri]
  502.     if {$bad == 0} {
  503.     INFO "ERROR: cannot extract URI from '$line1'"
  504.     close $fromSock
  505.     insertData meta "----- closed connection -----"
  506.     insertData meta "waiting for new connection..." 
  507.     }
  508.     set state(uri) $uri                ;# For debugging
  509.     array set URI [::uri::split $uri]
  510.     if {$URI(port) == ""} { set URI(port) 80 }
  511.     set bad [catch {set sockServ [socket $URI(host) $URI(port)]} reason]
  512.     if {$bad} {
  513.     set msg "cannot connect to $URI(host):$URI(port) => $reason"
  514.     INFO $msg
  515.     close $fromSock
  516.     ERROR $msg
  517.     insertData meta "----- closed connection -----"
  518.     insertData meta "waiting for new connection..." 
  519.     return
  520.     }
  521.     INFO "fowarding to $URI(host):$URI(port)" meta2
  522.     fileevent $fromSock readable \
  523.     [list sockReadable $fromSock $sockServ client]
  524.     fconfigure $sockServ -blocking 0 -buffering none -translation binary
  525.     fileevent $sockServ readable \
  526.     [list sockReadable $sockServ $fromSock server]
  527.     puts -nonewline $sockServ $data
  528. }
  529. ##+##########################################################################
  530. # clntConnect -- Called when we get a new client connection
  531. proc clntConnect {sockClnt ip port} {
  532.     global state SP
  533.  
  534.     set state(sockClnt) $sockClnt
  535.     set state(meta) ""
  536.     
  537.     INFO "connect from [fconfigure $sockClnt -sockname] $port" meta2
  538.     if {$state(proxy) || $SP(servHost) == {} || $SP(servHost) == "none"} {
  539.     set sockServ ""
  540.     } else {
  541.     set n [catch {set sockServ [socket $SP(servHost) $SP(servPort)]} reason]
  542.     if {$n} {
  543.         INFO "cannot connect: $reason"
  544.         close $sockClnt
  545.         ERROR "cannot connect to $SP(servHost) $SP(servPort): $reason"
  546.         insertData meta "----- closed connection -----"
  547.         insertData meta "waiting for new connection..." 
  548.         
  549.     }
  550.     INFO "connecting to $SP(servHost):$SP(servPort)" meta2
  551.     }
  552.  
  553.     ;# Configure connection to the client
  554.     fconfigure $sockClnt -blocking 0 -buffering none -translation binary
  555.     fileevent $sockClnt readable \
  556.         [list sockReadable $sockClnt $sockServ client]
  557.  
  558.     ;# Configure connection to the server
  559.     if {[string length $sockServ]} {
  560.     fconfigure $sockServ -blocking 0 -buffering none -translation binary
  561.     fileevent $sockServ readable \
  562.         [list sockReadable $sockServ $sockClnt server]
  563.     }
  564. }
  565. ##+##########################################################################
  566. # DoListen
  567. # Opens the socket server to listen for connections. It first closes it if
  568. # it is already open.
  569. proc DoListen {} {
  570.     global state SP
  571.  
  572.     set rval 1
  573.     catch {close $state(sockClnt)}        ;# Only the last open connection
  574.     
  575.     ;# Close old listener if it exists
  576.     if {$state(listen) != ""} {
  577.     set n [catch {close $state(listen)} emsg]
  578.     if {$n} { INFO "socket close error: $emsg"}
  579.     set state(listen) ""
  580.     update                    ;# Need else socket below fails
  581.     }
  582.  
  583.     # Listen on clntPort or proxyPort for incoming connections
  584.     set port $SP(clntPort)
  585.     if {$state(proxy)} {set port $SP(proxyPort)}
  586.     set n [catch {set state(listen) [socket -server clntConnect $port]} emsg]
  587.     
  588.     if {$n} {
  589.     INFO "socket open error: $emsg"
  590.     set state(title) "not connected"
  591.     set rval 0
  592.     } else {
  593.     if {$state(proxy)} {
  594.         set state(title) "proxy localhost:$SP(proxyPort)"
  595.     } else {
  596.         set state(title) "localhost:$SP(clntPort) <--> "
  597.         append state(title) "$SP(servHost):$SP(servPort)"
  598.     }
  599.     INFO $state(title)
  600.     INFO "waiting for new connection..."
  601.     }
  602.     wm title . "SockSpy -- $state(title)"
  603.     return $rval
  604. }
  605. ##+##########################################################################
  606. # GetSetup -- Prompts the user for client port, server host and server port
  607. proc GetSetup {} {
  608.     global state SP ok
  609.     array set save [array get SP]
  610.     set ok 0                    ;# Assume cancelling
  611.  
  612.     ;# Put in some default values
  613.     if {![string length $SP(proxyPort)]} {set SP(proxyPort) 8080}
  614.     if {![string length $SP(clntPort)]}     {set SP(clntPort) 8080}
  615.     if {![string length $SP(servPort)]}     {set SP(servPort) 80}
  616.     
  617.     if {! $state(gui)} {
  618.     catch {close $state(listen)}
  619.  
  620.     set d "no" ; if {$state(proxy)} { set d yes }
  621.     set p [Prompt "Proxy mode" $d]
  622.     if {[regexp -nocase {^y$|^yes$} $p]} {
  623.         set state(proxy) 1
  624.         set SP(proxyPort) [Prompt "proxy port" $SP(proxyPort)]
  625.     } else {
  626.         set state(proxy) 0
  627.         set SP(clntPort) [Prompt "Client port" $SP(clntPort)]
  628.         set SP(servHost) [Prompt "Server host" $SP(servHost)]
  629.         set SP(servPort) [Prompt "Server port" $SP(servPort)]
  630.     }
  631.     DoListen
  632.     return
  633.     } 
  634.  
  635.     destroy .dlg
  636.     toplevel .dlg
  637.     wm title .dlg "SockSpy Setup"
  638.     wm geom .dlg +176+176
  639.     #wm transient .dlg .
  640.     
  641.     label .dlg.top -bd 2 -relief raised
  642.     set msg    "You can configure SockSpy to either forward data\n"
  643.     append msg "a fixed server and port or to use the HTTP Proxy\n"
  644.     append msg "protocol to dynamically determine the server and\n"
  645.     append msg "port to forward data to."
  646.  
  647.     frame .dlg.fforward
  648.     frame .dlg.fproxy
  649.     frame .dlg.fcmdline
  650.  
  651.     label .dlg.msg -text $msg -justify left
  652.     radiobutton .dlg.forward -text "Use fixed server forwarding" \
  653.     -variable state(proxy)    -value 0 -anchor w -command GetSetup2
  654.     label .dlg.fl1 -text "Client Port:" -anchor e
  655.     entry .dlg.fe1 -textvariable SP(clntPort)
  656.  
  657.     label .dlg.fl2 -text "Server Host:" -anchor e
  658.     entry .dlg.fe2 -textvariable SP(servHost)
  659.     label .dlg.fl3 -text "Server Port:" -anchor e
  660.     entry .dlg.fe3 -textvariable SP(servPort)
  661.     
  662.     radiobutton .dlg.proxy -text "Use HTTP Proxying" \
  663.     -variable state(proxy)    -value 1 -anchor w -command GetSetup2
  664.     label .dlg.pl1 -text "Proxy Port:" -anchor e
  665.     entry .dlg.pe1 -textvariable SP(proxyPort)
  666.  
  667.     label .dlg.cllabel -text "Command Line Equivalent"
  668.     entry .dlg.clvar -textvariable SP(cmdLine) \
  669.     -borderwidth 2 -relief sunken
  670.     # -state readonly doesn't seem to work, sigh
  671.  
  672.     button .dlg.ok -text OK -width 10 -command ValidForm
  673.     button .dlg.cancel -text Cancel -width 10 -command [list destroy .dlg]
  674.     
  675.     grid .dlg.top -row 0 -column 0 -columnspan 3 -sticky ew -padx 10 -pady 10
  676.     grid columnconfigure .dlg 0 -weight 1
  677.     grid x .dlg.ok .dlg.cancel -padx 10
  678.     grid configure .dlg.ok -padx 0
  679.     grid rowconfigure .dlg 2 -minsize 8
  680.     
  681.     pack .dlg.msg -in .dlg.top -side top -fill x -padx 10 -pady 5
  682.     pack .dlg.fforward .dlg.fproxy .dlg.fcmdline -in .dlg.top \
  683.     -side top -fill x -padx 10 -pady 10
  684.     
  685.     grid .dlg.cllabel -in .dlg.fcmdline -row 0 -column 0 -sticky w 
  686.     grid .dlg.clvar -in .dlg.fcmdline -row 1 -column 0 -sticky ew
  687.     grid columnconfigure .dlg.fcmdline 0 -weight 1
  688.     # no need for row/col configure
  689.  
  690.     grid .dlg.proxy - - -in .dlg.fproxy -sticky w
  691.     grid x .dlg.pl1 .dlg.pe1 -in .dlg.fproxy -sticky ew
  692.     grid columnconfigure .dlg.fproxy 0 -minsize .2i
  693.     grid columnconfigure .dlg.fproxy 2 -weight 1
  694.     grid columnconfigure .dlg.fproxy 3 -minsize 10
  695.     grid rowconfigure .dlg.fproxy 2 -minsize 10
  696.  
  697.     grid .dlg.forward - - -in .dlg.fforward -sticky w
  698.     grid x .dlg.fl1 .dlg.fe1 -in .dlg.fforward -sticky ew
  699.     grid x .dlg.fl2 .dlg.fe2 -in .dlg.fforward -sticky ew
  700.     grid x .dlg.fl3 .dlg.fe3 -in .dlg.fforward -sticky ew
  701.     grid columnconfigure .dlg.fforward 0 -minsize .2i
  702.     grid columnconfigure .dlg.fforward 2 -weight 1
  703.     grid columnconfigure .dlg.fforward 3 -minsize 10
  704.     grid rowconfigure .dlg.fforward 4 -minsize 10
  705.     raise .dlg
  706.  
  707.     bind .dlg.forward <Return>    [bind all <Key-Tab>]
  708.     bind .dlg.proxy <Return>  [bind all <Key-Tab>]
  709.     bind .dlg.fe1 <Return> [bind all <Key-Tab>]
  710.     bind .dlg.fe2 <Return> [bind all <Key-Tab>]
  711.     bind .dlg.fe3 <Return> [list .dlg.ok invoke]
  712.     bind .dlg.pe1 <Return> [list .dlg.ok invoke]
  713.  
  714.     GetSetup2
  715.     .dlg.pe1 icursor end
  716.     .dlg.fe2 icursor end
  717.  
  718.     # trace all variables involved in the Setup window 
  719.     trace variable state(proxy) w cmdlineUpdate
  720.     trace variable SP w cmdlineUpdate
  721.     cmdlineUpdate SP servHost w
  722.  
  723.     if {$state(proxy)} { focus -force .dlg.pe1 } { focus -force .dlg.fe2 }
  724.  
  725.     raise .dlg
  726.     tkwait window .dlg
  727.     wm deiconify .  
  728.  
  729.     if {$ok} {
  730.     DoListen
  731.     } else {
  732.     array set SP [array get save]
  733.     }
  734. }
  735. ##+##########################################################################
  736. # GetSetup2 -- toggles between forwarding and proxying modes in the dialog
  737. proc GetSetup2 {} {
  738.     global state
  739.     array set s {1 normal 0 disabled}
  740.     if {! $state(proxy)} { array set s {0 normal 1 disabled} }
  741.     
  742.     .dlg.pl1 config -state $s(1)
  743.     .dlg.pe1 config -state $s(1)
  744.     foreach w {1 2 3} {
  745.     .dlg.fl$w config -state $s(0)
  746.     .dlg.fe$w config -state $s(0)
  747.     }
  748. }
  749. ##+##########################################################################
  750. # ValidForm -- if setup dialog has valid entries then kill the dialog
  751. proc ValidForm {} {
  752.     global state SP ok
  753.     set ok 0
  754.     if {$state(proxy)} {
  755.     if {$SP(proxyPort) != ""} {set ok 1}
  756.     } elseif {$SP(clntPort) !="" && $SP(servHost) !="" && $SP(servPort) !=""} {
  757.     set ok 1
  758.     }
  759.     if {$ok} {destroy .dlg}
  760.     return $ok
  761. }
  762. ##+#########################################################################
  763. #
  764. # cmdlineUpdate
  765. #
  766. # cmdlineUpdate watches the connection variables and updates the command-line
  767. # equivalent.
  768. #
  769. proc cmdlineUpdate {X elt X} {
  770.     global SP
  771.  
  772.     # Check that port values are integers and that server host is not empty.
  773.     if {$::state(proxy)} {
  774.     set SP(cmdLine) "sockspy -proxy $SP(proxyPort)"
  775.     if {! [string is integer -strict $SP(proxyPort)]} {
  776.         set SP(cmdLine) "none (invalid proxy port above)"
  777.     }
  778.     return
  779.     }
  780.     
  781.     if {$SP(servHost) == ""} {
  782.     set SP(cmdLine) "none (invalid server host above)"
  783.     return
  784.     }
  785.     foreach elt {clntPort servPort} lbl {"client port" "server port"} {
  786.     if {! [string is integer -strict $SP($elt)]} {
  787.         set SP(cmdLine) "none (invalid $lbl above)"
  788.         return
  789.     }
  790.     }
  791.     set SP(cmdLine) "sockspy $SP(clntPort) $SP(servHost) $SP(servPort)"
  792. }
  793. ##+##########################################################################
  794. # Prompt -- Non-gui way to get input from the user.
  795. proc Prompt {prompt {default ""}} {
  796.     if {$default != ""} {
  797.     puts -nonewline "$prompt ($default): "
  798.     } else {
  799.     puts -nonewline "$prompt: "
  800.     }
  801.     flush stdout
  802.     set n [gets stdin line]
  803.  
  804.     if {$n == 0 && $default != ""} {
  805.     set line $default
  806.     }
  807.     return $line
  808. }
  809. ##+##########################################################################
  810. # Shutdown -- Closes the listen port before exiting
  811. proc Shutdown {} {
  812.     global state
  813.  
  814.     catch {close $state(listen)}
  815.     stateSaveReal                ;# save all state info NOW!
  816.     exit
  817. }
  818. ##+##########################################################################
  819. # ButtonBar -- Toggles the visibility of the button bar
  820. proc ButtonBar {} {
  821.     global state
  822.  
  823.     if {$state(bbar)} {                ;# Need to add button bar
  824.     pack .cmd -side top -fill x -pady 5 -in .bbar
  825.     } else {
  826.     pack forget .cmd
  827.     .bbar config -height 1            ;# Need this to give remove gap
  828.     }
  829. }
  830. ##+##########################################################################
  831. # ToggleExtract -- Toggles the visibility of the extract window
  832. proc ToggleExtract {{how 0}} {
  833.     global state
  834.  
  835.     if {$how == -1} {                ;# Hard kill
  836.     destroy .extract
  837.     set state(extract) 0
  838.     return
  839.     }
  840.     if {$state(extract)} {
  841.     createExtract
  842.     } else {
  843.     catch {wm withdraw .extract}
  844.     }
  845. }
  846. ##+##########################################################################
  847. # ToggleWrap -- turns on or off wrap in the text window
  848. proc ToggleWrap {} {
  849.     global state
  850.     array set x {0 none 1 char}
  851.     .out configure -wrap $x($state(autowrap))
  852. }
  853. ##+##########################################################################
  854. # ToggleCapture -- puts up a help message
  855. proc ToggleCapture {} {
  856.     global state
  857.     if {$state(capture)} {
  858.     INFO "Data capture display enabled" meta
  859.     .out config -bg white
  860.     } else {
  861.     INFO "Data capture display disabled" meta 1
  862.     .out config -bg grey88
  863.     }
  864. }
  865. ##+##########################################################################
  866. # Help -- a simple help system
  867. proc Help {} {
  868.     destroy .help
  869.     toplevel .help
  870.     wm title .help "SockSpy Help"
  871.     wm geom .help "+[expr {[winfo x .] + 50}]+[expr {[winfo y .] + 50}]"
  872.  
  873.     text .help.t -relief raised -wrap word -width 70 -height 25 \
  874.     -padx 10 -pady 10 -cursor {} -yscrollcommand {.help.sb set}
  875.     scrollbar .help.sb -orient vertical -command {.help.t yview}
  876.     button .help.dismiss -text Dismiss -command {destroy .help}
  877.     pack .help.dismiss -side bottom -pady 10
  878.     pack .help.sb -side right -fill y
  879.     pack .help.t -side top -expand 1 -fill both
  880.  
  881.     set bold "[font actual [.help.t cget -font]] -weight bold"
  882.     set fixed "[font actual [.help.t cget -font]] -family courier"
  883.     .help.t tag config title -justify center -foregr red -font "Times 20 bold"
  884.     .help.t tag configure title2 -justify center -font "Times 12 bold"
  885.     .help.t tag configure header -font $bold
  886.     .help.t tag configure n -lmargin1 15 -lmargin2 15
  887.     .help.t tag configure fixed -font $fixed -lmargin1 25 -lmargin2 55
  888.  
  889.     .help.t insert end "SockSpy\n" title
  890.     .help.t insert end "Authors: Tom Poindexter and Keith Vetter\n\n" title2
  891.  
  892.     set m "SockSpy lets you watch the conversation of a tcp client and server. "
  893.     append m "SockSpy acts much like a gateway: it waits for a tcp connection, "
  894.     append m "then connects to the real server. Data from the client is passed "
  895.     append m "on to the server, and data from the server is passed onto the "
  896.     append m "client.\n\n"
  897.  
  898.     append m "Along the way, the data streams are also displayed in text "
  899.     append m "widget with data sent from the client displayed in green, data "
  900.     append m "from the server in blue and connection metadata in red. The data "
  901.     append m "can be displayed as printable ASCII or both hex and "
  902.     append m "printables.\n\n"
  903.     .help.t insert end "What is SockSpy?\n" header $m n
  904.  
  905.     set m "Why might you want to use SockSpy? Debugging tcp client/server "
  906.     append m "programs, examining protocols and diagnosing network problems "
  907.     append m "are top candidates. Perhaps you just want to figure out how "
  908.     append m "something works. I've used it to bypass firewalls, to rediscover "
  909.     append m "my lost smtp password, to access a news server on a remote "
  910.     append m "network, etc.\n\nIt's not a replacement for heavy-duty tools "
  911.     append m "such as 'tcpdump' and other passive packet sniffers. On the "
  912.     append m "other hand, SockSpy doesn't require any special privileges to "
  913.     append m "run (unless of course, you try to listen on a Unix reserved tcp "
  914.     append m "port less than 1024.)\n\n"
  915.     .help.t insert end "Why Use SockSpy?\n" header $m n
  916.  
  917.     set m "Just double click on SockSpy to start it. You will be prompted for "
  918.     append m "various connection parameters described below.\n\n"
  919.     append m "Alternatively, you can specify the connection parameters on the "
  920.     append m "command line. This is also how you can run SockSpy in text mode "
  921.     append m "without a GUI.\n\n"
  922.     append m "To start SockSpy from the command line:\n"
  923.     .help.t insert end "How to Use SockSpy\n" header $m n
  924.     
  925.     set m "$ sockspy <listen-port> <server-host> <server-port>\n  or\n"
  926.     append m "$ sockspy -proxy <proxy-port>\n\n"
  927.     .help.t insert end $m fixed
  928.  
  929.     set m "To start SockSpy in text mode without a GUI:\n"
  930.     .help.t insert end $m n
  931.     set m "$ tclsh sockspy <listen-port> <server-host> <server-port>\n    or\n"
  932.     append m "$ tclsh sockspy -proxy <proxy-port>\n\n"
  933.     .help.t insert end $m fixed
  934.  
  935.     set m    "<listen-port>: the tcp port on which to listen. Clients should "
  936.     append m "connect to this port.\n"
  937.     append m "<server-host>:  the host where the real server runs.\n"
  938.     append m "<server-port>:  the tcp port on which the real server listens.\n"
  939.     append m "<proxy-port>:  the tcp port on which to listen in proxy-mode.\n\n"
  940.     .help.t insert end $m n
  941.  
  942.     set m "In proxy mode SockSpy works like a simple HTTP proxy server. "
  943.     append m "Instead of forwarding to a fixed server and port, it follows the "
  944.     append m "HTTP proxy protocol and reads the server information from the "
  945.     append m "first line of HTTP request.\n\n"
  946.     append m "You can turn on proxy mode by selecting it in the SockSpy Setup "
  947.     append m "dialog, or by specifying -proxy on the command line.\n\n"
  948.     .help.t insert end "Proxy Mode\n" header $m n
  949.  
  950.     set m "The extract window lets you extract specific parts of the "
  951.     append m "data stream. As data arrives from the client, server, or as "
  952.     append m "metadata, it gets matched against the appropriate regular "
  953.     append m "expression filter. If it matches, the data gets displayed "
  954.     append m "in the extract window. (Malformed regular expression are "
  955.     append m "silently ignored.)\n\n"
  956.     .help.t insert end "Extract Window\n" header $m n
  957.  
  958.     set m "To spy on HTTP connections to a server, type:\n"
  959.     .help.t insert end "Example\n" header $m n
  960.     .help.t insert end "  sockspy 8080 www.some.com 80\n" fixed
  961.     .help.t insert end "and point your browser to\n" n
  962.     .help.t insert end "  http://localhost:8080/index.html\n\n" fixed
  963.     .help.t insert end "Alternatively, you could configure your browser to " n
  964.     .help.t insert end "use localhost and port 8000 as its proxy, and then " n
  965.     .help.t insert end "type:\n" n
  966.     .help.t insert end "  sockspy -proxy 8000\n" fixed
  967.     .help.t insert end "and user your browser normally.\n" n
  968.     
  969.     .help.t config -state disabled
  970. }
  971. ##+##########################################################################
  972. # About -- simple about box
  973. proc About {} {
  974.     set m "SockSpy  version $::state(version)\n"
  975.     append m "by Tom Poindexter and Keith Vetter\n"
  976.     append m "Copyright 1998-[clock format [clock seconds] -format %Y]\n\n"
  977.     append m "A program to eavesdrop on a tcp client server conversation."
  978.     tk_messageBox -icon info -title "About SockSpy" -message $m -parent .
  979. }
  980. ##+##########################################################################
  981. # DoExtract -- Displays any data matching the RE in the extract window
  982. proc DoExtract {who data timestamp} {
  983.     global state extract
  984.  
  985.     if {! $state(gui)} return
  986.     if {! [info exists extract($who)]} return
  987.     if {! [winfo exists .extract]} return
  988.     
  989.     regsub -all \r $data "" data
  990.     foreach line [split $data \n] {
  991.     if {$extract($who) == ""} continue
  992.     catch {
  993.         if {[regexp -nocase $extract($who) $line]} {
  994.         .extract.out insert end "$timestamp" time_$who
  995.         .extract.out insert end "$line\n" $who
  996.         }
  997.     }
  998.     }
  999.     if {$state(autoscroll)} {
  1000.     .extract.out see end
  1001.     }
  1002. }
  1003. ##+##########################################################################
  1004. # stateRestore - Initialize save/restore package and do restore.
  1005. proc stateRestore {} {
  1006.     global env state SP extract
  1007.  
  1008.     switch $::tcl_platform(platform) "macintosh" {
  1009.     set stateFile [file join $env(PREF_FOLDER) "SockSpy Preferences"]
  1010.     } "windows" {
  1011.     set stateFile [file join $env(HOME) "sockspy.cfg"]
  1012.     } "unix" {
  1013.     if {[info exists env(DOTDIR)]} {
  1014.         set stateFile [file join $env(DOTDIR) .sockspy]
  1015.     } else {
  1016.         set stateFile [file join $env(HOME) .sockspy]
  1017.     }
  1018.     }
  1019.     
  1020.     # complain only if it exists and we fail to read it successsfully
  1021.     if {[file exists $stateFile]} {
  1022.     uplevel #0 source $stateFile
  1023.     }
  1024.     
  1025.     set state(stateFile) $stateFile
  1026.     
  1027.     foreach v $::saveList {
  1028.     trace variable $v w stateSave
  1029.     }
  1030. }
  1031. ##+#########################################################################
  1032. #
  1033. # stateSave and stateSaveReal - Save program state.
  1034. #
  1035. # Two procs are used to do this.  stateSave is called to schedule the save.
  1036. # stateSaveReal is called to actually do the save.
  1037. #
  1038. # stateSave schedules the save a short time in the future to avoid interfering
  1039. # with the UI.    This is especially a problem with the "extract" variables which
  1040. # aren't edited from a modal dialogue and thus have no associated "OK" button
  1041. # to tell us when to save them.     (The alternative would be to save them after
  1042. # every keystroke - yuk.)
  1043. #
  1044. proc stateSave {a b c} {
  1045.     catch {after cancel $::state(saveId)}
  1046.     set ::state(saveId) [after 5000 stateSaveReal]
  1047. }
  1048.  
  1049. proc stateSaveReal {} {
  1050.     global state SP extract
  1051.  
  1052.     # silently ignore open failure
  1053.     if {[catch {open $state(stateFile) w} sf]} return
  1054.  
  1055.     set now [clock format [clock seconds] -format %c]
  1056.     puts $sf "# SockSpy Initialization File"
  1057.     puts $sf "# Written by SockSpy $state(version) on $now."
  1058.     puts $sf "#"
  1059.     puts $sf "# Warning: If you edit this file while SockSpy is running, "
  1060.     puts $sf "# edits will be lost! Also, only edit the lines already here. "
  1061.     puts $sf "# If you add procs or more variables, they will not be saved."
  1062.     
  1063.     puts $sf ""
  1064.     foreach v $::saveList {
  1065.     puts $sf "set $v \"[string map {[ \\[ \\ \\\\} [set $v]]\""
  1066.     }
  1067.     close $sf
  1068. }
  1069.  
  1070. ################################################################
  1071. ################################################################
  1072. ################################################################
  1073.  
  1074. set state(gui) [info exists tk_version]
  1075. if {[catch {package present uri}]} {
  1076.     ERROR "ERROR: SockSpy requires the uri package from tcllib."
  1077.     exit 1
  1078. }
  1079.     
  1080. timestampInit
  1081. stateRestore
  1082.  
  1083. if {$state(gui)} createMain
  1084.  
  1085. if {[lindex $argv 0] == "-local"} {
  1086.     set argv [list 8080 localhost 80]
  1087.     set argc 3
  1088. }
  1089.  
  1090. if {[lindex $argv 0] == "-proxy"} {
  1091.     set state(proxy) 1
  1092.     if {$argc == 2} {
  1093.     set SP(proxyPort) [lindex $argv 1]
  1094.     DoListen
  1095.     } else {
  1096.     GetSetup
  1097.     }
  1098. } else {
  1099.     if {$argc >= 1} { set SP(clntPort) [lindex $argv 0] }
  1100.     if {$argc >= 2} { set SP(servHost) [lindex $argv 1] }
  1101.     if {$argc >= 3} { set SP(servPort) [lindex $argv 2] }
  1102.     if {$argc >= 3} {
  1103.     DoListen
  1104.     } else {
  1105.     GetSetup
  1106.     }
  1107. }
  1108.  
  1109. if {$state(extract)} createExtract
  1110.  
  1111. if {! $state(gui)} {
  1112.     vwait forever                ;# tclsh needs this
  1113. } else {
  1114.     wm deiconify .
  1115. }
  1116.