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