diff options
author | April Chin <April.Chin@Sun.COM> | 2008-12-27 14:59:38 -0800 |
---|---|---|
committer | April Chin <April.Chin@Sun.COM> | 2008-12-27 14:59:38 -0800 |
commit | 7c2fbfb345896881c631598ee3852ce9ce33fb07 (patch) | |
tree | 4b173b5657508562dfc0aa05f7d056d1e9add505 /usr/src/lib/libshell/common/scripts/shircbot.sh | |
parent | 6071ac1de68fed78e1e10052045bbb5f1732a263 (diff) | |
download | illumos-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.sh | 429 |
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 |