summaryrefslogtreecommitdiff
path: root/usr/src/lib/libshell/common/scripts/shircbot.sh
diff options
context:
space:
mode:
authorApril Chin <April.Chin@Sun.COM>2008-12-27 14:59:38 -0800
committerApril Chin <April.Chin@Sun.COM>2008-12-27 14:59:38 -0800
commit7c2fbfb345896881c631598ee3852ce9ce33fb07 (patch)
tree4b173b5657508562dfc0aa05f7d056d1e9add505 /usr/src/lib/libshell/common/scripts/shircbot.sh
parent6071ac1de68fed78e1e10052045bbb5f1732a263 (diff)
downloadillumos-gate-7c2fbfb345896881c631598ee3852ce9ce33fb07.tar.gz
PSARC/2008/094 ksh93 Update 1
PSARC/2008/344 ksh93 Integration Update 1 Amendments 1 PSARC/2008/589 Remove /usr/bin/printf from PSARC case 2008 094 6619428 *ksh93* RFE: Update ksh93 in Solaris to ast-ksh.2008-11-04 6788659 RFE: Update libpp in Solaris to ast-open.2008-07-25 6561901 RFE: Add "shcomp" (shell script compiler) + kernel module to exec binary sh code 6599668 RFE: Move consumers of alias.sh over to ksh93 6595183 *ksh93* RFE: Update ksh93-integration demo code 6775901 *ksh93* no C message catalogs are generated for ksh93 6451262 *sleep* RFE: /usr/bin/sleep should support floating-point values 6687139 *ksh93* command substitution, exec, and stdout redirection cause allocation loop 6703761 *ksh93* crashes in script containing uncommon output redirections 6715496 *ksh93* SEGVs on array reinitialization 6713682 *ksh93* Creating a compound variable in a subshell "bleeds through" to the calling subshell 6672350 *ksh93* causes parent shell to die when child shell is suspended 6745015 *ksh93* VARIABLE=`command substitution` assignment is not reliable on OpenSolaris 6710205 *ksh93* problem with command substitution (within back quotes) containing \$' 6737600 *ksh93* exits debugger when user presses ctrl-c 6748645 *ksh93* fc -l -e - is mis-parsed, outputs wrong error message "-e - requires single argument" 6754020 *ksh93* does weird '[' expansion 6753538 *ksh93* umask modification leaks out of a ksh93 subshell 6766246 *ksh93* bug in pattern matching 6763594 *ksh93* executes command after "command" builtin twice on failure 6762665 *ksh93* Difficult-to-reproduce SIGSEGV in ksh93
Diffstat (limited to 'usr/src/lib/libshell/common/scripts/shircbot.sh')
-rw-r--r--usr/src/lib/libshell/common/scripts/shircbot.sh429
1 files changed, 429 insertions, 0 deletions
diff --git a/usr/src/lib/libshell/common/scripts/shircbot.sh b/usr/src/lib/libshell/common/scripts/shircbot.sh
new file mode 100644
index 0000000000..2ce067ad6c
--- /dev/null
+++ b/usr/src/lib/libshell/common/scripts/shircbot.sh
@@ -0,0 +1,429 @@
+#!/usr/bin/ksh93
+
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+#
+# shircbot - a simple IRC client/bot demo
+#
+
+# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
+export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin
+
+# Make sure all math stuff runs in the "C" locale to avoid problems
+# with alternative # radix point representations (e.g. ',' instead of
+# '.' in de_DE.*-locales). This needs to be set _before_ any
+# floating-point constants are defined in this script).
+if [[ "${LC_ALL}" != "" ]] ; then
+ export \
+ LC_MONETARY="${LC_ALL}" \
+ LC_MESSAGES="${LC_ALL}" \
+ LC_COLLATE="${LC_ALL}" \
+ LC_CTYPE="${LC_ALL}"
+ unset LC_ALL
+fi
+export LC_NUMERIC=C
+
+function fatal_error
+{
+ print -u2 "${progname}: $*"
+ exit 1
+}
+
+# Definition for a IRC session class
+typeset -T ircsession_t=(
+ typeset -C server=(
+ typeset name
+ integer port
+ )
+
+ typeset nick="ksh93irc"
+
+ typeset running=true
+
+ integer fd=-1
+
+ function createsession
+ {
+ set -o xtrace
+
+ _.server.name=$1
+ _.server.port=$2
+ _.nick=$3
+
+ redirect {_.fd}<>"/dev/tcp/${_.server.name}/${_.server.port}"
+ (( $? == 0 )) || { print -n2 $"Could not open server connection." ; return 1 ; }
+
+ printf "fd=%d\n" _.fd
+
+ return 0
+ }
+
+ function login
+ {
+ {
+ printf "USER %s %s %s %s\n" "${_.nick}" "${_.nick}" "${_.nick}" "${_.nick}"
+ printf "NICK %s\n" "${_.nick}"
+ } >&${_.fd}
+
+ return 0
+ }
+
+ function join_channel
+ {
+ printf "JOIN %s\n" "$1" >&${_.fd}
+
+ return 0
+ }
+
+ function mainloop
+ {
+ typeset line
+ float -S last_tick=0
+ # We use the linebuf_t class here since network traffic
+ # isn't guranteed to fit a single $'\n'-terminated line
+ # into one TCP package. linebuf_t buffers characters
+ # until it has one complete line. This avoids the need for
+ # async I/O normally used by IRC clients
+ linebuf_t serverbuf
+ linebuf_t clientbuf
+ integer fd=${_.fd}
+
+ set -o xtrace
+
+ _.login
+
+ while ${_.running} ; do
+ while serverbuf.readbuf line <&${fd} ; do
+ _.dispatch_serverevent "$line"
+ done
+
+ while clientbuf.readbuf line </dev/stdin ; do
+ printf "client: %q\n" "${line}"
+ printf "%s\n" "${line}" >&${fd}
+ done
+
+ # call mainloop_tick function in intervals to handle
+ # async events (e.g. automatic /join etc.)
+ if (( (SECONDS-last_tick) > 5. )) ; then
+ (( last_tick=SECONDS ))
+ _.mainloop_tick
+ fi
+ done
+
+ return 0
+ }
+
+ function mainloop_tick
+ {
+ return 0
+ }
+
+ function dispatch_serverevent
+ {
+ typeset line="$1"
+
+ case "${line}" in
+ ~(El)PING)
+ typeset -C ping_args=(
+ line="$line"
+ )
+ _.serverevent_ping "ping_args"
+ ;;
+ ~(El):.*\ PRIVMSG)
+ typeset -C privmsg_args=(
+ typeset line="$line"
+ typeset msguser="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\1}"
+ typeset msgchannel="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\3}"
+ typeset msg="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\4}"
+ )
+ _.serverevent_privmsg "privmsg_args"
+ ;;
+ ~(El):.*\ INVITE)
+ typeset -C invite_args=(
+ typeset line="$line"
+ typeset inviteuser="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\1}"
+ typeset invitenick="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\3}"
+ typeset invitechannel="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\4}"
+ )
+ _.serverevent_invite "invite_args"
+ ;;
+ *)
+ printf "server: %q\n" "${line}"
+ ;;
+ esac
+
+ return 0
+ }
+
+ function serverevent_privmsg
+ {
+ nameref args=$1
+ typeset msguser="${args.msguser}"
+ typeset msgchannel="${args.msgchannel}"
+ typeset msg="${args.msg}"
+
+ printf "#privms: user=%q, channel=%q, msg=%q\n" "$msguser" "$msgchannel" "$msg"
+
+ return 0
+ }
+
+ function serverevent_invite
+ {
+ nameref args=$1
+
+ printf "JOIN %s\n" "${args.invitechannel/:/}" >&${_.fd}
+
+ return 0
+ }
+
+ function send_privmsg
+ {
+ typeset channel="$1"
+ typeset msg="$2"
+
+ # Do we have to escape any characters in "msg" ?
+ printf "PRIVMSG %s :%s\n" "${channel}" "${msg}" >&${_.fd}
+
+ return 0
+ }
+
+ function serverevent_ping
+ {
+ nameref args=$1
+
+ printf "PONG %s\n" "${args.line/~(Elr)([^ ]+) ([^ ]+).*/\2}" >&${_.fd}
+
+ return 0
+ }
+)
+
+# line buffer class
+# The buffer class tries to read characters from the given <fd> until
+# it has read a whole line.
+typeset -T linebuf_t=(
+ typeset buf
+
+ function reset
+ {
+ _.buf=""
+ return 0
+ }
+
+ function readbuf
+ {
+ nameref var=$1
+ typeset ch
+
+ while IFS='' read -t 0.2 -N 1 ch ; do
+ [[ "$ch" == $'\r' ]] && continue
+
+ if [[ "$ch" == $'\n' ]] ; then
+ var="${_.buf}"
+ _.reset
+ return 0
+ fi
+
+ _.buf+="$ch"
+ done
+
+ return 1
+ }
+)
+
+function usage
+{
+ OPTIND=0
+ getopts -a "${progname}" "${shircbot_usage}" OPT '-?'
+ exit 2
+}
+
+# program start
+# (be carefull with builtins here - they are unconditionally available
+# in the shell's "restricted" mode)
+builtin basename
+builtin sum
+
+typeset progname="${ basename "${0}" ; }"
+
+typeset -r shircbot_usage=$'+
+[-?\n@(#)\$Id: shircbot (Roland Mainz) 2008-10-31 \$\n]
+[-author?Roland Mainz <roland.mainz@sun.com>]
+[-author?Roland Mainz <roland.mainz@nrubsig.org>]
+[+NAME?shircbot - simple IRC bot demo]
+[+DESCRIPTION?\bshircbot\b is a small demo IRC bot which provides
+ a simple IRC bot with several subcommands.]
+[n:nickname?IRC nickname for this bot.]:[nick]
+[s:ircserver?IRC servername.]:[servername]
+[j:joinchannel?IRC servername.]:[channelname]
+[+SEE ALSO?\bksh93\b(1)]
+'
+
+typeset -C config=(
+ typeset nickname="${LOGNAME}bot"
+ typeset servername="irc.freenode.net"
+ integer port=6667
+ typeset -a join_channels
+)
+
+while getopts -a "${progname}" "${shircbot_usage}" OPT ; do
+# printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
+ case ${OPT} in
+ n) config.nickname="${OPTARG}" ;;
+ s) config.servername="${OPTARG}" ;;
+ j) config.join_channels+=( "${OPTARG}" ) ;;
+ *) usage ;;
+ esac
+done
+shift $((OPTIND-1))
+
+# if no channel was provided we join a predefined set of channels
+if (( ${#config.join_channels[@]} == 0 )) ; then
+ if [[ "${config.servername}" == "irc.freenode.net" ]] ; then
+ config.join_channels+=( "#opensolaris" )
+ config.join_channels+=( "#opensolaris-dev" )
+ config.join_channels+=( "#opensolaris-arc" )
+ config.join_channels+=( "#ksh" )
+ elif [[ "${config.servername}" == ~(E)irc.(sfbay|sweden) ]] ; then
+ config.join_channels+=( "#onnv" )
+ fi
+fi
+
+print "## Start."
+
+ircsession_t mybot
+
+# override ircsession_t::serverevent_privmsg with a new method for our bot
+function mybot.serverevent_privmsg
+{
+ nameref args=$1
+ typeset msguser="${args.msguser}"
+ typeset msgchannel="${args.msgchannel}"
+ typeset msg="${args.msg}"
+
+ printf "#message: user=%q, channel=%q, msg=%q\n" "$msguser" "$msgchannel" "$msg"
+
+ # Check if we get a private message
+ if [[ "${msgchannel}" == "${_.nick}" ]] ; then
+ # ${msgchannel} point to our own nick if we got a private message,
+ # we need to extract the sender's nickname from ${msguser} and put
+ # it into msgchannel
+ msgchannel="${msguser/~(El):(.*)!.*/\1}"
+ else
+ # check if this is a command for this bot
+ [[ "$msg" != ~(Eli):${_.nick}:[[:space:]] ]] && return 0
+ fi
+
+ # strip beginning (e.g. ":<nick>:" or ":") plus extra spaces
+ msg="${msg/~(Eli)(:${_.nick})*:[[:space:]]*/}"
+
+ printf "botmsg=%q\n" "$msg"
+
+ case "$msg" in
+ ~(Eli)date)
+ _.send_privmsg "$msgchannel" "$(
+ ( printf "%(%Y-%m-%d, %Th/%Z)T\n" )
+ )"
+ ;;
+ ~(Eli)echo)
+ _.send_privmsg "$msgchannel" "${msg#*echo}"
+ ;;
+ ~(Eli)exitbot)
+ typeset exitkey="$(print "$msguser" | sum -x sha1)" # this is unwise&&insecure
+ if [[ "$msg" == *${exitkey}* ]] ; then
+ _.running=false
+ fi
+ ;;
+ ~(Eli)help)
+ _.send_privmsg "$msgchannel" "$(
+ printf "Hello, this is shircbot, written in ksh93 (%s). " "${.sh.version}"
+ printf "Subcommands are 'say hello', 'math <math-expr>', 'stocks', 'uuid', 'date' and 'echo'."
+ )"
+ ;;
+ ~(Eli)math)
+ if [[ "${msg}" == ~(E)[\`\$] ]] ; then
+ # "restricted" shell mode would prevent any damage but we try to be carefull...
+ _.send_privmsg "$msgchannel" "Syntax error."
+ else
+ typeset mathexpr="${msg#*math}"
+
+ printf "Calculating '%s'\n" "${mathexpr}"
+ _.send_privmsg "$msgchannel" "$(
+ ( printf 'export PATH=/usr/$RANDOM/foo ; set -o restricted ; printf "%%s = %%.40g\n" "%s" $(( %s ))\n' "${mathexpr}" "${mathexpr}" | source /dev/stdin 2>&1 )
+ )"
+ fi
+ ;;
+ ~(Eli)say\ hello)
+ _.send_privmsg "$msgchannel" "Hello, this is a bot."
+ ;;
+ ~(Eli)stocks)
+ typeset stockmsg tickersymbol
+ for tickersymbol in "JAVA" "IBM" "AAPL" "HPQ" ; do
+ stockmsg="$( /usr/sfw/bin/wget -q -O /dev/stdout "http://quote.yahoo.com/d/quotes.csv?f=sl1d1t1c1ohgv&e=.csv&s=${tickersymbol}" 2>&1 )"
+ _.send_privmsg "$msgchannel" "${tickersymbol}: ${stockmsg//,/ }"
+ done
+ ;;
+ ~(Eli)uuid)
+ _.send_privmsg "$msgchannel" "$(
+ ( print "%(%Y%M%D%S%N)T$((RANDOM))%s\n" "${msguser}" | sum -x sha256 )
+ )"
+ ;;
+ esac
+
+ return 0
+}
+
+# Automatically join the list of channels listed in |config.join_channels|
+# after the client is connected to the server for some time
+function mybot.mainloop_tick
+{
+ integer -S autojoin_done=2
+ integer i
+
+ if (( autojoin_done-- == 0 && ${#config.join_channels[@]} > 0 )) ; then
+ print "# Autojoin channels..."
+
+ for ((i=0 ; i < ${#config.join_channels[@]} ; i++ )) ; do
+ mybot.join_channel "${config.join_channels[i]}"
+ done
+ fi
+
+ return 0
+}
+
+mybot.createsession "${config.servername}" ${config.port} "${config.nickname}"
+
+# This is a network-facing application - once we've set eveything up
+# we set PATH to a random value and switch to the shell's restricted
+# mode to make sure noone can escape the jail.
+#export PATH=/usr/$RANDOM/foo
+#set -o restricted
+
+mybot.mainloop
+
+print "## End."
+
+exit 0