# # Copyright (C) 2010 Alexandros Stergiakis <alsterg@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # #//# # The "tools" module provides commands to aid network and host troubleshooting. # # @todo Interructive version of all commands. Currently only 'Traceroute', 'MailSend' and 'MailRecv' have interructive versions. #//# module require base 1.0 module require interface 1.0 module provide tools 1.0 namespace eval ::module::tools { namespace import ::helper::* ::module::api::* \ ::email::* ::module::interface::dlist_interfaces proc description {} { return "Troubleshooting tools and other aids" } proc version {} { } proc check {} { foreach feature [list \ CONFIG_NSLOOKUP \ CONFIG_PING \ CONFIG_TRACEROUTE \ CONFIG_TELNET] { if {! [::helper::busybox_has $feature]} { ;# Can return error. error "Busybox doesn't have support for $feature." } } } proc reset {} { } proc constructor {} { log::Info "Loading \"tools\" module: [description]" check reset # Finally load Command Specs sysconf loadspecs "modules/tools/tools.specs" } proc destructor {} { # First unload Command Specs sysconf remove "tools" reset } ################ # Handlers ################ # @interactive command Telnet {cmdline argstart sid out no arguments args} { if {[dict exists $arguments PORT]} { set port [dict get $arguments PORT] if {! [isipport $port]} { ferror "illegal port number" [getpos PORT] } } else { set port {} } if {[dict exists $arguments USER]} { set user "-l [dict get $arguments USER]" } else { set user {} } set host [dict get $arguments HOST] if {! [ishostname $host] && ! [isip $host]} { ferror "illegal IP address or hostname" [getpos HOST] } ptyexec telnet {*}$user $host {*}$port return } command Ping {cmdline argstart sid out no arguments args} { if {[dict exists $arguments SIZE]} { set size [dict get $arguments SIZE] # IP header (20 bytes) + ICMP header (8 bytes) + payload if {! [string is integer $size] || ! (0 <= $size <= 65000)} { ;# Somewhat less, to be on the safe side. ( @magic-number ) ferror "illegal payload size" [getpos SIZE] } set size "-s $size" } else { set size {} } ;# 56 bytes. if {[dict exists $arguments COUNT]} { set count [dict get $arguments COUNT] if {! [string is integer $count] || ! (1 <= $count <= 1000000)} { ;# We have to pick some upper bound. ( @magic-number ) ferror "illegal number" [getpos COUNT] } set count "-c $count" } else { set count {} } ;# For ever. if {[dict exists $arguments IPADDR]} { set src [dict get $arguments IPADDR] if {! [isip $src]} { ferror "illegal IP address" [getpos IPADDR] } set src "-I $src" } elseif {[dict exists $arguments INTERFACE]} { set src "-I [dict get $arguments INTERFACE]" } else { set src {} } ;# We don't care which IP address will use as source. set host [dict get $arguments HOST] if {! [ishostname $host] && ! [isip $host]} { ferror "illegal IP address or hostname" [getpos HOST] } ptyexec -root -- ping {*}$size {*}$count {*}$src $host return } command Nslookup {cmdline argstart sid out no arguments args} { set host [dict get $arguments HOST] if {! [ishostname $host] && ! [isip $host]} { ferror "illegal IP address or hostname" [getpos HOST] } if {[dict exists $arguments SERVER]} { set server [dict get $arguments SERVER] if {! [isip $server]} { ferror "illegal ip address" [getpos SERVER] } } else { set server {} } ptyexec -- nslookup $host {*}$server return } # @interactive command Traceroute {cmdline argstart sid out no arguments args} { Global DNS_LOOKUPS if {[dict exists $arguments HOST]} { set host [dict get $arguments HOST] if {! [ishostname $host] && ! [isip $host]} { ferror "illegal IP address or hostname" [getpos HOST] } ptyexec -root -- traceroute -v {*}[? !$DNS_LOOKUPS "-n"] $host return } # Ask the user for details: # @magic-number : Various magic numbers follow: set target [ask -eval {expr {[isip %%] || [ishostname %%]}} "Target IP address or hostname: "] set payload [ask -type integer -default 56 -min 0 -max 65000 "Size of data payload: "] set switches {} lappend switches [ask -switch "-s" -check isip -default {} -noswitch "IP address to use as the source address: "] lappend switches [ask -switch "-i" -list [dlist_interfaces] -default {} -noswitch "Interface to use to send probe packets: "] set icmp [ask -switch "-I" -type boolean -default n "Use ICMP ECHO instead of UDP datagrams: "] if {$icmp ne "-I"} { set port [ask -switch "-p" -check isipport -default 33434 "Base UDP port number used in probes: "] } lappend switches $icmp lappend switches [ask -switch "-n" -neg -type boolean -default y "Display hostnames instead of IP addresses: "] lappend switches [ask -switch "-f" -type integer -default 1 -min 1 -max 256 "First Time to Live: "] lappend switches [ask -switch "-m" -type integer -default 100 -min 1 -max 256 "Maximum Time to Live: "] lappend switches [ask -switch "-q" -type integer -default 3 -min 1 -max 10 "Number of probes per TTL: "] lappend switches [ask -switch "-w" -type integer -default 3 -min 1 -max 100 "Time in seconds to wait for a response: "] lappend switches [ask -switch "-z" -type integer -default 0 -noswitch -min 0 -max 100000 "Time in milliseconds to wait between consequtive TTLs: "] lappend switches [ask -switch "-t" -type integer -default 0 -min 0 -max 256 "Type-of-service in probe packets: "] lappend switches [ask -switch "-F" -type boolean -default n "Set the don't fragment bit: "] lappend switches [ask -switch "-l" -type boolean -default n "Show TTL of reply packets: "] #lappend switches [ask -switch "-r" -type boolean -default n "Skip routing table: "] #lappend switches [ask -switch "-g" -default {} "Loose source route gateway (max 8 IPs): "] sputs "" ;# newline set switches [concat {*}$switches] ptyexec -root -- traceroute -v {*}$switches $target $payload return } # @block # @interactive command MailSend {cmdline argstart sid out no arguments args} { Global TIMEOUT if {[dict exists $arguments SERVER]} { # Information is provided on the command line. set server [dict get $arguments SERVER] if {! ([isip $server] || [ishostname $server])} { ferror "Not a valid hostname or IP address" [getpos SERVER] } set port [getval PORT 25] if {! [isipport $port]} { ferror "Not a valid port number" [getpos PORT] } set username [getval USERNAME {}] set password [getval PASSWORD {}] set from [dict get $arguments FROM] if {! [isemail $from]} { ferror "Not a valid email address" [getpos FROM] } set to [dict get $arguments TO] if {! [isemail $to]} { ferror "Not a valid email address" [getpos TO] } set subject [dict get $arguments SUBJECT] set body [dict get $arguments MESSAGE] } else { # Ask for the necessary information. set server [ask -eval {expr {[isip %%] || [ishostname %%]}} "Hostname or IP address of SMTP Server: "] set port [ask -check isipport -default 25 "Port number the SMTP Server is listening at: "] if {[ask -type boolean -default n "Authenticate on the Server: "]} { set username [ask "Username: "] set password [ask -noecho "Password: "] } else { set username {} set password {} } set from [ask -check isemail "From: "] set to [ask -check isemail "To: "] set subject [ask -length 100 "Subject: "] set body [ask -end "\n.\n" "Body: (end with a single \".\" on a line of its own)\n"] sputs "\nSending e-mail..." } timeout $TIMEOUT { ::email::smtpSend $from $to $subject $body $server $port $username $password } # If we reach here, it means no error occured before. Email was sent. puts "Email sent successfully" return } # @block # @interactive command MailRead {cmdline argstart sid out no arguments args} { Global TIMEOUT Session $sid COLUMNS if {[dict exists $arguments SERVER]} { set server [dict get $arguments SERVER] if {! ([isip $server] || [ishostname $server])} { ferror "Not a valid hostname or IP address" [getpos SERVER] } set username [dict get $arguments USERNAME] set password [dict get $arguments PASSWORD] set port [getval PORT 110] if {! [isipport $port]} { ferror "Not a valid port number" [getpos PORT] } timeout $TIMEOUT { set popsock [::email::popOpen $server $port $username $password] set messages [::email::popRead $popsock] lassign [::email::popCount $popsock] msgcount msgsize ::email::popClose $popsock } puts "\nYou have $msgcount new emails, totaling $msgsize bytes\n" foreach msg $messages { puts [string repeat _ $COLUMNS] puts [lindex $msg 0] } return } # else: set server [ask -eval {expr {[isip %%] || [ishostname %%]}} "Hostname or IP address of POP3 Server: "] set port [ask -check isipport -default 110 "Port number the POP3 Server is listening at: "] set username [ask "Username: "] set password [ask -noecho "Password: "] sputs -nonewline "\nConnecting..." timeout $TIMEOUT { set popsock [::email::popOpen $server $port $username $password] } sputs "OK" sputs -nonewline "Reading Inbox..." timeout $TIMEOUT { set messages [::email::popRead $popsock] } sputs "OK" timeout $TIMEOUT { lassign [::email::popCount $popsock] msgcount msgsize } sputs "\nYou have $msgcount new emails, totaling $msgsize bytes\n" if {$msgcount > 0} { sputs "Press Enter to continue." ask -length 0 -default {} -hidedef "" } for {set i 0} {$i < $msgcount} {incr i} { sputs [string repeat _ $COLUMNS] sputs [lindex $messages $i 0]\n set choice [ask -list {n p d a} "Next (n) - Print again (p) - Delete (d) - Abort (a): "] switch -exact -- $choice { "n" { continue } "p" { incr i -1 } "d" { timeout $TIMEOUT { ::email::popDelete $popsock $i+1 } } "a" { break } } } sputs -nonewline "Closing mailbox..." timeout $TIMEOUT { ::email::popClose $popsock } sputs "OK" return } # @block command MailCheck {cmdline argstart sid out no arguments args} { Global TIMEOUT set server [dict get $arguments SERVER] if {! ([isip $server] || [ishostname $server])} { ferror "not a valid hostname or IP address" [getpos SERVER] } set username [dict get $arguments USERNAME] set password [dict get $arguments PASSWORD] set port [getval PORT 110] timeout $TIMEOUT { set popsock [::email::popOpen $server $port $username $password] lassign [::email::popCount $popsock] msgcount msgsize ::email::popClose $popsock } puts "You have $msgcount new emails, totaling $msgsize bytes" return } } ;# End of Namespace