c11 safe string handling support
[vpp.git] / src / vpp / app / vppctl.c
1 /*
2  * Copyright (c) 2017 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <sys/socket.h>
17 #include <sys/un.h>
18 #include <sys/epoll.h>
19 #include <sys/ioctl.h>
20 #include <signal.h>
21 #include <termios.h>
22 #include <unistd.h>
23 #include <string.h>
24
25 #define DEBUG 0
26
27 #if DEBUG
28 #define TELCMDS
29 #define TELOPTS
30 #endif
31
32 #include <arpa/telnet.h>
33
34 #include <vppinfra/mem.h>
35 #include <vppinfra/format.h>
36 #include <vppinfra/socket.h>
37
38 #define SOCKET_FILE "/run/vpp/cli.sock"
39
40 volatile int window_resized = 0;
41 struct termios orig_tio;
42
43 static void
44 send_ttype (clib_socket_t * s, int is_interactive)
45 {
46   char *term;
47
48   term = is_interactive ? getenv ("TERM") : "vppctl";
49   if (term == NULL)
50     term = "dumb";
51
52   clib_socket_tx_add_formatted (s, "%c%c%c" "%c%s" "%c%c",
53                                 IAC, SB, TELOPT_TTYPE, 0, term, IAC, SE);
54   clib_socket_tx (s);
55 }
56
57 static void
58 send_naws (clib_socket_t * s)
59 {
60   struct winsize ws;
61
62   if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
63     {
64       clib_unix_warning ("ioctl(TIOCGWINSZ)");
65       return;
66     }
67
68   clib_socket_tx_add_formatted (s, "%c%c%c" "%c%c%c%c" "%c%c",
69                                 IAC, SB, TELOPT_NAWS,
70                                 ws.ws_col >> 8, ws.ws_col & 0xff,
71                                 ws.ws_row >> 8, ws.ws_row & 0xff, IAC, SE);
72   clib_socket_tx (s);
73 }
74
75 static void
76 signal_handler_winch (int signum)
77 {
78   window_resized = 1;
79 }
80
81 static void
82 signal_handler_term (int signum)
83 {
84   tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
85 }
86
87 static u8 *
88 process_input (u8 * str, clib_socket_t * s, int is_interactive,
89                int *sent_ttype)
90 {
91   int i = 0;
92
93   while (i < vec_len (s->rx_buffer))
94     {
95       if (s->rx_buffer[i] == IAC)
96         {
97           if (s->rx_buffer[i + 1] == SB)
98             {
99               u8 *sb = 0;
100               char opt = s->rx_buffer[i + 2];
101               i += 3;
102               while (s->rx_buffer[i] != IAC)
103                 vec_add1 (sb, s->rx_buffer[i++]);
104
105 #if DEBUG
106               clib_warning ("SB %s\n  %U", TELOPT (opt),
107                             format_hexdump, sb, vec_len (sb));
108 #endif
109               vec_free (sb);
110               i += 2;
111               if (opt == TELOPT_TTYPE)
112                 {
113                   send_ttype (s, is_interactive);
114                   *sent_ttype = 1;
115                 }
116               else if (is_interactive && opt == TELOPT_NAWS)
117                 send_naws (s);
118             }
119           else
120             {
121 #if DEBUG
122               clib_warning ("IAC at %d, IAC %s %s", i,
123                             TELCMD (s->rx_buffer[i + 1]),
124                             TELOPT (s->rx_buffer[i + 2]));
125 #endif
126               i += 3;
127             }
128         }
129       else
130         vec_add1 (str, s->rx_buffer[i++]);
131     }
132   vec_reset_length (s->rx_buffer);
133   return str;
134 }
135
136
137 int
138 main (int argc, char *argv[])
139 {
140   clib_socket_t _s = { 0 }, *s = &_s;
141   clib_error_t *error = 0;
142   struct epoll_event event;
143   struct sigaction sa;
144   struct termios tio;
145   int efd = -1;
146   u8 *str = 0;
147   u8 *cmd = 0;
148   int do_quit = 0;
149   int is_interactive = 0;
150   int acked = 1;                /* counts messages from VPP; starts at 1 */
151   int sent_ttype = 0;
152
153
154   clib_mem_init (0, 64ULL << 10);
155
156   /* process command line */
157   argc--;
158   argv++;
159
160   if (argc > 1 && strncmp (argv[0], "-s", 2) == 0)
161     {
162       s->config = argv[1];
163       argc -= 2;
164       argv += 2;
165     }
166   else
167     s->config = SOCKET_FILE;
168
169   while (argc--)
170     cmd = format (cmd, "%s%c", (argv++)[0], argc ? ' ' : 0);
171
172   s->flags = CLIB_SOCKET_F_IS_CLIENT;
173
174   error = clib_socket_init (s);
175   if (error)
176     goto done;
177
178   is_interactive = isatty (STDIN_FILENO) && cmd == 0;
179
180   if (is_interactive)
181     {
182       /* Capture terminal resize events */
183       clib_memset (&sa, 0, sizeof (struct sigaction));
184       sa.sa_handler = signal_handler_winch;
185       if (sigaction (SIGWINCH, &sa, 0) < 0)
186         {
187           error = clib_error_return_unix (0, "sigaction");
188           goto done;
189         }
190
191       /* Capture SIGTERM to reset tty settings */
192       sa.sa_handler = signal_handler_term;
193       if (sigaction (SIGTERM, &sa, 0) < 0)
194         {
195           error = clib_error_return_unix (0, "sigaction");
196           goto done;
197         }
198
199       /* Save the original tty state so we can restore it later */
200       if (tcgetattr (STDIN_FILENO, &orig_tio) < 0)
201         {
202           error = clib_error_return_unix (0, "tcgetattr");
203           goto done;
204         }
205
206       /* Tweak the tty settings */
207       tio = orig_tio;
208       /* echo off, canonical mode off, ext'd input processing off */
209       tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
210       tio.c_cc[VMIN] = 1;       /* 1 byte at a time */
211       tio.c_cc[VTIME] = 0;      /* no timer */
212
213       if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio) < 0)
214         {
215           error = clib_error_return_unix (0, "tcsetattr");
216           goto done;
217         }
218     }
219
220   efd = epoll_create1 (0);
221
222   /* register STDIN */
223   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
224   event.data.fd = STDIN_FILENO;
225   if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0)
226     {
227       /* ignore EPERM; it means stdin is something like /dev/null */
228       if (errno != EPERM)
229         {
230           error = clib_error_return_unix (0, "epoll_ctl[%d]", STDIN_FILENO);
231           goto done;
232         }
233     }
234
235   /* register socket */
236   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
237   event.data.fd = s->fd;
238   if (epoll_ctl (efd, EPOLL_CTL_ADD, s->fd, &event) != 0)
239     {
240       error = clib_error_return_unix (0, "epoll_ctl[%d]", s->fd);
241       goto done;
242     }
243
244   while (1)
245     {
246       int n;
247
248       if (window_resized)
249         {
250           window_resized = 0;
251           send_naws (s);
252         }
253
254       if ((n = epoll_wait (efd, &event, 1, -1)) < 0)
255         {
256           /* maybe we received signal */
257           if (errno == EINTR)
258             continue;
259
260           error = clib_error_return_unix (0, "epoll_wait");
261           goto done;
262         }
263
264       if (n == 0)
265         continue;
266
267       if (event.data.fd == STDIN_FILENO)
268         {
269           int n;
270           char c[100];
271
272           if (!sent_ttype)
273             continue;           /* not ready for this yet */
274
275           n = read (STDIN_FILENO, c, sizeof (c));
276           if (n > 0)
277             {
278               memcpy (clib_socket_tx_add (s, n), c, n);
279               error = clib_socket_tx (s);
280               if (error)
281                 goto done;
282             }
283           else if (n < 0)
284             clib_warning ("read rv=%d", n);
285           else                  /* EOF */
286             do_quit = 1;
287         }
288       else if (event.data.fd == s->fd)
289         {
290           error = clib_socket_rx (s, 100);
291           if (error)
292             break;
293
294           if (clib_socket_rx_end_of_file (s))
295             break;
296
297           str = process_input (str, s, is_interactive, &sent_ttype);
298
299           if (vec_len (str) > 0)
300             {
301               int len = vec_len (str);
302               u8 *p = str, *q = str;
303
304               while (len)
305                 {
306                   /* Search for and skip NUL bytes */
307                   while (q < (p + len) && *q)
308                     q++;
309
310                   n = write (STDOUT_FILENO, p, q - p);
311                   if (n < 0)
312                     {
313                       error = clib_error_return_unix (0, "write");
314                       goto done;
315                     }
316
317                   while (q < (p + len) && !*q)
318                     {
319                       q++;
320                       acked++;  /* every NUL is an acknowledgement */
321                     }
322                   len -= q - p;
323                   p = q;
324                 }
325
326               vec_reset_length (str);
327             }
328
329           if (do_quit && do_quit < acked)
330             {
331               /* Ask the other end to close the connection */
332               clib_socket_tx_add_formatted (s, "quit\n");
333               clib_socket_tx (s);
334               do_quit = 0;
335             }
336           if (cmd && sent_ttype)
337             {
338               /* We wait until after the TELNET TTYPE option has been sent.
339                * That is to make sure the session at the VPP end has switched
340                * to line-by-line mode, and thus avoid prompts and echoing.
341                * Note that it does also disable further TELNET option processing.
342                */
343               clib_socket_tx_add_formatted (s, "%s\n", cmd);
344               clib_socket_tx (s);
345               vec_free (cmd);
346               do_quit = acked;  /* quit after the next response */
347             }
348         }
349       else
350         {
351           error = clib_error_return (0, "unknown fd");
352           goto done;
353         }
354     }
355
356   error = clib_socket_close (s);
357
358 done:
359   vec_free (cmd);
360   vec_free (str);
361   if (efd > -1)
362     close (efd);
363
364   if (is_interactive)
365     tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
366
367   if (error)
368     {
369       clib_error_report (error);
370       return 1;
371     }
372
373   return 0;
374 }
375
376 /* *INDENT-ON* */
377
378 /*
379  * fd.io coding-style-patch-verification: ON
380  *
381  * Local Variables:
382  * eval: (c-set-style "gnu")
383  * End:
384  */