Check-in [cf468d004f]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial work to date
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:cf468d004f4177a54b14f468befb7733e248ac56
User & Date: rkeene 2014-07-02 06:29:00
Context
2014-07-02
06:36
Updated to use "eval" in the shell for safer execution check-in: 6cdd98d6d0 user: rkeene tags: trunk
06:29
Initial work to date check-in: cf468d004f user: rkeene tags: trunk
06:27
initial empty check-in check-in: 7b1a8cd2f0 user: rkeene tags: trunk
Changes

Added .fossil-settings/ignore-glob.



>
1
netexec

Added Makefile.













>
>
>
>
>
>
1
2
3
4
5
6
netexec: bin/netexec lib/getopt/getopt.tcl netexec.sed
	sed -f netexec.sed bin/netexec > netexec
	chmod +x netexec

clean:
	rm -f netexec

Added bin/netexec.





















































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#! /usr/bin/env tclsh

set sshopts [list]
lappend sshopts -o ForwardAgent=no -o StrictHostKeyChecking=no -o CheckHostIP=yes -o UserKnownHostsFile=$env(HOME)/.ssh/netexec_known_hosts
lappend sshopts -o PasswordAuthentication=no -o BatchMode=yes -o PreferredAuthentications=publickey
lappend sshopts -o RequestTTY=no

lappend auto_path ../lib

package require getopt

getopt flag arg $argv {
	-h - --help {
		help
	}
	-D: {
		set opts(delay) $arg
	}
	-f: {
		set opts(file) $arg
	}
	-g: {
		lappend opts(groups) $arg
	}
	unknown {
		puts stderr "Unknown option: \"$arg\""

		exit 1
	}
	missing {
		puts stderr "Option (\"$arg\") requires a value"

		exit 1
	}
	arglist {
		set argv $arg
	}
}

set hosts $opts(file)

proc issue_command_to_host {host cmdid} {
	set sock $::socks($host)

	catch {
		puts -nonewline $sock "echo START_$cmdid;"
		puts -nonewline $sock "$::commands($cmdid);"
		puts -nonewline $sock "echo;"
		puts $sock "echo END_$cmdid;"

		flush $sock
	}
}

proc issue_command {cmd hosts} {
	set cmdid [expr rand()][expr rand()][expr rand()][expr rand()][expr rand()]
	set ::commands($cmdid) $cmd

	foreach host $hosts {
		set ::host_data([list $host $cmdid]) [list]
		set ::commands_completed($cmdid) [list]

		issue_command_to_host $host $cmdid
	}

	while 1 {
		vwait ::commands_completed($cmdid)

		if {[llength $::commands_completed($cmdid)] == [llength $hosts]} {
			break
		}
	}

	unset ::commands($cmdid)
	unset ::commands_completed($cmdid)

	set retval [list]
	foreach host $hosts {
		set data $::host_data([list $host $cmdid])
		if {[lindex $data end] == ""} {
			set data [lrange $data 0 end-1]
		}

		lappend retval $host $data
		unset ::host_data([list $host $cmdid])
	}

	return $retval
}

proc get_data_from_host {host sock} {

	gets $sock line

	if {[info exists ::debug]} {
		puts stderr "$host\($sock) -> $line"
		flush stderr
	}

	foreach cmdid [array names ::commands] {
		if {$line == "START_$cmdid"} {
			set ::host_command($host) $cmdid

			return
		}
		if {$line == "END_$cmdid" && $::host_command($host) == $cmdid} {
			lappend ::commands_completed($cmdid) $host

			unset ::host_command($host)

			return
		}
	}

	if {[info exists ::host_command($host)]} {
		set cmdid $::host_command($host)
	}

	if {[eof $sock]} {
		catch {
			close $sock
		}

		if {[info exists cmdid]} {
			if {[lsearch -exact $::commands_completed($cmdid) $host] == -1} {
				lappend ::commands_completed($cmdid) $host
			}
		}

		unset -nocomplain ::host_command($host)
		unset -nocomplain ::socks($host)
	}

	if {![info exists cmdid]} {
		return
	}

	lappend ::host_data([list $host $cmdid]) $line
	
}

