# Description: allows any irc client to connect to RoxIRC and share a single irc session (like psybnc) package require roxirc 2.0 procs command_rc AddToPrefs rcport "num 1025 65535" 4545 AddToPrefs rcupdatelines "num 0 1000" 20 AddToPrefs rcpass string "" proc command_rc {window line} { global rc prefs irc set line [rele $line] switch -glob -- [lindex $line 0] { on { if {$rc(amserver)} { Echo .0 {[ info ] RC server is already on} {info default} return } if {$prefs(rcpass) == ""} {Echo .0 {[ info ] WARNING: no RC password set, use /set RCPASS} {info default}} if {[catch "socket -server [namespace qualifiers [namespace origin command_rc]]::ServerConnect $prefs(rcport)" err]} { Echo .0 "\[ info \] Error: could not bind rc server port $prefs(rcport): $err" {info default} return } Echo .0 "\[ info \] RC server accepting connections on port $prefs(rcport)" {info default} array set rc [list amserver 1 server $err] } off { Echo .0 {[ info ] RC server is off} {info default} if {!$rc(amserver)} {return} if {[catch {close $rc(server)} err]} {Echo .0 "\[ error \] Could not close RC socket: [geterr $err]" {error default}} array set [list rc amserver 0 server {}] } "" { if {$rc(amserver)} { Echo $window "\[ info \] RC server is accepting connection on port [lindex [fconfigure $rc(server) -sockname] 2], [llength $rc(client)] clients" } else { Echo $window {[ info ] RC server is off} {info default} } } default { Echo $window {[ info ] Unknown RC command} {info default} } } } proc AddClient {sock} { global rc if {[lsearch -exact $rc(client) $sock] == -1} { lappend rc(client) $sock } else { Echo .0 "error adding client" } } proc DelClient {sock} { global rc if {[set num [lsearch -exact $rc(client) $sock]] != -1} { set rc(client) [lreplace $rc(client) $num $num] } else { Echo .0 "error deleting client" } } proc ServerConnect {sock addr port} { Echo .0 "\[ info \] RC connection from $addr" {info default} fileevent $sock readable [list [namespace qualifiers [namespace origin ServerConnect]]::ServerAuth $sock $addr] fconfigure $sock -blocking 0 -buffering none after 5000 [list [namespace qualifiers [namespace origin ServerConnect]]::ServerKill $sock $addr] } proc ServerKill {sock addr} { global rc if {[lsearch -exact $rc(client) $sock] == "-1"} { catch {close $sock} Echo .0 "\[ info \] Closed RC client connection from $addr" {info default} } } proc ServerAuth {sock addr} { global rc prefs if {[catch {gets $sock} line]} { close $sock Echo .0 "\[ info \] Error reading password from $addr" {info default} return } if {$line == ""} {return} if {$line != "PASS $prefs(rcpass)"} { close $sock Echo .0 "\[ info \] Bad password from $addr" {info default} return } Echo .0 "\[ info \] Password accepted from $addr" {info default} AddClient $sock fileevent $sock readable [list [namespace current]::ServerParse $sock] global me server away info set host [info hostname] ServerSend2 $sock ":$host 001 $me :You are connected to a remote session on $host which is connected to $server" ServerSend2 $sock ":$host 221 $me :+[join [umode] {}]" if {$away} {ServerSend2 $sock ":$host 306 $me :You have been marked as being away"} # ServerSend2 $sock ":$host NOTICE $me :Resuming session" # ServerFillWindow .0 $sock # ServerSend2 $sock ":$host NOTICE $me :Resume completed" foreach x [activechannelwindows] { ServerSend2 $sock ":$me!remotesession JOIN :$info(channel,$x)" ServerSend2 $sock ":$host 332 $me $info(channel,$x) :[topic $info(channel,$x)]" #ServerSend2 $sock ":$host 333 $me $info(channel,$x) :0" set names [$x.middle.right.nicks.list get 0 end] for {set i 0} {$i < [llength $names]} {incr i 50} { ServerSend2 $sock ":$host 353 $me @ $info(channel,$x) :[join [lrange $names $i [expr $i + 49]]]" } ServerSend2 $sock ":$host 366 $me $info(channel,$x) :End of /NAMES list" #ServerSend2 $sock ":$host NOTICE $info(channel,$x) :" ServerSend2 $sock ":$host NOTICE $info(channel,$x) :Resuming session" ServerFillWindow $x $sock ServerSend2 $sock ":$host NOTICE $info(channel,$x) :Resume completed" #ServerSend2 $sock ":$host NOTICE $info(channel,$x) :" } } proc ServerFillWindow {win sock} { global info prefs server set host [info hostname] foreach x [split [$info(text,$win) get end-$prefs(rcupdatelines)l end] "\n"] { if {$x != ""} { set x [string range $x [expr [string first " " $x] + 1] end] ServerSend2 $sock ":>!server@$host PRIVMSG $info(channel,$win) :$x" } } } proc ServerParse {sock} { set line "" catch [list gets $sock line] if {[eof $sock]} { if {[catch {fconfigure $sock -peername} addr]} { Echo .0 "\[ info \] RC client disconnected" } else { Echo .0 "\[ info \] RC client [lindex $addr 0] disconnected" } DelClient $sock close $sock return } if {$line == ""} {return} #puts "client said: $line" if {[set pos [string first " :" $line]] > -1} { set header [string range $line 0 [expr $pos - 1]] set line2 [string range $line [expr $pos + 2] end] } else { set line [string trim $line] set pos [string last " " $line] set header [string range $line 0 [expr $pos - 1]] set line2 [string range $line [expr $pos + 1] end] } switch -exact -- [string toupper [lindex $header 0]] { CMD { .0.cmdline delete 0 end .0.cmdline insert 0 /[join [lrange [split $line] 1 end]] Command .0.cmdline return } PING { ServerSend2 $sock ":[info hostname] PONG [info hostname] :[lindex $header 1]" return } PRIVMSG { global info me if {[info exists info(window,[lindex $header 1])] && [winfo exists $info(window,[lindex $header 1])]} { Echo $info(window,[lindex $header 1]) "<$me> $line2" {me default} } } NOTICE { if {[string match \001*\001 $line]} {return} } NICK { global connecting if {[info exists connecting]} {return} ServerSend2 $sock ":[info hostname] NOTICE * :To change your nick use /quote CMD nick " return } USER {return} ISON {return} QUIT {return} } Send $line } proc parse {header line} { global rc if {$rc(client) != ""} { set numeric [lindex $header 1] switch -exact -- $numeric { 001 { set line "Remote session on [info hostname] connected to [lindex $header 0]" set header [list [info hostname] 001 [lindex $header end]] } } #puts "to all clients: [join $header] :$line" foreach x $rc(client) { if {[catch {puts $x ":[join $header] :$line"} msg]} { Echo .0 "\[ info \] Error sending to RC client: [geterror $msg]" {info default} close $x DelClient $x } } } } proc ServerSend2 {sock line} { global rc #puts "to $sock client: $line" if {[catch {puts $sock $line} msg]} { Echo .0 "\[ info \] Error sending to RC client: [geterror $msg]" {info default} } } proc unload {} { global rc catch {close $rc(server)} foreach x $rc(client) {catch {close $x}} RemoveFromPrefs rcport RemoveFromPrefs rcupdatelines RemoveFromPrefs rcpass unset rc foreach x [split [info body ::Parse] \n] { if {$x != "[namespace current]::parse \$header \$line"} {lappend body $x} } proc ::Parse [info args ::Parse] [join $body \n] } proc help {window line} { switch -exact -- $line { remote { Echo $window {[ help ] Commands added by this script: /rc} {help default} Echo $window {[ help ] Variables added by this script: RCPASS RCPORT RCUPDATELINES} {help default} Echo $window {[ help ] RCPASS is the password that clients must use to connect to the rc server, do not leave this blank} {help default} Echo $window {[ help ] RCPORT set the port that the rc server listens on, the default is 4545} {help default} Echo $window {[ help ] RCUPDATELINES is how many lines of the buffer to send to new clients} {help default} } rc { Echo $window {[ help ] Usage: /rc [on|off]} {help default} Echo $window {[ help ] If no argument is given then the status of the rc server is printed, otherwise the rc service is turned on or off} {help default} } } } startup array set ::rc [list amserver 0 client "" server ""] set body [info body ::Parse] append body "\n[namespace current]::parse \$header \$line" proc ::Parse [info args ::Parse] $body