125
|
1 /* ctrltelnet.c - Telnet controler
|
|
2 * Copyright (C) 2005-2007 Sven Almgren <sven@tras.se>
|
|
3 *
|
|
4 * This program is free software; you can redistribute it and/or modify
|
|
5 * it under the terms of the GNU General Public License as published by
|
|
6 * the Free Software Foundation; either version 2 of the License, or
|
|
7 * (at your option) any later version.
|
|
8 *
|
|
9 * This program is distributed in the hope that it will be useful,
|
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 * GNU Library General Public License for more details.
|
|
13 *
|
|
14 * You should have received a copy of the GNU General Public License along
|
|
15 * with this program; if not, write to the Free Software Foundation,
|
|
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
17 *
|
|
18 */
|
|
19
|
|
20 #define STR(x) _STR(x)
|
|
21 #define _STR(x) __STR(x)
|
|
22 #define __STR(x) #x
|
|
23
|
|
24 #include "config.h"
|
|
25 #include "ctrl_telnet.h"
|
|
26 #include "minmax.h"
|
|
27 #include "trace.h"
|
|
28
|
|
29 #include <stdio.h>
|
|
30 #include <stdlib.h>
|
|
31 #include <string.h>
|
|
32 #include <sys/select.h> /* For select */
|
|
33 #include <sys/time.h>
|
|
34 #include <unistd.h> /* For pipe */
|
|
35 #include <sys/types.h>
|
|
36 #include <sys/socket.h>
|
|
37 #include <netinet/in.h>
|
|
38 #include <pthread.h>
|
|
39 #include <stdarg.h>
|
|
40
|
|
41 #if (defined(____DISABLE_MUTEX) || 0)
|
|
42 #define pthread_mutex_lock(x) printf(">>>> Locking " __FILE__ ":" STR(__LINE__) " \t" #x "\n");
|
|
43 #define pthread_mutex_unlock(x) printf("<<<< Unlocking " __FILE__ ":" STR(__LINE__) " \t" #x "\n");
|
|
44 #endif
|
|
45
|
|
46 /**
|
|
47 * @brief Structure holding data between the staring rutine and the thread
|
|
48 */
|
|
49 typedef struct telnet_thread_data_t
|
|
50 {
|
|
51 pthread_t thread;
|
|
52
|
|
53 /* Litening socket */
|
|
54 int listener;
|
|
55
|
|
56 /* Socket used to terminate loop:
|
|
57 0 is reading and 1 is sending, kill by sending to 1 */
|
|
58 int killer[2];
|
|
59
|
|
60 /* Our socket address */
|
|
61 struct sockaddr_in local_address;
|
|
62
|
|
63 /* Shared data buffer that can be used by others... */
|
|
64 char shared_buffer[CTRL_TELNET_SHARED_BUFFER_SIZE];
|
|
65
|
|
66 ctrl_telnet_client *clients;
|
|
67 } telnet_thread_data;
|
|
68
|
|
69 /**
|
|
70 * @brief Struct for registerd commands
|
|
71 */
|
|
72 typedef struct telnet_function_list_t
|
|
73 {
|
|
74 /* Function name, or keyword, if you like */
|
|
75 char *name;
|
|
76 char *description;
|
|
77 ctrl_telnet_command_ptr function;
|
|
78
|
|
79 struct telnet_function_list_t *next;
|
|
80 } telnet_function_list;
|
|
81
|
|
82 /* Static yes used to set socketoptions */
|
|
83 static int yes = 1;
|
|
84 static telnet_thread_data ttd;
|
|
85 static telnet_function_list* functions = NULL;
|
|
86 static pthread_mutex_t functions_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
87 static pthread_mutex_t startstop_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
88 static pthread_mutex_t shared_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
89 static int started = 0;
|
|
90
|
|
91 /* Threadfunction, core in telnet controler */
|
|
92 /**
|
|
93 * @brief Thread function
|
|
94 *
|
|
95 * @param data Not used, leave as NULL
|
|
96 */
|
|
97 static void *ctrl_telnet_thread (void *data);
|
|
98
|
|
99 /**
|
|
100 * @brief Adds a new client to our list of new ones
|
|
101 *
|
|
102 * @param client to add
|
|
103 */
|
|
104 static void ctrl_telnet_client_add (ctrl_telnet_client *client);
|
|
105
|
|
106 /**
|
|
107 * @brief Removes "client" from our list of clients
|
|
108 */
|
|
109 static void ctrl_telnet_client_remove (ctrl_telnet_client *client);
|
|
110
|
|
111 /**
|
|
112 * @brief Updates an fd_set to contain the current set of clients
|
|
113 *
|
|
114 * @return max fd found in list
|
|
115 */
|
|
116 static int ctrl_telnet_fix_fdset (fd_set* readable);
|
|
117
|
|
118 static void ctrl_telnet_tokenize (char *raw, int *argc, char ***argv);
|
|
119
|
|
120 static int ctrl_telnet_client_recv (ctrl_telnet_client *client);
|
|
121 static int ctrl_telnet_client_execute (ctrl_telnet_client *client);
|
|
122 static int ctrl_telnet_client_execute_line (ctrl_telnet_client *client,
|
|
123 char *line);
|
|
124 static int ctrl_telnet_client_execute_line_safe (ctrl_telnet_client *client,
|
|
125 char *line);
|
|
126 static void ctrl_telnet_register_internals();
|
|
127
|
|
128 /**
|
|
129 * @brief Starts a Telnet bound control interface
|
|
130 *
|
|
131 * @return 0 on success, -1 on error
|
|
132 */
|
|
133 int
|
|
134 ctrl_telnet_start (int port)
|
|
135 {
|
|
136 /* Start by making us threadsafe... */
|
|
137 pthread_mutex_lock (&startstop_lock);
|
|
138
|
|
139 /* Create listener socket */
|
|
140 ttd.listener = socket (PF_INET, SOCK_STREAM, 0);
|
|
141 if (ttd.listener == -1)
|
|
142 {
|
|
143 perror ("socket");
|
|
144 pthread_mutex_unlock (&startstop_lock);
|
|
145 return -1;
|
|
146 }
|
|
147
|
|
148 /* Clears us from "address already in use" errors */
|
|
149 if (setsockopt (ttd.listener, SOL_SOCKET, SO_REUSEADDR,
|
|
150 &yes, sizeof (int)) == -1)
|
|
151 perror ("setsockopt");
|
|
152
|
|
153 ttd.local_address.sin_family = AF_INET;
|
|
154 ttd.local_address.sin_addr.s_addr = INADDR_ANY;
|
|
155 ttd.local_address.sin_port = htons (port);
|
|
156 memset (&ttd.local_address.sin_zero, '\0',
|
|
157 sizeof (ttd.local_address.sin_zero));
|
|
158
|
|
159 if (bind (ttd.listener, (struct sockaddr *) &ttd.local_address,
|
|
160 sizeof (ttd.local_address)) == -1)
|
|
161 {
|
|
162 perror ("bind");
|
|
163 pthread_mutex_unlock (&startstop_lock);
|
|
164 return -1;
|
|
165 }
|
|
166
|
|
167 if (listen (ttd.listener, CTRL_TELNET_BACKLOG) == -1)
|
|
168 {
|
|
169 perror ("listen");
|
|
170 pthread_mutex_unlock (&startstop_lock);
|
|
171 return -1;
|
|
172 }
|
|
173
|
|
174 print_log (ULOG_NORMAL, "Listening on telnet port %u\n", port);
|
|
175
|
|
176 /* Create killer pipes */
|
|
177 if (pipe (ttd.killer))
|
|
178 {
|
|
179 perror ("Failed to create killer pipe");
|
|
180 pthread_mutex_unlock (&startstop_lock);
|
|
181 return -1; /* FIXME. Kill all sockets... not critical,, but still */
|
|
182 }
|
|
183
|
|
184 if (pthread_create (&ttd.thread, NULL, ctrl_telnet_thread, NULL))
|
|
185 {
|
|
186 /* FIXME: Killall sockets... */
|
|
187 perror ("Failed to create thread");
|
|
188 pthread_mutex_unlock (&startstop_lock);
|
|
189 return -1;
|
|
190 }
|
|
191
|
|
192 started = 1;
|
|
193 ctrl_telnet_register_internals ();
|
|
194 pthread_mutex_unlock (&startstop_lock);
|
|
195
|
|
196 return 0;
|
|
197 }
|
|
198
|
|
199 /**
|
|
200 * @brief Stops all telnet bound control interfaces
|
|
201 */
|
|
202 void
|
|
203 ctrl_telnet_stop (void)
|
|
204 {
|
|
205 pthread_mutex_lock (&startstop_lock);
|
|
206
|
|
207 if (!started)
|
|
208 {
|
|
209 pthread_mutex_unlock (&startstop_lock);
|
|
210 return;
|
|
211 }
|
|
212
|
|
213 /* yes is int, which is bigger then char, so this should be safe */
|
|
214 write (ttd.killer[1], &yes, sizeof (char));
|
|
215
|
|
216 pthread_mutex_unlock (&startstop_lock);
|
|
217 pthread_join (ttd.thread, NULL);
|
|
218 }
|
|
219
|
|
220 /**
|
|
221 * @brief Telnet thread function
|
|
222 */
|
|
223 static void *
|
|
224 ctrl_telnet_thread (void *a __attribute__ ((unused)))
|
|
225 {
|
|
226 /* fd_set with readable clients */
|
|
227 fd_set fd_readable;
|
|
228
|
|
229 /* Pointer to a client object */
|
|
230 ctrl_telnet_client *client;
|
|
231
|
|
232 int fd_max;
|
|
233
|
|
234 while (1)
|
|
235 {
|
|
236 /* Get fds */
|
|
237 fd_max = ctrl_telnet_fix_fdset (&fd_readable);
|
|
238
|
|
239 if (select (fd_max + 1, &fd_readable, NULL, NULL, NULL) == -1)
|
|
240 {
|
|
241 perror ("select");
|
|
242 /* FIXME: Close sockets */
|
|
243 return NULL;
|
|
244 }
|
|
245
|
|
246 /* Check killer */
|
|
247 if (FD_ISSET (ttd.killer[0], &fd_readable))
|
|
248 {
|
|
249 /* FIXME: TODO: Shut down sockets... */
|
|
250
|
|
251 /* Close listener and killer */
|
|
252 close (ttd.listener);
|
|
253 close (ttd.killer[0]);
|
|
254 close (ttd.killer[1]);
|
|
255
|
|
256 /* Check which fds that had anyhting to say... */
|
|
257 client = ttd.clients;
|
|
258
|
|
259 /* Say goodby to clients */
|
|
260 while (client)
|
|
261 {
|
|
262 ctrl_telnet_client *current = client;
|
|
263 ctrl_telnet_client_send (current,
|
|
264 "\nServer is going down, Bye bye\n");
|
|
265 client = client->next;
|
|
266 ctrl_telnet_client_remove (current);
|
|
267 }
|
|
268
|
|
269 pthread_mutex_lock (&functions_lock);
|
|
270
|
|
271 while (functions)
|
|
272 {
|
|
273 telnet_function_list *head = functions;
|
|
274 functions = functions->next;
|
|
275
|
|
276 free (head->name);
|
|
277 if (head->description)
|
|
278 free (head->description);
|
|
279
|
|
280 free (head);
|
|
281 }
|
|
282
|
|
283 pthread_mutex_unlock (&functions_lock);
|
|
284
|
|
285 return NULL;
|
|
286 }
|
|
287
|
|
288 /* Check for new connection */
|
|
289 if (FD_ISSET (ttd.listener, &fd_readable))
|
|
290 {
|
|
291 socklen_t sl_addr;
|
|
292
|
|
293 /* Create client object */
|
|
294 client = malloc (sizeof (ctrl_telnet_client));
|
|
295
|
|
296 if (!client)
|
|
297 {
|
|
298 perror ("Failed to create new client");
|
|
299 return NULL;
|
|
300 }
|
|
301
|
|
302 memset (client, '\0', sizeof (ctrl_telnet_client));
|
|
303 sl_addr = sizeof (client->remote_address);
|
|
304
|
|
305 client->socket = accept (ttd.listener,
|
|
306 (struct sockaddr *) &client->remote_address,
|
|
307 &sl_addr);
|
|
308 if (client->socket == -1)
|
|
309 {
|
|
310 perror ("accept");
|
|
311 free (client);
|
|
312 }
|
|
313 else
|
|
314 {
|
|
315 ctrl_telnet_client_add (client);
|
|
316 ctrl_telnet_client_execute_line_safe (client, "banner");
|
|
317 ctrl_telnet_client_sendf (client, "For a list of registered commands type \"help\"\n");
|
|
318 ctrl_telnet_client_send (client, "\n> ");
|
|
319 }
|
|
320 }
|
|
321
|
|
322 /* Check which fds that had anyhting to say... */
|
|
323 client = ttd.clients;
|
|
324
|
|
325 /* Run through all clients and check if there's data avalible
|
|
326 with FD_ISSET(current->socket) */
|
|
327 while (client)
|
|
328 {
|
|
329 ctrl_telnet_client *current = client;
|
|
330 client = client->next;
|
|
331
|
|
332 if (FD_ISSET (current->socket, &fd_readable))
|
|
333 {
|
|
334 if (ctrl_telnet_client_recv (current) <= 0)
|
|
335 {
|
|
336 ctrl_telnet_client_remove (current);
|
|
337 continue;
|
|
338 }
|
|
339
|
|
340 if (current->ready)
|
|
341 {
|
|
342 ctrl_telnet_client_execute (current);
|
|
343
|
|
344 if (!current->exiting)
|
|
345 ctrl_telnet_client_send (current, "\n> ");
|
|
346 else
|
|
347 ctrl_telnet_client_remove (current);
|
|
348 }
|
|
349 }
|
|
350 }
|
|
351 }
|
|
352 }
|
|
353
|
|
354 /**
|
|
355 * @brief Adds a new client to our list of new ones
|
|
356 *
|
|
357 * @note This funtion is only called from a single thread,
|
|
358 * as such it won't need to be threadsafe
|
|
359 * @param client to add
|
|
360 */
|
|
361 static void
|
|
362 ctrl_telnet_client_add (ctrl_telnet_client *client)
|
|
363 {
|
|
364 client->next = ttd.clients;
|
|
365 ttd.clients = client;
|
|
366 }
|
|
367
|
|
368 /**
|
|
369 * @brief Removes "client" from our list of clients
|
|
370 *
|
|
371 * @note This funtion is only called from a single thread,
|
|
372 * as such it won't need to be threadsafe
|
|
373 * @param client to remove
|
|
374 */
|
|
375 static void
|
|
376 ctrl_telnet_client_remove (ctrl_telnet_client *client)
|
|
377 {
|
|
378 ctrl_telnet_client *tmp;
|
|
379
|
|
380 /* Start by dealing with our head */
|
|
381 if (client == ttd.clients)
|
|
382 ttd.clients = client->next;
|
|
383 else
|
|
384 {
|
|
385 for (tmp = ttd.clients; tmp->next; tmp = tmp->next)
|
|
386 {
|
|
387 if (tmp->next == client)
|
|
388 {
|
|
389 tmp->next = tmp->next->next;
|
|
390 break;
|
|
391 }
|
|
392 }
|
|
393 }
|
|
394
|
|
395 close (client->socket);
|
|
396
|
|
397 free (client);
|
|
398 }
|
|
399
|
|
400 /**
|
|
401 * @brief Clears readable fd_set and adds every client to it,
|
|
402 * returns max fd found
|
|
403 *
|
|
404 * @param readable fd_set to update
|
|
405 * @return Biggest fd
|
|
406 */
|
|
407 static int
|
|
408 ctrl_telnet_fix_fdset (fd_set *readable)
|
|
409 {
|
|
410 int maxfd;
|
|
411 ctrl_telnet_client *client;
|
|
412
|
|
413 maxfd = MAX (ttd.killer[0], ttd.listener);
|
|
414
|
|
415 FD_ZERO (readable);
|
|
416 FD_SET (ttd.listener, readable);
|
|
417 FD_SET (ttd.killer[0], readable);
|
|
418
|
|
419 client = ttd.clients;
|
|
420
|
|
421 while (client)
|
|
422 {
|
|
423 if (client->socket > maxfd)
|
|
424 maxfd = client->socket;
|
|
425
|
|
426 FD_SET (client->socket, readable);
|
|
427
|
|
428 client = client->next;
|
|
429 }
|
|
430
|
|
431 return maxfd;
|
|
432 }
|
|
433
|
|
434 static int
|
|
435 ctrl_telnet_client_recv (ctrl_telnet_client *client)
|
|
436 {
|
|
437 int i;
|
|
438 int nbytes;
|
|
439 int buffer_free = CTRL_CLIENT_RECV_BUFFER_SIZE - client->buffer_recv_current - 1;
|
|
440
|
|
441 nbytes = recv (client->socket,
|
|
442 client->buffer_recv + client->buffer_recv_current,
|
|
443 buffer_free, 0);
|
|
444 if (nbytes <= 0)
|
|
445 {
|
|
446 close (client->socket);
|
|
447 return nbytes;
|
|
448 }
|
|
449
|
|
450 client->buffer_recv_current += nbytes;
|
|
451 client->buffer_recv[client->buffer_recv_current] = '\0';
|
|
452
|
|
453 for (i = 0; i < client->buffer_recv_current; i++)
|
|
454 if (client->buffer_recv[i] == '\n')
|
|
455 client->ready = 1;
|
|
456
|
|
457 return nbytes;
|
|
458 }
|
|
459
|
|
460 int
|
|
461 ctrl_telnet_client_send (const ctrl_telnet_client *client, const char *string)
|
|
462 {
|
|
463 const char* cc = string;
|
|
464 int len = strlen (cc);
|
|
465 int sent = 0;
|
|
466 int senttotal = 0;
|
|
467
|
|
468 while ((cc - string) < len)
|
|
469 {
|
|
470 /* Use nonblocking just as a precation...
|
|
471 and a failed write won't _really_ kill us */
|
|
472 sent = send (client->socket, string, len - (cc - string), MSG_DONTWAIT);
|
|
473
|
|
474 /* This will mark the socket as dead... just to be safe..
|
|
475 and its only a telnet interface... reconnect and do it again */
|
|
476 if (sent == -1)
|
|
477 return -1;
|
|
478
|
|
479 senttotal += sent;
|
|
480 cc += sent;
|
|
481 }
|
|
482
|
|
483 return senttotal;
|
|
484 }
|
|
485
|
|
486 int
|
|
487 ctrl_telnet_client_sendf (const ctrl_telnet_client *client,
|
|
488 const char *format, ...)
|
|
489 {
|
|
490 int retval;
|
|
491 va_list ap;
|
|
492 int len;
|
|
493
|
|
494 pthread_mutex_lock (&shared_lock);
|
|
495
|
|
496 va_start (ap, format);
|
|
497 len = vsnprintf (ttd.shared_buffer,
|
|
498 CTRL_TELNET_SHARED_BUFFER_SIZE, format, ap);
|
|
499 va_end (ap);
|
|
500
|
|
501 /* Check if the message fitted inside the buffer, if not,
|
|
502 either exit or adjust len to be buffersize, I choose exit for now */
|
|
503 if (len >= CTRL_TELNET_SHARED_BUFFER_SIZE)
|
|
504 {
|
|
505 pthread_mutex_unlock (&shared_lock);
|
|
506 /* FIXME: Return error or send what we've got? */
|
|
507 return -1; /* Buffer was to small */
|
|
508 }
|
|
509
|
|
510 /* TODO: Might be good to have the option to specify str length so
|
|
511 send doesn't have to recompute it... */
|
|
512 retval = ctrl_telnet_client_send (client, ttd.shared_buffer);
|
|
513
|
|
514 pthread_mutex_unlock (&shared_lock);
|
|
515
|
|
516 return retval;
|
|
517 }
|
|
518
|
|
519 int
|
|
520 ctrl_telnet_client_sendsf (const ctrl_telnet_client *client,
|
|
521 char *buffer, int buffersize,
|
|
522 const char *format, ...)
|
|
523 {
|
|
524 va_list ap;
|
|
525 int len;
|
|
526
|
|
527 va_start (ap, format);
|
|
528 len = vsnprintf (buffer, buffersize, format, ap);
|
|
529 va_end (ap);
|
|
530
|
|
531 /* Check if the message fitted inside the buffer, if not,
|
|
532 either exit or adjust len to be buffersize, I choose exit for now */
|
|
533 if (len >= buffersize)
|
|
534 return -1; /* Buffer was to small */
|
|
535
|
|
536 /* TODO: Might be good to have the option to specify str length
|
|
537 so send doesn't have to recompute it... */
|
|
538 return ctrl_telnet_client_send (client, buffer);
|
|
539 }
|
|
540
|
|
541 /* FIXME: Ulgy non optimised version */
|
|
542 static int
|
|
543 ctrl_telnet_client_execute (ctrl_telnet_client *client)
|
|
544 {
|
|
545 int i = 0;
|
|
546
|
|
547 /* Check buffer for complete lines and execute them,,, */
|
|
548 for (i = 0; i < client->buffer_recv_current; i++)
|
|
549 {
|
|
550 if (client->buffer_recv[i] == '\n' || client->buffer_recv[i] == '\r')
|
|
551 {
|
|
552 /* Replace newline with null (or \r) */
|
|
553 client->buffer_recv[i] = '\0';
|
|
554
|
|
555 /* Send line to execution */
|
|
556 ctrl_telnet_client_execute_line_safe (client, client->buffer_recv);
|
|
557
|
|
558 /* Check if next is either newline or CR, strip that too, if needed */
|
|
559 if ((i + 1 < CTRL_CLIENT_RECV_BUFFER_SIZE) &&
|
|
560 (client->buffer_recv[i+1]=='\n' || client->buffer_recv[i+1]=='\r'))
|
|
561 client->buffer_recv[++i] = '\0';
|
|
562
|
|
563 /* Remove processed line */
|
|
564 memmove (client->buffer_recv, client->buffer_recv + i,
|
|
565 client->buffer_recv_current - 1);
|
|
566 client->buffer_recv_current -= (i + 1);
|
|
567 i = -1;
|
|
568 }
|
|
569 }
|
|
570
|
|
571 return 0; /* No syntax error checking yet */
|
|
572 }
|
|
573
|
|
574 static int
|
|
575 ctrl_telnet_client_execute_line_safe (ctrl_telnet_client *client, char *line)
|
|
576 {
|
|
577 int retval;
|
|
578
|
|
579 pthread_mutex_lock (&functions_lock);
|
|
580 retval = ctrl_telnet_client_execute_line (client, line);
|
|
581 pthread_mutex_unlock (&functions_lock);
|
|
582
|
|
583 return retval;
|
|
584 }
|
|
585
|
|
586 static int
|
|
587 ctrl_telnet_client_execute_line (ctrl_telnet_client *client, char *line)
|
|
588 {
|
|
589 int argc = 0;
|
|
590 char **argv = NULL;
|
|
591 telnet_function_list *node;
|
|
592 char *line2 = strdup (line); /* To make it safer */
|
|
593 ctrl_telnet_tokenize (line2, &argc, &argv);
|
|
594
|
|
595 node = functions;
|
|
596
|
|
597 if (*argv[0] == '\0')
|
|
598 {
|
|
599 free (argv);
|
|
600 free (line2);
|
|
601 return 0;
|
|
602 }
|
|
603
|
|
604 while (node)
|
|
605 {
|
|
606 if (!strcmp (node->name, argv[0]))
|
|
607 {
|
|
608 node->function (client, argc, argv);
|
|
609 break;
|
|
610 }
|
|
611
|
|
612 node = node->next;
|
|
613 }
|
|
614
|
|
615 if (!node)
|
|
616 ctrl_telnet_client_sendf (client, "%s: Command not found\n", argv[0]);
|
|
617
|
|
618 free (argv);
|
|
619 free (line2);
|
|
620
|
|
621 return strlen (line);
|
|
622 }
|
|
623
|
|
624 void
|
|
625 ctrl_telnet_register (const char *funcname,
|
|
626 ctrl_telnet_command_ptr funcptr,
|
|
627 const char *description)
|
|
628 {
|
|
629 telnet_function_list *function;
|
|
630
|
|
631 function = malloc (sizeof (telnet_function_list));
|
|
632 function->name = strdup (funcname); /* Mayby use strndup...? */
|
|
633 function->description = description ? strdup (description) : NULL;
|
|
634 function->function = funcptr;
|
|
635
|
|
636 pthread_mutex_lock (&functions_lock);
|
|
637 function->next = functions;
|
|
638 functions = function;
|
|
639 pthread_mutex_unlock (&functions_lock);
|
|
640 }
|
|
641
|
|
642 /* Warning: This WILL edit the input string... use strdup or something
|
|
643 if needed, also remember to free() argv as the first array is dynamic */
|
|
644 /* If *argv != NULL it'll first be free()ed... or realloc,
|
|
645 make sure to clear *argv to null on initialization */
|
|
646 static void
|
|
647 ctrl_telnet_tokenize (char *raw, int *argc, char ***argv)
|
|
648 {
|
|
649 int i;
|
|
650 int has_backslash = 0;
|
|
651 int has_quote = 0;
|
|
652 char *pc = raw;
|
|
653
|
|
654 if (!raw || !argc || !argv)
|
|
655 {
|
|
656 perror ("NULL in " __FILE__ " at line " STR (__LINE__));
|
|
657 return;
|
|
658 }
|
|
659
|
|
660 /* (1/3) First run is just to count our arguments... */
|
|
661 *argc = (raw[0] == '\0') ? 0 : 1;
|
|
662
|
|
663 pc = raw;
|
|
664 while (*pc)
|
|
665 {
|
|
666 switch (*pc)
|
|
667 {
|
|
668 case '\\':
|
|
669 if (!has_backslash)
|
|
670 has_backslash = 2; /* FULHACK */
|
|
671 break;
|
|
672 case ' ':
|
|
673 if (!has_backslash && !has_quote)
|
|
674 (*argc)++;
|
|
675 break;
|
|
676 case '"':
|
|
677 if (!has_backslash)
|
|
678 has_quote = !has_quote;
|
|
679
|
|
680 break;
|
|
681 }
|
|
682
|
|
683 /* When we get a BS we set it to two, this makes it one,
|
|
684 next run it will still be 1, then one after that is zero... FULHACK */
|
|
685 if (has_backslash)
|
|
686 has_backslash--;
|
|
687
|
|
688 pc++;
|
|
689 }
|
|
690
|
|
691 /* Create argv */
|
|
692 *argv = malloc (sizeof (char **) * ((*argc) + 1));
|
|
693
|
|
694 /* (2/3) Parse throu one more time, this time filling argv (Pass 2 / 3) */
|
|
695 i = 0;
|
|
696 pc = raw;
|
|
697 has_backslash = 0;
|
|
698 has_quote = 0;
|
|
699 (*argv)[0] = raw;
|
|
700
|
|
701 while (*pc)
|
|
702 {
|
|
703 switch (*pc)
|
|
704 {
|
|
705 case '\\':
|
|
706 if (!has_backslash)
|
|
707 has_backslash = 2; /* FULHACK */
|
|
708 break;
|
|
709 case ' ':
|
|
710 if (!has_backslash && !has_quote)
|
|
711 {
|
|
712 *pc = '\0';
|
|
713 (*argv)[++i] = pc+1;
|
|
714 pc++;
|
|
715 continue;
|
|
716 }
|
|
717 break;
|
|
718 case '"':
|
|
719 if (!has_backslash)
|
|
720 has_quote = !has_quote;
|
|
721 break;
|
|
722 }
|
|
723
|
|
724 /* When we get a BS we set it to two, this makes it one,
|
|
725 next run it will still be 1, then one after that is zero... FULHACK */
|
|
726 if (has_backslash)
|
|
727 has_backslash--;
|
|
728
|
|
729 pc++;
|
|
730 }
|
|
731
|
|
732 /* Make last element (argc) point to null... */
|
|
733 (*argv)[++i] = NULL;
|
|
734
|
|
735 /* (3/3) Parse arguments to remove escapings and such */
|
|
736 for (i = 0; (*argv)[i]; i++)
|
|
737 {
|
|
738 /* Set up environment */
|
|
739 pc = (*argv)[i];
|
|
740 has_backslash = 0;
|
|
741 has_quote = 0;
|
|
742
|
|
743 /* Remove leading and ending quotes, if existing */
|
|
744 if (*pc == '"')
|
|
745 {
|
|
746 int len = strlen (pc);
|
|
747
|
|
748 if (len > 0 && pc[len - 1] == '"')
|
|
749 pc[len - 1] = '\0';
|
|
750 memmove (pc, pc + 1, len);
|
|
751 }
|
|
752
|
|
753 /* Remove any special characters */
|
|
754 while (*pc)
|
|
755 {
|
|
756 switch (*pc)
|
|
757 {
|
|
758 case '\\':
|
|
759 if (!has_backslash)
|
|
760 {
|
|
761 has_backslash = 2; /* FULHACK */
|
|
762 break;
|
|
763 }
|
|
764 /* Else: fall through */
|
|
765 case ' ':
|
|
766 case '"':
|
|
767 if (has_backslash)
|
|
768 {
|
|
769 pc--;
|
|
770 memmove (pc, pc + 1, strlen (pc)); /* FIXME: Not cheap */
|
|
771 }
|
|
772 break;
|
|
773 }
|
|
774
|
|
775 /* When we get a BS we set it to two, this makes it one,
|
|
776 next run it will still be 1, then one after that is zero... */
|
|
777 if (has_backslash)
|
|
778 has_backslash--;
|
|
779
|
|
780 pc++;
|
|
781 }
|
|
782 }
|
|
783 }
|
|
784
|
|
785 static void
|
|
786 help (ctrl_telnet_client *client, int argc, char **argv)
|
|
787 {
|
|
788 int hidden = 0;
|
|
789 ctrl_telnet_client_execute_line (client, "banner");
|
|
790
|
|
791 if (argc < 2)
|
|
792 {
|
|
793 ctrl_telnet_client_send (client, "\n");
|
|
794 ctrl_telnet_client_send (client, "Usage: help TOPIC\n");
|
|
795 ctrl_telnet_client_send (client, "Valid topics are\n");
|
|
796 ctrl_telnet_client_send
|
|
797 (client, " commands - For a list of registed commands\n");
|
|
798 ctrl_telnet_client_send
|
|
799 (client, " syntax - For a description of the interface syntax\n");
|
|
800 return;
|
|
801 }
|
|
802 else
|
|
803 {
|
|
804 if (!strcmp ("commands", argv[1]))
|
|
805 {
|
|
806 telnet_function_list *node;
|
|
807
|
|
808 node = functions;
|
|
809 ctrl_telnet_client_send
|
|
810 (client, "Registered command (command - description)\n");
|
|
811 ctrl_telnet_client_send
|
|
812 (client, "=======================================================\n");
|
|
813
|
|
814 while (node)
|
|
815 {
|
|
816 /* Make functions without descriptions invisible */
|
|
817 if (node->description)
|
|
818 ctrl_telnet_client_sendf (client, " %s - %s\n",
|
|
819 node->name, node->description);
|
|
820 else
|
|
821 hidden++;
|
|
822
|
|
823 node = node->next;
|
|
824 }
|
|
825
|
|
826 if (hidden)
|
|
827 ctrl_telnet_client_sendf
|
|
828 (client, "There's also %i hidden functions\n", hidden);
|
|
829 } /* commands */
|
|
830 else if (!strcmp ("syntax", argv[1]))
|
|
831 {
|
|
832 ctrl_telnet_client_send
|
|
833 (client, "Syntax is easy: command parameters\n");
|
|
834 ctrl_telnet_client_send
|
|
835 (client, " Each new word is a new argument, unless the space is precided\n");
|
|
836 ctrl_telnet_client_send
|
|
837 (client, " a backslash (\\), or if a set of words are surrounded by quotes\n");
|
|
838 ctrl_telnet_client_send
|
|
839 (client, " (\"). To get a litteral quote you can escape it as \\\".\n");
|
|
840 ctrl_telnet_client_send (client, "\n");
|
|
841 ctrl_telnet_client_send (client, "STUB\n");
|
|
842 }
|
|
843 else
|
|
844 ctrl_telnet_client_send (client, "Unknown topic\n");
|
|
845 }
|
|
846 }
|
|
847
|
|
848 static void
|
|
849 banner (ctrl_telnet_client *client,
|
|
850 int argc __attribute__ ((unused)),
|
|
851 char **argv __attribute__ ((unused)))
|
|
852 {
|
|
853 ctrl_telnet_client_sendf (client, "%s (%s) (Built %s)\n",
|
|
854 PACKAGE_NAME, VERSION, __DATE__);
|
|
855 }
|
|
856
|
|
857 static void
|
|
858 echo (ctrl_telnet_client *client, int argc, char **argv)
|
|
859 {
|
|
860 int i;
|
|
861
|
|
862 for (i = 1; i < argc; i++)
|
|
863 ctrl_telnet_client_sendf (client, "%s%s", (i > 1 ? " " : ""), argv[i]);
|
|
864 ctrl_telnet_client_send (client, "\n");
|
|
865 }
|
|
866
|
|
867 static void
|
|
868 echod (ctrl_telnet_client *client, int argc, char **argv)
|
|
869 {
|
|
870 int i;
|
|
871
|
|
872 ctrl_telnet_client_sendf (client, "Argc: %i\n", argc);
|
|
873
|
|
874 for (i = 0; i < argc; i++)
|
|
875 ctrl_telnet_client_sendf (client, "%i: '%s'\n", i, argv[i]);
|
|
876 }
|
|
877
|
|
878 static void
|
|
879 ctrl_telnet_exit (ctrl_telnet_client *client,
|
|
880 int argc __attribute__ ((unused)),
|
|
881 char **argv __attribute__ ((unused)))
|
|
882 {
|
|
883 client->exiting = 1;
|
|
884 ctrl_telnet_client_send (client, "Bye bye\n");
|
|
885 }
|
|
886
|
|
887 static void
|
|
888 ctrl_telnet_register_internals (void)
|
|
889 {
|
|
890 ctrl_telnet_register ("echo", echo, "Echos all arguments");
|
|
891 ctrl_telnet_register ("echod", echod, "Echos all arguments but with each argument on a new line... DEBUG");
|
|
892 ctrl_telnet_register ("help", help, "Display help");
|
|
893 ctrl_telnet_register ("banner", banner, NULL);
|
|
894 ctrl_telnet_register ("exit", ctrl_telnet_exit, "Exits this interface (Or CTRL+D then Enter)");
|
|
895 /* CTRL+D... But it has to be fallowd by a new line */
|
|
896 ctrl_telnet_register ("\4", ctrl_telnet_exit, NULL);
|
|
897 }
|