proc connect_host {host} {
	set ::socks($host) [open |[list ssh {*}$::sshopts $host 2>@1] "w+"]
	fileevent $::socks($host) readable [list get_data_from_host $host $::socks($host)]
}

proc prompt {} {
	puts -nonewline "netexec$ "
	flush stdout
}

proc handle_command {cmd} {
	switch -- [string trim $cmd] {
		"exit" {
			foreach host [array names ::socks] {
				catch {
					close $::socks($host)
				}
			}
			exit
		}
		"" {
			return [list]
		}
	}

	set hosts [array names ::socks]

	# Direct input
	if {[array names ::commands] != ""} {
		foreach host $hosts {
			puts $::socks($host) $cmd
			flush $::socks($host)
		}

		return [list]
	}

	# Issue a command
	return [issue_command $cmd $hosts]
}

proc get_command {} {
	gets stdin cmd
	array set results [handle_command $cmd]

	foreach host [lsort -dictionary [array names results]] {
		foreach line $results($host) {
			puts "$host: $line"
		}
	}

	prompt
}

foreach host $hosts {
	connect_host $host
}

prompt
fileevent stdin readable [list get_command]
vwait forever

Added lib/getopt/getopt.tcl.









































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#! /usr/bin/env tclsh

package require Tcl 8.5

proc getopt {optvar argvar list body} {
    upvar 1 $optvar option $argvar value
    set arg(missing) [dict create pattern missing argument 0]
    set arg(unknown) [dict create pattern unknown argument 0]
    foreach {pat code} $body {
       switch -glob -- $pat {
           -- {# end-of-options option}
           --?*: {# long option requiring an argument
               set arg([string range $pat 0 end-1]) \
                 [dict create pattern $pat argument 1]
           }
           --?* {# long option without an argument
               set arg($pat) [dict create pattern $pat argument 0]
           }
           -?* {# short options
               set last ""; foreach c [split [string range $pat 1 end] ""] {
                   if {$c eq ":" && $last ne ""} {
                       dict set arg($last) argument 1
                       set last ""
                   } else {
                       set arg(-$c) [dict create pattern $pat argument 0]
                       set last -$c
                   }
               }
           }
       }
    }
    while {[llength $list]} {
       set rest [lassign $list opt]
       # Does it look like an option?
       if {$opt eq "-" || [string index $opt 0] ne "-"} break
       # Is it the end-of-options option?
       if {$opt eq "--"} {set list $rest; break}
       set option [string range $opt 0 1]
       set value 1
       if {$option eq "--"} {
           # Long format option
           if {[info exists arg($opt)]} {
               set option $opt
           } elseif {[llength [set match [array names arg $opt*]]] == 1} {
               set option [lindex $match 0]
           } else {
               # Unknown or ambiguous option
               set value $opt
               set option unknown
           }
           if {[dict get $arg($option) argument]} {
               if {[llength $rest]} {
                   set rest [lassign $rest value]
               } else {
                   set value $option
                   set option missing
               }
           }
       } elseif {![info exists arg($option)]} {
           set value $option
           set option unknown
           if {[string length $opt] > 2} {
               set rest [lreplace $list 0 0 [string replace $opt 1 1]]
           }
       } elseif {[dict get $arg($option) argument]} {
           if {[string length $opt] > 2} {
               set value [string range $opt 2 end]
           } elseif {[llength $rest]} {
               set rest [lassign $rest value]
           } else {
               set value $option
               set option missing
           }
       } elseif {[string length $opt] > 2} {
           set rest [lreplace $list 0 0 [string replace $opt 1 1]]
       }
       uplevel 1 [list switch -- [dict get $arg($option) pattern] $body]
       set list $rest
    }
    set option arglist
    set value $list
    uplevel 1 [list switch -- arglist $body]
}
package provide getopt 0.1

Added lib/getopt/pkgIndex.tcl.



>
1
package ifneeded getopt 0.1 [list source [file join $dir getopt.tcl]]

Added netexec.sed.











>
>
>
>
>
1
2
3
4
5
/^lappend auto_path/ d
/^package require getopt/{
	r lib/getopt/getopt.tcl
	d
}