#! /bin/sh # # ircsink - send stdin to IRC # # SYNOPSIS # ircsink [--nick=NICK] [--port=PORT] [--secure] # --server=SERVER --target=TARGET # # ircsink --help # # DESCRIPTION # ircsink sends lines from standard input to the specified IRC target. # # --command=NOTICE (default: PRIVMSG) # Whether a PRIVMSG or NOTICE is sent. # # --help # # --nick=NICK (default: the system's host name) # # --port=PORT (default: 6667, or 6697 if --secure is specified) # # --sasl=plain # If specified, then use the PLAIN SASL mechanism. # # --sasl-pass=@FILE # --sasl-pass=$VAR # --sasl-pass="PASS # Password to be used for SASL authentication, which can be read either # from a file, an environment variable, or passed in as argument. # # Note: the characters @, $, and " have to be verbatim and might need to be # quoted in the shell, e.g. ircsink --sasl-pass=\$SASL_PASS # # --sasl-user=USER (default: NICK) # User to be used for SASL authentication. # # --secure # If specified, then the connection will be made using openssl. # Otherwise netcat will be used. # # --server=SERVER # # --target=TARGET # The target for receiving messages. # Can be either a channel's or a user's name. # set -efu main() {( exec 3<&0 _args=$(getopt -n "$0" -s sh \ -l command:,help,nick:,port:,sasl:,sasl-pass:,sasl-user:,secure,server:,target: \ -- "$0" "$@") eval set -- "$_args" unset _args command= help= nick= port= sasl_mech= sasl_pass= sasl_user= secure= server= target= while :; do case $1 in --command) command=$2; shift 2;; --help) help=1; shift;; --nick) nick=$2; shift 2;; --port) port=$2; shift 2;; --sasl) case $2 in plain) sasl_mech=$2; shift 2;; *) echo "$0: unsupported SASL mechanism: $2" >&2; exit 1;; esac ;; --sasl-pass) case $2 in \@*) sasl_pass=$(cat "${2:1}"); shift 2;; \$*) sasl_pass=$(env | awk -F= -v name="${2:1}" '$1==name{print$2}'); shift 2;; \"*) sasl_pass=${2:1}; shift 2;; *) echo "$0: don't know how to get SASL password: $2" >&2; exit 1;; esac ;; --sasl-user) sasl_user=$2; shift 2;; --secure) secure=1; shift;; --server) server=$2; shift 2;; --target) target=$2; shift 2;; --) shift; break;; esac done case $# in 0) :;; 1) echo "$0: bad argument: $*" >&2; exit 1;; *) echo "$0: bad arguments: $*" >&2; exit 1;; esac if test "$help" = 1; then print_help exit fi if test -t 3; then echo "$0: error: ircsink cannot be used interactively" >&2 exit 1 fi if test -z "$command"; then command=PRIVMSG fi if test -z "$nick"; then nick=$(hostname) fi if test -n "$sasl_mech" && test -z "$sasl_user"; then sasl_user=$nick fi if test -n "$sasl_mech" && test -z "$secure"; then echo "$0: error: cannot use --sasl without --secure" >&2 exit 1 fi if test -z "$port"; then case $secure in '') port=6667;; 1) port=6697;; *) echo "$0: missing argument: --port" >&2 exit 1 ;; esac fi if test -z "$server"; then echo "$0: missing argument: --server" >&2 exit 1 fi if test -z "$target"; then echo "$0: missing argument: --target" >&2 exit 1 fi is_channel() { case $1 in \#*) :;; *) ! :;; esac } # echo2 and cat2 are used output to both, stdout and stderr # This is used to see what we send to the irc server. (debug output) echo2() { echo "$*"; echo "$*" >&2; } cat2() { awk '{ print $0 print $0 > "/dev/stderr" }' } # command_cat transforms stdin to a privmsg or notice command_cat() { awk -v command="$command" -v target="$target" '{ print command" "target" :"$0 }' } ircat() { if test "$secure" = 1; then openssl s_client -connect "$server:$port" else nc "$server" "$port" fi } # ircin is used to feed the output of netcat back to the "irc client" # so we can implement expect-like behavior with sed^_^ # XXX mkselfdestructingtmpfifo would be nice instead of this cruft tmpdir=$(mktemp --tmpdir -d ircsink_XXXXXXXX) cd "$tmpdir" mkfifo ircin trap cleanup EXIT cleanup() { trap - EXIT rm ircin cd "$OLDPWD" rmdir "$tmpdir" } { LOGNAME=${LOGNAME-$USER} echo2 "USER $LOGNAME 0 * :$LOGNAME@$(hostname)" echo2 "NICK $nick" sed -nru ' # wait for messages that indicate that the connection is ready # RPL_UMODEIS (221) # RPL_ENDOFMOTD (376) # ERR_NOMOTD (422) /^:[^ ]* (MODE|221|376|422) /q # answer any PING messages while waiting s/^PING (:.*)/PONG \1/p ' case $sasl_mech in plain) echo2 "CAP REQ :sasl" sed -nru '/^:[^ ]* CAP [0-9A-Za-z]+ ACK :?sasl/q' echo2 "AUTHENTICATE PLAIN" sed -nru '/^AUTHENTICATE \+\r$/q' echo sasl user: $sasl_user >&2 message=$(printf '\0%s\0%s' "$sasl_user" "$sasl_pass" | base64 -w0) echo "AUTHENTICATE $message" echo "AUTHENTICATE ***REDACTED***" >&2 # wait for SASL to finish # RPL_SASLSUCCESS (903) # ERR_SASLFAIL (904) # ERR_SASLTOOLONG (905) sasl_result=$(sed -nru 's/^:[^ ]* (90[345]) .*/\1/p;T;q') if test $sasl_result -ne 903; then echo "$0: error: SASL authentication failed" >&2 exit 1 fi ;; esac if is_channel "$target"; then echo2 "JOIN $target" fi command_cat <&3 \ | cat2 if is_channel "$target"; then echo2 "PART $target" # wait for PART confirmation sed -n '/:'"$nick"'![^ ]* PART /q' fi echo2 'QUIT :Gone to have lunch' } < ircin \ | ircat | tee -a ircin )} print_help() { sed -nr ' 0,/^$/{ /^#!/d s/^#($| )//p } ' "$0" } main "$@"