1/***********************************************************************
2*
3* l2tp/handlers/cmd.c
4*
5* Simple (VERY simple) command-processor for the L2TP daemon.
6*
7* Copyright (C) 2002 Roaring Penguin Software Inc.
8*
9* This software may be distributed under the terms of the GNU General
10* Public License, Version 2, or (at your option) any later version.
11*
12* LIC: GPL
13*
14***********************************************************************/
15
16static char const RCSID[] =
17"$Id: cmd.c,v 1.1.1.1 2008/10/15 03:31:00 james26_jang Exp $";
18
19#include "l2tp.h"
20#include "dstring.h"
21#include "event_tcp.h"
22#include <string.h>
23#include <errno.h>
24#include <sys/socket.h>
25#include <sys/un.h>
26#include <stdlib.h>
27#include <stdio.h>
28#include <unistd.h>
29#include <fcntl.h>
30#include <sys/stat.h>
31#include <netdb.h>
32#include <signal.h>
33#include <resolv.h>
34
35#define HANDLER_NAME "cmd"
36
37static int process_option(EventSelector *, char const *, char const *);
38static void cmd_acceptor(EventSelector *es, int fd);
39static void cmd_handler(EventSelector *es,
40			int fd, char *buf, int len, int flag, void *data);
41
42
43static void cmd_exit(EventSelector *es, int fd);
44static void cmd_start_session(EventSelector *es, int fd, char *buf);
45static void cmd_stop_session(EventSelector *es, int fd, char *buf);
46static void cmd_dump_sessions(EventSelector *es, int fd, char *buf);
47static void cmd_reply(EventSelector *es, int fd, char const *msg);
48
49static option_handler my_option_handler = {
50    NULL, HANDLER_NAME, process_option
51};
52
53/* Socket name for commands */
54static char const *sockname = NULL;
55
56static l2tp_opt_descriptor my_opts[] = {
57    { "socket-path",     OPT_TYPE_STRING, &sockname },
58    { NULL,              OPT_TYPE_BOOL,   NULL}
59};
60
61
62/**********************************************************************
63* %FUNCTION: describe_session
64* %ARGUMENTS:
65*  ses -- an L2TP session
66*  str -- a dynamic string to which description is appended
67* %RETURNS:
68*  Nothing
69* %DESCRIPTION:
70*  Dumps session description into str
71***********************************************************************/
72static void
73describe_session(l2tp_session *ses,
74		 dynstring *str)
75{
76    char buf[1024];
77
78    sprintf(buf, "Session %s MyID %d AssignedID %d",
79	    (ses->we_are_lac ? "LAC" : "LNS"),
80	    (int) ses->my_id, (int) ses->assigned_id);
81    dynstring_append(str, buf);
82    sprintf(buf, " State %s\n",
83	    l2tp_session_state_name(ses));
84    dynstring_append(str, buf);
85}
86
87/**********************************************************************
88* %FUNCTION: describe_tunnel
89* %ARGUMENTS:
90*  tunnel -- an L2TP tunnel
91*  str -- a dynamic string to which description is appended
92* %RETURNS:
93*  Nothing
94* %DESCRIPTION:
95*  Dumps tunnel description into str
96***********************************************************************/
97static void
98describe_tunnel(l2tp_tunnel *tunnel,
99		dynstring *str)
100{
101    char buf[1024];
102    l2tp_session *ses;
103    void *cursor;
104
105    sprintf(buf, "Tunnel MyID %d AssignedID %d",
106	    (int) tunnel->my_id, (int) tunnel->assigned_id);
107    dynstring_append(str, buf);
108    sprintf(buf, " NumSessions %d", (int) hash_num_entries(&tunnel->sessions_by_my_id));
109    dynstring_append(str, buf);
110    sprintf(buf, " PeerIP %s State %s\n", inet_ntoa(tunnel->peer_addr.sin_addr),
111	    l2tp_tunnel_state_name(tunnel));
112
113    dynstring_append(str, buf);
114
115    /* Describe each session */
116    for (ses = l2tp_tunnel_first_session(tunnel, &cursor);
117	 ses;
118	 ses = l2tp_tunnel_next_session(tunnel, &cursor)) {
119	describe_session(ses, str);
120    }
121}
122
123/**********************************************************************
124* %FUNCTION: handler_init
125* %ARGUMENTS:
126*  es -- event selector
127* %RETURNS:
128*  Nothing
129* %DESCRIPTION:
130*  Initializes the command processor's option handler.  We do not
131*  actually start a command processor until last option has been parsed.
132***********************************************************************/
133void
134handler_init(EventSelector *es)
135{
136    l2tp_option_register_section(&my_option_handler);
137}
138
139/**********************************************************************
140* %FUNCTION: process_option
141* %ARGUMENTS:
142*  es -- event selector
143*  name, value -- name and value of option
144* %RETURNS:
145*  0 on success; -1 on failure.
146* %DESCRIPTION:
147*  Processes options.  When last option has been processed, begins
148*  command handler.
149***********************************************************************/
150static int
151process_option(EventSelector *es,
152	       char const *name,
153	       char const *value)
154{
155    struct sockaddr_un addr;
156    socklen_t len;
157    int fd;
158
159    if (!strcmp(name, "*begin*")) return 0;
160    if (strcmp(name, "*end*")) {
161	return l2tp_option_set(es, name, value, my_opts);
162    }
163
164    /* We have hit the end of our options.  Open command socket */
165    if (!sockname) {
166	sockname = "/var/run/l2tpctrl";
167    }
168
169    (void) remove(sockname);
170    fd = socket(AF_LOCAL, SOCK_STREAM, 0);
171    if (fd < 0) {
172	l2tp_set_errmsg("cmd: process_option: socket: %s", strerror(errno));
173	return -1;
174    }
175
176    memset(&addr, 0, sizeof(addr));
177    addr.sun_family = AF_LOCAL;
178    strncpy(addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
179
180    len = sizeof(addr);
181    if (bind(fd, (struct sockaddr *) &addr, SUN_LEN(&addr)) < 0) {
182	l2tp_set_errmsg("cmd: process_option: bind: %s", strerror(errno));
183	close(fd);
184	return -1;
185    }
186    (void) chmod(sockname, 0600);
187    if (listen(fd, 5) < 0) {
188	l2tp_set_errmsg("cmd: process_option: listen: %s", strerror(errno));
189	close(fd);
190	return -1;
191    }
192
193    /* Ignore sigpipe */
194    signal(SIGPIPE, SIG_IGN);
195
196    /* Add an accept handler */
197    if (!EventTcp_CreateAcceptor(es, fd, cmd_acceptor)) {
198	l2tp_set_errmsg("cmd: process_option: EventTcp_CreateAcceptor: %s", strerror(errno));
199	close(fd);
200	return -1;
201    }
202    return 0;
203}
204
205/**********************************************************************
206* %FUNCTION: cmd_acceptor
207* %ARGUMENTS:
208*  es -- event selector
209*  fd -- file descriptor of accepted connection
210* %RETURNS:
211*  Nothing
212* %DESCRIPTION:
213*  Accepts a control connection and sets up read event.
214***********************************************************************/
215static void
216cmd_acceptor(EventSelector *es, int fd)
217{
218    EventTcp_ReadBuf(es, fd, 512, '\n', cmd_handler, 5, NULL);
219}
220
221/**********************************************************************
222* %FUNCTION: cmd_handler
223* %ARGUMENTS:
224*  es -- event selector
225*  fd -- file descriptor
226*  buf -- buffer which was read
227*  len -- length of data
228*  flag -- flags
229*  data -- not used
230* %RETURNS:
231*  Nothing
232* %DESCRIPTION:
233*  Processes a command from the user
234***********************************************************************/
235static void
236cmd_handler(EventSelector *es,
237	    int fd,
238	    char *buf,
239	    int len,
240	    int flag,
241	    void *data)
242{
243    char word[512];
244
245    if (flag == EVENT_TCP_FLAG_IOERROR || flag == EVENT_TCP_FLAG_TIMEOUT) {
246	close(fd);
247	return;
248    }
249    if (len < 511) {
250	buf[len+1] = 0;
251    } else {
252	buf[len] = 0;
253    }
254
255    /* Chop off newline */
256    if (len && (buf[len-1] == '\n')) {
257	buf[len-1] = 0;
258    }
259
260    /* Get first word */
261    buf = (char *) l2tp_chomp_word(buf, word);
262
263    if (!strcmp(word, "exit")) {
264	cmd_exit(es, fd);
265    } else if (!strcmp(word, "start-session")) {
266	cmd_start_session(es, fd, buf);
267    } else if (!strcmp(word, "stop-session")) {
268	cmd_stop_session(es, fd, buf);
269    } else if (!strcmp(word, "dump-sessions")) {
270	cmd_dump_sessions(es, fd, buf);
271    } else {
272	cmd_reply(es, fd, "ERR Unknown command");
273    }
274}
275
276/**********************************************************************
277* %FUNCTION: cmd_reply
278* %ARGUMENTS:
279*  es -- event selector
280*  fd -- file descriptor
281*  msg -- message
282* %RETURNS:
283*  Nothing
284* %DESCRIPTION:
285*  Schedules reply to be shot back to user
286***********************************************************************/
287static void
288cmd_reply(EventSelector *es,
289	  int fd,
290	  char const *msg)
291{
292    EventTcp_WriteBuf(es, fd, (char *) msg, strlen(msg), NULL, 10, NULL);
293}
294
295/**********************************************************************
296* %FUNCTION: cmd_exit
297* %ARGUMENTS:
298*  es -- Event selector
299*  fd -- command file descriptor
300* %RETURNS:
301*  Nothing
302* %DESCRIPTION:
303*  Tears down tunnels and quits
304***********************************************************************/
305static void
306cmd_exit(EventSelector *es,
307	 int fd)
308{
309    cmd_reply(es, fd, "OK Shutting down");
310    l2tp_tunnel_stop_all("Stopped by system administrator");
311    l2tp_cleanup();
312    exit(0);
313}
314
315/**********************************************************************
316* %FUNCTION: cmd_start_session
317* %ARGUMENTS:
318*  es -- event selector
319*  fd -- command file descriptor
320*  buf -- rest of command from user
321* %RETURNS:
322*  Nothing
323* %DESCRIPTION:
324*  Starts an L2TP session, if possible
325***********************************************************************/
326static void
327cmd_start_session(EventSelector *es,
328		  int fd,
329		  char *buf)
330{
331    char peer[512];
332    struct hostent *he;
333    struct sockaddr_in haddr;
334    l2tp_peer *p;
335    l2tp_session *sess;
336
337    buf = (char *) l2tp_chomp_word(buf, peer);
338    he = gethostbyname(peer);
339    if (!he) {
340	cmd_reply(es, fd, "ERR Unknown peer - gethostbyname failed");
341	return;
342    }
343    memcpy(&haddr.sin_addr, he->h_addr, sizeof(haddr.sin_addr));
344    p = l2tp_peer_find(&haddr, NULL);
345    if (!p) {
346	cmd_reply(es, fd, "ERR Unknown peer");
347	return;
348    }
349    sess = l2tp_session_call_lns(p, "foobar", es, NULL);
350    if (!sess) {
351	cmd_reply(es, fd, l2tp_get_errmsg());
352	return;
353    }
354
355    /* Form reply */
356    sprintf(peer, "OK %d %d",
357	    (int) sess->tunnel->my_id,
358	    (int) sess->my_id);
359    cmd_reply(es, fd, peer);
360}
361
362/**********************************************************************
363* %FUNCTION: cmd_stop_session
364* %ARGUMENTS:
365*  es -- event selector
366*  fd -- command file descriptor
367*  buf -- rest of command from user
368* %RETURNS:
369*  Nothing
370* %DESCRIPTION:
371*  Stops an L2TP session, identified by (Tunnel, Session) pair.
372***********************************************************************/
373static void
374cmd_stop_session(EventSelector *es,
375		  int fd,
376		  char *buf)
377{
378    char junk[512];
379    l2tp_tunnel *tunnel;
380    l2tp_session *sess;
381    unsigned int x;
382
383    buf = (char *) l2tp_chomp_word(buf, junk);
384    if (sscanf(junk, "%u", &x) != 1) {
385	cmd_reply(es, fd, "ERR Syntax error: stop-session tid sid");
386	return;
387    }
388    tunnel = l2tp_tunnel_find_by_my_id((uint16_t) x);
389    if (!tunnel) {
390	cmd_reply(es, fd, "ERR No such tunnel");
391	return;
392    }
393
394
395    buf = (char *) l2tp_chomp_word(buf, junk);
396    if (sscanf(junk, "%u", &x) != 1) {
397	cmd_reply(es, fd, "ERR Syntax error: stop-session tid sid");
398	return;
399    }
400    sess = l2tp_tunnel_find_session(tunnel, (uint16_t) x);
401
402    if (!sess) {
403	cmd_reply(es, fd, "ERR No such session");
404	return;
405    }
406
407    /* Stop the session */
408    l2tp_session_send_CDN(sess, RESULT_GENERAL_REQUEST, ERROR_OK,
409			  "Call terminated by operator");
410    cmd_reply(es, fd, "OK Session stopped");
411}
412
413/**********************************************************************
414* %FUNCTION: cmd_dump_sessions
415* %ARGUMENTS:
416*  es -- event selector
417*  fd -- command file descriptor
418*  buf -- rest of command from user
419* %RETURNS:
420*  Nothing
421* %DESCRIPTION:
422*  Dumps info about all currently-active tunnels and sessions
423***********************************************************************/
424static void
425cmd_dump_sessions(EventSelector *es,
426		  int fd,
427		  char *buf)
428{
429    dynstring str;
430    char tmp[256];
431    void *cursor;
432    char const *ans;
433    l2tp_tunnel *tunnel;
434
435    dynstring_init(&str);
436
437    dynstring_append(&str, "OK\n");
438
439    /* Print info about each tunnel */
440    sprintf(tmp, "NumL2TPTunnels %d\n", l2tp_num_tunnels());
441    dynstring_append(&str, tmp);
442
443    for (tunnel = l2tp_first_tunnel(&cursor);
444	 tunnel;
445	 tunnel = l2tp_next_tunnel(&cursor)) {
446	describe_tunnel(tunnel, &str);
447    }
448
449    /* If something went wrong, say so... */
450    ans = dynstring_data(&str);
451    if (!ans) {
452	cmd_reply(es, fd, "ERR Out of memory");
453	return;
454    }
455
456    cmd_reply(es, fd, ans);
457    dynstring_free(&str);
458}
459
460