1 /*****************************************************************************
2 * Copyright (C) 2001 The Regents of the University of California.
3 * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
4 * Written by Andrew Uselton <uselton2@llnl.gov>
5 * UCRL-CODE-2002-008.
6 *
7 * This file is part of PowerMan, a remote power management program.
8 * For details, see http://code.google.com/p/powerman/
9 *
10 * PowerMan is free software; you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free
12 * Software Foundation; either version 2 of the License, or (at your option)
13 * any later version.
14 *
15 * PowerMan is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18 * for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with PowerMan; if not, write to the Free Software Foundation, Inc.,
22 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
23 \*****************************************************************************/
24
25 /*
26 * Implement connect/disconnect/preprocess methods for tcp/telnet devices.
27 *
28 * NOTE: we always do telnet when we are doing tcp. If there ever is a telnet
29 * device that requires some proactive option negotiation, or a non-telnet
30 * device that manages to confuse the telnet state machine, we could easily
31 * implement a 'notelnet' flag. Right now it seems innocuous to leave the
32 * telnet machine running.
33 */
34
35 #if HAVE_CONFIG_H
36 #include "config.h"
37 #endif /* HAVE_CONFIG_H */
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <ctype.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <sys/socket.h>
44 #include <sys/types.h>
45 #include <netdb.h>
46 #include <assert.h>
47 #define TELOPTS
48 #define TELCMDS
49 #include <arpa/telnet.h>
50 #include <sys/time.h>
51
52 #include "list.h"
53 #include "hostlist.h"
54 #include "cbuf.h"
55 #include "xtypes.h"
56 #include "parse_util.h"
57 #include "xmalloc.h"
58 #include "xpoll.h"
59 #include "pluglist.h"
60 #include "arglist.h"
61 #include "xregex.h"
62 #include "device_private.h"
63 #include "error.h"
64 #include "debug.h"
65 #include "device_tcp.h"
66 #include "xpty.h"
67
68 #ifndef HAVE_SOCKLEN_T
69 typedef int socklen_t; /* socklen_t is uint32_t in Posix.1g */
70 #endif /* !HAVE_SOCKLEN_T */
71
72 typedef enum { TELNET_NONE, TELNET_CMD, TELNET_OPT } TelnetState;
73 typedef struct {
74 char *host;
75 char *port;
76 TelnetState tstate; /* state of telnet processing */
77 unsigned char tcmd; /* buffered telnet command */
78 bool quiet; /* don't report idle timeout messages */
79 struct addrinfo *addrs;
80 struct addrinfo *cur;
81 } TcpDev;
82
83 static void _telnet_init(Device *dev);
84 static void _telnet_preprocess(Device * dev);
85
_parse_options(TcpDev * tcp,char * flags)86 static void _parse_options(TcpDev *tcp, char *flags)
87 {
88 char *tmp = xstrdup(flags);
89 char *opt = strtok(tmp, ",");
90
91 while (opt) {
92 if (strcmp(opt, "quiet") == 0)
93 tcp->quiet = TRUE;
94 else
95 err_exit(FALSE, "bad device option: %s\n", opt);
96 opt = strtok(NULL, ",");
97 }
98 xfree(tmp);
99 }
100
tcp_create(char * host,char * port,char * flags)101 void *tcp_create(char *host, char *port, char *flags)
102 {
103 TcpDev *tcp = (TcpDev *)xmalloc(sizeof(TcpDev));
104 struct addrinfo hints;
105 int error;
106
107 tcp->host = xstrdup(host);
108 tcp->port = xstrdup(port);
109 tcp->tstate = TELNET_NONE;
110 tcp->tcmd = 0;
111 tcp->quiet = FALSE;
112 if (flags)
113 _parse_options(tcp, flags);
114
115 /* Store a list of possible addresses/families to connect to
116 * in the tcp->addrs linked list.
117 */
118 memset(&hints, 0, sizeof(hints));
119 hints.ai_family = PF_UNSPEC;
120 hints.ai_socktype = SOCK_STREAM;
121 if ((error = getaddrinfo(tcp->host, tcp->port, &hints, &tcp->addrs)) != 0)
122 err_exit(FALSE, "getaddrinfo %s:%s: %s", tcp->host, tcp->port,
123 gai_strerror(error));
124 if (tcp->addrs == NULL)
125 err_exit(FALSE, "no addresses for server %s:%s", tcp->host, tcp->port);
126 tcp->cur = tcp->addrs;
127
128 return (void *)tcp;
129 }
130
tcp_destroy(void * data)131 void tcp_destroy(void *data)
132 {
133 TcpDev *tcp = (TcpDev *)data;
134
135 if (tcp->host)
136 xfree(tcp->host);
137 if (tcp->port)
138 xfree(tcp->port);
139 if (tcp->addrs)
140 freeaddrinfo(tcp->addrs);
141
142 xfree(tcp);
143 }
144
145 /* After a successful connect, perform some management on
146 * the connection. Returns TRUE on success, FALSE on error.
147 */
tcp_finish_connect_one(Device * dev)148 static bool tcp_finish_connect_one(Device *dev)
149 {
150 int rc;
151 int error = 0;
152 socklen_t len = sizeof(error);
153
154 rc = getsockopt(dev->fd, SOL_SOCKET, SO_ERROR, &error, &len);
155 /*
156 * If an error occurred, Berkeley-derived implementations
157 * return 0 with the pending error in 'error'. But Solaris
158 * returns -1 with the pending error in 'errno'. -dun
159 */
160 if (rc < 0)
161 error = errno;
162 if (! error) {
163 dev->connect_state = DEV_CONNECTED;
164 dev->stat_successful_connects++;
165 _telnet_init(dev);
166 return TRUE;
167 }
168 return FALSE;
169 }
170
171 /* Obtain a socket for the specified address and attempt to connect it.
172 * Return TRUE on completion or connection in progress, FALSE on error.
173 */
tcp_connect_one(Device * dev,struct addrinfo * addr)174 static bool tcp_connect_one(Device *dev, struct addrinfo *addr)
175 {
176 TcpDev *tcp = (TcpDev *)dev->data;
177 int opt;
178
179 assert(tcp->cur != NULL);
180
181 if ((dev->fd = socket(addr->ai_family, addr->ai_socktype, 0)) < 0)
182 return FALSE;
183 opt = 1;
184 if (setsockopt(dev->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
185 close(dev->fd);
186 return FALSE;
187 }
188 nonblock_set(dev->fd);
189
190 if (connect(dev->fd, addr->ai_addr, addr->ai_addrlen) >= 0)
191 return tcp_finish_connect_one(dev);
192 else if (errno == EINPROGRESS)
193 return TRUE;
194
195 close(dev->fd);
196 return FALSE;
197 }
198
199 /*
200 * Continue TCP connect when fd unblocks.
201 * Return FALSE on error, which triggers timed retry of tcp_connect().
202 * Return TRUE if connected or still connecting (on the next address).
203 */
tcp_finish_connect(Device * dev)204 bool tcp_finish_connect(Device * dev)
205 {
206 TcpDev *tcp;
207
208 assert(dev->magic == DEV_MAGIC);
209 assert(dev->connect_state == DEV_CONNECTING);
210
211 tcp = (TcpDev *)dev->data;
212
213 if (!tcp_finish_connect_one(dev)) {
214 tcp->cur = tcp->cur->ai_next;
215 while (tcp->cur && !tcp_connect_one(dev, tcp->cur))
216 tcp->cur = tcp->cur->ai_next;
217 if (tcp->cur == NULL)
218 dev->connect_state = DEV_NOT_CONNECTED;
219 }
220 switch(dev->connect_state) {
221 case DEV_NOT_CONNECTED:
222 err(FALSE, "tcp_finish_connect(%s): connection refused", dev->name);
223 break;
224 case DEV_CONNECTED:
225 if (!tcp->quiet)
226 err(FALSE, "tcp_finish_connect(%s): connected", dev->name);
227 break;
228 case DEV_CONNECTING:
229 if (!tcp->quiet)
230 err(FALSE, "tcp_finish_connect(%s): connecting", dev->name);
231 break;
232 }
233 return (dev->connect_state != DEV_NOT_CONNECTED);
234 }
235
236 /*
237 * Initiate a non-blocking TCP connect. tcp_finish_connect() will try to
238 * finish the job when the main poll() loop unblocks again, unless we
239 * finish here.
240 */
tcp_connect(Device * dev)241 bool tcp_connect(Device * dev)
242 {
243 TcpDev *tcp;
244
245 assert(dev->magic == DEV_MAGIC);
246 assert(dev->connect_state == DEV_NOT_CONNECTED);
247 assert(dev->fd == NO_FD);
248
249 tcp = (TcpDev *)dev->data;
250
251 dev->connect_state = DEV_CONNECTING;
252 while (tcp->cur && !tcp_connect_one(dev, tcp->cur))
253 tcp->cur = tcp->cur->ai_next;
254 if (tcp->cur == NULL)
255 dev->connect_state = DEV_NOT_CONNECTED;
256
257 switch(dev->connect_state) {
258 case DEV_NOT_CONNECTED:
259 err(FALSE, "tcp_connect(%s): connection refused", dev->name);
260 break;
261 case DEV_CONNECTED:
262 if (!tcp->quiet)
263 err(FALSE, "tcp_connect(%s): connected", dev->name);
264 break;
265 case DEV_CONNECTING:
266 if (!tcp->quiet)
267 err(FALSE, "tcp_connect(%s): connecting", dev->name);
268 break;
269 }
270
271 return (dev->connect_state == DEV_CONNECTED);
272 }
273
274 /*
275 * Close the socket associated with this device.
276 */
tcp_disconnect(Device * dev)277 void tcp_disconnect(Device * dev)
278 {
279 TcpDev *tcp;
280
281 assert(dev->magic == DEV_MAGIC);
282 assert(dev->connect_state == DEV_CONNECTING
283 || dev->connect_state == DEV_CONNECTED);
284 tcp = (TcpDev *)dev->data;
285
286 dbg(DBG_DEVICE, "tcp_disconnect: %s on fd %d", dev->name, dev->fd);
287
288 /* close socket if open */
289 if (dev->fd >= 0) {
290 if (close(dev->fd) < 0)
291 err(TRUE, "tcp_disconnect: %s close fd %d", dev->name, dev->fd);
292 dev->fd = NO_FD;
293 }
294
295 if (!tcp->quiet)
296 err(FALSE, "tcp_disconnect(%s): disconnected", dev->name);
297 }
298
tcp_preprocess(Device * dev)299 void tcp_preprocess(Device *dev)
300 {
301 _telnet_preprocess(dev);
302 }
303
_telnet_sendopt(Device * dev,int cmd,int opt)304 static void _telnet_sendopt(Device *dev, int cmd, int opt)
305 {
306 unsigned char str[] = { IAC, cmd, opt };
307 int n;
308
309 dbg(DBG_TELNET, "%s: _telnet_sendopt: %s %s", dev->name,
310 TELCMD_OK(cmd) ? TELCMD(cmd) : "<unknown>",
311 TELOPT_OK(opt) ? TELOPT(opt) : "<unknown>");
312
313 n = cbuf_write(dev->to, str, 3, NULL);
314 if (n < 3)
315 err((n < 0), "_telnet_sendopt: cbuf_write returned %d", n);
316 }
317
318 #if 0
319 static void _telnet_sendcmd(Device *dev, unsigned char cmd)
320 {
321 unsigned char str[] = { IAC, cmd };
322 int n;
323
324 dbg(DBG_TELNET, "%s: _telnet_sendcmd: %s", dev->name,
325 TELCMD_OK(cmd) ? TELCMD(cmd) : "<unknown>");
326
327 n = cbuf_write(dev->to, str, 2, NULL);
328 if (n < 2)
329 err((n < 0), "_telnet_sendcmd: cbuf_write returned %d", n);
330 }
331 #endif
332
_telnet_recvcmd(Device * dev,int cmd)333 static void _telnet_recvcmd(Device *dev, int cmd)
334 {
335 dbg(DBG_TELNET, "%s: _telnet_recvcmd: %s", dev->name,
336 TELCMD_OK(cmd) ? TELCMD(cmd) : "<unknown>");
337 }
338
339
_telnet_recvopt(Device * dev,int cmd,int opt)340 static void _telnet_recvopt(Device *dev, int cmd, int opt)
341 {
342 TcpDev *tcp = (TcpDev *)dev->data;
343
344 dbg(DBG_TELNET, "%s: _telnet_recvopt: %s %s", dev->name,
345 TELCMD_OK(cmd) ? TELCMD(cmd) : "<unknown>",
346 TELOPT_OK(opt) ? TELOPT(opt) : "<unknown>");
347 switch (cmd) {
348 case DO:
349 switch (opt) {
350 case TELOPT_SGA: /* rfc 858 - suppress go ahead*/
351 case TELOPT_TM: /* rfc 860 - timing mark */
352 _telnet_sendopt(dev, WILL, opt);
353 break;
354 case TELOPT_TTYPE: /* rfc 1091 - terminal type */
355 case TELOPT_NAWS: /* rfc 1073 - window size */
356 /* next three added for gnat powerman/634 - jg */
357 case TELOPT_NEW_ENVIRON: /* environment variables */
358 case TELOPT_XDISPLOC: /* X display location */
359 case TELOPT_TSPEED: /* terminal speed */
360 /* next two added for newer baytechs - jg */
361 case TELOPT_ECHO: /* echo */
362 case TELOPT_LFLOW: /* remote flow control */
363 /* next one added for cyclades ts - jg */
364 case TELOPT_BINARY: /* 8-bit data path */
365 _telnet_sendopt(dev, WONT, opt);
366 break;
367 default:
368 if (!tcp->quiet)
369 err(0, "%s: _telnet_recvopt: ignoring %s %s", dev->name,
370 TELCMD_OK(cmd) ? TELCMD(cmd) : "<unknown>",
371 TELOPT_OK(opt) ? TELOPT(opt) : "<unknown>");
372 break;
373 }
374 break;
375 default:
376 break;
377 }
378 }
379
380 /*
381 * Called just after a connect so we can perform any option requests.
382 */
_telnet_init(Device * dev)383 static void _telnet_init(Device * dev)
384 {
385 TcpDev *tcp = (TcpDev *)dev->data;
386
387 tcp->tstate = TELNET_NONE;
388 tcp->tcmd = 0;
389 #if 0
390 _telnet_sendopt(dev, DONT, TELOPT_NAWS);
391 _telnet_sendopt(dev, DONT, TELOPT_TTYPE);
392 _telnet_sendopt(dev, DONT, TELOPT_ECHO);
393 #endif
394 }
395
396 /*
397 * Telnet state machine. This is called when new data has arrived in the
398 * input buffer. We get to look first to process any telnet escapes.
399 * Except for a little bit of state stored in the dev->u.tcp union,
400 * we do all the processing now.
401 */
_telnet_preprocess(Device * dev)402 static void _telnet_preprocess(Device * dev)
403 {
404 static unsigned char peek[MAX_DEV_BUF];
405 static unsigned char device[MAX_DEV_BUF];
406 TcpDev *tcp = (TcpDev *)dev->data;
407 int len, i, k;
408
409 len = cbuf_peek(dev->from, peek, MAX_DEV_BUF);
410 for (i = 0, k = 0; i < len; i++) {
411 switch (tcp->tstate) {
412 case TELNET_NONE:
413 if (peek[i] == IAC)
414 tcp->tstate = TELNET_CMD;
415 else
416 device[k++] = peek[i];
417 break;
418 case TELNET_CMD:
419 switch (peek[i]) {
420 case IAC: /* escaped IAC */
421 device[k++] = peek[i];
422 tcp->tstate = TELNET_NONE;
423 break;
424 case DONT: /* option commands - one more byte coming */
425 case DO:
426 case WILL:
427 case WONT:
428 tcp->tcmd = peek[i];
429 tcp->tstate = TELNET_OPT;
430 break;
431 default: /* single char commands - process immediately */
432 _telnet_recvcmd(dev, peek[i]);
433 tcp->tstate = TELNET_NONE;
434 break;
435 }
436 break;
437 case TELNET_OPT: /* option char - process stored command */
438 _telnet_recvopt(dev, tcp->tcmd, peek[i]);
439 tcp->tstate = TELNET_NONE;
440 break;
441 }
442 }
443 /* rewrite buffers if anything changed */
444 if (k < len) {
445 int n;
446
447 n = cbuf_drop(dev->from, len);
448 if (n < len)
449 err((n < 0), "_telnet_preprocess: cbuf_drop returned %d", n);
450 n = cbuf_write(dev->from, device, k, NULL);
451 if (n < k)
452 err((n < 0), "_telnet_preprocess: cbuf_write returned %d", n);
453 }
454 }
455
456 /*
457 * vi:tabstop=4 shiftwidth=4 expandtab
458 */
459