Overview | Index by: file name | procedure name | procedure call | annotation
tools-1.0.tm (annotations | original source)

#
#    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

Overview | Index by: file name | procedure name | procedure call | annotation
File generated 2010-03-13 at 22:28.