about summary refs log tree commit diff
path: root/Functions/TCP/tcp_open
blob: 091217788fe349a0f87f688944276148d0363824 (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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# 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

# Global set up for TCP function suite.

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

# TCP_SECONDS_START is only set if we override TCP_SECONDS locally,
# so provide a global value for convenience.  Should probably always be 0.
(( ${+TCP_SECONDS_START} )) || typeset -gF TCP_SECONDS_START

# Processing for new connection.

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

  # 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
      if ! tcp_on_open $sess $fd; then
	if [[ -z $quiet ]]; then
	  if (( ${#sessargs} )); then
	    print "Session $sess" \
"(host $sessargs[1], port $sessargs[2] fd $fd): tcp_on_open FAILED."
	  else
	    print "Session $sess (fd $fd) tcp_on_open FAILED."
	  fi
	  tcp_close -- $sess
	else
	  tcp_close -q -- $sess
	fi
	stat=1
	continue
      fi
    fi
  fi

  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
done

if [[ -z $TCP_SESS || -z $tcp_by_name[$TCP_SESS] ]]; then
  # careful in case we closed it again...
  if [[ -n $tcp_by_name[$sessnames[1]] ]]; then
    [[ -z $quiet ]] && print "Setting default TCP session $sessnames[1]"
    typeset -g TCP_SESS=$sessnames[1]
  fi
fi

return $stat