blob: 72259355b3fe24de2d31a061607b9f9a3680e099 [file] [log] [blame]
#!/bin/dash
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
TRY_BASH=1
if echo "$@" | grep -q -- "--dash"; then
TRY_BASH=0
fi
# NOTE: This script *almost* works in dash. Unfortunately, dash is missing
# the 'history' builtin and readline support is missing from the 'read'
# builtin.
#
# Please test that any changes continue to work in dash by running
# '/build/board-name/bin/dash crosh --dash' before checking them in.
if type "history" 2>/dev/null | grep -q "shell builtin"; then
IS_BASH=1
else
IS_BASH=0
fi
if [ "$TRY_BASH" = "1" -a "$IS_BASH" != "1" -a -x "/bin/bash" ]; then
# Relaunch in bash if we can.
exec /bin/bash $0 "$@"
fi
#
# Please keep the help text in alphabetical order!
#
HELP='
enterprise_ca_approve [--allow-self-signed] <url>
Approve an enterprise certificate authority. The <url> option should be
an http or https url to your enterprise Certificate Authority in PEM
format. This CA will be used to validate the signature of an enterprise
policy extension.
If the --allow-self-signed option is provided, then you may provide a self
signed CA. Use this only if you are certain of the source of the CA.
enterprise_ca_disapprove
Remove a previously approved enterprise certificate authority.
exit
Exit crosh.
ff_debug [<tag_expr>] [--help] [--list_valid_tags] [--reset]
Add and remove flimflam debugging tags.
gobi_fw <command> [args...]
Manipulate Qualcomm Gobi 3000 firmware.
help
Display this help.
modem <command> [args...]
Interact with the 3G modem. Run "modem help" for detailed help.
modem_set_carrier carrier-name
Configures the modem for the specified carrier.
network_logging <wifi | cellular | ethernet>
A function that enables a predefined set of tags useful for
debugging the specified device.
network_diag
A function that performs a suite of network diagnostics. Saves a copy
of the output to your download directory.
ping [-c count] [-i interval] [-n] [-s packet-size] [-W wait-time] <destination>
Send ICMP ECHO_REQUEST packets to a network host. If <destination> is "gw"
then the next hop gateway for the default route is used.
route [-n]
Display the routing tables.
secure_certificates [on|off]
Enables TPM-wrapped secure certificate storage in Chrome. This is off by
default and is currently a lab feature.
set_apn [-n <network-id>] [-u <username>] [-p <password>] <apn>
Set the APN to use when connecting to the network specified by <network-id>.
If <network-id> is not specified, use the network-id of the currently
registered network.
set_apn -c
Clear the APN to be used, so that the default APN will be used instead.
ssh
Starts the ssh subsystem.
ssh_forget_host
Remove a host from the list of known ssh hosts. This command displays
a menu of known hosts and prompts for the host to forget.
top
Run top.
tracepath [-n] <destination>[/port]
Trace the path/route to a network host.
wpa_debug [<debug_level>] [--help] [--list_valid_level] [--reset]
Set wpa_supplicant debugging level.
'
CHROMEOS_INSTALL=/usr/sbin/chromeos-install
if [ -f $CHROMEOS_INSTALL ]; then
# Install script exists. Test if it thinks we booted from a removable
# device. This is based on the knowledge that the install script checks
# that the root device is removable before validating its other arguments.
# Also, we expect that when it fails in this manner, it mentions the word
# "removable" on stdout.
if ! $CHROMEOS_INSTALL --dst=/dev/null | grep -q "removable"; then
REMOVABLE=1
fi
fi
if [ "$(echo "$@" | grep -- "--usb")" -o "$REMOVABLE" = "1" ]; then
. "$(dirname "$0")/crosh-usb"
fi
# TODO(rginda): Switch to the "real" dev mode file when we have one.
/usr/bin/crossystem cros_debug?1
DEVMODE=$((!$?))
# Force dev behavior on dev images
# TODO(wad,rspangler) replace with a crossystem arg once cros_debug checks this
# too.
if [ -f "/root/.dev_mode" ]; then
DEVMODE=1
fi
if [ "$(echo "$@" | grep -- "--dev")" -o "$DEVMODE" = "1" ]; then
. "$(dirname "$0")/crosh-dev"
fi
if [ -e "$(dirname $0)/crosh-workarounds" ]; then
. "$(dirname "$0")/crosh-workarounds"
fi
ctrl_c() {
return
}
trap ctrl_c SIGINT
shell_read() {
local prompt="$1"
shift
if [ "$IS_BASH" -eq "1" ]; then
# In bash, -e gives readline support.
read -p "$prompt" -e $@
else
read -p "$prompt" $@
fi
}
shell_history() {
if [ "$IS_BASH" -eq "1" ]; then
# In bash, the history builtin can be used to manage readline history
history $@
fi
}
cmd_help() {
echo "$HELP"
}
cmd_exit() {
exit
}
check_dforward() {
# Matches a port number between 8000 and 8999.
expr "$1" : '^8[0-9][0-9][0-9]' > /dev/null
}
check_forward() {
# Matches three things, separated by ':':
# A port number, between 8000 and 8999;
# A hostname
# A port number, unrestricted
expr "$1" : '^8[0-9][0-9][0-9]:[[:alnum:]][-[:alnum:].]*:[1-9][0-9]*$' \
> /dev/null
}
check_keyfile() {
# Allow files in /home/chronos/user, /media. Note that we *do* allow .., so
# this isn't actually a security barrier, just a molly-guard to keep users
# from putting keys in insecure storage.
if [ ! -r "$1" -a ! -r "$HOME/Downloads/$1" ]; then
return 1;
fi
(expr "$1" : "^$HOME" > /dev/null) || \
(expr "$1" : '^/media/' > /dev/null) || \
(expr "$1" : '^[^/]*$' > /dev/null)
}
cmd_ssh() {
local user=""
local host=""
local port="22"
local idfile=""
local dforwards=""
local forwards=""
local nocmd=""
local line
local cmd
local params
local id=$(uuidgen)
local defopts="-e none -F /etc/ssh/ssh_config"
local home="$(getent passwd "$USER" | cut -f6 -d':')"
rm -rf "$home/.ssh"
if [ ! -z "$1" ]; then
# Sets the username and host directly. We accept either 'user host' or
# 'user@host'.
local at_pos=$(expr index "$1" '@')
if [ $at_pos = 0 ]; then
user="$1"
host="$2"
else
user=$(substr "$1" "0" $(expr "$at_pos" - 1))
host=$(substr "$1" "$at_pos")
fi
fi
while [ 1 ]; do
if ! shell_read "ssh> " line; then continue; fi
local space_pos=$(expr index "$line" ' ')
if [ $space_pos = 0 ]; then
cmd="$line"
else
cmd=$(substr "$line" "0" "$space_pos")
params=$(substr "$line" "$space_pos")
fi
case "$cmd" in
key)
if check_keyfile "$params"; then
(cd "$HOME/Downloads" ; cp -- "$params" "$HOME/.ssh/key-$id")
chmod 600 "$HOME/.ssh/key-$id"
idfile="-i $HOME/.ssh/key-$id"
else
echo "File '$params' is not a valid key file. Key files must reside"
echo "under /media or $HOME. Key files in the Downloads directory may"
echo "be specified with an unqualified name."
fi
;;
dynamic-forward)
if ! check_dforward "$params"; then
echo "Invalid forward '$params'."
echo "Note that the local port number must be in 8000-8999."
else
dforwards="$dforwards -D$params"
fi
;;
forward)
if ! check_forward "$params"; then
echo "Invalid forward '$params'"
echo "Note that the local port number must be in 8000-8999."
else
forwards="$forwards -L$params"
fi
;;
nocmd)
nocmd="-N"
;;
port)
if ! check_digits "$params"; then
echo "Invalid port '$params'"
else
port="$params"
fi
;;
user)
user="$params"
;;
host)
host="$params"
;;
quit)
break
;;
connect)
if [ -z "$user" ]; then
echo "No username given."
elif [ -z "$host" ]; then
echo "No host given."
elif ! check_username "$user"; then
echo "Invalid username '$user'"
elif ! check_hostname "$host"; then
echo "Invalid hostname '$host'"
else
ssh $defopts $idfile $dforwards $forwards $nocmd -p "$port" \
-l "$user" "$host"
fi
;;
*)
echo "connect - connect"
echo "dynamic-forward port - dynamic socks proxy (-D)"
echo "forward port:host:port - static port forward (-L)"
echo "help - this"
echo "host <hostname> - remote hostname"
echo "key <file> - sets private key to use (-i)"
echo "nocmd - don't execute command (-N)"
echo "port <num> - port on remote host (-p)"
echo "quit - exit ssh subsystem"
echo "user <username> - username on remote host"
echo "Note that this program can only bind local ports in the range"
echo "8000-8999, inclusive."
;;
esac
done
}
cmd_ssh_forget_host() {
local known_hosts="$(readlink -f $HOME/.ssh/known_hosts)"
# Test that the known_hosts is a regular file and is not 0 length.
if [ ! -f "$known_hosts" -o ! -s "$known_hosts" ]; then
echo "No known hosts."
return
fi
local count="$(cat "$known_hosts" | wc -l)"
# Print an indexed list of the known hosts.
echo "Known hosts:"
awk '{ print " " NR ") " $1; }' "$known_hosts"
local hostno
while true; do
LINE_=""
shell_read "Please select a host to forget [1-$count]: " LINE_
if [ -z "$LINE_" ]; then
echo "Aborting."
return
fi
# Extract the numbers from the user input.
hostno="$(echo $LINE_ | sed 's/[^[:digit:]]*//g')"
# If they typed a number...
if [ -n "$hostno" ]; then
# And it was in the proper range...
if [ $hostno -gt 0 -a $hostno -le $count ]; then
# Then we can stop asking for input.
break
fi
fi
echo "Invalid selection. Please enter a number between 1 and $count."
done
local trimmed="$(awk -v I="$hostno" 'NR != I { print }' "$known_hosts")"
echo "$trimmed" > "$known_hosts"
chmod 644 "$known_hosts"
}
cmd_enterprise_ca_approve() {
local option=""
while [ "$(echo "$1" | cut -c1-2)" = "--" ]; do
if [ "$1" = "--allow-self-signed" ]; then
option="$option --allow_self_signed"
elif [ "$1" = "--allow-chaining" ]; then
# This option allows enterprise extensions which are signed by
# certificates that chain to this one. Because we're not currently
# doing any key usage checks, this can be pretty risky. If you're not
# sure that you need this, don't use it.
option="$option --allow_chaining"
else
echo "Unknown option: $1"
return 1
fi
shift
done
if [ -z "$1" ]; then
echo "Missing parameter: url"
return 1
fi
/usr/sbin/entdwife.sh -c approve $option -C "$1"
if dbus-send --system --type=method_call --print-reply \
--dest=org.chromium.SessionManager /org/chromium/SessionManager \
org.chromium.SessionManagerInterface.RestartEntd | grep -q error; then
echo "Please log out and then log back in to use this CA."
fi
}
cmd_enterprise_ca_disapprove() {
/usr/sbin/entdwife.sh -c disapprove
}
cmd_ff_debug(){
/usr/bin/ff_debug "$@"
}
cmd_wpa_debug(){
/usr/bin/wpa_debug "$@"
}
cmd_network_logging(){
if [ -n "$1" ]; then
case "$1" in
"wifi")
echo; /usr/bin/ff_debug service+wifi+inet+device+manager
echo; /usr/bin/wpa_debug msgdump
;;
"cellular")
echo; /usr/bin/ff_debug service+modem+device+manager
echo; /usr/bin/wpa_debug info
;;
"ethernet")
echo; /usr/bin/ff_debug service+ethernet+device+manager
echo; /usr/bin/wpa_debug info
;;
"--help")
echo "Usage: network_logging <wifi | cellular | ethernet>"
;;
*)
echo "Invalid parameter $1"
;;
esac
else
echo "Missing parameter wifi | cellular | ethernet"
fi
}
cmd_network_diag(){
/usr/bin/network_diagnostics
}
cmd_ping() {
local option=""
# NB: use printf to avoid echo interpreting -n
while [ "$(printf '%s' "$1" | cut -c1)" = "-" ]; do
# Do just enough parsing to filter/map options; we
# depend on ping to handle final validation
if [ "$1" = "-i" ]; then
shift; option="$option -i $1"
elif [ "$1" = "-c" ]; then
shift; option="$option -c $1"
elif [ "$1" = "-W" ]; then
shift; option="$option -W $1"
elif [ "$1" = "-s" ]; then
shift; option="$option -s $1"
elif [ "$1" = "-n" ]; then
option="$option -n"
else
echo "Unknown option: $1"
return 1
fi
shift
done
if [ -z "$1" ]; then
echo "Missing parameter: destination"
return 1
fi
local dest="$1"
if [ "$dest" = "gw" ]; then
# Convenient shorthand for the next-hop gateway attached
# to the default route; this means if you have a host named
# "gw" then you'll need to specify a FQDN or IP address.
dest=$(/sbin/route -n | awk '$1 == "0.0.0.0" { print $2; }')
if [ -z "$dest" ]; then
echo "Cannot determine primary gateway; routing table is:"
cmd_route -n
return 1
fi
fi
/bin/ping $option "$dest"
}
cmd_route() {
local option=""
# NB: use printf to avoid echo interpreting -n
while [ "$(printf '%s' "$1" | cut -c1)" = "-" ]; do
if [ "$1" = "-n" ]; then
option="$option -n"
else
echo "Unknown option: $1"
return 1
fi
shift
done
/sbin/route $option
}
cmd_tracepath() {
local option=""
# NB: use printf to avoid echo interpreting -n
while [ "$(printf '%s' "$1" | cut -c1)" = "-" ]; do
if [ "$1" = "-n" ]; then
option="$option -n"
else
echo "Unknown option: $1"
return 1
fi
shift
done
if [ -z "$1" ]; then
echo "Missing parameter: destination"
return 1
fi
/usr/sbin/tracepath $option "$1"
}
cmd_top() {
# -s is "secure" mode, which disables kill, renice, and change display/sleep
# interval.
top -s
}
cmd_gobi_fw() {
/usr/bin/gobi-fw "$@"
}
cmd_modem() {
/usr/bin/modem "$@"
}
cmd_modem_set_carrier() {
/usr/bin/modem_set_carrier "$@"
}
# TODO(crosbug.com/14277): Remove once this is default.
cmd_secure_certificates() {
local flag_file="/home/chronos/.cryptohome-init-pkcs11"
local enabled=0
local changed=0
[ -r ${flag_file} ] && enabled=1
if [ -z "$1" ]; then
if [ ${enabled} -eq 1 ]; then
echo "Currently enabled"
else
echo "Currently disabled"
fi
return
fi
if [ "$1" = "on" ]; then
[ ${enabled} -eq 0 ] && changed=1
touch "${flag_file}"
else
[ $enabled -eq 1 ] && changed=1
rm -f $flag_file
fi
if [ $changed -eq 1 ]; then
echo "You must reboot for this to take effect"
else
echo "No change."
fi
}
cmd_set_apn() {
/usr/bin/set_apn "$@"
}
cmd_autest() {
/usr/bin/update_engine_client --omaha_url autest
}
substr() {
local str="$1"
local start="$2"
local end="$3"
if [ "$IS_BASH" = "1" ]; then
# NB: use printf to avoid echo interpreting -n
if [ -z "$end" ]; then
printf '%s\n' ${str:$start}
else
printf '%s\n' ${str:$start:$end}
fi
return
fi
start=$(expr "$start" + 1)
if [ ! -z "$end" ]; then
end=$(expr "$end" - 1)
fi
echo "$str" | cut -c${start}-${end}
}
dispatch() {
local line="$1"
local command=""
local params=""
local space_pos=$(expr index "$line" ' ')
if [ $space_pos = 0 ]; then
command=$line
else
command=$(substr "$line" "0" "$space_pos")
params=$(substr "$line" "$space_pos")
fi
if ! type "cmd_$command" 2>/dev/null | head -1 | grep -q "function"; then
echo "Unknown command: '$command'"
else
command="cmd_$command"
$command $params
fi
}
# Checks that a given string looks like a hostname or IPv4 address (starts
# with an alphanumeric and contains only alphanumeric, '.', or '-'
# characters).
check_hostname() {
expr "$1" : '^[[:alnum:]][-[:alnum:].]*$' > /dev/null
}
# Checks that a given string could plausibly be an IPv6 address
# (hexadecimal, ':', and '.' characters only, followed by an optional zone
# index consisting of a '%' and a device name).
check_ipv6() {
echo "$1" | /bin/grep -E -q '^[0-9a-fA-F:.]+(%[a-z0-9]+)?$'
}
# Checks that a given string starts with an alphanumeric, and contains only
# alphanumeric and zero or more of "_:.~%$^\-"
check_username() {
expr "$1" : '^[[:alnum:]][[:alnum:]_:.~%$^\-]*$' > /dev/null
}
check_digits() {
expr "$1" : '^[[:digit:]]*$' > /dev/null
}
repl() {
echo "Welcome to crosh, type 'help' for a list of commands."
if [ "$IS_BASH" != "1" ]; then
echo "Sorry, line editing and command history disabled due to" \
"shell limitations."
fi
while [ 1 ]; do
if shell_read "crosh> " LINE_; then
if [ ! -z "$LINE_" ]; then
shell_history -s "$LINE_"
dispatch "$LINE_"
fi
else
echo
return 1
fi
done
}
INPUTRC="$(dirname "$0")/inputrc.crosh"
HISTFILE="$HOME/.crosh_history"
shell_history -r $HISTFILE
repl
shell_history -w $HISTFILE