about summary refs log tree commit diff
path: root/Functions/TCP/tcp_open
blob: d9d5a96da7478956b3304b8b605028f93c349091 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# Open a TCP session, add it to the list, handle it with zle if that's running.
# Unless using -a, -f, -l or -s, first two arguments are host and port.
#
# Remaining argument, if any, is the name of the session, which mustn't
# clash with an existing one.  If none is given, the number of the
# connection is used (i.e. first connection is 1, etc.), or the first
# available integer if that is already in use.
#
# Session names, whether provided on the command line or in the
# .ztcp_sessions file should not be `clever'.  A clever name is one
# with characters that won't work.  This includes whitespace and an
# inconsistent set of punctuation characters.  If in doubt, stick
# to alphanumeric, underscore and non-initial hyphen.
#
# -a fd   Accept a connection on fd and make that the session.
#         This will block until a successful incoming connection is received.
#
#         fd is probably a value returned by ztcp -l; no front-end
#         is currently provided for that but it should simply be
#         a matter of calling `ztcp -l port' and storing $REPLY, then
#         closing the listened port with `ztcp -c $stored_fd'.
#
# -f fd   `Fake' tcp connection on the given file descriptor.  This
#         could be, for example, a file descriptor already opened to
#         a named pipe.  It should not be a regular file, however.
#         Note that it is not a good idea for two different sessions
#         to be attempting to read from the same named pipe, so if
#         both ends of the pipe are to be handled by zsh, at least
#         one should use the `-z' option.
#
# -l sesslist
# -s sessname
#         Open by session name or comma separated list; either may
#         be repeated as many times as necessary.  The session must be
#	  listed in the file ${ZDOTDIR:-$HOME}/.ztcp_sessions.  Lines in
#	  this file look exactly like a tcp_open command line except the
#	  session name is at the start, for example
#           sess1 pwspc 2811
#         has the effect of
#           tcp_open pwspc 2811 sess1
#         Remaining arguments (other than options) to tcp_open are
#         not allowed.  Options in .ztcp_sessions are not handled.
#	  The file must be edited by hand.
#
# -z      Don't install a zle handler for reading on the file descriptor.
#	  Otherwise, if zle is enabled, the file descriptor will
#         be tested while at the shell prompt and any input automatically
#         printed in the same way as job control notification.
#
# If a session is successfully opened, and if the function `tcp_on_open'
# exists, it is run with the arguments session_name, session_fd.

emulate -L zsh
setopt extendedglob cbases

zmodload -i zsh/net/tcp || return 1
autoload -U zgprintf tcp_alias tcp_close tcp_command tcp_expect tcp_fd_handler
autoload -U tcp_log tcp_output tcp_proxy tcp_read tcp_rename tcp_send
autoload -U tcp_sess tcp_spam tcp_talk tcp_wait

local opt accept fake nozle sessfile sess quiet
local -a sessnames sessargs
integer stat

while getopts "a:f:l:qs:z" opt; do
    case $opt in
	(a) accept=$OPTARG
            if [[ $accept != [[:digit:]]## ]]; then
		print "option -a takes a file descriptor" >&2
		return 1
	    fi
	    ;;
	(f) fake=$OPTARG
	    if [[ $fake != [[:digit:]]## ]]; then
		print "option -f takes a file descriptor" >&2
		return 1
	    fi
	    ;;
	(l) sessnames+=(${(s.,.)OPTARG})
            ;;
	(q) quiet=1
            ;;
	(s) sessnames+=($OPTARG)
            ;;
	(z) nozle=1
            ;;
	(*) return 1
            ;;
    esac
done
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))

(( ${+tcp_by_fd} ))   || typeset -gA tcp_by_fd
(( ${+tcp_by_name} )) || typeset -gA tcp_by_name 
typeset -A sessassoc

if (( ${#sessnames} )); then
    if [[ $# -ne 0 || -n $accept || -n $fake ]]; then
	print "Incompatible arguments with \`-s' option." >&2
	return 1
    fi
    for sess in ${sessnames}; do
	sessassoc[$sess]=
    done

    sessfile=${ZDOTDIR:-$HOME}/.ztcp_sessions
    if [[ ! -r $sessfile ]]; then
	print "No session file: $sessfile" >&2
	return 1
    fi
    while read -A sessargs; do
	[[ ${sessargs[1]} = '#'* ]] && continue
	if ((  ${+sessassoc[${sessargs[1]}]} )); then
	    sessassoc[${sessargs[1]}]="${sessargs[2,-1]}"
	fi
    done < $sessfile
    for sess in ${sessnames}; do
	if [[ -z $sessassoc[$sess] ]]; then
	    print "Couldn't find session $sess in $sessfile." >&2
	    return 1
	fi
    done
else
    if [[ -z $accept && -z $fake ]]; then
	if (( $# < 2 )); then
	    set -- wrong number of arguments
	else
	    host=$1 port=$2
	    shift $(( $# > 1 ? 2 : 1 ))
	fi
    fi
    if [[ -n $1 ]]; then
	sessnames=($1)
	shift
    else
	sessnames=($(( ${#tcp_by_fd} + 1 )))
	while [[ -n $tcp_by_name[$sessnames[1]] ]]; do
	    (( sessnames[1]++ ))
	done
    fi
    sessassoc[$sessnames[1]]="$host $port"
fi

if (( $# )); then
  print "Usage: $0 [-z] [-a fd | -f fd | host port [ session ] ]
  $0 [-z] [ -s session | -l sesslist ] ..." >&2
  return 1
fi

local REPLY fd
for sess in $sessnames; do
    if [[ -n $tcp_by_name[$sess] ]]; then
	print "Session \`$sess' already exists." >&2
	return 1
    fi

    sessargs=()
    if [[ -n $fake ]]; then
	fd=$fake;
    else
	if [[ -n $accept ]]; then
	    ztcp -a $accept || return 1
	else
	    sessargs=(${=sessassoc[$sess]})
	    ztcp $sessargs || return 1
	fi
	fd=$REPLY
    fi

    tcp_by_fd[$fd]=$sess
    tcp_by_name[$sess]=$fd

    [[ -o zle && -z $nozle ]] && zle -F $fd tcp_fd_handler
    if [[ -z $quiet ]]; then
	if (( ${#sessargs} )); then
	    print "Session $sess" \
"(host $sessargs[1], port $sessargs[2] fd $fd) opened OK."
	else
	    print "Session $sess (fd $fd) opened OK."
	fi
    fi

    # needed for new completion system, so I'm not too sanguine
    # about requiring this here...
    if zmodload -i zsh/parameter; then
	if (( ${+functions[tcp_on_open]} )); then
	    tcp_on_open $sess $fd
	fi
    fi
done

if [[ -z $TCP_SESS ]]; then
    [[ -z $quiet ]] && print "Setting default TCP session $sessnames[1]"
    TCP_SESS=$sessnames[1]
fi

return $stat