API
telnetConn.cpp
Go to the documentation of this file.
1 /** \file telnetConn.cpp
2  * \brief Managing a connection to a telnet device.
3  * \author Jared R. Males (jaredmales@gmail.com)
4  *
5  * \ingroup tty_files
6  */
7 
8 
9 #include <cstring>
10 
11 #include <sys/types.h>
12 #include <sys/socket.h>
13 #include <netdb.h>
14 #include <netinet/in.h>
15 
16 #include "telnetConn.hpp"
17 
18 
19 namespace MagAOX
20 {
21 namespace tty
22 {
23 
25 {
26  /* clean up */
27  if(m_telnet) telnet_free(m_telnet);
28  if(m_sock) close(m_sock);
29 }
30 
31 int telnetConn::connect( const std::string & host,
32  const std::string & port
33  )
34 {
35  //First cleanup any previous connections.
36  if(m_telnet) telnet_free(m_telnet);
37  m_telnet = 0;
38  if(m_sock) close(m_sock);
39  m_sock = 0;
40 
41  m_loggedin = 0;
42 
43  struct sockaddr_in addr;
44  struct addrinfo *ai;
45  struct addrinfo hints;
46 
47  /* look up server host */
48  memset(&hints, 0, sizeof(hints));
49  hints.ai_family = AF_UNSPEC;
50  hints.ai_socktype = SOCK_STREAM;
51  if( getaddrinfo(host.c_str(), port.c_str(), &hints, &ai) != 0)
52  {
53  return TELNET_E_GETADDR;
54  }
55 
56  /* create server m_socket */
57  if ((m_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
58  {
59  return TELNET_E_SOCKET;
60  }
61 
62  /* bind server socket */
63  memset(&addr, 0, sizeof(addr));
64  addr.sin_family = AF_INET;
65  if (bind(m_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
66  {
67  return TELNET_E_BIND;
68  }
69 
70  /* connect */
71  if (::connect(m_sock, ai->ai_addr, ai->ai_addrlen) == -1)
72  {
73  return TELNET_E_CONNECT;
74  }
75 
76  /* free address lookup info */
77  freeaddrinfo(ai);
78 
79  /* initialize the telnet box */
80  m_telnet = telnet_init(telopts, telnetConn::event_handler, 0, this);
81 
82  if(m_telnet == nullptr)
83  {
84  return TELNET_E_TELNETINIT;
85  }
86 
87  return TTY_E_NOERROR;
88 }
89 
90 int telnetConn::login( const std::string & username,
91  const std::string & password
92  )
93 {
94  char buffer[1024];
95  int rs;
96 
97  struct pollfd pfd[1];
98 
99  /* initialize poll descriptors */
100  memset(pfd, 0, sizeof(pfd));
101  pfd[0].fd = m_sock;
102  pfd[0].events = POLLIN;
103 
104  //Loop while waiting on the login process to complete.
105  #ifdef TELNET_DEBUG
106  std::cerr << "Starting poll\n";
107  #endif
108  int pollrv;
109  while ( (pollrv = poll(pfd, 1, 30000)) > 0)
110  {
111  #ifdef TELNET_DEBUG
112  std::cerr << "Polled\n";
113  #endif
114  /* read from client */
115  if (pfd[0].revents & POLLIN)
116  {
117  #ifdef TELNET_DEBUG
118  std::cerr << "Starting read\n";
119  #endif
120  if ((rs = recv(m_sock, buffer, sizeof(buffer), 0)) > 0)
121  {
122  #ifdef TELNET_DEBUG
123  std::cerr << "read: " << rs << "bytes\n";
124  #endif
125  telnet_recv(m_telnet, buffer, rs);
126  if(m_EHError != TTY_E_NOERROR) return m_EHError;
127  }
128  else if (rs == 0)
129  {
130  break;
131  }
132  else
133  {
134  fprintf(stderr, "recv(client) failed: %s\n",
135  strerror(errno));
136  return TTY_E_ERRORONREAD;
137  }
138  }
139 
141  {
142  int rv = write(username + "\n", 1000);
143  if(rv != TTY_E_NOERROR) return rv;
144 
146  }
147 
149  {
150  int rv = write(password + "\n", 1000);
151  if(rv != TTY_E_NOERROR) return rv;
152 
154  }
155 
157  {
158  break;
159  }
160  #ifdef TELNET_DEBUG
161  std::cerr << "polling\n";
162  #endif
163  }
164 
165  if(pollrv == 0)
166  {
167  #ifdef TELNET_DEBUG
168  std::cerr << "login timed out\n";
169  #endif
170  return TELNET_E_LOGINTIMEOUT;
171  }
172 
173  return TTY_E_NOERROR;
174 }
175 
177 {
179  return TTY_E_NOERROR;
180 }
181 
182 int telnetConn::write( const std::string & buffWrite,
183  int timeoutWrite
184  )
185 {
186  double t0;
187  struct pollfd pfd;
188 
189  errno = 0;
190  pfd.fd = m_sock;
191  pfd.events = POLLOUT;
192 
193  std::string _buffWrite;
194  telnetCRLF(_buffWrite, buffWrite);
195 
196 
197  t0 = mx::sys::get_curr_time();
198 
199  size_t totWritten = 0;
200  while( totWritten < _buffWrite.size())
201  {
202  int timeoutCurrent = timeoutWrite - (mx::sys::get_curr_time()-t0)*1000;
203  if(timeoutCurrent < 0) return TTY_E_TIMEOUTONWRITE;
204 
205  int rv = poll( &pfd, 1, timeoutCurrent);
206  if( rv == 0 ) return TTY_E_TIMEOUTONWRITEPOLL;
207  else if( rv < 0 ) return TTY_E_ERRORONWRITEPOLL;
208 
209  telnet_send(m_telnet, _buffWrite.c_str(), _buffWrite.size());
210  totWritten = _buffWrite.size();
211 
212  #ifdef TELNET_DEBUG
213  std::cerr << "Wrote " << totWritten << " chars of " << buffWrite.size() << "\n";
214  #endif
215 
216 
217  if( ( mx::sys::get_curr_time()-t0)*1000 > timeoutWrite ) return TTY_E_TIMEOUTONWRITE;
218  }
219 
220  return TTY_E_NOERROR;
221 }
222 
223 int telnetConn::read( const std::string & eot,
224  int timeoutRead,
225  bool clear
226  )
227 {
228  int rv;
229  int timeoutCurrent;
230  double t0;
231 
232  struct pollfd pfd;
233 
234  errno = 0;
235 
236  pfd.fd = m_sock;
237  pfd.events = POLLIN;
238 
239  char buffRead[TELNET_BUFFSIZE];
240 
241  //Start timeout clock for reading.
242  t0 = mx::sys::get_curr_time();
243  timeoutCurrent = timeoutRead;
244 
245  //Now read the response up to the eot.
246  if(clear) m_strRead = "";
247 
248 
249  rv = poll( &pfd, 1, timeoutCurrent);
250  if( rv == 0 ) return TTY_E_TIMEOUTONREADPOLL;
251  if( rv < 0 ) return TTY_E_ERRORONREADPOLL;
252 
253  rv = ::read(m_sock, buffRead, TELNET_BUFFSIZE);
254  if( rv < 0 ) return TTY_E_ERRORONREAD;
255  buffRead[rv] = '\0';
256 
257  telnet_recv(m_telnet, buffRead, rv);
258  if(m_EHError != TTY_E_NOERROR) return m_EHError;
259 
260  while( !isEndOfTrans(m_strRead, eot) )
261  {
262  timeoutCurrent = timeoutRead - (mx::sys::get_curr_time()-t0)*1000;
263  if(timeoutCurrent < 0) return TTY_E_TIMEOUTONREAD;
264 
265  rv = poll( &pfd, 1, timeoutCurrent);
266  if( rv == 0 ) return TTY_E_TIMEOUTONREADPOLL;
267  if( rv < 0 ) return TTY_E_ERRORONREADPOLL;
268 
269  rv = ::read(m_sock, buffRead, TELNET_BUFFSIZE);
270  if( rv < 0 ) return TTY_E_ERRORONREAD;
271  buffRead[rv] ='\0';
272 
273  telnet_recv(m_telnet, buffRead, rv);
274  if(m_EHError != TTY_E_NOERROR) return m_EHError;
275 
276  #ifdef TELNET_DEBUG
277  std::cerr << "telnetRead: read " << rv << " bytes. buffRead=" << buffRead << "\n";
278  #endif
279  }
280 
281 
282  return TTY_E_NOERROR;
283 
284 
285 }
286 
287 int telnetConn::read( int timeoutRead,
288  bool clear
289  )
290 {
291  return read(m_prompt, timeoutRead, clear);
292 }
293 
294 int telnetConn::writeRead( const std::string & strWrite,
295  bool swallowEcho,
296  int timeoutWrite,
297  int timeoutRead
298  )
299 {
300  m_strRead.clear();
301 
302  int rv;
303 
304  //Write First
305  rv = write( strWrite, timeoutWrite);
306  if(rv != TTY_E_NOERROR) return rv;
307 
308  //Now read response from console
309  int timeoutCurrent;
310  double t0;
311 
312  struct pollfd pfd;
313  pfd.fd = m_sock;
314  pfd.events = POLLIN;
315 
316 
317  //Start timeout clock for reading.
318  t0 = mx::sys::get_curr_time();;
319 
320  if(swallowEcho)
321  {
322  char buffRead[TELNET_BUFFSIZE];
323 
324  //First swallow the echo.
325  while( m_strRead.size() <= strWrite.size() )
326  {
327  timeoutCurrent = timeoutRead - (mx::sys::get_curr_time()-t0)*1000;
328  if(timeoutCurrent < 0) return TTY_E_TIMEOUTONREAD;
329 
330  rv = poll( &pfd, 1, timeoutCurrent);
331  if( rv == 0 ) return TTY_E_TIMEOUTONREADPOLL;
332  if( rv < 0 ) return TTY_E_ERRORONREADPOLL;
333 
334  rv = ::read(m_sock, buffRead, TELNET_BUFFSIZE);
335  if( rv < 0 ) return TTY_E_ERRORONREAD;
336 
337  telnet_recv(m_telnet, buffRead, rv);
338  if(m_EHError != TTY_E_NOERROR) return m_EHError;
339  }
340 
341  m_strRead.erase(0, strWrite.size());
342  }
343 
345 
346  timeoutCurrent = timeoutRead - (mx::sys::get_curr_time()-t0)*1000;
347  if(timeoutCurrent < 0) return TTY_E_TIMEOUTONREAD;
348 
349  //Now read the response up to the eot.
350  return read(timeoutCurrent, false);
351 }
352 
353 int telnetConn::send(int sock, const char *buffer, size_t size)
354 {
355  /* send data */
356  while (size > 0)
357  {
358  int rs;
359 
360  if ((rs = ::send(sock, buffer, size, 0)) == -1)
361  {
362  fprintf(stderr, "send() failed: %s\n", strerror(errno));
363  return TTY_E_ERRORONWRITE;
364  }
365  else if (rs == 0)
366  {
367  fprintf(stderr, "send() unexpectedly returned 0\n");
368  return TTY_E_ERRORONWRITE;
369  }
370  /* update pointer and size to see if we've got more to send */
371  buffer += rs;
372  size -= rs;
373  }
374 
375  return TTY_E_NOERROR;
376 }
377 
378 void telnetConn::event_handler( telnet_t *telnet,
379  telnet_event_t *ev,
380  void *user_data
381  )
382 {
383  telnetConn * cs = static_cast<telnetConn*>(user_data);
384  int sock = cs->m_sock;
385 
386  //Always reset the error at beginning.
387  cs->m_EHError = 0;
388 
389  switch (ev->type)
390  {
391  /* data received */
392  case TELNET_EV_DATA:
393  {
394  #ifdef TELNET_DEBUG
395  std::cerr << "Got ev->data.size: " << ev->data.size << "\n";
396  #endif
397 
398  //First we remove the various control chars from the front.
399  if(ev->data.size == 0) break;
400 
401  char * buf = const_cast<char *>(ev->data.buffer);
402  buf[ev->data.size] = 0;
403 
404  int nn = 0;
405  for(size_t i=0; i<ev->data.size; ++i)
406  {
407  if(ev->data.buffer[i] < 32)
408  {
409  ++buf;
410  ++nn;
411  continue;
412  }
413  break;
414  }
415 
416  #ifdef TELNET_DEBUG
417  std::cerr << "removed: " << nn << "\n";
418  #endif
419 
420  //Now we check for '\0' characters inside the data
421  //this is maybe a bug workaround for tripp lite pdu LX card . . .
422  int mm = 0;
423  for(size_t i=nn; i < ev->data.size;++i)
424  {
425  if( ev->data.buffer[i] == 0)
426  {
427  ++mm;
428  buf[i-nn] = '\n';
429  }
430  }
431 
432  #ifdef TELNET_DEBUG
433  std::cerr << "dezeroed: " << mm << "\n";
434  #endif
435 
436  //Now make it a string so we can make use of it.
437  std::string sbuf(buf);
438 
439  if(sbuf.size() == 0) break;
440 
441  #ifdef TELNET_DEBUG
442  std::cerr << "ev->data: " << sbuf << "\n";
443  #endif
444 
445  if(cs->m_loggedin < TELNET_LOGGED_IN) //we aren't logged in yet
446  {
448  {
449  if(sbuf.find(cs->m_usernamePrompt) != std::string::npos)
450  {
452  }
453  break;
454  }
455 
457  {
458  if( sbuf.find(cs->m_passwordPrompt) != std::string::npos)
459  {
461  }
462  break;
463  }
464 
466  {
467  if( sbuf.find(cs->m_prompt) != std::string::npos)
468  {
470  }
471  break;
472  }
473  }
474 
475  //Always append
476  cs->m_strRead += sbuf;
477  break;
478  }
479  /* data must be sent */
480  case TELNET_EV_SEND:
481  {
482  send(sock, ev->data.buffer, ev->data.size);
483  break;
484  }
485  /* request to enable remote feature (or receipt) */
486  case TELNET_EV_WILL:
487  {
488  /* we'll agree to turn off our echo if server wants us to stop */
489  //if (ev->neg.telopt == TELNET_TELOPT_ECHO) do_echo = 0;
490  break;
491  }
492  /* notification of disabling remote feature (or receipt) */
493  case TELNET_EV_WONT:
494  {
495  break;
496  }
497  /* request to enable local feature (or receipt) */
498  case TELNET_EV_DO:
499  {
500  break;
501  }
502  /* demand to disable local feature (or receipt) */
503  case TELNET_EV_DONT:
504  {
505  break;
506  }
507  /* respond to TTYPE commands */
508  case TELNET_EV_TTYPE:
509  {
510  /* respond with our terminal type, if requested */
511  if (ev->ttype.cmd == TELNET_TTYPE_SEND)
512  {
513  telnet_ttype_is(telnet, getenv("TERM"));
514  }
515  break;
516  }
517  /* respond to particular subnegotiations */
518  case TELNET_EV_SUBNEGOTIATION:
519  {
520  break;
521  }
522  /* error */
523  case TELNET_EV_ERROR:
524  {
525  fprintf(stderr, "ERROR: %s\n", ev->error.msg);
527  break;
528  }
529  default:
530  {
531  /* ignore */
532  break;
533  }
534  }
535 }
536 
537 } //namespace tty
538 } //namespace MagAOX
539 
static const telnet_telopt_t telopts[]
libtelnet option table.
Definition: telnetConn.hpp:49
bool isEndOfTrans(const std::string &strRead, const std::string &eot)
Check if the end of the buffer contains the end-of-transmission string.
Definition: ttyIOUtils.cpp:116
std::ostream & cerr()
int telnetCRLF(std::string &telnetStr, const std::string &inputStr)
Replace lone \r and \n with \r\n for telnet-ness.
Definition: ttyIOUtils.cpp:26
Definition: dm.hpp:24
A Telnet connection manager, wrapping libtelnet.
Definition: telnetConn.hpp:81
int m_EHError
Used to indicate an error occurred in the event handler callback.
Definition: telnetConn.hpp:107
int login(const std::string &username, const std::string &password)
Manage the login process on this device.
Definition: telnetConn.cpp:90
std::string m_usernamePrompt
The device's username entry prompt, used for managing login.
Definition: telnetConn.hpp:87
int write(const std::string &buffWrite, int timeoutWrite)
Write to a telnet connection.
Definition: telnetConn.cpp:182
telnet_t * m_telnet
libtelnet telnet_t structure
Definition: telnetConn.hpp:84
~telnetConn()
D'tor, conducts connection cleanup.
Definition: telnetConn.cpp:24
static int send(int sock, const char *buffer, size_t size)
Internal send for use by event_handler.
Definition: telnetConn.cpp:353
std::string m_strRead
The accumulated string read from the device.
Definition: telnetConn.hpp:113
static void event_handler(telnet_t *telnet, telnet_event_t *ev, void *user_data)
Event handler callback for libtelnet processing.
Definition: telnetConn.cpp:378
int m_loggedin
Flag denoting the login state.
Definition: telnetConn.hpp:104
int m_sock
The socket file descriptor.
Definition: telnetConn.hpp:82
int noLogin()
Set flags as if we're logged in, used when device doesn't require it.
Definition: telnetConn.cpp:176
std::string m_passwordPrompt
The device's password entry prompt, used for managing login.
Definition: telnetConn.hpp:90
std::string m_prompt
The device's prompt, used for detecting end of transmission.
Definition: telnetConn.hpp:92
int writeRead(const std::string &strWrite, bool swallowEcho, int timeoutWrite, int timeoutRead)
Write to a telnet connection, then get the reply.
Definition: telnetConn.cpp:294
int read(const std::string &eot, int timeoutRead, bool clear=true)
Read from a telnet connection, until end-of-transmission string is read.
Definition: telnetConn.cpp:223
int connect(const std::string &host, const std::string &port)
Connect to the device.
Definition: telnetConn.cpp:31
Managing a connection to a telnet device.
#define TELNET_LOGGED_IN
Definition: telnetConn.hpp:61
#define TELNET_WAITING_USER
Definition: telnetConn.hpp:56
#define TELNET_WAITING_PROMPT
Definition: telnetConn.hpp:60
#define TELNET_BUFFSIZE
Definition: telnetConn.hpp:43
#define TELNET_GOT_PASS
Definition: telnetConn.hpp:59
#define TELNET_GOT_USER
Definition: telnetConn.hpp:57
#define TELNET_WAITING_PASS
Definition: telnetConn.hpp:58
#define TTY_E_TIMEOUTONREAD
Definition: ttyErrors.hpp:27
#define TELNET_E_BIND
Definition: ttyErrors.hpp:36
#define TTY_E_TIMEOUTONREADPOLL
Definition: ttyErrors.hpp:24
#define TTY_E_ERRORONWRITE
Definition: ttyErrors.hpp:22
#define TTY_E_ERRORONWRITEPOLL
Definition: ttyErrors.hpp:21
#define TTY_E_ERRORONREADPOLL
Definition: ttyErrors.hpp:25
#define TTY_E_ERRORONREAD
Definition: ttyErrors.hpp:26
#define TELNET_E_TELNETINIT
Definition: ttyErrors.hpp:38
#define TELNET_E_GETADDR
Definition: ttyErrors.hpp:34
#define TTY_E_TIMEOUTONWRITEPOLL
Definition: ttyErrors.hpp:20
#define TELNET_E_LOGINTIMEOUT
Definition: ttyErrors.hpp:40
#define TTY_E_TIMEOUTONWRITE
Definition: ttyErrors.hpp:23
#define TELNET_E_EHERROR
Definition: ttyErrors.hpp:39
#define TTY_E_NOERROR
Definition: ttyErrors.hpp:15
#define TELNET_E_CONNECT
Definition: ttyErrors.hpp:37
#define TELNET_E_SOCKET
Definition: ttyErrors.hpp:35