add document describing what users will need to be aware of when upgrading to this...
[asterisk-bristuff.git] / utils / astman.c
blobf072dcd5779c6f77a36061b861cb5ac16ef1d35b
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * ASTerisk MANager
25 #include <newt.h>
26 #include <stdio.h>
27 #include <sys/time.h>
28 #include <netdb.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <sys/socket.h>
32 #include <sys/select.h>
33 #include <fcntl.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <unistd.h>
38 #include <stdlib.h>
40 #include "asterisk/md5.h"
41 #include "asterisk/linkedlists.h"
43 #undef gethostbyname
45 #define MAX_HEADERS 80
46 #define MAX_LEN 256
49 * 2005.05.27 - different versions of newt define the type of the buffer
50 * for the 5th argument to newtEntry() as char ** or const char ** . To
51 * let the code compile cleanly with -Werror, we cast it to void * through
52 * _NEWT_CAST.
54 #define _NEWT_CAST (void *)
56 #define DEFAULT_MANAGER_PORT 5038
58 struct message {
59 unsigned int hdrcount;
60 char headers[MAX_HEADERS][MAX_LEN];
63 static struct ast_mansession {
64 struct sockaddr_in sin;
65 int fd;
66 char inbuf[MAX_LEN];
67 int inlen;
68 } session;
70 struct ast_chan {
71 char name[80];
72 char exten[20];
73 char context[20];
74 char priority[20];
75 char callerid[40];
76 char state[10];
77 AST_LIST_ENTRY(ast_chan) list;
80 static AST_LIST_HEAD_NOLOCK_STATIC(chans, ast_chan);
82 /* dummy functions to be compatible with the Asterisk core for md5.c */
83 void ast_register_file_version(const char *file, const char *version);
84 void ast_register_file_version(const char *file, const char *version)
88 void ast_unregister_file_version(const char *file);
89 void ast_unregister_file_version(const char *file)
93 int ast_add_profile(const char *, uint64_t scale);
94 int ast_add_profile(const char *s, uint64_t scale)
96 return -1;
99 int64_t ast_profile(int, int64_t);
100 int64_t ast_profile(int key, int64_t val)
102 return 0;
104 int64_t ast_mark(int, int start1_stop0);
105 int64_t ast_mark(int key, int start1_stop0)
107 return 0;
110 /* end of dummy functions */
112 static struct ast_chan *find_chan(char *name)
114 struct ast_chan *chan;
115 AST_LIST_TRAVERSE(&chans, chan, list) {
116 if (!strcmp(name, chan->name))
117 return chan;
119 chan = malloc(sizeof(struct ast_chan));
120 if (chan) {
121 memset(chan, 0, sizeof(struct ast_chan));
122 strncpy(chan->name, name, sizeof(chan->name) - 1);
123 AST_LIST_INSERT_TAIL(&chans, chan, list);
125 return chan;
128 static void del_chan(char *name)
130 struct ast_chan *chan;
131 AST_LIST_TRAVERSE_SAFE_BEGIN(&chans, chan, list) {
132 if (!strcmp(name, chan->name)) {
133 AST_LIST_REMOVE_CURRENT(&chans, list);
134 free(chan);
135 return;
138 AST_LIST_TRAVERSE_SAFE_END
141 static void fdprintf(int fd, char *fmt, ...)
143 char stuff[4096];
144 va_list ap;
145 va_start(ap, fmt);
146 vsnprintf(stuff, sizeof(stuff), fmt, ap);
147 va_end(ap);
148 write(fd, stuff, strlen(stuff));
151 static char *get_header(struct message *m, char *var)
153 char cmp[80];
154 int x;
155 snprintf(cmp, sizeof(cmp), "%s: ", var);
156 for (x=0;x<m->hdrcount;x++)
157 if (!strncasecmp(cmp, m->headers[x], strlen(cmp)))
158 return m->headers[x] + strlen(cmp);
159 return "";
162 static int event_newstate(struct ast_mansession *s, struct message *m)
164 struct ast_chan *chan;
165 chan = find_chan(get_header(m, "Channel"));
166 strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
167 return 0;
170 static int event_newexten(struct ast_mansession *s, struct message *m)
172 struct ast_chan *chan;
173 chan = find_chan(get_header(m, "Channel"));
174 strncpy(chan->exten, get_header(m, "Extension"), sizeof(chan->exten) - 1);
175 strncpy(chan->context, get_header(m, "Context"), sizeof(chan->context) - 1);
176 strncpy(chan->priority, get_header(m, "Priority"), sizeof(chan->priority) - 1);
177 return 0;
180 static int event_newchannel(struct ast_mansession *s, struct message *m)
182 struct ast_chan *chan;
183 chan = find_chan(get_header(m, "Channel"));
184 strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
185 strncpy(chan->callerid, get_header(m, "Callerid"), sizeof(chan->callerid) - 1);
186 return 0;
189 static int event_status(struct ast_mansession *s, struct message *m)
191 struct ast_chan *chan;
192 chan = find_chan(get_header(m, "Channel"));
193 strncpy(chan->state, get_header(m, "State"), sizeof(chan->state) - 1);
194 strncpy(chan->callerid, get_header(m, "Callerid"), sizeof(chan->callerid) - 1);
195 strncpy(chan->exten, get_header(m, "Extension"), sizeof(chan->exten) - 1);
196 strncpy(chan->context, get_header(m, "Context"), sizeof(chan->context) - 1);
197 strncpy(chan->priority, get_header(m, "Priority"), sizeof(chan->priority) - 1);
198 return 0;
201 static int event_hangup(struct ast_mansession *s, struct message *m)
203 del_chan(get_header(m, "Channel"));
204 return 0;
207 static int event_ignore(struct ast_mansession *s, struct message *m)
209 return 0;
212 static int event_rename(struct ast_mansession *s, struct message *m)
214 struct ast_chan *chan;
215 chan = find_chan(get_header(m, "Oldname"));
216 strncpy(chan->name, get_header(m, "Newname"), sizeof(chan->name) - 1);
217 return 0;
219 static struct event {
220 char *event;
221 int (*func)(struct ast_mansession *s, struct message *m);
222 } events[] = {
223 { "Newstate", event_newstate },
224 { "Newchannel", event_newchannel },
225 { "Newexten", event_newexten },
226 { "Hangup", event_hangup },
227 { "Rename", event_rename },
228 { "Status", event_status },
229 { "Link", event_ignore },
230 { "Unlink", event_ignore },
231 { "StatusComplete", event_ignore }
234 static int process_message(struct ast_mansession *s, struct message *m)
236 int x;
237 char event[80] = "";
238 strncpy(event, get_header(m, "Event"), sizeof(event) - 1);
239 if (!strlen(event)) {
240 fprintf(stderr, "Missing event in request");
241 return 0;
243 for (x=0;x<sizeof(events) / sizeof(events[0]);x++) {
244 if (!strcasecmp(event, events[x].event)) {
245 if (events[x].func(s, m))
246 return -1;
247 break;
250 if (x >= sizeof(events) / sizeof(events[0]))
251 fprintf(stderr, "Ignoring unknown event '%s'", event);
252 #if 0
253 for (x=0;x<m->hdrcount;x++) {
254 printf("Header: %s\n", m->headers[x]);
256 #endif
257 return 0;
260 static void rebuild_channels(newtComponent c)
262 void *prev = NULL;
263 struct ast_chan *chan;
264 char tmpn[42];
265 char tmp[256];
266 int x=0;
267 prev = newtListboxGetCurrent(c);
268 newtListboxClear(c);
269 AST_LIST_TRAVERSE(&chans, chan, list) {
270 snprintf(tmpn, sizeof(tmpn), "%s (%s)", chan->name, chan->callerid);
271 if (strlen(chan->exten))
272 snprintf(tmp, sizeof(tmp), "%-30s %8s -> %s@%s:%s",
273 tmpn, chan->state,
274 chan->exten, chan->context, chan->priority);
275 else
276 snprintf(tmp, sizeof(tmp), "%-30s %8s",
277 tmpn, chan->state);
278 newtListboxAppendEntry(c, tmp, chan);
279 x++;
281 if (!x)
282 newtListboxAppendEntry(c, " << No Active Channels >> ", NULL);
283 newtListboxSetCurrentByKey(c, prev);
286 static int has_input(struct ast_mansession *s)
288 int x;
289 for (x=1;x<s->inlen;x++)
290 if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r'))
291 return 1;
292 return 0;
295 static int get_input(struct ast_mansession *s, char *output)
297 /* output must have at least sizeof(s->inbuf) space */
298 int res;
299 int x;
300 struct timeval tv = {0, 0};
301 fd_set fds;
302 for (x=1;x<s->inlen;x++) {
303 if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r')) {
304 /* Copy output data up to and including \r\n */
305 memcpy(output, s->inbuf, x + 1);
306 /* Add trailing \0 */
307 output[x+1] = '\0';
308 /* Move remaining data back to the front */
309 memmove(s->inbuf, s->inbuf + x + 1, s->inlen - x);
310 s->inlen -= (x + 1);
311 return 1;
314 if (s->inlen >= sizeof(s->inbuf) - 1) {
315 fprintf(stderr, "Dumping long line with no return from %s: %s\n", inet_ntoa(s->sin.sin_addr), s->inbuf);
316 s->inlen = 0;
318 FD_ZERO(&fds);
319 FD_SET(s->fd, &fds);
320 res = select(s->fd + 1, &fds, NULL, NULL, &tv);
321 if (res < 0) {
322 fprintf(stderr, "Select returned error: %s\n", strerror(errno));
323 } else if (res > 0) {
324 res = read(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen);
325 if (res < 1)
326 return -1;
327 s->inlen += res;
328 s->inbuf[s->inlen] = '\0';
329 } else {
330 return 2;
332 return 0;
335 static int input_check(struct ast_mansession *s, struct message **mout)
337 static struct message m;
338 int res;
340 if (mout)
341 *mout = NULL;
343 for(;;) {
344 res = get_input(s, m.headers[m.hdrcount]);
345 if (res == 1) {
346 #if 0
347 fprintf(stderr, "Got header: %s", m.headers[m.hdrcount]);
348 fgetc(stdin);
349 #endif
350 /* Strip trailing \r\n */
351 if (strlen(m.headers[m.hdrcount]) < 2)
352 continue;
353 m.headers[m.hdrcount][strlen(m.headers[m.hdrcount]) - 2] = '\0';
354 if (!strlen(m.headers[m.hdrcount])) {
355 if (mout && strlen(get_header(&m, "Response"))) {
356 *mout = &m;
357 return 0;
359 if (process_message(s, &m))
360 break;
361 memset(&m, 0, sizeof(&m));
362 } else if (m.hdrcount < MAX_HEADERS - 1)
363 m.hdrcount++;
364 } else if (res < 0) {
365 return -1;
366 } else if (res == 2)
367 return 0;
369 return -1;
372 static struct message *wait_for_response(int timeout)
374 struct message *m;
375 struct timeval tv;
376 int res;
377 fd_set fds;
378 for (;;) {
379 tv.tv_sec = timeout / 1000;
380 tv.tv_usec = (timeout % 1000) * 1000;
381 FD_SET(session.fd, &fds);
382 res = select(session.fd + 1, &fds, NULL, NULL, &tv);
383 if (res < 1)
384 break;
385 if (input_check(&session, &m) < 0) {
386 return NULL;
388 if (m)
389 return m;
391 return NULL;
394 static int manager_action(char *action, char *fmt, ...)
396 struct ast_mansession *s;
397 char tmp[4096];
398 va_list ap;
400 s = &session;
401 fdprintf(s->fd, "Action: %s\r\n", action);
402 va_start(ap, fmt);
403 vsnprintf(tmp, sizeof(tmp), fmt, ap);
404 va_end(ap);
405 write(s->fd, tmp, strlen(tmp));
406 fdprintf(s->fd, "\r\n");
407 return 0;
410 static int show_message(char *title, char *msg)
412 newtComponent form;
413 newtComponent label;
414 newtComponent ok;
415 struct newtExitStruct es;
417 newtCenteredWindow(60,7, title);
419 label = newtLabel(4,1,msg);
420 ok = newtButton(27, 3, "OK");
421 form = newtForm(NULL, NULL, 0);
422 newtFormAddComponents(form, label, ok, NULL);
423 newtFormRun(form, &es);
424 newtPopWindow();
425 newtFormDestroy(form);
426 return 0;
429 static newtComponent showform;
430 static int show_doing(char *title, char *tmp)
432 struct newtExitStruct es;
433 newtComponent label;
434 showform = newtForm(NULL, NULL, 0);
435 newtCenteredWindow(70,4, title);
436 label = newtLabel(3,1,tmp);
437 newtFormAddComponents(showform,label, NULL);
438 newtFormSetTimer(showform, 200);
439 newtFormRun(showform, &es);
440 return 0;
443 static int hide_doing(void)
445 newtPopWindow();
446 newtFormDestroy(showform);
447 return 0;
450 static void try_status(void)
452 struct message *m;
453 manager_action("Status", "");
454 m = wait_for_response(10000);
455 if (!m) {
456 show_message("Status Failed", "Timeout waiting for response");
457 } else if (strcasecmp(get_header(m, "Response"), "Success")) {
458 show_message("Status Failed Failed", get_header(m, "Message"));
463 static void try_hangup(newtComponent c)
465 struct ast_chan *chan;
466 struct message *m;
468 chan = newtListboxGetCurrent(c);
469 if (chan) {
470 manager_action("Hangup", "Channel: %s\r\n", chan->name);
471 m = wait_for_response(10000);
472 if (!m) {
473 show_message("Hangup Failed", "Timeout waiting for response");
474 } else if (strcasecmp(get_header(m, "Response"), "Success")) {
475 show_message("Hangup Failed", get_header(m, "Message"));
481 static int get_user_input(char *msg, char *buf, int buflen)
483 newtComponent form;
484 newtComponent ok;
485 newtComponent cancel;
486 newtComponent inpfield;
487 const char *input;
488 int res = -1;
489 struct newtExitStruct es;
491 newtCenteredWindow(60,7, msg);
493 inpfield = newtEntry(5, 2, "", 50, _NEWT_CAST &input, 0);
494 ok = newtButton(22, 3, "OK");
495 cancel = newtButton(32, 3, "Cancel");
496 form = newtForm(NULL, NULL, 0);
497 newtFormAddComponents(form, inpfield, ok, cancel, NULL);
498 newtFormRun(form, &es);
499 strncpy(buf, input, buflen - 1);
500 if (es.u.co == ok)
501 res = 0;
502 else
503 res = -1;
504 newtPopWindow();
505 newtFormDestroy(form);
506 return res;
509 static void try_redirect(newtComponent c)
511 struct ast_chan *chan;
512 char dest[256];
513 struct message *m;
514 char channame[256];
515 char tmp[80];
516 char *context;
518 chan = newtListboxGetCurrent(c);
519 if (chan) {
520 strncpy(channame, chan->name, sizeof(channame) - 1);
521 snprintf(tmp, sizeof(tmp), "Enter new extension for %s", channame);
522 if (get_user_input(tmp, dest, sizeof(dest)))
523 return;
524 if ((context = strchr(dest, '@'))) {
525 *context = '\0';
526 context++;
527 manager_action("Redirect", "Channel: %s\r\nContext: %s\r\nExten: %s\r\nPriority: 1\r\n", chan->name,context,dest);
528 } else {
529 manager_action("Redirect", "Channel: %s\r\nExten: %s\r\nPriority: 1\r\n", chan->name, dest);
531 m = wait_for_response(10000);
532 if (!m) {
533 show_message("Hangup Failed", "Timeout waiting for response");
534 } else if (strcasecmp(get_header(m, "Response"), "Success")) {
535 show_message("Hangup Failed", get_header(m, "Message"));
541 static int manage_calls(char *host)
543 newtComponent form;
544 newtComponent quit;
545 newtComponent hangup;
546 newtComponent redirect;
547 newtComponent channels;
548 struct newtExitStruct es;
549 char tmp[80];
551 /* Mark: If there's one thing you learn from this code, it is this...
552 Never, ever fly Air France. Their customer service is absolutely
553 the worst. I've never heard the words "That's not my problem" as
554 many times as I have from their staff -- It should, without doubt
555 be their corporate motto if it isn't already. Don't bother giving
556 them business because you're just a pain in their side and they
557 will be sure to let you know the first time you speak to them.
559 If you ever want to make me happy just tell me that you, too, will
560 never fly Air France again either (in spite of their excellent
561 cuisine).
563 Update by oej: The merger with KLM has transferred this
564 behaviour to KLM as well.
565 Don't bother giving them business either...
567 Only if you want to travel randomly without luggage, you
568 might pick either of them.
571 snprintf(tmp, sizeof(tmp), "Asterisk Manager at %s", host);
572 newtCenteredWindow(74, 20, tmp);
573 form = newtForm(NULL, NULL, 0);
574 newtFormWatchFd(form, session.fd, NEWT_FD_READ);
575 newtFormSetTimer(form, 100);
576 quit = newtButton(62, 16, "Quit");
577 redirect = newtButton(35, 16, "Redirect");
578 hangup = newtButton(50, 16, "Hangup");
579 channels = newtListbox(1,1,14, NEWT_FLAG_SCROLL);
580 newtFormAddComponents(form, channels, redirect, hangup, quit, NULL);
581 newtListboxSetWidth(channels, 72);
583 show_doing("Getting Status", "Retrieving system status...");
584 try_status();
585 hide_doing();
587 for(;;) {
588 newtFormRun(form, &es);
589 if (has_input(&session) || (es.reason == NEWT_EXIT_FDREADY)) {
590 if (input_check(&session, NULL)) {
591 show_message("Disconnected", "Disconnected from remote host");
592 break;
594 } else if (es.reason == NEWT_EXIT_COMPONENT) {
595 if (es.u.co == quit)
596 break;
597 if (es.u.co == hangup) {
598 try_hangup(channels);
599 } else if (es.u.co == redirect) {
600 try_redirect(channels);
603 rebuild_channels(channels);
605 newtFormDestroy(form);
606 return 0;
609 static int login(char *hostname)
611 newtComponent form;
612 newtComponent cancel;
613 newtComponent login;
614 newtComponent username;
615 newtComponent password;
616 newtComponent label;
617 newtComponent ulabel;
618 newtComponent plabel;
619 const char *user;
620 const char *pass;
621 struct message *m;
622 struct newtExitStruct es;
623 char tmp[55];
624 struct hostent *hp;
625 int res = -1;
627 session.fd = socket(AF_INET, SOCK_STREAM, 0);
628 if (session.fd < 0) {
629 snprintf(tmp, sizeof(tmp), "socket() failed: %s\n", strerror(errno));
630 show_message("Socket failed", tmp);
631 return -1;
634 snprintf(tmp, sizeof(tmp), "Looking up %s\n", hostname);
635 show_doing("Connecting....", tmp);
638 hp = gethostbyname(hostname);
639 if (!hp) {
640 snprintf(tmp, sizeof(tmp), "No such address: %s\n", hostname);
641 show_message("Host lookup failed", tmp);
642 return -1;
644 hide_doing();
645 snprintf(tmp, sizeof(tmp), "Connecting to %s", hostname);
646 show_doing("Connecting...", tmp);
648 session.sin.sin_family = AF_INET;
649 session.sin.sin_port = htons(DEFAULT_MANAGER_PORT);
650 memcpy(&session.sin.sin_addr, hp->h_addr, sizeof(session.sin.sin_addr));
652 if (connect(session.fd,(struct sockaddr*)&session.sin, sizeof(session.sin))) {
653 snprintf(tmp, sizeof(tmp), "%s failed: %s\n", hostname, strerror(errno));
654 show_message("Connect Failed", tmp);
655 return -1;
658 hide_doing();
660 login = newtButton(5, 6, "Login");
661 cancel = newtButton(25, 6, "Cancel");
662 newtCenteredWindow(40, 10, "Asterisk Manager Login");
663 snprintf(tmp, sizeof(tmp), "Host: %s", hostname);
664 label = newtLabel(4,1, tmp);
666 ulabel = newtLabel(4,2,"Username:");
667 plabel = newtLabel(4,3,"Password:");
669 username = newtEntry(14, 2, "", 20, _NEWT_CAST &user, 0);
670 password = newtEntry(14, 3, "", 20, _NEWT_CAST &pass, NEWT_FLAG_HIDDEN);
672 form = newtForm(NULL, NULL, 0);
673 newtFormAddComponents(form, username, password, login, cancel, label, ulabel, plabel,NULL);
674 newtFormRun(form, &es);
675 if (es.reason == NEWT_EXIT_COMPONENT) {
676 if (es.u.co == login) {
677 snprintf(tmp, sizeof(tmp), "Logging in '%s'...", user);
678 show_doing("Logging in", tmp);
679 /* Check to see if the remote host supports MD5 Authentication */
680 manager_action("Challenge", "AuthType: MD5\r\n");
681 m = wait_for_response(10000);
682 if (m && !strcasecmp(get_header(m, "Response"), "Success")) {
683 char *challenge = get_header(m, "Challenge");
684 int x;
685 int len = 0;
686 char md5key[256] = "";
687 struct MD5Context md5;
688 unsigned char digest[16];
689 MD5Init(&md5);
690 MD5Update(&md5, (unsigned char *)challenge, strlen(challenge));
691 MD5Update(&md5, (unsigned char *)pass, strlen(pass));
692 MD5Final(digest, &md5);
693 for (x=0; x<16; x++)
694 len += sprintf(md5key + len, "%2.2x", digest[x]);
695 manager_action("Login",
696 "AuthType: MD5\r\n"
697 "Username: %s\r\n"
698 "Key: %s\r\n",
699 user, md5key);
700 m = wait_for_response(10000);
701 hide_doing();
702 if (!strcasecmp(get_header(m, "Response"), "Success")) {
703 res = 0;
704 } else {
705 show_message("Login Failed", get_header(m, "Message"));
707 } else {
708 memset(m, 0, sizeof(m));
709 manager_action("Login",
710 "Username: %s\r\n"
711 "Secret: %s\r\n",
712 user, pass);
713 m = wait_for_response(10000);
714 hide_doing();
715 if (m) {
716 if (!strcasecmp(get_header(m, "Response"), "Success")) {
717 res = 0;
718 } else {
719 show_message("Login Failed", get_header(m, "Message"));
725 newtFormDestroy(form);
726 return res;
729 int main(int argc, char *argv[])
731 if (argc < 2) {
732 fprintf(stderr, "Usage: astman <host>\n");
733 exit(1);
735 newtInit();
736 newtCls();
737 newtDrawRootText(0, 0, "Asterisk Manager (C)2002, Linux Support Services, Inc.");
738 newtPushHelpLine("Welcome to the Asterisk Manager!");
739 if (login(argv[1])) {
740 newtFinished();
741 exit(1);
743 manage_calls(argv[1]);
744 newtFinished();
745 return 0;