1 #! /bin/bash 2 3 # SSH Options to use when SSH'ing 4 SSH_CMD="${SSH_CMD:-ssh}" 5 SCP_CMD="${SCP_CMD:-scp}" 6 SSHOPTS="-o ForwardAgent=no -o StrictHostKeyChecking=no -o CheckHostIP=yes -o UserKnownHostsFile=${HOME}/.ssh/netexec_known_hosts" 7 SSH_CMDONLY_OPTS='-q' 8 SSH_PTY='/dev/null' 9 spawn_rate='1' 10 11 if ! echo "${SSH_CMD}" | grep '^sshpass ' >/dev/null; then 12 SSHOPTS="${SSHOPTS} -o PasswordAuthentication=no -o BatchMode=yes -o PreferredAuthentications=publickey" 13 fi 14 15 # These variabls are used by the "cleanup" function for sanity 16 unset TMPLOGDIR TMPSCRIPTFILE 17 18 # URLs 19 ## Default host is a group called "default" 20 DEFAULT_HOSTS="+default" 21 22 ## URL to download groups 23 GROUPS_URLS=( 24 "${HOME}/.netexec/groups" 25 "/usr/local/etc/netexec/groups" 26 ) 27 28 function show_help() { 29 echo "Usage: netexec [-hrs] [-g <groups> [-f <filename>] [-z <zonename>]" >&2 30 echo " [-c <file>] [-t <timeout>] [-o <directory>] [-l <limit>]" >&2 31 echo " [-D <delay> dest|commands..." >&2 32 echo " -h This help" >&2 33 echo " -r Perform operations as root." >&2 34 echo " -s Treat commands as script to run rather than our own" >&2 35 echo " -S Start netexec in shell mode" >&2 36 echo " -T Allocate tty (passes \"-tt\" to ssh)" >&2 37 echo " -g <groups> Operate on a group of systems" >&2 38 echo " -f <filename> Filename containing white-space seperated list of hostnames" >&2 39 echo " (default is to download list from specified URL)" >&2 40 echo " -z <zonename> Zone to run the commands on (usually \"global\")" >&2 41 echo " -c <filename> Specifies that a file should be copied, rather than commands" >&2 42 echo " run" >&2 43 echo " -o <directory> Save the output from each host in one file per host in the" >&2 44 echo " specified directory" >&2 45 echo " -t <timeout> Global timeout for commands on remote systems (in seconds)" >&2 46 echo " -l <limit> Specify a limit on the number of child processes that run" >&2 47 echo " simultaneously" >&2 48 echo " -D <delay> Specify a delay to be used when spawning children" >&2 49 echo " (default: ${spawn_rate})" >&2 50 echo " dest Specifies the destination of the copy (if -c specified)" >&2 51 echo " commands... Specifies the commands to run." >&2 52 } 53 54 unset join_pids 55 unset run_pids 56 function cleanup() { 57 if [ -n "${TMPSCRIPTFILE}" ]; then 58 rm -f "${TMPSCRIPTFILE}" 59 fi 60 61 if [ -n "${TMPLOGDIR}" ]; then 62 rm -rf "${TMPLOGDIR}" 63 fi 64 65 if [ -f "${TMP_PTY_FILE}" ]; then 66 rm -f "${TMP_PTY_FILE}" 67 fi 68 69 if [ -f "${TMP_PTY_SHELLFILE}" ]; then 70 rm -f "${TMP_PTY_SHELLFILE}" 71 fi 72 73 if [ -n "${TMP_SCRIPT_PID}" ]; then 74 kill "${TMP_SCRIPT_PID}" 75 fi 76 77 if [ "${opt_netexecshell}" = '1' ]; then 78 if [ -n "${join_pids[*]}" ]; then 79 kill -9 "${join_pids[@]}" 80 fi 81 82 if [ -n "${run_pids[*]}" ]; then 83 kill -9 "${run_pids[@]}" 84 fi 85 86 if [ -n "${idxlist[*]}" ]; then 87 local pids 88 89 ## Close all file descriptors 90 unset pids 91 for idx in "${idxlist[@]}"; do 92 ( 93 eval "exec ${cmdfd[$idx]}<&-" 94 eval "exec ${resfd[$idx]}<&-" 95 ) & 96 pids[${idx}]="$!" 97 done 98 99 ## Wait for closing to terminate 100 wait "${pids[@]}" 101 fi 102 103 ## Save history buffer to disk 104 history -w ~/.netexec_history >/dev/null 2>/dev/null 105 fi 106 } 107 108 function timeout() { 109 local timeout timein childpid 110 111 timeout="$1" 112 shift 113 114 "$@" & 115 childpid="$!" 116 117 for ((timein=0; timein < $timeout; timein++)) { 118 kill -0 "${childpid}" >/dev/null 2>/dev/null || break 119 120 sleep 1 121 } 122 123 if kill -0 "${childpid}" >/dev/null 2>/dev/null; then 124 kill -9 "${childpid}" 125 fi 126 127 wait "${childpid}" 128 } 129 130 # 1. Register a cleanup handler should we be asked to terminate 131 trap "echo \"Cleaning up...\"; cleanup; exit 1" SIGINT 132 133 # 2. Process user aguments 134 ## 2.b. Iterate over every option and check to see if we understand it 135 copyfile="" 136 usesudo="0" 137 usescript="0" 138 maxchildren='0' 139 outdir="" 140 groups="" 141 opt_netexecshell='0' 142 while getopts 'hrsSTz:f:g:c:o:t:l:D:' ch; do 143 case "${ch}" in 144 h) 145 show_help 2>&1 146 exit 0 147 ;; 148 r) 149 usesudo="1" 150 ;; 151 s) 152 usescript="1" 153 ;; 154 S) 155 opt_netexecshell='1' 156 ;; 157 T) 158 SSH_CMDONLY_OPTS="${SSH_CMDONLY_OPTS} -tt" 159 160 if [ "$(uname -s)" = "SunOS" ]; then 161 # On Solaris, we have to create a fake tty to use otherwise ssh produces an error: 162 # tcgetattr: No such device or address 163 TMP_PTY_FILE="${TMPDIR:-/tmp}/netexec-pty.$$${RANDOM}${RANDOM}${RANDOM}" 164 TMP_PTY_SHELLFILE="${TMPDIR:-/tmp}/netexec-pty-shell.$$${RANDOM}${RANDOM}${RANDOM}" 165 cat << \_EOF_ > "${TMP_PTY_SHELLFILE}" 166 #! /bin/bash 167 168 echo "SSH_TTY=`tty`" 169 echo "TMP_SCRIPT_PID=$$" 170 171 while true; do 172 sleep 86400 173 done 174 _EOF_ 175 chmod 700 "${TMP_PTY_SHELLFILE}" 176 177 SHELL="${TMP_PTY_SHELLFILE}" script "${TMP_PTY_FILE}" </dev/null >/dev/null 2>/dev/null & 178 179 for try in {1..10}; do 180 SSH_PTY="$(cat "${TMP_PTY_FILE}" | sed "s|$(echo -e '\r')||" | grep '^SSH_TTY=' | cut -f 2- -d = | head -1)" 181 TMP_SCRIPT_PID="$(cat "${TMP_PTY_FILE}" | sed "s|$(echo -e '\r')||" | grep '^TMP_SCRIPT_PID=' | cut -f 2 -d = | head -1)" 182 183 if [ -n "${SSH_PTY}" -a -n "${TMP_SCRIPT_PID}" ]; then 184 break 185 fi 186 187 sleep "${try}" 188 done 189 190 if [ -z "${SSH_PTY}" ]; then 191 SSH_PTY='/dev/null' 192 fi 193 fi 194 195 ;; 196 z) 197 target_zonename="${OPTARG}" 198 ;; 199 f) 200 hostsfile="${OPTARG}" 201 202 if [ -z "${hostsfile}" ]; then 203 echo "Hosts file (-f) option may not be empty" >&2 204 205 exit 1 206 fi 207 ;; 208 g) 209 groups="${OPTARG}" 210 ;; 211 c) 212 copyfile="${OPTARG}" 213 ;; 214 o) 215 outdir="$(cd "${OPTARG}" 2>/dev/null && pwd)" 216 if [ -z "${outdir}" ]; then 217 echo "Specified output directory \"$1\" does not exist, aborting." >&2 218 219 exit 1 220 fi 221 ;; 222 t) 223 globaltimeout="${OPTARG}" 224 ;; 225 l) 226 maxchildren="${OPTARG}" 227 228 if [ "${maxchildren}" -lt '0' ]; then 229 echo "Specified limit (${maxchildren}) must be integer greater than or" >&2 230 echo "equal to 0. Aborting." >&2 231 232 exit 1 233 fi 234 ;; 235 D) 236 spawn_rate="${OPTARG}" 237 ;; 238 *) 239 echo "Unknown option (${ch})" >&2 240 241 show_help 242 243 exit 1 244 ;; 245 esac 246 done 247 248 shift $[${OPTIND} - 1] 249 250 ## 2.c. Verify argument sanity 251 ### 2.c.i. Verify that we were asked to do something productive 252 if [ -z "$*" ]; then 253 if [ "${opt_netexecshell}" != '1' ]; then 254 if [ -z "${copyfile}" ]; then 255 echo "No commands to run, exiting." >&2 256 else 257 echo "Destination not specified, exiting." >&2 258 fi 259 260 show_help 261 262 exit 1 263 fi 264 else 265 if [ "${opt_netexecshell}" = '1' ]; then 266 echo "No commands may be specified in shell mode" >&2 267 268 exit 1 269 fi 270 fi 271 272 ### 2.c.ii. Verify that we support the operation 273 #### 2.c.ii.A. No copy and root, currently 274 if [ "${usesudo}" = "1" -a -n "${copyfile}" ]; then 275 echo "Currently we do not support copying files as root." >&2 276 277 exit 1 278 fi 279 280 #### 2.c.ii.B. No copying with shell mode 281 if [ "${opt_netexecshell}" = '1' -a -n "${copyfile}" ]; then 282 echo "File copy is not supported in shell mode" >&2 283 284 exit 1 285 fi 286 287 #### 2.c.ii.C. Shell mode does not support limited children 288 if [ "${maxchildren}" != '0' -a "${opt_netexecshell}" = '1' ]; then 289 echo "Process limit not supported in shell mode" >&2 290 291 exit 1 292 fi 293 294 ### 2.c.iii. If arguments require copying, update destination with arguments 295 if [ -n "${copyfile}" ]; then 296 copyfiledest="$*" 297 fi 298 299 ### 2.c.iv. If arguments require running a command, construct a script that we 300 ### can copy to the host and run to ensure a consistent experience 301 if [ -z "${copyfile}" -a "${opt_netexecshell}" = '0' ]; then 302 #### 2.c.iv.(a). Create script 303 TMPSCRIPTFILE="${TMPDIR:-/tmp}/netexec-script-$$${RANDOM}${RANDOM}${RANDOM}.deleteme" 304 touch "${TMPSCRIPTFILE}" || exit 1 305 chmod 700 "${TMPSCRIPTFILE}" || exit 1 306 307 #### 2.c.iv.(b). Create script header 308 cat << \__EOF__ > "${TMPSCRIPTFILE}" 309 #! /bin/bash 310 311 # Abort on failure 312 set -e 313 314 # Setup sane path 315 PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin:/usr/nsh/bin:/usr/sfw/bin:/opt/sfw/bin:/usr/ucb" 316 export PATH 317 318 # Setup sane umask 319 umask 022 320 321 __EOF__ 322 323 #### 2.c.iv.(c). Put user commands in the script 324 if [ "${usescript}" = '0' ]; then 325 echo "$*" >> "${TMPSCRIPTFILE}" 326 else 327 echo '' >> "${TMPSCRIPTFILE}" 328 echo '# Disable abort on error, most scripts do not expect it' >> "${TMPSCRIPTFILE}" 329 echo 'set +e' >> "${TMPSCRIPTFILE}" 330 echo '' >> "${TMPSCRIPTFILE}" 331 cat "$@" >> "${TMPSCRIPTFILE}" 332 fi 333 334 #### 2.c.iv.(d). Update script to declare victory if nothing failed 335 cat << \__EOF__ >> "${TMPSCRIPTFILE}" 336 337 exit 0 338 __EOF__ 339 fi 340 341 ## 2.d. If we are doing this as root, use sudo 342 if [ "${usesudo}" = "1" ]; then 343 call_sudo='sudo' 344 else 345 call_sudo='' 346 fi 347 348 # 3. Determine which hosts to operate on 349 if [ -n "${hostsfile}" ]; then 350 ## 3.a. If a file was specified, load its contents into memory 351 if echo "${hostsfile}" | grep '/' >/dev/null || [ -e "${hostsfile}" ]; then 352 ### 3.a.i. If the file specified exists or has a '/' in it treat it as a filename.. 353 HOSTS="$(cat "${hostsfile}")" 354 else 355 ### 3.a.ii. ... Otherwise treat it as a list of hosts 356 HOSTS="${hostsfile}" 357 fi 358 else 359 ## 3.b. Otherwise, pull in all hosts (unless a group file was specified, then pull in none) 360 if [ -z "${groups}" ]; then 361 HOSTS="${DEFAULT_HOSTS}" 362 fi 363 fi 364 365 ## 3.c. Pull in groups 366 for level in 1 2 3 4 5 6 7 8 9 10; do 367 for group in ${groups}; do 368 for groups_url in "${GROUPS_URLS[@]}" __notfound__; do 369 if [ "${groups_url}" = '__notfound__' ]; then 370 echo "Unable to download group for \"${group}\", aborting." >&2 371 372 exit 1 373 fi 374 375 group_url="${groups_url}/${group}" 376 377 if [ -f "${group_url}" ]; then 378 HOSTS="${HOSTS} $(cat "${group_url}")" 379 else 380 HOSTS="${HOSTS} $(wget --no-check-certificate -O - "${group_url}" 2>/dev/null)" || continue 381 fi 382 383 break 384 done 385 done 386 387 ## 3.d. Normalize list of hosts 388 HOSTS="$(echo "${HOSTS} " | sed 's@#.*@@' | tr ' ' "\n" | grep -v '^ *$' | dd conv=lcase 2>/dev/null | sort -u)" 389 390 if ! echo "${HOSTS}" | grep '^+' >/dev/null; then 391 break 392 fi 393 394 groups="$(echo "${HOSTS}" | grep '^+' | sed 's@^+@@')" 395 HOSTS="$(echo "${HOSTS}" | grep -v '^+')" 396 done 397 398 # 4. Create a temporary directory to store our results 399 TMPLOGDIR="${TMPDIR:-/tmp}/netexec-logdir-$$${RANDOM}${RANDOM}${RANDOM}" 400 export TMPLOGDIR 401 mkdir "${TMPLOGDIR}" || exit 1 402 chmod 700 "${TMPLOGDIR}" || exit 1 403 404 # 5. Start up a temporary SSH agent with our service account's private keys 405 ## NOT DONE 406 407 # 6. Process each host 408 if [ -n "${TMPSCRIPTFILE}" ]; then 409 script="$(cat "${TMPSCRIPTFILE}")" 410 script_quoted="$(set | grep '^script=' | cut -f 2- -d '=' | sed "s@'@'\"'\"'@g")" 411 fi 412 413 if [ "${opt_netexecshell}" = '1' ]; then 414 numhosts="$(HOSTS=(${HOSTS}); echo "${#HOSTS[@]}")" 415 idx=0 416 fi 417 418 for host in ${HOSTS}; do 419 ( 420 ## 6.c. Figure out the name of the zone, if specified 421 if [ -n "${target_zonename}" ]; then 422 zonename="$(timeout 120 ${SSH_CMD} ${SSHOPTS} ${SSH_CMDONLY_OPTS} "${host}" zonename 2>/dev/null | tail -1)" 423 424 if [ "${zonename}" != "${target_zonename}" ]; then 425 exit 0 426 fi 427 fi 428 429 ## 6.d. Perform operation 430 if [ -n "${copyfile}" ]; then 431 mode='copy' 432 elif [ "${opt_netexecshell}" = '1' ]; then 433 mode="shell" 434 else 435 mode="ssh" 436 fi 437 case "${mode}" in 438 ssh) 439 ### 6.d.ii. Run our script 440 if [ -z "${globaltimeout}" ]; then 441 ${SSH_CMD} ${SSHOPTS} ${SSH_CMDONLY_OPTS} "${host}" "/bin/bash -c 'script=${script_quoted}; PATH=\"/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin:/usr/nsh/bin:/usr/sfw/bin:/opt/sfw/bin:/usr/ucb\"; export PATH; ${call_sudo} /bin/bash -c \"\${script}\"'" < "${SSH_PTY}" 442 else 443 timeout "${globaltimeout}" ${SSH_CMD} ${SSHOPTS} ${SSH_CMDONLY_OPTS} "${host}" "/bin/bash -c 'script=${script_quoted}; PATH=\"/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin:/usr/nsh/bin:/usr/sfw/bin:/opt/sfw/bin:/usr/ucb\"; export PATH; ${call_sudo} /bin/bash -c \"\${script}\"'" < "${SSH_PTY}" 444 fi 445 retval="$?" 446 ;; 447 copy) 448 if [ -z "${globaltimeout}" ]; then 449 ${SCP_CMD} ${SSHOPTS} "${copyfile}" "${host}:${copyfiledest}" 450 else 451 timeout "${globaltimeout}" scp ${SSHOPTS} "${copyfile}" "${host}:${copyfiledest}" 452 fi 453 retval="$?" 454 ;; 455 shell) 456 infile="${TMPLOGDIR}/${host}@in" 457 outfile="${TMPLOGDIR}/${host}@out" 458 459 mkfifo "${infile}" "${outfile}" 460 461 ${SSH_CMD} ${SSHOPTS} ${SSH_CMDONLY_OPTS} "${host}" "${call_sudo} /bin/sh -c 'test -x /bin/bash && exec /bin/bash -s; exec /bin/sh -s' 2>&1" < "${infile}" > "${outfile}" 462 retval="$?" 463 464 rm -f "${infile}" "${outfile}" 465 466 touch "${infile}" "${outfile}" 467 ;; 468 esac 469 470 if [ "${retval}" != "0" ]; then 471 echo "[error] Exited with non-zero return value: ${retval}" 472 473 exit 1 474 fi 475 ) >"${TMPLOGDIR}/${host}" 2>&1 & 476 477 if [ "${opt_netexecshell}" = '1' ]; then 478 idx=$[$idx + 1] 479 480 echo -ne "\rStarting SSH sessions: ${idx}/${numhosts}" 481 fi 482 483 # If we are using an SSH agent then avoid overloading it by limiting 484 # rate of children spawned 485 if [ "${opt_netexecshell}" != '1' ]; then 486 if [ "${spawn_rate}" != '0' -a -n "${spawn_rate}" ]; then 487 sleep "${spawn_rate}" 488 fi 489 fi 490 491 # If we have been asked to limit number of children, do so 492 if [ "${maxchildren}" -gt '0' ]; then 493 while true; do 494 numchildren="$(jobs -p | wc -l | awk '{ print $1 }')" 495 if [ "${numchildren}" -lt "${maxchildren}" ]; then 496 break 497 fi 498 499 sleep 5 500 done 501 fi 502 done 503 504 # 7. Wait for SSH processes to complete 505 506 if [ "${opt_netexecshell}" = '1' ]; then 507 function run_remote_command() { 508 local cmdfd resfd cmd_quoted 509 local marker result 510 511 cmdfd="$1" 512 resfd="$2" 513 cmd_quoted="$3" 514 515 marker="${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}" 516 echo "echo -n '${marker}_START'; eval ${cmd_quoted}; echo '${marker}_END'" >&${cmdfd} 517 518 result="$(sed "s@${marker}_START@@;/${marker}_END\$/ {s@${marker}_END\$@@;q}" <&${resfd})" 519 520 echo "${result}" 521 } 522 523 function hostname_to_idx() { 524 local system 525 local idx name 526 527 system="$1" 528 529 for idx in "${idxlist[@]}"; do 530 name="${names[${idx}]}" 531 case "${system}" in 532 ~*) 533 system_re="$(echo "${system}" | cut -c 2-)" 534 if echo "${name}" | grep "${system_re}" >/dev/null 2>/dev/null; then 535 echo "${idx}" 536 fi 537 ;; 538 *) 539 if [ "${name}" = "${system}" ]; then 540 echo "${idx}" 541 return 542 fi 543 ;; 544 esac 545 done 546 } 547 548 echo "" 549 550 idx=-1 551 for host in ${HOSTS}; do 552 idx=$[$idx + 1] 553 554 cmdfile="${TMPLOGDIR}/${host}@in" 555 resfile="${TMPLOGDIR}/${host}@out" 556 557 echo -ne "\rConnecting to sockets: $[$idx + 1]/$numhosts" 558 559 for try in {0..9} __failed__; do 560 if [ "${try}" = '__failed__' ]; then 561 continue 562 fi 563 564 if [ -e "${cmdfile}" -a -e "${resfile}" ]; then 565 if [ -p "${cmdfile}" -a -p "${resfile}" ]; then 566 break 567 fi 568 569 continue 570 fi 571 572 sleep $[${try} * 2] 573 done 574 575 idxlist[$idx]="${idx}" 576 names[$idx]="${host}" 577 logfiles[$idx]="${TMPLOGDIR}/${host}@tmp" 578 cmdfiles[$idx]="${cmdfile}" 579 resfiles[$idx]="${resfile}" 580 cmdfd[$idx]=$[$idx + 32] 581 resfd[$idx]=$[$idx + 32 + ${numhosts}] 582 583 eval "exec ${cmdfd[$idx]}<&-" 584 eval "exec ${resfd[$idx]}<&-" 585 eval "exec ${cmdfd[$idx]}>\"${cmdfile}\"" 586 eval "exec ${resfd[$idx]}<\"${resfile}\"" 587 588 if [ "${spawn_rate}" != '0' -a -n "${spawn_rate}" ]; then 589 sleep "${spawn_rate}" 590 fi 591 done 592 echo "" 593 594 echo -n "Verifying connectivity: " 595 596 unset join_pids 597 for idx in "${idxlist[@]}"; do 598 ( 599 cmdfile="${cmdfiles[$idx]}" 600 resfile="${resfiles[$idx]}" 601 marker="${RANDOM}${RANDOM}${RANDOM}${RANDOM}" 602 echo "PS1=''; echo; echo ${marker}" >&${cmdfd[$idx]} 603 604 failed='0' 605 for try in {0..9} __failed__; do 606 if [ "${try}" = '__failed__' ] || [ ! -p "${cmdfile}" -o ! -p "${resfile}" ]; then 607 failed='1' 608 609 break 610 fi 611 612 read -t 10 -r -u ${resfd[${idx}]} line 2>/dev/null 613 614 if [ "${line}" = "${marker}" ]; then 615 break 616 fi 617 618 sleep $[${try} * 2] 619 done 620 621 if [ "${failed}" = '0' ]; then 622 echo -n " ${names[$idx]}..." 623 else 624 echo -n " ${names[$idx]}(FAILED)..." 625 rm -f "${cmdfile}" 626 rm -f "${resfile}" 627 fi 628 ) & 629 join_pids[${idx}]="$!" 630 done 631 632 wait "${join_pids[@]}" 633 unset join_pids 634 echo "" 635 636 history -r ~/.netexec_history >/dev/null 2>/dev/null 637 638 working_idxlist=("${idxlist[@]}") 639 while true; do 640 IFS='' read -r -e -p 'netexec$ ' cmd || break 641 642 # Normalize command a bit 643 cmd="$(echo "${cmd}" | sed 's@^ *@@')" 644 645 # Ignore empty commands 646 if [ -z "${cmd}" ]; then 647 continue 648 fi 649 650 # Pull command from history, if appropriate 651 if echo "${cmd}" | grep '^![0-9][0-9]*$' >/dev/null; then 652 cmd="$(history -p "${cmd}")" 653 654 echo "netexec>> ${cmd}" 655 fi 656 657 # Write command to history 658 history -s "${cmd}" 659 660 # Handle local overrides 661 unset run_idxlist 662 run_idxlist=("${working_idxlist[@]}") 663 case "${cmd}" in 664 clear|clear\ *|history|history\ *) 665 eval "${cmd}" 666 667 continue 668 ;; 669 exit) 670 break 671 ;; 672 vi|vi\ *|sudo\ vi|sudo\ vi\ *) 673 template_idx='0' 674 675 if echo "${cmd}" | grep '^sudo ' >/dev/null; then 676 vi_sudo='1' 677 vi_sudo_cmd='sudo ' 678 else 679 vi_sudo='0' 680 vi_sudo_cmd='' 681 fi 682 683 remote_filename="$(echo "${cmd}" | sed 's@^\(sudo\)* *vi *@@;s@ *$@@')" 684 685 if [ -z "${remote_filename}" ]; then 686 echo "Usage: vi <filename>" >&2 687 688 continue 689 fi 690 691 contents="$(run_remote_command "${cmdfd[${template_idx}]}" "${resfd[${template_idx}]}" "${vi_sudo_cmd}cat ${remote_filename} 2>/dev/null")" 692 693 tmp_filename="${TMPDIR:-/tmp}/netexec-vi-$$-$(echo "${contents}" | openssl sha1 | sed 's@.*= *@@')" 694 695 if [ -e "${tmp_filename}" ]; then 696 echo -n 'netexec>> Edit session for this file is already in progress, continue (YES/no) ? ' 697 read no 698 if [ "${no}" = 'no' ]; then 699 rm -f "${tmp_filename}" 700 fi 701 fi 702 703 if [ ! -e "${tmp_filename}" ]; then 704 echo "${contents}" > "${tmp_filename}" 705 fi 706 707 vi "${tmp_filename}" 708 709 echo -n 'netexec>> Ready to deploy (yes/NO) ? ' 710 read yes 711 if [ "${yes}" != 'yes' ]; then 712 echo "netexec>> NOT deploying file." 713 714 continue 715 fi 716 717 contents="$(cat "${tmp_filename}")" 718 rm -f "${tmp_filename}" 719 720 echo "netexec>> Deploying ${remote_filename}" 721 722 tmp_marker="$$${RANDOM}${RANDOM}${RANDOM}${RANDOM}" 723 724 if [ "${vi_sudo}" = '1' ]; then 725 remote_filename_unquoted="$(eval echo "${remote_filename}")" 726 remote_filename_quoted="$(set | grep '^remote_filename_unquoted=' | cut -f 2- -d '=')" 727 728 vi_redirect="| sudo bash -c \"cat > ${remote_filename_quoted}\"" 729 else 730 vi_redirect="> ${remote_filename}" 731 fi 732 733 cmd='cat << \__EOF__'"${tmp_marker}"'__ '"${vi_redirect}"' 734 '"${contents}"' 735 __EOF__'"${tmp_marker}"'__ 736 ' 737 ;; 738 passwd|passwd\ *) 739 echo "Not currently supported" 740 continue 741 ;; 742 !cp|!cp\ *) 743 echo "Not currently supported" 744 continue 745 ;; 746 !remove\ *) 747 system="$(echo "${cmd}" | awk '{ print $2 }')" 748 for idx in "${idxlist[@]}"; do 749 name="${names[${idx}]}" 750 if [ "${name}" = "${system}" ]; then 751 unset idxlist[$idx] 752 fi 753 done 754 755 continue 756 ;; 757 !run\ *) 758 system="$(echo "${cmd}" | cut -f 2 -d ' ')" 759 cmd="$(echo "${cmd}" | cut -f 3- -d ' ')" 760 761 unset run_idxlist 762 run_idxlist=() 763 764 tmpidx=-1 765 for add_idx in $(hostname_to_idx "${system}"); do 766 tmpidx=$[$tmpidx + 1] 767 run_idxlist[$tmpidx]="${add_idx}" 768 done 769 ;; 770 !subset|!subset\ *) 771 systems="$(echo "${cmd}" | sed 's@^!subset *@@')" 772 773 unset working_idxlist 774 working_idxlist=() 775 776 if [ -z "${systems}" ]; then 777 working_idxlist=("${idxlist[@]}") 778 else 779 tmpidx=-1 780 for system in $systems; do 781 for add_idx in $(hostname_to_idx "${system}"); do 782 tmpidx=$[$tmpidx + 1] 783 working_idxlist[$tmpidx]="${add_idx}" 784 done 785 done 786 fi 787 788 continue 789 ;; 790 !help) 791 echo "NETEXEC SHELL MODE COMMANDS:" 792 echo " clear Clear the screen" 793 echo " history Display command history buffer" 794 echo " exit Terminate netexec" 795 echo " vi Edit a file and distribute it" 796 echo " passwd Change a user's password" 797 echo " !subset <hosts...> Work with a subets of hosts" 798 echo " !cp <src> <dest> Copy a file from the local system to all systems" 799 echo " !remove <host> Remove a host from list of hosts" 800 echo " !run <host> <cmd> Run a command on a single host" 801 echo " !help This help" 802 continue 803 ;; 804 esac 805 806 # Ensure no "!" commands get transmitted 807 case "${cmd}" in 808 !*) 809 echo "Invalid netexec command ${cmd}" >&2 810 continue 811 ;; 812 esac 813 814 # Handle local pipe mode 815 if echo "${cmd}" | grep '!|' >/dev/null; then 816 local_cmd="$(echo "${cmd}" | sed 's@^.*!|@@')" 817 cmd="$(echo "${cmd}" | sed 's@!|.*$@@')" 818 else 819 local_cmd='cat' 820 fi 821 822 # Quote our command string 823 cmd_quoted="$(set | grep '^cmd=' | cut -f 2- -d '=')" 824 825 if [ -z "${run_idxlist[*]}" ]; then 826 continue 827 fi 828 829 # Run and collect results 830 unset run_pids 831 for idx in "${run_idxlist[@]}"; do 832 ( 833 cmdfile="${cmdfiles[$idx]}" 834 resfile="${resfiles[$idx]}" 835 if [ ! -p "${cmdfile}" -o ! -p "${resfile}" ]; then 836 echo "SSH DIED" 837 838 exit 1 839 fi 840 841 run_remote_command "${cmdfd[${idx}]}" "${resfd[${idx}]}" "${cmd_quoted}" 842 ) > "${logfiles[${idx}]}" & 843 run_pids[${idx}]="$!" 844 done 845 846 wait "${run_pids[@]}" 847 unset run_pids 848 849 for idx in "${run_idxlist[@]}"; do 850 name="${names[${idx}]}" 851 logfile="${logfiles[${idx}]}" 852 853 result="$(cat "${logfile}" 2>/dev/null)" 854 855 if [ -z "${result}" ]; then 856 result="(no output)" 857 fi 858 859 echo "${result}" | sed 's|^|'"${name}"': |' 860 done | eval "${local_cmd}" 861 done 862 863 # Cleanup 864 cleanup 865 866 # Terminate 867 exit 0 868 fi 869 870 wait 871 872 # 8. Print the results, let globbing sort the hostnames 873 ( 874 cd "${TMPLOGDIR}" || exit 1 875 for host in ${HOSTS}; do 876 if [ -f "${host}" ]; then 877 if [ -s "${host}" ]; then 878 cat "${host}" | sed "s|^|${host}: |" 879 else 880 echo "${host}: (no results)" 881 fi 882 883 if [ -n "${outdir}" ]; then 884 cat "${host}" > "${outdir}/${host}" 885 fi 886 else 887 echo "${host}: [error] No results found!" 888 889 if [ -n "${outdir}" ]; then 890 echo '[error] No results found!' > "${outdir}/${host}" 891 fi 892 fi 893 done 894 ) 895 896 # 9. Clean up 897 cleanup 898 899 # 10. Declare victory 900 exit 0 |