Merged revisions 119742 via svnmerge from
[asterisk-bristuff.git] / main / manager.c
blob6af81c69629957ffd2c086bdc6efaa7766f00efc
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, 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.
19 /*! \file
21 * \brief The Asterisk Management Interface - AMI
23 * \author Mark Spencer <markster@digium.com>
25 * \extref OpenSSL http://www.openssl.org - for AMI/SSL
27 * At the moment this file contains a number of functions, namely:
29 * - data structures storing AMI state
30 * - AMI-related API functions, used by internal asterisk components
31 * - handlers for AMI-related CLI functions
32 * - handlers for AMI functions (available through the AMI socket)
33 * - the code for the main AMI listener thread and individual session threads
34 * - the http handlers invoked for AMI-over-HTTP by the threads in main/http.c
36 * \ref amiconf
39 /*! \addtogroup Group_AMI AMI functions
41 /*! @{
42 Doxygen group */
44 #include "asterisk.h"
46 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
48 #include "asterisk/_private.h"
49 #include "asterisk/paths.h" /* use various ast_config_AST_* */
50 #include <ctype.h>
51 #include <sys/time.h>
52 #include <signal.h>
53 #include <sys/mman.h>
55 #include "asterisk/channel.h"
56 #include "asterisk/file.h"
57 #include "asterisk/manager.h"
58 #include "asterisk/module.h"
59 #include "asterisk/config.h"
60 #include "asterisk/callerid.h"
61 #include "asterisk/lock.h"
62 #include "asterisk/cli.h"
63 #include "asterisk/app.h"
64 #include "asterisk/pbx.h"
65 #include "asterisk/md5.h"
66 #include "asterisk/acl.h"
67 #include "asterisk/utils.h"
68 #include "asterisk/tcptls.h"
69 #include "asterisk/http.h"
70 #include "asterisk/ast_version.h"
71 #include "asterisk/threadstorage.h"
72 #include "asterisk/linkedlists.h"
73 #include "asterisk/version.h"
74 #include "asterisk/term.h"
75 #include "asterisk/astobj2.h"
76 #include "asterisk/features.h"
78 enum error_type {
79 UNKNOWN_ACTION = 1,
80 UNKNOWN_CATEGORY,
81 UNSPECIFIED_CATEGORY,
82 UNSPECIFIED_ARGUMENT,
83 FAILURE_ALLOCATION,
84 FAILURE_DELCAT,
85 FAILURE_EMPTYCAT,
86 FAILURE_UPDATE,
87 FAILURE_DELETE,
88 FAILURE_APPEND
92 /*!
93 * Linked list of events.
94 * Global events are appended to the list by append_event().
95 * The usecount is the number of stored pointers to the element,
96 * excluding the list pointers. So an element that is only in
97 * the list has a usecount of 0, not 1.
99 * Clients have a pointer to the last event processed, and for each
100 * of these clients we track the usecount of the elements.
101 * If we have a pointer to an entry in the list, it is safe to navigate
102 * it forward because elements will not be deleted, but only appended.
103 * The worst that can happen is seeing the pointer still NULL.
105 * When the usecount of an element drops to 0, and the element is the
106 * first in the list, we can remove it. Removal is done within the
107 * main thread, which is woken up for the purpose.
109 * For simplicity of implementation, we make sure the list is never empty.
111 struct eventqent {
112 int usecount; /*!< # of clients who still need the event */
113 int category;
114 unsigned int seq; /*!< sequence number */
115 AST_LIST_ENTRY(eventqent) eq_next;
116 char eventdata[1]; /*!< really variable size, allocated by append_event() */
119 static AST_LIST_HEAD_STATIC(all_events, eventqent);
121 static int displayconnects = 1;
122 static int allowmultiplelogin = 1;
123 static int timestampevents;
124 static int httptimeout = 60;
125 static int manager_enabled = 0;
126 static int webmanager_enabled = 0;
128 static int block_sockets;
129 static int num_sessions;
131 static int manager_debug; /*!< enable some debugging code in the manager */
133 /*! \brief
134 * Descriptor for a manager session, either on the AMI socket or over HTTP.
136 * \note
137 * AMI session have managerid == 0; the entry is created upon a connect,
138 * and destroyed with the socket.
139 * HTTP sessions have managerid != 0, the value is used as a search key
140 * to lookup sessions (using the mansession_id cookie).
142 #define MAX_BLACKLIST_CMD_LEN 2
143 static struct {
144 char *words[AST_MAX_CMD_LEN];
145 } command_blacklist[] = {
146 {{ "module", "load", NULL }},
147 {{ "module", "unload", NULL }},
150 struct mansession {
151 pthread_t ms_t; /*!< Execution thread, basically useless */
152 ast_mutex_t __lock; /*!< Thread lock -- don't use in action callbacks, it's already taken care of */
153 /* XXX need to document which fields it is protecting */
154 struct sockaddr_in sin; /*!< address we are connecting from */
155 FILE *f; /*!< fdopen() on the underlying fd */
156 int fd; /*!< descriptor used for output. Either the socket (AMI) or a temporary file (HTTP) */
157 int inuse; /*!< number of HTTP sessions using this entry */
158 int needdestroy; /*!< Whether an HTTP session should be destroyed */
159 pthread_t waiting_thread; /*!< Sleeping thread using this descriptor */
160 uint32_t managerid; /*!< Unique manager identifier, 0 for AMI sessions */
161 time_t sessionstart; /*!< Session start time */
162 time_t sessiontimeout; /*!< Session timeout if HTTP */
163 char username[80]; /*!< Logged in username */
164 char challenge[10]; /*!< Authentication challenge */
165 int authenticated; /*!< Authentication status */
166 int readperm; /*!< Authorization for reading */
167 int writeperm; /*!< Authorization for writing */
168 char inbuf[1025]; /*!< Buffer */
169 /* we use the extra byte to add a '\0' and simplify parsing */
170 int inlen; /*!< number of buffered bytes */
171 int send_events; /*!< XXX what ? */
172 struct eventqent *last_ev; /*!< last event processed. */
173 int writetimeout; /*!< Timeout for ast_carefulwrite() */
174 int pending_event; /*!< Pending events indicator in case when waiting_thread is NULL */
175 AST_LIST_ENTRY(mansession) list;
178 #define NEW_EVENT(m) (AST_LIST_NEXT(m->last_ev, eq_next))
180 static AST_LIST_HEAD_STATIC(sessions, mansession);
182 /*! \brief user descriptor, as read from the config file.
184 * \note It is still missing some fields -- e.g. we can have multiple permit and deny
185 * lines which are not supported here, and readperm/writeperm/writetimeout
186 * are not stored.
188 struct ast_manager_user {
189 char username[80];
190 char *secret;
191 struct ast_ha *ha; /*!< ACL setting */
192 int readperm; /*! Authorization for reading */
193 int writeperm; /*! Authorization for writing */
194 int writetimeout; /*! Per user Timeout for ast_carefulwrite() */
195 int displayconnects; /*!< XXX unused */
196 int keep; /*!< mark entries created on a reload */
197 AST_RWLIST_ENTRY(ast_manager_user) list;
200 /*! \brief list of users found in the config file */
201 static AST_RWLIST_HEAD_STATIC(users, ast_manager_user);
203 /*! \brief list of actions registered */
204 static AST_RWLIST_HEAD_STATIC(actions, manager_action);
206 /*! \brief list of hooks registered */
207 static AST_RWLIST_HEAD_STATIC(manager_hooks, manager_custom_hook);
209 /*! \brief Add a custom hook to be called when an event is fired */
210 void ast_manager_register_hook(struct manager_custom_hook *hook)
212 AST_RWLIST_WRLOCK(&manager_hooks);
213 AST_RWLIST_INSERT_TAIL(&manager_hooks, hook, list);
214 AST_RWLIST_UNLOCK(&manager_hooks);
215 return;
218 /*! \brief Delete a custom hook to be called when an event is fired */
219 void ast_manager_unregister_hook(struct manager_custom_hook *hook)
221 AST_RWLIST_WRLOCK(&manager_hooks);
222 AST_RWLIST_REMOVE(&manager_hooks, hook, list);
223 AST_RWLIST_UNLOCK(&manager_hooks);
224 return;
227 /*! \brief
228 * Event list management functions.
229 * We assume that the event list always has at least one element,
230 * and the delete code will not remove the last entry even if the
233 #if 0
234 static time_t __deb(time_t start, const char *msg)
236 time_t now = time(NULL);
237 ast_verbose("%4d th %p %s\n", (int)(now % 3600), pthread_self(), msg);
238 if (start != 0 && now - start > 5)
239 ast_verbose("+++ WOW, %s took %d seconds\n", msg, (int)(now - start));
240 return now;
243 static void LOCK_EVENTS(void)
245 time_t start = __deb(0, "about to lock events");
246 AST_LIST_LOCK(&all_events);
247 __deb(start, "done lock events");
250 static void UNLOCK_EVENTS(void)
252 __deb(0, "about to unlock events");
253 AST_LIST_UNLOCK(&all_events);
256 static void LOCK_SESS(void)
258 time_t start = __deb(0, "about to lock sessions");
259 AST_LIST_LOCK(&sessions);
260 __deb(start, "done lock sessions");
263 static void UNLOCK_SESS(void)
265 __deb(0, "about to unlock sessions");
266 AST_LIST_UNLOCK(&sessions);
268 #endif
270 int check_manager_enabled()
272 return manager_enabled;
275 int check_webmanager_enabled()
277 return (webmanager_enabled && manager_enabled);
281 * Grab a reference to the last event, update usecount as needed.
282 * Can handle a NULL pointer.
284 static struct eventqent *grab_last(void)
286 struct eventqent *ret;
288 AST_LIST_LOCK(&all_events);
289 ret = AST_LIST_LAST(&all_events);
290 /* the list is never empty now, but may become so when
291 * we optimize it in the future, so be prepared.
293 if (ret)
294 ast_atomic_fetchadd_int(&ret->usecount, 1);
295 AST_LIST_UNLOCK(&all_events);
296 return ret;
300 * Purge unused events. Remove elements from the head
301 * as long as their usecount is 0 and there is a next element.
303 static void purge_events(void)
305 struct eventqent *ev;
307 AST_LIST_LOCK(&all_events);
308 while ( (ev = AST_LIST_FIRST(&all_events)) &&
309 ev->usecount == 0 && AST_LIST_NEXT(ev, eq_next)) {
310 AST_LIST_REMOVE_HEAD(&all_events, eq_next);
311 ast_free(ev);
313 AST_LIST_UNLOCK(&all_events);
317 * helper functions to convert back and forth between
318 * string and numeric representation of set of flags
320 static struct permalias {
321 int num;
322 char *label;
323 } perms[] = {
324 { EVENT_FLAG_SYSTEM, "system" },
325 { EVENT_FLAG_CALL, "call" },
326 { EVENT_FLAG_LOG, "log" },
327 { EVENT_FLAG_VERBOSE, "verbose" },
328 { EVENT_FLAG_COMMAND, "command" },
329 { EVENT_FLAG_AGENT, "agent" },
330 { EVENT_FLAG_USER, "user" },
331 { EVENT_FLAG_CONFIG, "config" },
332 { EVENT_FLAG_DTMF, "dtmf" },
333 { EVENT_FLAG_REPORTING, "reporting" },
334 { EVENT_FLAG_CDR, "cdr" },
335 { EVENT_FLAG_DIALPLAN, "dialplan" },
336 { EVENT_FLAG_ORIGINATE, "originate" },
337 { -1, "all" },
338 { 0, "none" },
341 /*! \brief Convert authority code to a list of options */
342 static char *authority_to_str(int authority, struct ast_str **res)
344 int i;
345 char *sep = "";
347 (*res)->used = 0;
348 for (i = 0; i < (sizeof(perms) / sizeof(perms[0])) - 1; i++) {
349 if (authority & perms[i].num) {
350 ast_str_append(res, 0, "%s%s", sep, perms[i].label);
351 sep = ",";
355 if ((*res)->used == 0) /* replace empty string with something sensible */
356 ast_str_append(res, 0, "<none>");
358 return (*res)->str;
361 /*! Tells you if smallstr exists inside bigstr
362 which is delim by delim and uses no buf or stringsep
363 ast_instring("this|that|more","this",'|') == 1;
365 feel free to move this to app.c -anthm */
366 static int ast_instring(const char *bigstr, const char *smallstr, const char delim)
368 const char *val = bigstr, *next;
370 do {
371 if ((next = strchr(val, delim))) {
372 if (!strncmp(val, smallstr, (next - val)))
373 return 1;
374 else
375 continue;
376 } else
377 return !strcmp(smallstr, val);
378 } while (*(val = (next + 1)));
380 return 0;
383 static int get_perm(const char *instr)
385 int x = 0, ret = 0;
387 if (!instr)
388 return 0;
390 for (x = 0; x < (sizeof(perms) / sizeof(perms[0])); x++) {
391 if (ast_instring(instr, perms[x].label, ','))
392 ret |= perms[x].num;
395 return ret;
399 * A number returns itself, false returns 0, true returns all flags,
400 * other strings return the flags that are set.
402 static int strings_to_mask(const char *string)
404 const char *p;
406 if (ast_strlen_zero(string))
407 return -1;
409 for (p = string; *p; p++)
410 if (*p < '0' || *p > '9')
411 break;
412 if (!p) /* all digits */
413 return atoi(string);
414 if (ast_false(string))
415 return 0;
416 if (ast_true(string)) { /* all permissions */
417 int x, ret = 0;
418 for (x = 0; x<sizeof(perms) / sizeof(perms[0]); x++)
419 ret |= perms[x].num;
420 return ret;
422 return get_perm(string);
425 static int check_manager_session_inuse(const char *name)
427 struct mansession *session = NULL;
429 AST_LIST_LOCK(&sessions);
430 AST_LIST_TRAVERSE(&sessions, session, list) {
431 if (!strcasecmp(session->username, name))
432 break;
434 AST_LIST_UNLOCK(&sessions);
436 return session ? 1 : 0;
441 * lookup an entry in the list of registered users.
442 * must be called with the list lock held.
444 static struct ast_manager_user *get_manager_by_name_locked(const char *name)
446 struct ast_manager_user *user = NULL;
448 AST_RWLIST_TRAVERSE(&users, user, list)
449 if (!strcasecmp(user->username, name))
450 break;
451 return user;
454 /*! \brief Get displayconnects config option.
455 * \param s manager session to get parameter from.
456 * \return displayconnects config option value.
458 static int manager_displayconnects (struct mansession *s)
460 struct ast_manager_user *user = NULL;
461 int ret = 0;
463 AST_RWLIST_RDLOCK(&users);
464 if ((user = get_manager_by_name_locked (s->username)))
465 ret = user->displayconnects;
466 AST_RWLIST_UNLOCK(&users);
468 return ret;
471 static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
473 struct manager_action *cur;
474 struct ast_str *authority;
475 int num, l, which;
476 char *ret = NULL;
477 switch (cmd) {
478 case CLI_INIT:
479 e->command = "manager show command";
480 e->usage =
481 "Usage: manager show command <actionname>\n"
482 " Shows the detailed description for a specific Asterisk manager interface command.\n";
483 return NULL;
484 case CLI_GENERATE:
485 l = strlen(a->word);
486 which = 0;
487 AST_RWLIST_RDLOCK(&actions);
488 AST_RWLIST_TRAVERSE(&actions, cur, list) {
489 if (!strncasecmp(a->word, cur->action, l) && ++which > a->n) {
490 ret = ast_strdup(cur->action);
491 break; /* make sure we exit even if ast_strdup() returns NULL */
494 AST_RWLIST_UNLOCK(&actions);
495 return ret;
497 authority = ast_str_alloca(80);
498 if (a->argc != 4)
499 return CLI_SHOWUSAGE;
501 AST_RWLIST_RDLOCK(&actions);
502 AST_RWLIST_TRAVERSE(&actions, cur, list) {
503 for (num = 3; num < a->argc; num++) {
504 if (!strcasecmp(cur->action, a->argv[num])) {
505 ast_cli(a->fd, "Action: %s\nSynopsis: %s\nPrivilege: %s\n%s\n",
506 cur->action, cur->synopsis,
507 authority_to_str(cur->authority, &authority),
508 S_OR(cur->description, ""));
512 AST_RWLIST_UNLOCK(&actions);
514 return CLI_SUCCESS;
517 static char *handle_mandebug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
519 switch (cmd) {
520 case CLI_INIT:
521 e->command = "manager debug [on|off]";
522 e->usage = "Usage: manager debug [on|off]\n Show, enable, disable debugging of the manager code.\n";
523 return NULL;
524 case CLI_GENERATE:
525 return NULL;
527 if (a->argc == 2)
528 ast_cli(a->fd, "manager debug is %s\n", manager_debug? "on" : "off");
529 else if (a->argc == 3) {
530 if (!strcasecmp(a->argv[2], "on"))
531 manager_debug = 1;
532 else if (!strcasecmp(a->argv[2], "off"))
533 manager_debug = 0;
534 else
535 return CLI_SHOWUSAGE;
537 return CLI_SUCCESS;
540 static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
542 struct ast_manager_user *user = NULL;
543 int l, which;
544 char *ret = NULL;
545 struct ast_str *rauthority = ast_str_alloca(80);
546 struct ast_str *wauthority = ast_str_alloca(80);
548 switch (cmd) {
549 case CLI_INIT:
550 e->command = "manager show user";
551 e->usage =
552 " Usage: manager show user <user>\n"
553 " Display all information related to the manager user specified.\n";
554 return NULL;
555 case CLI_GENERATE:
556 l = strlen(a->word);
557 which = 0;
558 if (a->pos != 3)
559 return NULL;
560 AST_RWLIST_RDLOCK(&users);
561 AST_RWLIST_TRAVERSE(&users, user, list) {
562 if ( !strncasecmp(a->word, user->username, l) && ++which > a->n ) {
563 ret = ast_strdup(user->username);
564 break;
567 AST_RWLIST_UNLOCK(&users);
568 return ret;
571 if (a->argc != 4)
572 return CLI_SHOWUSAGE;
574 AST_RWLIST_RDLOCK(&users);
576 if (!(user = get_manager_by_name_locked(a->argv[3]))) {
577 ast_cli(a->fd, "There is no manager called %s\n", a->argv[3]);
578 AST_RWLIST_UNLOCK(&users);
579 return CLI_SUCCESS;
582 ast_cli(a->fd, "\n");
583 ast_cli(a->fd,
584 " username: %s\n"
585 " secret: %s\n"
586 " acl: %s\n"
587 " read perm: %s\n"
588 " write perm: %s\n"
589 "displayconnects: %s\n",
590 (user->username ? user->username : "(N/A)"),
591 (user->secret ? "<Set>" : "(N/A)"),
592 (user->ha ? "yes" : "no"),
593 authority_to_str(user->readperm, &rauthority),
594 authority_to_str(user->writeperm, &wauthority),
595 (user->displayconnects ? "yes" : "no"));
597 AST_RWLIST_UNLOCK(&users);
599 return CLI_SUCCESS;
603 static char *handle_showmanagers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
605 struct ast_manager_user *user = NULL;
606 int count_amu = 0;
607 switch (cmd) {
608 case CLI_INIT:
609 e->command = "manager show users";
610 e->usage =
611 "Usage: manager show users\n"
612 " Prints a listing of all managers that are currently configured on that\n"
613 " system.\n";
614 return NULL;
615 case CLI_GENERATE:
616 return NULL;
618 if (a->argc != 3)
619 return CLI_SHOWUSAGE;
621 AST_RWLIST_RDLOCK(&users);
623 /* If there are no users, print out something along those lines */
624 if (AST_RWLIST_EMPTY(&users)) {
625 ast_cli(a->fd, "There are no manager users.\n");
626 AST_RWLIST_UNLOCK(&users);
627 return CLI_SUCCESS;
630 ast_cli(a->fd, "\nusername\n--------\n");
632 AST_RWLIST_TRAVERSE(&users, user, list) {
633 ast_cli(a->fd, "%s\n", user->username);
634 count_amu++;
637 AST_RWLIST_UNLOCK(&users);
639 ast_cli(a->fd, "-------------------\n");
640 ast_cli(a->fd, "%d manager users configured.\n", count_amu);
642 return CLI_SUCCESS;
646 /*! \brief CLI command manager list commands */
647 static char *handle_showmancmds(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
649 struct manager_action *cur;
650 struct ast_str *authority;
651 #define HSMC_FORMAT " %-15.15s %-15.15s %-55.55s\n"
652 switch (cmd) {
653 case CLI_INIT:
654 e->command = "manager show commands";
655 e->usage =
656 "Usage: manager show commands\n"
657 " Prints a listing of all the available Asterisk manager interface commands.\n";
658 return NULL;
659 case CLI_GENERATE:
660 return NULL;
662 authority = ast_str_alloca(80);
663 ast_cli(a->fd, HSMC_FORMAT, "Action", "Privilege", "Synopsis");
664 ast_cli(a->fd, HSMC_FORMAT, "------", "---------", "--------");
666 AST_RWLIST_RDLOCK(&actions);
667 AST_RWLIST_TRAVERSE(&actions, cur, list)
668 ast_cli(a->fd, HSMC_FORMAT, cur->action, authority_to_str(cur->authority, &authority), cur->synopsis);
669 AST_RWLIST_UNLOCK(&actions);
671 return CLI_SUCCESS;
674 /*! \brief CLI command manager list connected */
675 static char *handle_showmanconn(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
677 struct mansession *s;
678 time_t now = time(NULL);
679 #define HSMCONN_FORMAT1 " %-15.15s %-15.15s %-10.10s %-10.10s %-8.8s %-8.8s %-5.5s %-5.5s\n"
680 #define HSMCONN_FORMAT2 " %-15.15s %-15.15s %-10d %-10d %-8d %-8d %-5.5d %-5.5d\n"
681 int count = 0;
682 switch (cmd) {
683 case CLI_INIT:
684 e->command = "manager show connected";
685 e->usage =
686 "Usage: manager show connected\n"
687 " Prints a listing of the users that are currently connected to the\n"
688 "Asterisk manager interface.\n";
689 return NULL;
690 case CLI_GENERATE:
691 return NULL;
694 ast_cli(a->fd, HSMCONN_FORMAT1, "Username", "IP Address", "Start", "Elapsed", "FileDes", "HttpCnt", "Read", "Write");
696 AST_LIST_LOCK(&sessions);
697 AST_LIST_TRAVERSE(&sessions, s, list) {
698 ast_cli(a->fd, HSMCONN_FORMAT2, s->username, ast_inet_ntoa(s->sin.sin_addr), (int)(s->sessionstart), (int)(now - s->sessionstart), s->fd, s->inuse, s->readperm, s->writeperm);
699 count++;
701 AST_LIST_UNLOCK(&sessions);
703 ast_cli(a->fd, "%d users connected.\n", count);
705 return CLI_SUCCESS;
708 /*! \brief CLI command manager list eventq */
709 /* Should change to "manager show connected" */
710 static char *handle_showmaneventq(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
712 struct eventqent *s;
713 switch (cmd) {
714 case CLI_INIT:
715 e->command = "manager show eventq";
716 e->usage =
717 "Usage: manager show eventq\n"
718 " Prints a listing of all events pending in the Asterisk manger\n"
719 "event queue.\n";
720 return NULL;
721 case CLI_GENERATE:
722 return NULL;
724 AST_LIST_LOCK(&all_events);
725 AST_LIST_TRAVERSE(&all_events, s, eq_next) {
726 ast_cli(a->fd, "Usecount: %d\n", s->usecount);
727 ast_cli(a->fd, "Category: %d\n", s->category);
728 ast_cli(a->fd, "Event:\n%s", s->eventdata);
730 AST_LIST_UNLOCK(&all_events);
732 return CLI_SUCCESS;
735 /*! \brief CLI command manager reload */
736 static char *handle_manager_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
738 switch (cmd) {
739 case CLI_INIT:
740 e->command = "manager reload";
741 e->usage =
742 "Usage: manager reload\n"
743 " Reloads the manager configuration.\n";
744 return NULL;
745 case CLI_GENERATE:
746 return NULL;
748 if (a->argc > 2)
749 return CLI_SHOWUSAGE;
750 reload_manager();
751 return CLI_SUCCESS;
755 static struct ast_cli_entry cli_manager[] = {
756 AST_CLI_DEFINE(handle_showmancmd, "Show a manager interface command"),
757 AST_CLI_DEFINE(handle_showmancmds, "List manager interface commands"),
758 AST_CLI_DEFINE(handle_showmanconn, "List connected manager interface users"),
759 AST_CLI_DEFINE(handle_showmaneventq, "List manager interface queued events"),
760 AST_CLI_DEFINE(handle_showmanagers, "List configured manager users"),
761 AST_CLI_DEFINE(handle_showmanager, "Display information on a specific manager user"),
762 AST_CLI_DEFINE(handle_mandebug, "Show, enable, disable debugging of the manager code"),
763 AST_CLI_DEFINE(handle_manager_reload, "Reload manager configurations"),
767 * Decrement the usecount for the event; if it goes to zero,
768 * (why check for e->next ?) wakeup the
769 * main thread, which is in charge of freeing the record.
770 * Returns the next record.
772 static struct eventqent *unref_event(struct eventqent *e)
774 ast_atomic_fetchadd_int(&e->usecount, -1);
775 return AST_LIST_NEXT(e, eq_next);
778 static void ref_event(struct eventqent *e)
780 ast_atomic_fetchadd_int(&e->usecount, 1);
784 * destroy a session, leaving the usecount
786 static void free_session(struct mansession *s)
788 struct eventqent *eqe = s->last_ev;
789 if (s->f != NULL)
790 fclose(s->f);
791 ast_mutex_destroy(&s->__lock);
792 ast_free(s);
793 unref_event(eqe);
796 static void destroy_session(struct mansession *s)
798 AST_LIST_LOCK(&sessions);
799 AST_LIST_REMOVE(&sessions, s, list);
800 ast_atomic_fetchadd_int(&num_sessions, -1);
801 free_session(s);
802 AST_LIST_UNLOCK(&sessions);
806 * Generic function to return either the first or the last matching header
807 * from a list of variables, possibly skipping empty strings.
808 * At the moment there is only one use of this function in this file,
809 * so we make it static.
811 #define GET_HEADER_FIRST_MATCH 0
812 #define GET_HEADER_LAST_MATCH 1
813 #define GET_HEADER_SKIP_EMPTY 2
814 static const char *__astman_get_header(const struct message *m, char *var, int mode)
816 int x, l = strlen(var);
817 const char *result = "";
819 for (x = 0; x < m->hdrcount; x++) {
820 const char *h = m->headers[x];
821 if (!strncasecmp(var, h, l) && h[l] == ':' && h[l+1] == ' ') {
822 const char *x = h + l + 2;
823 /* found a potential candidate */
824 if (mode & GET_HEADER_SKIP_EMPTY && ast_strlen_zero(x))
825 continue; /* not interesting */
826 if (mode & GET_HEADER_LAST_MATCH)
827 result = x; /* record the last match so far */
828 else
829 return x;
833 return "";
837 * Return the first matching variable from an array.
838 * This is the legacy function and is implemented in therms of
839 * __astman_get_header().
841 const char *astman_get_header(const struct message *m, char *var)
843 return __astman_get_header(m, var, GET_HEADER_FIRST_MATCH);
847 struct ast_variable *astman_get_variables(const struct message *m)
849 int varlen, x, y;
850 struct ast_variable *head = NULL, *cur;
852 AST_DECLARE_APP_ARGS(args,
853 AST_APP_ARG(vars)[32];
856 varlen = strlen("Variable: ");
858 for (x = 0; x < m->hdrcount; x++) {
859 char *parse, *var, *val;
861 if (strncasecmp("Variable: ", m->headers[x], varlen))
862 continue;
863 parse = ast_strdupa(m->headers[x] + varlen);
865 AST_STANDARD_APP_ARGS(args, parse);
866 if (!args.argc)
867 continue;
868 for (y = 0; y < args.argc; y++) {
869 if (!args.vars[y])
870 continue;
871 var = val = ast_strdupa(args.vars[y]);
872 strsep(&val, "=");
873 if (!val || ast_strlen_zero(var))
874 continue;
875 cur = ast_variable_new(var, val, "");
876 cur->next = head;
877 head = cur;
881 return head;
885 * helper function to send a string to the socket.
886 * Return -1 on error (e.g. buffer full).
888 static int send_string(struct mansession *s, char *string)
890 int len = strlen(string); /* residual length */
891 char *src = string;
892 struct timeval start = ast_tvnow();
893 int n = 0;
895 for (;;) {
896 int elapsed;
897 struct pollfd fd;
898 n = fwrite(src, 1, len, s->f); /* try to write the string, non blocking */
899 if (n == len /* ok */ || n < 0 /* error */)
900 break;
901 len -= n; /* skip already written data */
902 src += n;
903 fd.fd = s->fd;
904 fd.events = POLLOUT;
905 n = -1; /* error marker */
906 elapsed = ast_tvdiff_ms(ast_tvnow(), start);
907 if (elapsed > s->writetimeout)
908 break;
909 if (poll(&fd, 1, s->writetimeout - elapsed) < 1)
910 break;
912 fflush(s->f);
913 return n < 0 ? -1 : 0;
917 * \brief thread local buffer for astman_append
919 * \note This can not be defined within the astman_append() function
920 * because it declares a couple of functions that get used to
921 * initialize the thread local storage key.
923 AST_THREADSTORAGE(astman_append_buf);
924 /*! \brief initial allocated size for the astman_append_buf */
925 #define ASTMAN_APPEND_BUF_INITSIZE 256
928 * utility functions for creating AMI replies
930 void astman_append(struct mansession *s, const char *fmt, ...)
932 va_list ap;
933 struct ast_str *buf;
935 if (!(buf = ast_str_thread_get(&astman_append_buf, ASTMAN_APPEND_BUF_INITSIZE)))
936 return;
938 va_start(ap, fmt);
939 ast_str_set_va(&buf, 0, fmt, ap);
940 va_end(ap);
942 if (s->f != NULL)
943 send_string(s, buf->str);
944 else
945 ast_verbose("fd == -1 in astman_append, should not happen\n");
948 /*! \note NOTE: XXX this comment is unclear and possibly wrong.
949 Callers of astman_send_error(), astman_send_response() or astman_send_ack() must EITHER
950 hold the session lock _or_ be running in an action callback (in which case s->busy will
951 be non-zero). In either of these cases, there is no need to lock-protect the session's
952 fd, since no other output will be sent (events will be queued), and no input will
953 be read until either the current action finishes or get_input() obtains the session
954 lock.
957 /*! \brief send a response with an optional message,
958 * and terminate it with an empty line.
959 * m is used only to grab the 'ActionID' field.
961 * Use the explicit constant MSG_MOREDATA to remove the empty line.
962 * XXX MSG_MOREDATA should go to a header file.
964 #define MSG_MOREDATA ((char *)astman_send_response)
965 static void astman_send_response_full(struct mansession *s, const struct message *m, char *resp, char *msg, char *listflag)
967 const char *id = astman_get_header(m, "ActionID");
969 astman_append(s, "Response: %s\r\n", resp);
970 if (!ast_strlen_zero(id))
971 astman_append(s, "ActionID: %s\r\n", id);
972 if (listflag)
973 astman_append(s, "Eventlist: %s\r\n", listflag); /* Start, complete, cancelled */
974 if (msg == MSG_MOREDATA)
975 return;
976 else if (msg)
977 astman_append(s, "Message: %s\r\n\r\n", msg);
978 else
979 astman_append(s, "\r\n");
982 void astman_send_response(struct mansession *s, const struct message *m, char *resp, char *msg)
984 astman_send_response_full(s, m, resp, msg, NULL);
987 void astman_send_error(struct mansession *s, const struct message *m, char *error)
989 astman_send_response_full(s, m, "Error", error, NULL);
992 void astman_send_ack(struct mansession *s, const struct message *m, char *msg)
994 astman_send_response_full(s, m, "Success", msg, NULL);
997 static void astman_start_ack(struct mansession *s, const struct message *m)
999 astman_send_response_full(s, m, "Success", MSG_MOREDATA, NULL);
1002 void astman_send_listack(struct mansession *s, const struct message *m, char *msg, char *listflag)
1004 astman_send_response_full(s, m, "Success", msg, listflag);
1008 /*! \brief
1009 Rather than braindead on,off this now can also accept a specific int mask value
1010 or a ',' delim list of mask strings (the same as manager.conf) -anthm
1012 static int set_eventmask(struct mansession *s, const char *eventmask)
1014 int maskint = strings_to_mask(eventmask);
1016 ast_mutex_lock(&s->__lock);
1017 if (maskint >= 0)
1018 s->send_events = maskint;
1019 ast_mutex_unlock(&s->__lock);
1021 return maskint;
1025 * Here we start with action_ handlers for AMI actions,
1026 * and the internal functions used by them.
1027 * Generally, the handlers are called action_foo()
1030 /* helper function for action_login() */
1031 static int authenticate(struct mansession *s, const struct message *m)
1033 const char *username = astman_get_header(m, "Username");
1034 const char *password = astman_get_header(m, "Secret");
1035 int error = -1;
1036 struct ast_manager_user *user = NULL;
1038 if (ast_strlen_zero(username)) /* missing username */
1039 return -1;
1041 /* locate user in locked state */
1042 AST_RWLIST_WRLOCK(&users);
1044 if (!(user = get_manager_by_name_locked(username))) {
1045 ast_log(LOG_NOTICE, "%s tried to authenticate with nonexistent user '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
1046 } else if (user->ha && !ast_apply_ha(user->ha, &(s->sin))) {
1047 ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
1048 } else if (!strcasecmp(astman_get_header(m, "AuthType"), "MD5")) {
1049 const char *key = astman_get_header(m, "Key");
1050 if (!ast_strlen_zero(key) && !ast_strlen_zero(s->challenge) && user->secret) {
1051 int x;
1052 int len = 0;
1053 char md5key[256] = "";
1054 struct MD5Context md5;
1055 unsigned char digest[16];
1057 MD5Init(&md5);
1058 MD5Update(&md5, (unsigned char *) s->challenge, strlen(s->challenge));
1059 MD5Update(&md5, (unsigned char *) user->secret, strlen(user->secret));
1060 MD5Final(digest, &md5);
1061 for (x = 0; x < 16; x++)
1062 len += sprintf(md5key + len, "%2.2x", digest[x]);
1063 if (!strcmp(md5key, key))
1064 error = 0;
1065 } else {
1066 ast_debug(1, "MD5 authentication is not possible. challenge: '%s'\n",
1067 S_OR(s->challenge, ""));
1069 } else if (password && user->secret && !strcmp(password, user->secret))
1070 error = 0;
1072 if (error) {
1073 ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
1074 AST_RWLIST_UNLOCK(&users);
1075 return -1;
1078 /* auth complete */
1080 ast_copy_string(s->username, username, sizeof(s->username));
1081 s->readperm = user->readperm;
1082 s->writeperm = user->writeperm;
1083 s->writetimeout = user->writetimeout;
1084 s->sessionstart = time(NULL);
1085 set_eventmask(s, astman_get_header(m, "Events"));
1087 AST_RWLIST_UNLOCK(&users);
1088 return 0;
1091 /*! \brief Manager PING */
1092 static char mandescr_ping[] =
1093 "Description: A 'Ping' action will ellicit a 'Pong' response. Used to keep the\n"
1094 " manager connection open.\n"
1095 "Variables: NONE\n";
1097 static int action_ping(struct mansession *s, const struct message *m)
1099 astman_append(s, "Response: Success\r\n"
1100 "Ping: Pong\r\n");
1101 return 0;
1104 static char mandescr_getconfig[] =
1105 "Description: A 'GetConfig' action will dump the contents of a configuration\n"
1106 "file by category and contents or optionally by specified category only.\n"
1107 "Variables: (Names marked with * are required)\n"
1108 " *Filename: Configuration filename (e.g. foo.conf)\n"
1109 " Category: Category in configuration file\n";
1111 static int action_getconfig(struct mansession *s, const struct message *m)
1113 struct ast_config *cfg;
1114 const char *fn = astman_get_header(m, "Filename");
1115 const char *category = astman_get_header(m, "Category");
1116 int catcount = 0;
1117 int lineno = 0;
1118 char *cur_category = NULL;
1119 struct ast_variable *v;
1120 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1122 if (ast_strlen_zero(fn)) {
1123 astman_send_error(s, m, "Filename not specified");
1124 return 0;
1126 if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
1127 astman_send_error(s, m, "Config file not found");
1128 return 0;
1131 astman_start_ack(s, m);
1132 while ((cur_category = ast_category_browse(cfg, cur_category))) {
1133 if (ast_strlen_zero(category) || (!ast_strlen_zero(category) && !strcmp(category, cur_category))) {
1134 lineno = 0;
1135 astman_append(s, "Category-%06d: %s\r\n", catcount, cur_category);
1136 for (v = ast_variable_browse(cfg, cur_category); v; v = v->next)
1137 astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
1138 catcount++;
1141 if (!ast_strlen_zero(category) && catcount == 0) /* TODO: actually, a config with no categories doesn't even get loaded */
1142 astman_append(s, "No categories found");
1143 ast_config_destroy(cfg);
1144 astman_append(s, "\r\n");
1146 return 0;
1149 static char mandescr_listcategories[] =
1150 "Description: A 'ListCategories' action will dump the categories in\n"
1151 "a given file.\n"
1152 "Variables:\n"
1153 " Filename: Configuration filename (e.g. foo.conf)\n";
1155 static int action_listcategories(struct mansession *s, const struct message *m)
1157 struct ast_config *cfg;
1158 const char *fn = astman_get_header(m, "Filename");
1159 char *category = NULL;
1160 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1161 int catcount = 0;
1163 if (ast_strlen_zero(fn)) {
1164 astman_send_error(s, m, "Filename not specified");
1165 return 0;
1167 if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
1168 astman_send_error(s, m, "Config file not found or file has invalid syntax");
1169 return 0;
1171 astman_start_ack(s, m);
1172 while ((category = ast_category_browse(cfg, category))) {
1173 astman_append(s, "Category-%06d: %s\r\n", catcount, category);
1174 catcount++;
1176 if (catcount == 0) /* TODO: actually, a config with no categories doesn't even get loaded */
1177 astman_append(s, "Error: no categories found");
1178 ast_config_destroy(cfg);
1179 astman_append(s, "\r\n");
1181 return 0;
1187 /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */
1188 static void json_escape(char *out, const char *in)
1190 for (; *in; in++) {
1191 if (*in == '\\' || *in == '\"')
1192 *out++ = '\\';
1193 *out++ = *in;
1195 *out = '\0';
1198 static char mandescr_getconfigjson[] =
1199 "Description: A 'GetConfigJSON' action will dump the contents of a configuration\n"
1200 "file by category and contents in JSON format. This only makes sense to be used\n"
1201 "using rawman over the HTTP interface.\n"
1202 "Variables:\n"
1203 " Filename: Configuration filename (e.g. foo.conf)\n";
1205 static int action_getconfigjson(struct mansession *s, const struct message *m)
1207 struct ast_config *cfg;
1208 const char *fn = astman_get_header(m, "Filename");
1209 char *category = NULL;
1210 struct ast_variable *v;
1211 int comma1 = 0;
1212 char *buf = NULL;
1213 unsigned int buf_len = 0;
1214 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1216 if (ast_strlen_zero(fn)) {
1217 astman_send_error(s, m, "Filename not specified");
1218 return 0;
1221 if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
1222 astman_send_error(s, m, "Config file not found");
1223 return 0;
1226 buf_len = 512;
1227 buf = alloca(buf_len);
1229 astman_start_ack(s, m);
1230 astman_append(s, "JSON: {");
1231 while ((category = ast_category_browse(cfg, category))) {
1232 int comma2 = 0;
1233 if (buf_len < 2 * strlen(category) + 1) {
1234 buf_len *= 2;
1235 buf = alloca(buf_len);
1237 json_escape(buf, category);
1238 astman_append(s, "%s\"%s\":[", comma1 ? "," : "", buf);
1239 if (!comma1)
1240 comma1 = 1;
1241 for (v = ast_variable_browse(cfg, category); v; v = v->next) {
1242 if (comma2)
1243 astman_append(s, ",");
1244 if (buf_len < 2 * strlen(v->name) + 1) {
1245 buf_len *= 2;
1246 buf = alloca(buf_len);
1248 json_escape(buf, v->name);
1249 astman_append(s, "\"%s", buf);
1250 if (buf_len < 2 * strlen(v->value) + 1) {
1251 buf_len *= 2;
1252 buf = alloca(buf_len);
1254 json_escape(buf, v->value);
1255 astman_append(s, "%s\"", buf);
1256 if (!comma2)
1257 comma2 = 1;
1259 astman_append(s, "]");
1261 astman_append(s, "}\r\n\r\n");
1263 ast_config_destroy(cfg);
1265 return 0;
1268 /* helper function for action_updateconfig */
1269 static enum error_type handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg, const char *dfn)
1271 int x;
1272 char hdr[40];
1273 const char *action, *cat, *var, *value, *match, *line;
1274 struct ast_category *category;
1275 struct ast_variable *v;
1277 for (x = 0; x < 100000; x++) {
1278 unsigned int object = 0;
1280 snprintf(hdr, sizeof(hdr), "Action-%06d", x);
1281 action = astman_get_header(m, hdr);
1282 if (ast_strlen_zero(action))
1283 break;
1284 snprintf(hdr, sizeof(hdr), "Cat-%06d", x);
1285 cat = astman_get_header(m, hdr);
1286 snprintf(hdr, sizeof(hdr), "Var-%06d", x);
1287 var = astman_get_header(m, hdr);
1288 snprintf(hdr, sizeof(hdr), "Value-%06d", x);
1289 value = astman_get_header(m, hdr);
1290 if (!ast_strlen_zero(value) && *value == '>') {
1291 object = 1;
1292 value++;
1294 snprintf(hdr, sizeof(hdr), "Match-%06d", x);
1295 match = astman_get_header(m, hdr);
1296 snprintf(hdr, sizeof(hdr), "Line-%06d", x);
1297 line = astman_get_header(m, hdr);
1298 if (!strcasecmp(action, "newcat")) {
1299 if (ast_strlen_zero(cat))
1300 return UNSPECIFIED_CATEGORY;
1301 if (!(category = ast_category_new(cat, dfn, -1)))
1302 return FAILURE_ALLOCATION;
1303 if (ast_strlen_zero(match)) {
1304 ast_category_append(cfg, category);
1305 } else
1306 ast_category_insert(cfg, category, match);
1307 } else if (!strcasecmp(action, "renamecat")) {
1308 if (ast_strlen_zero(cat) || ast_strlen_zero(value))
1309 return UNSPECIFIED_ARGUMENT;
1310 if (!(category = ast_category_get(cfg, cat)))
1311 return UNKNOWN_CATEGORY;
1312 ast_category_rename(category, value);
1313 } else if (!strcasecmp(action, "delcat")) {
1314 if (ast_strlen_zero(cat))
1315 return UNSPECIFIED_CATEGORY;
1316 if (ast_category_delete(cfg, cat))
1317 return FAILURE_DELCAT;
1318 } else if (!strcasecmp(action, "emptycat")) {
1319 if (ast_strlen_zero(cat))
1320 return UNSPECIFIED_CATEGORY;
1321 if (ast_category_empty(cfg, cat))
1322 return FAILURE_EMPTYCAT;
1323 } else if (!strcasecmp(action, "update")) {
1324 if (ast_strlen_zero(cat) || ast_strlen_zero(var))
1325 return UNSPECIFIED_ARGUMENT;
1326 if (!(category = ast_category_get(cfg,cat)))
1327 return UNKNOWN_CATEGORY;
1328 if (ast_variable_update(category, var, value, match, object))
1329 return FAILURE_UPDATE;
1330 } else if (!strcasecmp(action, "delete")) {
1331 if (ast_strlen_zero(cat) || (ast_strlen_zero(var) && ast_strlen_zero(line)))
1332 return UNSPECIFIED_ARGUMENT;
1333 if (!(category = ast_category_get(cfg, cat)))
1334 return UNKNOWN_CATEGORY;
1335 if (ast_variable_delete(category, var, match, line))
1336 return FAILURE_DELETE;
1337 } else if (!strcasecmp(action, "append")) {
1338 if (ast_strlen_zero(cat) || ast_strlen_zero(var))
1339 return UNSPECIFIED_ARGUMENT;
1340 if (!(category = ast_category_get(cfg, cat)))
1341 return UNKNOWN_CATEGORY;
1342 if (!(v = ast_variable_new(var, value, dfn)))
1343 return FAILURE_ALLOCATION;
1344 if (object || (match && !strcasecmp(match, "object")))
1345 v->object = 1;
1346 ast_variable_append(category, v);
1347 } else if (!strcasecmp(action, "insert")) {
1348 if (ast_strlen_zero(cat) || ast_strlen_zero(var) || ast_strlen_zero(line))
1349 return UNSPECIFIED_ARGUMENT;
1350 if (!(category = ast_category_get(cfg, cat)))
1351 return UNKNOWN_CATEGORY;
1352 if (!(v = ast_variable_new(var, value, dfn)))
1353 return FAILURE_ALLOCATION;
1354 ast_variable_insert(category, v, line);
1356 else {
1357 ast_log(LOG_WARNING, "Action-%06d: %s not handled\n", x, action);
1358 return UNKNOWN_ACTION;
1361 return 0;
1364 static char mandescr_updateconfig[] =
1365 "Description: A 'UpdateConfig' action will modify, create, or delete\n"
1366 "configuration elements in Asterisk configuration files.\n"
1367 "Variables (X's represent 6 digit number beginning with 000000):\n"
1368 " SrcFilename: Configuration filename to read(e.g. foo.conf)\n"
1369 " DstFilename: Configuration filename to write(e.g. foo.conf)\n"
1370 " Reload: Whether or not a reload should take place (or name of specific module)\n"
1371 " Action-XXXXXX: Action to Take (NewCat,RenameCat,DelCat,EmptyCat,Update,Delete,Append,Insert)\n"
1372 " Cat-XXXXXX: Category to operate on\n"
1373 " Var-XXXXXX: Variable to work on\n"
1374 " Value-XXXXXX: Value to work on\n"
1375 " Match-XXXXXX: Extra match required to match line\n"
1376 " Line-XXXXXX: Line in category to operate on (used with delete and insert actions)\n";
1378 static int action_updateconfig(struct mansession *s, const struct message *m)
1380 struct ast_config *cfg;
1381 const char *sfn = astman_get_header(m, "SrcFilename");
1382 const char *dfn = astman_get_header(m, "DstFilename");
1383 int res;
1384 const char *rld = astman_get_header(m, "Reload");
1385 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1386 enum error_type result;
1388 if (ast_strlen_zero(sfn) || ast_strlen_zero(dfn)) {
1389 astman_send_error(s, m, "Filename not specified");
1390 return 0;
1392 if (!(cfg = ast_config_load2(sfn, "manager", config_flags))) {
1393 astman_send_error(s, m, "Config file not found");
1394 return 0;
1396 result = handle_updates(s, m, cfg, dfn);
1397 if (!result) {
1398 ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
1399 res = config_text_file_save(dfn, cfg, "Manager");
1400 ast_config_destroy(cfg);
1401 if (res) {
1402 astman_send_error(s, m, "Save of config failed");
1403 return 0;
1405 astman_send_ack(s, m, NULL);
1406 if (!ast_strlen_zero(rld)) {
1407 if (ast_true(rld))
1408 rld = NULL;
1409 ast_module_reload(rld);
1411 } else {
1412 ast_config_destroy(cfg);
1413 switch(result) {
1414 case UNKNOWN_ACTION:
1415 astman_send_error(s, m, "Unknown action command");
1416 break;
1417 case UNKNOWN_CATEGORY:
1418 astman_send_error(s, m, "Given category does not exist");
1419 break;
1420 case UNSPECIFIED_CATEGORY:
1421 astman_send_error(s, m, "Category not specified");
1422 break;
1423 case UNSPECIFIED_ARGUMENT:
1424 astman_send_error(s, m, "Problem with category, value, or line (if required)");
1425 break;
1426 case FAILURE_ALLOCATION:
1427 astman_send_error(s, m, "Memory allocation failure, this should not happen");
1428 break;
1429 case FAILURE_DELCAT:
1430 astman_send_error(s, m, "Delete category did not complete successfully");
1431 break;
1432 case FAILURE_EMPTYCAT:
1433 astman_send_error(s, m, "Empty category did not complete successfully");
1434 break;
1435 case FAILURE_UPDATE:
1436 astman_send_error(s, m, "Update did not complete successfully");
1437 break;
1438 case FAILURE_DELETE:
1439 astman_send_error(s, m, "Delete did not complete successfully");
1440 break;
1441 case FAILURE_APPEND:
1442 astman_send_error(s, m, "Append did not complete successfully");
1443 break;
1446 return 0;
1449 static char mandescr_createconfig[] =
1450 "Description: A 'CreateConfig' action will create an empty file in the\n"
1451 "configuration directory. This action is intended to be used before an\n"
1452 "UpdateConfig action.\n"
1453 "Variables\n"
1454 " Filename: The configuration filename to create (e.g. foo.conf)\n";
1456 static int action_createconfig(struct mansession *s, const struct message *m)
1458 int fd;
1459 const char *fn = astman_get_header(m, "Filename");
1460 struct ast_str *filepath = ast_str_alloca(PATH_MAX);
1461 ast_str_set(&filepath, 0, "%s/", ast_config_AST_CONFIG_DIR);
1462 ast_str_append(&filepath, 0, "%s", fn);
1464 if ((fd = open(filepath->str, O_CREAT | O_EXCL, AST_FILE_MODE)) != -1) {
1465 close(fd);
1466 astman_send_ack(s, m, "New configuration file created successfully");
1467 } else
1468 astman_send_error(s, m, strerror(errno));
1470 return 0;
1473 /*! \brief Manager WAITEVENT */
1474 static char mandescr_waitevent[] =
1475 "Description: A 'WaitEvent' action will ellicit a 'Success' response. Whenever\n"
1476 "a manager event is queued. Once WaitEvent has been called on an HTTP manager\n"
1477 "session, events will be generated and queued.\n"
1478 "Variables: \n"
1479 " Timeout: Maximum time (in seconds) to wait for events, -1 means forever.\n";
1481 static int action_waitevent(struct mansession *s, const struct message *m)
1483 const char *timeouts = astman_get_header(m, "Timeout");
1484 int timeout = -1;
1485 int x;
1486 int needexit = 0;
1487 const char *id = astman_get_header(m, "ActionID");
1488 char idText[256];
1490 if (!ast_strlen_zero(id))
1491 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
1492 else
1493 idText[0] = '\0';
1495 if (!ast_strlen_zero(timeouts)) {
1496 sscanf(timeouts, "%i", &timeout);
1497 if (timeout < -1)
1498 timeout = -1;
1499 /* XXX maybe put an upper bound, or prevent the use of 0 ? */
1502 ast_mutex_lock(&s->__lock);
1503 if (s->waiting_thread != AST_PTHREADT_NULL)
1504 pthread_kill(s->waiting_thread, SIGURG);
1506 if (s->managerid) { /* AMI-over-HTTP session */
1508 * Make sure the timeout is within the expire time of the session,
1509 * as the client will likely abort the request if it does not see
1510 * data coming after some amount of time.
1512 time_t now = time(NULL);
1513 int max = s->sessiontimeout - now - 10;
1515 if (max < 0) /* We are already late. Strange but possible. */
1516 max = 0;
1517 if (timeout < 0 || timeout > max)
1518 timeout = max;
1519 if (!s->send_events) /* make sure we record events */
1520 s->send_events = -1;
1522 ast_mutex_unlock(&s->__lock);
1524 /* XXX should this go inside the lock ? */
1525 s->waiting_thread = pthread_self(); /* let new events wake up this thread */
1526 ast_debug(1, "Starting waiting for an event!\n");
1528 for (x = 0; x < timeout || timeout < 0; x++) {
1529 ast_mutex_lock(&s->__lock);
1530 if (NEW_EVENT(s))
1531 needexit = 1;
1532 /* We can have multiple HTTP session point to the same mansession entry.
1533 * The way we deal with it is not very nice: newcomers kick out the previous
1534 * HTTP session. XXX this needs to be improved.
1536 if (s->waiting_thread != pthread_self())
1537 needexit = 1;
1538 if (s->needdestroy)
1539 needexit = 1;
1540 ast_mutex_unlock(&s->__lock);
1541 if (needexit)
1542 break;
1543 if (s->managerid == 0) { /* AMI session */
1544 if (ast_wait_for_input(s->fd, 1000))
1545 break;
1546 } else { /* HTTP session */
1547 sleep(1);
1550 ast_debug(1, "Finished waiting for an event!\n");
1551 ast_mutex_lock(&s->__lock);
1552 if (s->waiting_thread == pthread_self()) {
1553 struct eventqent *eqe;
1554 astman_send_response(s, m, "Success", "Waiting for Event completed.");
1555 while ( (eqe = NEW_EVENT(s)) ) {
1556 ref_event(eqe);
1557 if (((s->readperm & eqe->category) == eqe->category) &&
1558 ((s->send_events & eqe->category) == eqe->category)) {
1559 astman_append(s, "%s", eqe->eventdata);
1561 s->last_ev = unref_event(s->last_ev);
1563 astman_append(s,
1564 "Event: WaitEventComplete\r\n"
1565 "%s"
1566 "\r\n", idText);
1567 s->waiting_thread = AST_PTHREADT_NULL;
1568 } else {
1569 ast_debug(1, "Abandoning event request!\n");
1571 ast_mutex_unlock(&s->__lock);
1572 return 0;
1575 static char mandescr_listcommands[] =
1576 "Description: Returns the action name and synopsis for every\n"
1577 " action that is available to the user\n"
1578 "Variables: NONE\n";
1580 /*! \note The actionlock is read-locked by the caller of this function */
1581 static int action_listcommands(struct mansession *s, const struct message *m)
1583 struct manager_action *cur;
1584 struct ast_str *temp = ast_str_alloca(BUFSIZ); /* XXX very large ? */
1586 astman_start_ack(s, m);
1587 AST_RWLIST_TRAVERSE(&actions, cur, list) {
1588 if (s->writeperm & cur->authority || cur->authority == 0)
1589 astman_append(s, "%s: %s (Priv: %s)\r\n",
1590 cur->action, cur->synopsis, authority_to_str(cur->authority, &temp));
1592 astman_append(s, "\r\n");
1594 return 0;
1597 static char mandescr_events[] =
1598 "Description: Enable/Disable sending of events to this manager\n"
1599 " client.\n"
1600 "Variables:\n"
1601 " EventMask: 'on' if all events should be sent,\n"
1602 " 'off' if no events should be sent,\n"
1603 " 'system,call,log' to select which flags events should have to be sent.\n";
1605 static int action_events(struct mansession *s, const struct message *m)
1607 const char *mask = astman_get_header(m, "EventMask");
1608 int res;
1610 res = set_eventmask(s, mask);
1611 if (res > 0)
1612 astman_append(s, "Response: Success\r\n"
1613 "Events: On\r\n");
1614 else if (res == 0)
1615 astman_append(s, "Response: Success\r\n"
1616 "Events: Off\r\n");
1617 return 0;
1620 static char mandescr_logoff[] =
1621 "Description: Logoff this manager session\n"
1622 "Variables: NONE\n";
1624 static int action_logoff(struct mansession *s, const struct message *m)
1626 astman_send_response(s, m, "Goodbye", "Thanks for all the fish.");
1627 return -1;
1630 static int action_login(struct mansession *s, const struct message *m)
1632 if (authenticate(s, m)) {
1633 sleep(1);
1634 astman_send_error(s, m, "Authentication failed");
1635 return -1;
1637 s->authenticated = 1;
1638 if (manager_displayconnects(s))
1639 ast_verb(2, "%sManager '%s' logged on from %s\n", (s->managerid ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr));
1640 ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->managerid ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr));
1641 astman_send_ack(s, m, "Authentication accepted");
1642 return 0;
1645 static int action_challenge(struct mansession *s, const struct message *m)
1647 const char *authtype = astman_get_header(m, "AuthType");
1649 if (!strcasecmp(authtype, "MD5")) {
1650 if (ast_strlen_zero(s->challenge))
1651 snprintf(s->challenge, sizeof(s->challenge), "%ld", ast_random());
1652 ast_mutex_lock(&s->__lock);
1653 astman_start_ack(s, m);
1654 astman_append(s, "Challenge: %s\r\n\r\n", s->challenge);
1655 ast_mutex_unlock(&s->__lock);
1656 } else {
1657 astman_send_error(s, m, "Must specify AuthType");
1659 return 0;
1662 static char mandescr_hangup[] =
1663 "Description: Hangup a channel\n"
1664 "Variables: \n"
1665 " Channel: The channel name to be hungup\n";
1667 static int action_hangup(struct mansession *s, const struct message *m)
1669 struct ast_channel *c = NULL;
1670 const char *name = astman_get_header(m, "Channel");
1671 if (ast_strlen_zero(name)) {
1672 astman_send_error(s, m, "No channel specified");
1673 return 0;
1675 c = ast_get_channel_by_name_locked(name);
1676 if (!c) {
1677 astman_send_error(s, m, "No such channel");
1678 return 0;
1680 ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
1681 ast_channel_unlock(c);
1682 astman_send_ack(s, m, "Channel Hungup");
1683 return 0;
1686 static char mandescr_setvar[] =
1687 "Description: Set a global or local channel variable.\n"
1688 "Variables: (Names marked with * are required)\n"
1689 " Channel: Channel to set variable for\n"
1690 " *Variable: Variable name\n"
1691 " *Value: Value\n";
1693 static int action_setvar(struct mansession *s, const struct message *m)
1695 struct ast_channel *c = NULL;
1696 const char *name = astman_get_header(m, "Channel");
1697 const char *varname = astman_get_header(m, "Variable");
1698 const char *varval = astman_get_header(m, "Value");
1700 if (ast_strlen_zero(varname)) {
1701 astman_send_error(s, m, "No variable specified");
1702 return 0;
1705 if (!ast_strlen_zero(name)) {
1706 c = ast_get_channel_by_name_locked(name);
1707 if (!c) {
1708 astman_send_error(s, m, "No such channel");
1709 return 0;
1713 pbx_builtin_setvar_helper(c, varname, S_OR(varval, ""));
1715 if (c)
1716 ast_channel_unlock(c);
1718 astman_send_ack(s, m, "Variable Set");
1720 return 0;
1723 static char mandescr_getvar[] =
1724 "Description: Get the value of a global or local channel variable.\n"
1725 "Variables: (Names marked with * are required)\n"
1726 " Channel: Channel to read variable from\n"
1727 " *Variable: Variable name\n"
1728 " ActionID: Optional Action id for message matching.\n";
1730 static int action_getvar(struct mansession *s, const struct message *m)
1732 struct ast_channel *c = NULL;
1733 const char *name = astman_get_header(m, "Channel");
1734 const char *varname = astman_get_header(m, "Variable");
1735 char *varval;
1736 char workspace[1024] = "";
1738 if (ast_strlen_zero(varname)) {
1739 astman_send_error(s, m, "No variable specified");
1740 return 0;
1743 if (!ast_strlen_zero(name)) {
1744 c = ast_get_channel_by_name_locked(name);
1745 if (!c) {
1746 astman_send_error(s, m, "No such channel");
1747 return 0;
1751 if (varname[strlen(varname) - 1] == ')') {
1752 ast_func_read(c, (char *) varname, workspace, sizeof(workspace));
1753 varval = workspace;
1754 } else {
1755 pbx_retrieve_variable(c, varname, &varval, workspace, sizeof(workspace), NULL);
1758 if (c)
1759 ast_channel_unlock(c);
1760 astman_start_ack(s, m);
1761 astman_append(s, "Variable: %s\r\nValue: %s\r\n\r\n", varname, varval);
1763 return 0;
1766 static char mandescr_status[] =
1767 "Description: Lists channel status along with requested channel vars.\n"
1768 "Variables: (Names marked with * are required)\n"
1769 " *Channel: Name of the channel to query for status\n"
1770 " Variables: Comma ',' separated list of variables to include\n"
1771 " ActionID: Optional ID for this transaction\n"
1772 "Will return the status information of each channel along with the\n"
1773 "value for the specified channel variables.\n";
1776 /*! \brief Manager "status" command to show channels */
1777 /* Needs documentation... */
1778 static int action_status(struct mansession *s, const struct message *m)
1780 const char *name = astman_get_header(m, "Channel");
1781 const char *cvariables = astman_get_header(m, "Variables");
1782 char *variables = ast_strdupa(S_OR(cvariables, ""));
1783 struct ast_channel *c;
1784 char bridge[256];
1785 struct timeval now = ast_tvnow();
1786 long elapsed_seconds = 0;
1787 int channels = 0;
1788 int all = ast_strlen_zero(name); /* set if we want all channels */
1789 const char *id = astman_get_header(m, "ActionID");
1790 char idText[256];
1791 AST_DECLARE_APP_ARGS(vars,
1792 AST_APP_ARG(name)[100];
1794 struct ast_str *str = ast_str_create(1000);
1796 if (!ast_strlen_zero(id))
1797 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
1798 else
1799 idText[0] = '\0';
1801 if (all)
1802 c = ast_channel_walk_locked(NULL);
1803 else {
1804 c = ast_get_channel_by_name_locked(name);
1805 if (!c) {
1806 astman_send_error(s, m, "No such channel");
1807 return 0;
1810 astman_send_ack(s, m, "Channel status will follow");
1812 if (!ast_strlen_zero(cvariables)) {
1813 AST_STANDARD_APP_ARGS(vars, variables);
1816 /* if we look by name, we break after the first iteration */
1817 while (c) {
1818 if (!ast_strlen_zero(cvariables)) {
1819 int i;
1820 ast_str_reset(str);
1821 for (i = 0; i < vars.argc; i++) {
1822 char valbuf[512], *ret = NULL;
1824 if (vars.name[i][strlen(vars.name[i]) - 1] == ')') {
1825 if (ast_func_read(c, vars.name[i], valbuf, sizeof(valbuf)) < 0) {
1826 valbuf[0] = '\0';
1828 ret = valbuf;
1829 } else {
1830 pbx_retrieve_variable(c, vars.name[i], &ret, valbuf, sizeof(valbuf), NULL);
1833 ast_str_append(&str, 0, "Variable: %s=%s\r\n", vars.name[i], ret);
1837 channels++;
1838 if (c->_bridge)
1839 snprintf(bridge, sizeof(bridge), "BridgedChannel: %s\r\nBridgedUniqueid: %s\r\n", c->_bridge->name, c->_bridge->uniqueid);
1840 else
1841 bridge[0] = '\0';
1842 if (c->pbx) {
1843 if (c->cdr) {
1844 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
1846 astman_append(s,
1847 "Event: Status\r\n"
1848 "Privilege: Call\r\n"
1849 "Channel: %s\r\n"
1850 "CallerIDNum: %s\r\n"
1851 "CallerIDName: %s\r\n"
1852 "Accountcode: %s\r\n"
1853 "ChannelState: %d\r\n"
1854 "ChannelStateDesc: %s\r\n"
1855 "Context: %s\r\n"
1856 "Extension: %s\r\n"
1857 "Priority: %d\r\n"
1858 "Seconds: %ld\r\n"
1859 "%s"
1860 "Uniqueid: %s\r\n"
1861 "%s"
1862 "%s"
1863 "\r\n",
1864 c->name,
1865 S_OR(c->cid.cid_num, ""),
1866 S_OR(c->cid.cid_name, ""),
1867 c->accountcode,
1868 c->_state,
1869 ast_state2str(c->_state), c->context,
1870 c->exten, c->priority, (long)elapsed_seconds, bridge, c->uniqueid, str->str, idText);
1871 } else {
1872 astman_append(s,
1873 "Event: Status\r\n"
1874 "Privilege: Call\r\n"
1875 "Channel: %s\r\n"
1876 "CallerIDNum: %s\r\n"
1877 "CallerIDName: %s\r\n"
1878 "Account: %s\r\n"
1879 "State: %s\r\n"
1880 "%s"
1881 "Uniqueid: %s\r\n"
1882 "%s"
1883 "%s"
1884 "\r\n",
1885 c->name,
1886 S_OR(c->cid.cid_num, "<unknown>"),
1887 S_OR(c->cid.cid_name, "<unknown>"),
1888 c->accountcode,
1889 ast_state2str(c->_state), bridge, c->uniqueid, str->str, idText);
1891 ast_channel_unlock(c);
1892 if (!all)
1893 break;
1894 c = ast_channel_walk_locked(c);
1896 astman_append(s,
1897 "Event: StatusComplete\r\n"
1898 "%s"
1899 "Items: %d\r\n"
1900 "\r\n", idText, channels);
1901 ast_free(str);
1902 return 0;
1905 static char mandescr_sendtext[] =
1906 "Description: Sends A Text Message while in a call.\n"
1907 "Variables: (Names marked with * are required)\n"
1908 " *Channel: Channel to send message to\n"
1909 " *Message: Message to send\n"
1910 " ActionID: Optional Action id for message matching.\n";
1912 static int action_sendtext(struct mansession *s, const struct message *m)
1914 struct ast_channel *c = NULL;
1915 const char *name = astman_get_header(m, "Channel");
1916 const char *textmsg = astman_get_header(m, "Message");
1917 int res = 0;
1919 if (ast_strlen_zero(name)) {
1920 astman_send_error(s, m, "No channel specified");
1921 return 0;
1924 if (ast_strlen_zero(textmsg)) {
1925 astman_send_error(s, m, "No Message specified");
1926 return 0;
1929 c = ast_get_channel_by_name_locked(name);
1930 if (!c) {
1931 astman_send_error(s, m, "No such channel");
1932 return 0;
1935 res = ast_sendtext(c, textmsg);
1936 ast_channel_unlock(c);
1938 if (res > 0)
1939 astman_send_ack(s, m, "Success");
1940 else
1941 astman_send_error(s, m, "Failure");
1943 return res;
1946 static char mandescr_redirect[] =
1947 "Description: Redirect (transfer) a call.\n"
1948 "Variables: (Names marked with * are required)\n"
1949 " *Channel: Channel to redirect\n"
1950 " ExtraChannel: Second call leg to transfer (optional)\n"
1951 " *Exten: Extension to transfer to\n"
1952 " *Context: Context to transfer to\n"
1953 " *Priority: Priority to transfer to\n"
1954 " ActionID: Optional Action id for message matching.\n";
1956 /*! \brief action_redirect: The redirect manager command */
1957 static int action_redirect(struct mansession *s, const struct message *m)
1959 const char *name = astman_get_header(m, "Channel");
1960 const char *name2 = astman_get_header(m, "ExtraChannel");
1961 const char *exten = astman_get_header(m, "Exten");
1962 const char *context = astman_get_header(m, "Context");
1963 const char *priority = astman_get_header(m, "Priority");
1964 struct ast_channel *chan, *chan2 = NULL;
1965 int pi = 0;
1966 int res;
1968 if (ast_strlen_zero(name)) {
1969 astman_send_error(s, m, "Channel not specified");
1970 return 0;
1972 if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) {
1973 if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
1974 astman_send_error(s, m, "Invalid priority\n");
1975 return 0;
1978 /* XXX watch out, possible deadlock - we are trying to get two channels!!! */
1979 chan = ast_get_channel_by_name_locked(name);
1980 if (!chan) {
1981 char buf[256];
1982 snprintf(buf, sizeof(buf), "Channel does not exist: %s", name);
1983 astman_send_error(s, m, buf);
1984 return 0;
1986 if (ast_check_hangup(chan)) {
1987 astman_send_error(s, m, "Redirect failed, channel not up.\n");
1988 ast_channel_unlock(chan);
1989 return 0;
1991 if (!ast_strlen_zero(name2))
1992 chan2 = ast_get_channel_by_name_locked(name2);
1993 if (chan2 && ast_check_hangup(chan2)) {
1994 astman_send_error(s, m, "Redirect failed, extra channel not up.\n");
1995 ast_channel_unlock(chan);
1996 ast_channel_unlock(chan2);
1997 return 0;
1999 res = ast_async_goto(chan, context, exten, pi);
2000 if (!res) {
2001 if (!ast_strlen_zero(name2)) {
2002 if (chan2)
2003 res = ast_async_goto(chan2, context, exten, pi);
2004 else
2005 res = -1;
2006 if (!res)
2007 astman_send_ack(s, m, "Dual Redirect successful");
2008 else
2009 astman_send_error(s, m, "Secondary redirect failed");
2010 } else
2011 astman_send_ack(s, m, "Redirect successful");
2012 } else
2013 astman_send_error(s, m, "Redirect failed");
2014 if (chan)
2015 ast_channel_unlock(chan);
2016 if (chan2)
2017 ast_channel_unlock(chan2);
2018 return 0;
2021 static char mandescr_atxfer[] =
2022 "Description: Attended transfer.\n"
2023 "Variables: (Names marked with * are required)\n"
2024 " *Channel: Transferer's channel\n"
2025 " *Exten: Extension to transfer to\n"
2026 " *Context: Context to transfer to\n"
2027 " *Priority: Priority to transfer to\n"
2028 " ActionID: Optional Action id for message matching.\n";
2030 static int action_atxfer(struct mansession *s, const struct message *m)
2032 const char *name = astman_get_header(m, "Channel");
2033 const char *exten = astman_get_header(m, "Exten");
2034 const char *context = astman_get_header(m, "Context");
2035 const char *priority = astman_get_header(m, "Priority");
2036 struct ast_channel *chan = NULL;
2037 struct ast_call_feature *atxfer_feature = NULL;
2038 char *feature_code = NULL;
2039 int priority_int = 0;
2041 if (ast_strlen_zero(name)) {
2042 astman_send_error(s, m, "No channel specified\n");
2043 return 0;
2045 if (ast_strlen_zero(exten)) {
2046 astman_send_error(s, m, "No extension specified\n");
2047 return 0;
2049 if (ast_strlen_zero(context)) {
2050 astman_send_error(s, m, "No context specified\n");
2051 return 0;
2053 if (ast_strlen_zero(priority)) {
2054 astman_send_error(s, m, "No priority specified\n");
2055 return 0;
2058 if (sscanf(priority, "%d", &priority_int) != 1 && (priority_int = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
2059 astman_send_error(s, m, "Invalid Priority\n");
2060 return 0;
2063 if (!(atxfer_feature = ast_find_call_feature("atxfer"))) {
2064 astman_send_error(s, m, "No attended transfer feature found\n");
2065 return 0;
2068 if (!(chan = ast_get_channel_by_name_locked(name))) {
2069 astman_send_error(s, m, "Channel specified does not exist\n");
2070 return 0;
2073 for (feature_code = atxfer_feature->exten; feature_code && *feature_code; ++feature_code) {
2074 struct ast_frame f = {AST_FRAME_DTMF, *feature_code};
2075 ast_queue_frame(chan, &f);
2078 for (feature_code = (char *)exten; feature_code && *feature_code; ++feature_code) {
2079 struct ast_frame f = {AST_FRAME_DTMF, *feature_code};
2080 ast_queue_frame(chan, &f);
2083 astman_send_ack(s, m, "Atxfer successfully queued\n");
2084 ast_channel_unlock(chan);
2086 return 0;
2089 static int check_blacklist(const char *cmd)
2091 char *cmd_copy, *cur_cmd;
2092 char *cmd_words[MAX_BLACKLIST_CMD_LEN] = { NULL, };
2093 int i;
2095 cmd_copy = ast_strdupa(cmd);
2096 for (i = 0; i < MAX_BLACKLIST_CMD_LEN && (cur_cmd = strsep(&cmd_copy, " ")); i++) {
2097 cur_cmd = ast_strip(cur_cmd);
2098 if (ast_strlen_zero(cur_cmd)) {
2099 i--;
2100 continue;
2103 cmd_words[i] = cur_cmd;
2106 for (i = 0; i < ARRAY_LEN(command_blacklist); i++) {
2107 int j, match = 1;
2109 for (j = 0; command_blacklist[i].words[j]; j++) {
2110 if (ast_strlen_zero(cmd_words[j]) || strcasecmp(cmd_words[j], command_blacklist[i].words[j])) {
2111 match = 0;
2112 break;
2116 if (match) {
2117 return 1;
2121 return 0;
2124 static char mandescr_command[] =
2125 "Description: Run a CLI command.\n"
2126 "Variables: (Names marked with * are required)\n"
2127 " *Command: Asterisk CLI command to run\n"
2128 " ActionID: Optional Action id for message matching.\n";
2130 /*! \brief Manager command "command" - execute CLI command */
2131 static int action_command(struct mansession *s, const struct message *m)
2133 const char *cmd = astman_get_header(m, "Command");
2134 const char *id = astman_get_header(m, "ActionID");
2135 char *buf, *final_buf;
2136 char template[] = "/tmp/ast-ami-XXXXXX"; /* template for temporary file */
2137 int fd = mkstemp(template);
2138 off_t l;
2140 if (ast_strlen_zero(cmd)) {
2141 astman_send_error(s, m, "No command provided");
2142 return 0;
2145 if (check_blacklist(cmd)) {
2146 astman_send_error(s, m, "Command blacklisted");
2147 return 0;
2150 astman_append(s, "Response: Follows\r\nPrivilege: Command\r\n");
2151 if (!ast_strlen_zero(id))
2152 astman_append(s, "ActionID: %s\r\n", id);
2153 /* FIXME: Wedge a ActionID response in here, waiting for later changes */
2154 ast_cli_command(fd, cmd); /* XXX need to change this to use a FILE * */
2155 l = lseek(fd, 0, SEEK_END); /* how many chars available */
2157 /* This has a potential to overflow the stack. Hence, use the heap. */
2158 buf = ast_calloc(1, l + 1);
2159 final_buf = ast_calloc(1, l + 1);
2160 if (buf) {
2161 lseek(fd, 0, SEEK_SET);
2162 read(fd, buf, l);
2163 buf[l] = '\0';
2164 if (final_buf) {
2165 term_strip(final_buf, buf, l);
2166 final_buf[l] = '\0';
2168 astman_append(s, "%s", S_OR(final_buf, buf));
2169 ast_free(buf);
2171 close(fd);
2172 unlink(template);
2173 astman_append(s, "--END COMMAND--\r\n\r\n");
2174 if (final_buf)
2175 ast_free(final_buf);
2176 return 0;
2179 /*! \brief helper function for originate */
2180 struct fast_originate_helper {
2181 char tech[AST_MAX_EXTENSION];
2182 char data[AST_MAX_EXTENSION];
2183 int timeout;
2184 int format; /*!< Codecs used for a call */
2185 char app[AST_MAX_APP];
2186 char appdata[AST_MAX_EXTENSION];
2187 char cid_name[AST_MAX_EXTENSION];
2188 char cid_num[AST_MAX_EXTENSION];
2189 char context[AST_MAX_CONTEXT];
2190 char exten[AST_MAX_EXTENSION];
2191 char idtext[AST_MAX_EXTENSION];
2192 char account[AST_MAX_ACCOUNT_CODE];
2193 int priority;
2194 struct ast_variable *vars;
2197 static void *fast_originate(void *data)
2199 struct fast_originate_helper *in = data;
2200 int res;
2201 int reason = 0;
2202 struct ast_channel *chan = NULL;
2203 char requested_channel[AST_CHANNEL_NAME];
2205 if (!ast_strlen_zero(in->app)) {
2206 res = ast_pbx_outgoing_app(in->tech, in->format, in->data, in->timeout, in->app, in->appdata, &reason, 1,
2207 S_OR(in->cid_num, NULL),
2208 S_OR(in->cid_name, NULL),
2209 in->vars, in->account, &chan);
2210 } else {
2211 res = ast_pbx_outgoing_exten(in->tech, in->format, in->data, in->timeout, in->context, in->exten, in->priority, &reason, 1,
2212 S_OR(in->cid_num, NULL),
2213 S_OR(in->cid_name, NULL),
2214 in->vars, in->account, &chan);
2217 if (!chan)
2218 snprintf(requested_channel, AST_CHANNEL_NAME, "%s/%s", in->tech, in->data);
2219 /* Tell the manager what happened with the channel */
2220 manager_event(EVENT_FLAG_CALL, "OriginateResponse",
2221 "%s"
2222 "Response: %s\r\n"
2223 "Channel: %s\r\n"
2224 "Context: %s\r\n"
2225 "Exten: %s\r\n"
2226 "Reason: %d\r\n"
2227 "Uniqueid: %s\r\n"
2228 "CallerIDNum: %s\r\n"
2229 "CallerIDName: %s\r\n",
2230 in->idtext, res ? "Failure" : "Success", chan ? chan->name : requested_channel, in->context, in->exten, reason,
2231 chan ? chan->uniqueid : "<null>",
2232 S_OR(in->cid_num, "<unknown>"),
2233 S_OR(in->cid_name, "<unknown>")
2236 /* Locked by ast_pbx_outgoing_exten or ast_pbx_outgoing_app */
2237 if (chan)
2238 ast_channel_unlock(chan);
2239 ast_free(in);
2240 return NULL;
2243 static char mandescr_originate[] =
2244 "Description: Generates an outgoing call to a Extension/Context/Priority or\n"
2245 " Application/Data\n"
2246 "Variables: (Names marked with * are required)\n"
2247 " *Channel: Channel name to call\n"
2248 " Exten: Extension to use (requires 'Context' and 'Priority')\n"
2249 " Context: Context to use (requires 'Exten' and 'Priority')\n"
2250 " Priority: Priority to use (requires 'Exten' and 'Context')\n"
2251 " Application: Application to use\n"
2252 " Data: Data to use (requires 'Application')\n"
2253 " Timeout: How long to wait for call to be answered (in ms)\n"
2254 " CallerID: Caller ID to be set on the outgoing channel\n"
2255 " Variable: Channel variable to set, multiple Variable: headers are allowed\n"
2256 " Account: Account code\n"
2257 " Async: Set to 'true' for fast origination\n";
2259 static int action_originate(struct mansession *s, const struct message *m)
2261 const char *name = astman_get_header(m, "Channel");
2262 const char *exten = astman_get_header(m, "Exten");
2263 const char *context = astman_get_header(m, "Context");
2264 const char *priority = astman_get_header(m, "Priority");
2265 const char *timeout = astman_get_header(m, "Timeout");
2266 const char *callerid = astman_get_header(m, "CallerID");
2267 const char *account = astman_get_header(m, "Account");
2268 const char *app = astman_get_header(m, "Application");
2269 const char *appdata = astman_get_header(m, "Data");
2270 const char *async = astman_get_header(m, "Async");
2271 const char *id = astman_get_header(m, "ActionID");
2272 const char *codecs = astman_get_header(m, "Codecs");
2273 struct ast_variable *vars = astman_get_variables(m);
2274 char *tech, *data;
2275 char *l = NULL, *n = NULL;
2276 int pi = 0;
2277 int res;
2278 int to = 30000;
2279 int reason = 0;
2280 char tmp[256];
2281 char tmp2[256];
2282 int format = AST_FORMAT_SLINEAR;
2284 pthread_t th;
2285 if (!name) {
2286 astman_send_error(s, m, "Channel not specified");
2287 return 0;
2289 if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) {
2290 if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
2291 astman_send_error(s, m, "Invalid priority\n");
2292 return 0;
2295 if (!ast_strlen_zero(timeout) && (sscanf(timeout, "%d", &to) != 1)) {
2296 astman_send_error(s, m, "Invalid timeout\n");
2297 return 0;
2299 ast_copy_string(tmp, name, sizeof(tmp));
2300 tech = tmp;
2301 data = strchr(tmp, '/');
2302 if (!data) {
2303 astman_send_error(s, m, "Invalid channel\n");
2304 return 0;
2306 *data++ = '\0';
2307 ast_copy_string(tmp2, callerid, sizeof(tmp2));
2308 ast_callerid_parse(tmp2, &n, &l);
2309 if (n) {
2310 if (ast_strlen_zero(n))
2311 n = NULL;
2313 if (l) {
2314 ast_shrink_phone_number(l);
2315 if (ast_strlen_zero(l))
2316 l = NULL;
2318 if (!ast_strlen_zero(codecs)) {
2319 format = 0;
2320 ast_parse_allow_disallow(NULL, &format, codecs, 1);
2322 if (ast_true(async)) {
2323 struct fast_originate_helper *fast = ast_calloc(1, sizeof(*fast));
2324 if (!fast) {
2325 res = -1;
2326 } else {
2327 if (!ast_strlen_zero(id))
2328 snprintf(fast->idtext, sizeof(fast->idtext), "ActionID: %s\r\n", id);
2329 ast_copy_string(fast->tech, tech, sizeof(fast->tech));
2330 ast_copy_string(fast->data, data, sizeof(fast->data));
2331 ast_copy_string(fast->app, app, sizeof(fast->app));
2332 ast_copy_string(fast->appdata, appdata, sizeof(fast->appdata));
2333 if (l)
2334 ast_copy_string(fast->cid_num, l, sizeof(fast->cid_num));
2335 if (n)
2336 ast_copy_string(fast->cid_name, n, sizeof(fast->cid_name));
2337 fast->vars = vars;
2338 ast_copy_string(fast->context, context, sizeof(fast->context));
2339 ast_copy_string(fast->exten, exten, sizeof(fast->exten));
2340 ast_copy_string(fast->account, account, sizeof(fast->account));
2341 fast->format = format;
2342 fast->timeout = to;
2343 fast->priority = pi;
2344 if (ast_pthread_create_detached(&th, NULL, fast_originate, fast)) {
2345 res = -1;
2346 } else {
2347 res = 0;
2350 } else if (!ast_strlen_zero(app)) {
2351 /* To run the System application (or anything else that goes to shell), you must have the additional System privilege */
2352 if (!(s->writeperm & EVENT_FLAG_SYSTEM)
2353 && (
2354 strcasestr(app, "system") == 0 || /* System(rm -rf /)
2355 TrySystem(rm -rf /) */
2356 strcasestr(app, "exec") || /* Exec(System(rm -rf /))
2357 TryExec(System(rm -rf /)) */
2358 strcasestr(app, "agi") || /* AGI(/bin/rm,-rf /)
2359 EAGI(/bin/rm,-rf /) */
2360 strstr(appdata, "SHELL") || /* NoOp(${SHELL(rm -rf /)}) */
2361 strstr(appdata, "EVAL") /* NoOp(${EVAL(${some_var_containing_SHELL})}) */
2362 )) {
2363 astman_send_error(s, m, "Originate with certain 'Application' arguments requires the additional System privilege, which you do not have.");
2364 return 0;
2366 res = ast_pbx_outgoing_app(tech, format, data, to, app, appdata, &reason, 1, l, n, vars, account, NULL);
2367 } else {
2368 if (exten && context && pi)
2369 res = ast_pbx_outgoing_exten(tech, format, data, to, context, exten, pi, &reason, 1, l, n, vars, account, NULL);
2370 else {
2371 astman_send_error(s, m, "Originate with 'Exten' requires 'Context' and 'Priority'");
2372 return 0;
2375 if (!res)
2376 astman_send_ack(s, m, "Originate successfully queued");
2377 else
2378 astman_send_error(s, m, "Originate failed");
2379 return 0;
2382 /*! \brief Help text for manager command mailboxstatus
2384 static char mandescr_mailboxstatus[] =
2385 "Description: Checks a voicemail account for status.\n"
2386 "Variables: (Names marked with * are required)\n"
2387 " *Mailbox: Full mailbox ID <mailbox>@<vm-context>\n"
2388 " ActionID: Optional ActionID for message matching.\n"
2389 "Returns number of messages.\n"
2390 " Message: Mailbox Status\n"
2391 " Mailbox: <mailboxid>\n"
2392 " Waiting: <count>\n"
2393 "\n";
2395 static int action_mailboxstatus(struct mansession *s, const struct message *m)
2397 const char *mailbox = astman_get_header(m, "Mailbox");
2398 int ret;
2400 if (ast_strlen_zero(mailbox)) {
2401 astman_send_error(s, m, "Mailbox not specified");
2402 return 0;
2404 ret = ast_app_has_voicemail(mailbox, NULL);
2405 astman_start_ack(s, m);
2406 astman_append(s, "Message: Mailbox Status\r\n"
2407 "Mailbox: %s\r\n"
2408 "Waiting: %d\r\n\r\n", mailbox, ret);
2409 return 0;
2412 static char mandescr_mailboxcount[] =
2413 "Description: Checks a voicemail account for new messages.\n"
2414 "Variables: (Names marked with * are required)\n"
2415 " *Mailbox: Full mailbox ID <mailbox>@<vm-context>\n"
2416 " ActionID: Optional ActionID for message matching.\n"
2417 "Returns number of urgent, new and old messages.\n"
2418 " Message: Mailbox Message Count\n"
2419 " Mailbox: <mailboxid>\n"
2420 " UrgentMessages: <count>\n"
2421 " NewMessages: <count>\n"
2422 " OldMessages: <count>\n"
2423 "\n";
2424 static int action_mailboxcount(struct mansession *s, const struct message *m)
2426 const char *mailbox = astman_get_header(m, "Mailbox");
2427 int newmsgs = 0, oldmsgs = 0, urgentmsgs = 0;;
2429 if (ast_strlen_zero(mailbox)) {
2430 astman_send_error(s, m, "Mailbox not specified");
2431 return 0;
2433 ast_app_inboxcount(mailbox, &urgentmsgs, &newmsgs, &oldmsgs);
2434 astman_start_ack(s, m);
2435 astman_append(s, "Message: Mailbox Message Count\r\n"
2436 "Mailbox: %s\r\n"
2437 "UrgMessages: %d\r\n"
2438 "NewMessages: %d\r\n"
2439 "OldMessages: %d\r\n"
2440 "\r\n",
2441 mailbox, urgentmsgs, newmsgs, oldmsgs);
2442 return 0;
2445 static char mandescr_extensionstate[] =
2446 "Description: Report the extension state for given extension.\n"
2447 " If the extension has a hint, will use devicestate to check\n"
2448 " the status of the device connected to the extension.\n"
2449 "Variables: (Names marked with * are required)\n"
2450 " *Exten: Extension to check state on\n"
2451 " *Context: Context for extension\n"
2452 " ActionId: Optional ID for this transaction\n"
2453 "Will return an \"Extension Status\" message.\n"
2454 "The response will include the hint for the extension and the status.\n";
2456 static int action_extensionstate(struct mansession *s, const struct message *m)
2458 const char *exten = astman_get_header(m, "Exten");
2459 const char *context = astman_get_header(m, "Context");
2460 char hint[256] = "";
2461 int status;
2462 if (ast_strlen_zero(exten)) {
2463 astman_send_error(s, m, "Extension not specified");
2464 return 0;
2466 if (ast_strlen_zero(context))
2467 context = "default";
2468 status = ast_extension_state(NULL, context, exten);
2469 ast_get_hint(hint, sizeof(hint) - 1, NULL, 0, NULL, context, exten);
2470 astman_start_ack(s, m);
2471 astman_append(s, "Message: Extension Status\r\n"
2472 "Exten: %s\r\n"
2473 "Context: %s\r\n"
2474 "Hint: %s\r\n"
2475 "Status: %d\r\n\r\n",
2476 exten, context, hint, status);
2477 return 0;
2480 static char mandescr_timeout[] =
2481 "Description: Hangup a channel after a certain time.\n"
2482 "Variables: (Names marked with * are required)\n"
2483 " *Channel: Channel name to hangup\n"
2484 " *Timeout: Maximum duration of the call (sec)\n"
2485 "Acknowledges set time with 'Timeout Set' message\n";
2487 static int action_timeout(struct mansession *s, const struct message *m)
2489 struct ast_channel *c;
2490 const char *name = astman_get_header(m, "Channel");
2491 double timeout = atof(astman_get_header(m, "Timeout"));
2492 struct timeval tv = { timeout, 0 };
2494 if (ast_strlen_zero(name)) {
2495 astman_send_error(s, m, "No channel specified");
2496 return 0;
2498 if (!timeout || timeout < 0) {
2499 astman_send_error(s, m, "No timeout specified");
2500 return 0;
2502 c = ast_get_channel_by_name_locked(name);
2503 if (!c) {
2504 astman_send_error(s, m, "No such channel");
2505 return 0;
2508 tv.tv_usec = (timeout - tv.tv_sec) * 1000000.0;
2509 ast_channel_setwhentohangup_tv(c, tv);
2510 ast_channel_unlock(c);
2511 astman_send_ack(s, m, "Timeout Set");
2512 return 0;
2516 * Send any applicable events to the client listening on this socket.
2517 * Wait only for a finite time on each event, and drop all events whether
2518 * they are successfully sent or not.
2520 static int process_events(struct mansession *s)
2522 int ret = 0;
2524 ast_mutex_lock(&s->__lock);
2525 if (s->f != NULL) {
2526 struct eventqent *eqe;
2528 while ( (eqe = NEW_EVENT(s)) ) {
2529 ref_event(eqe);
2530 if (!ret && s->authenticated &&
2531 (s->readperm & eqe->category) == eqe->category &&
2532 (s->send_events & eqe->category) == eqe->category) {
2533 if (send_string(s, eqe->eventdata) < 0)
2534 ret = -1; /* don't send more */
2536 s->last_ev = unref_event(s->last_ev);
2539 ast_mutex_unlock(&s->__lock);
2540 return ret;
2543 static char mandescr_userevent[] =
2544 "Description: Send an event to manager sessions.\n"
2545 "Variables: (Names marked with * are required)\n"
2546 " *UserEvent: EventStringToSend\n"
2547 " Header1: Content1\n"
2548 " HeaderN: ContentN\n";
2550 static int action_userevent(struct mansession *s, const struct message *m)
2552 const char *event = astman_get_header(m, "UserEvent");
2553 char body[2048] = "";
2554 int x, bodylen = 0;
2555 for (x = 0; x < m->hdrcount; x++) {
2556 if (strncasecmp("UserEvent:", m->headers[x], strlen("UserEvent:"))) {
2557 ast_copy_string(body + bodylen, m->headers[x], sizeof(body) - bodylen - 3);
2558 bodylen += strlen(m->headers[x]);
2559 ast_copy_string(body + bodylen, "\r\n", 3);
2560 bodylen += 2;
2564 manager_event(EVENT_FLAG_USER, "UserEvent", "UserEvent: %s\r\n%s", event, body);
2565 return 0;
2568 static char mandescr_coresettings[] =
2569 "Description: Query for Core PBX settings.\n"
2570 "Variables: (Names marked with * are optional)\n"
2571 " *ActionID: ActionID of this transaction\n";
2573 /*! \brief Show PBX core settings information */
2574 static int action_coresettings(struct mansession *s, const struct message *m)
2576 const char *actionid = astman_get_header(m, "ActionID");
2577 char idText[150];
2579 if (!ast_strlen_zero(actionid))
2580 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
2581 else
2582 idText[0] = '\0';
2584 astman_append(s, "Response: Success\r\n"
2585 "%s"
2586 "AMIversion: %s\r\n"
2587 "AsteriskVersion: %s\r\n"
2588 "SystemName: %s\r\n"
2589 "CoreMaxCalls: %d\r\n"
2590 "CoreMaxLoadAvg: %f\r\n"
2591 "CoreRunUser: %s\r\n"
2592 "CoreRunGroup: %s\r\n"
2593 "CoreMaxFilehandles: %d\r\n"
2594 "CoreRealTimeEnabled: %s\r\n"
2595 "CoreCDRenabled: %s\r\n"
2596 "CoreHTTPenabled: %s\r\n"
2597 "\r\n",
2598 idText,
2599 AMI_VERSION,
2600 ast_get_version(),
2601 ast_config_AST_SYSTEM_NAME,
2602 option_maxcalls,
2603 option_maxload,
2604 ast_config_AST_RUN_USER,
2605 ast_config_AST_RUN_GROUP,
2606 option_maxfiles,
2607 ast_realtime_enabled() ? "Yes" : "No",
2608 check_cdr_enabled() ? "Yes" : "No",
2609 check_webmanager_enabled() ? "Yes" : "No"
2611 return 0;
2614 static char mandescr_corestatus[] =
2615 "Description: Query for Core PBX status.\n"
2616 "Variables: (Names marked with * are optional)\n"
2617 " *ActionID: ActionID of this transaction\n";
2619 /*! \brief Show PBX core status information */
2620 static int action_corestatus(struct mansession *s, const struct message *m)
2622 const char *actionid = astman_get_header(m, "ActionID");
2623 char idText[150];
2624 char startuptime[150];
2625 char reloadtime[150];
2626 struct ast_tm tm;
2628 if (!ast_strlen_zero(actionid))
2629 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
2630 else
2631 idText[0] = '\0';
2633 ast_localtime(&ast_startuptime, &tm, NULL);
2634 ast_strftime(startuptime, sizeof(startuptime), "%H:%M:%S", &tm);
2635 ast_localtime(&ast_lastreloadtime, &tm, NULL);
2636 ast_strftime(reloadtime, sizeof(reloadtime), "%H:%M:%S", &tm);
2638 astman_append(s, "Response: Success\r\n"
2639 "%s"
2640 "CoreStartupTime: %s\r\n"
2641 "CoreReloadTime: %s\r\n"
2642 "CoreCurrentCalls: %d\r\n"
2643 "\r\n",
2644 idText,
2645 startuptime,
2646 reloadtime,
2647 ast_active_channels()
2649 return 0;
2652 static char mandescr_reload[] =
2653 "Description: Send a reload event.\n"
2654 "Variables: (Names marked with * are optional)\n"
2655 " *ActionID: ActionID of this transaction\n"
2656 " *Module: Name of the module to reload\n";
2658 /*! \brief Send a reload event */
2659 static int action_reload(struct mansession *s, const struct message *m)
2661 const char *module = astman_get_header(m, "Module");
2662 int res = ast_module_reload(S_OR(module, NULL));
2664 if (res == 2)
2665 astman_send_ack(s, m, "Module Reloaded");
2666 else
2667 astman_send_error(s, m, s == 0 ? "No such module" : "Module does not support reload");
2668 return 0;
2671 static char mandescr_coreshowchannels[] =
2672 "Description: List currently defined channels and some information\n"
2673 " about them.\n"
2674 "Variables:\n"
2675 " ActionID: Optional Action id for message matching.\n";
2677 /*! \brief Manager command "CoreShowChannels" - List currently defined channels
2678 * and some information about them. */
2679 static int action_coreshowchannels(struct mansession *s, const struct message *m)
2681 const char *actionid = astman_get_header(m, "ActionID");
2682 char actionidtext[256];
2683 struct ast_channel *c = NULL;
2684 int numchans = 0;
2685 int duration, durh, durm, durs;
2687 if (!ast_strlen_zero(actionid))
2688 snprintf(actionidtext, sizeof(actionidtext), "ActionID: %s\r\n", actionid);
2689 else
2690 actionidtext[0] = '\0';
2692 astman_send_listack(s, m, "Channels will follow", "start");
2694 while ((c = ast_channel_walk_locked(c)) != NULL) {
2695 struct ast_channel *bc = ast_bridged_channel(c);
2696 char durbuf[10] = "";
2698 if (c->cdr && !ast_tvzero(c->cdr->start)) {
2699 duration = (int)(ast_tvdiff_ms(ast_tvnow(), c->cdr->start) / 1000);
2700 durh = duration / 3600;
2701 durm = (duration % 3600) / 60;
2702 durs = duration % 60;
2703 snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
2706 astman_append(s,
2707 "Channel: %s\r\n"
2708 "UniqueID: %s\r\n"
2709 "Context: %s\r\n"
2710 "Extension: %s\r\n"
2711 "Priority: %d\r\n"
2712 "ChannelState: %d\r\n"
2713 "ChannelStateDesc: %s\r\n"
2714 "Application: %s\r\n"
2715 "ApplicationData: %s\r\n"
2716 "CallerIDnum: %s\r\n"
2717 "Duration: %s\r\n"
2718 "AccountCode: %s\r\n"
2719 "BridgedChannel: %s\r\n"
2720 "BridgedUniqueID: %s\r\n"
2721 "\r\n", c->name, c->uniqueid, c->context, c->exten, c->priority, c->_state, ast_state2str(c->_state),
2722 c->appl ? c->appl : "", c->data ? S_OR(c->data, ""): "",
2723 S_OR(c->cid.cid_num, ""), durbuf, S_OR(c->accountcode, ""), bc ? bc->name : "", bc ? bc->uniqueid : "");
2724 ast_channel_unlock(c);
2725 numchans++;
2728 astman_append(s,
2729 "Event: CoreShowChannelsComplete\r\n"
2730 "EventList: Complete\r\n"
2731 "ListItems: %d\r\n"
2732 "%s"
2733 "\r\n", numchans, actionidtext);
2735 return 0;
2738 static char mandescr_modulecheck[] =
2739 "Description: Checks if Asterisk module is loaded\n"
2740 "Variables: \n"
2741 " ActionID: <id> Action ID for this transaction. Will be returned.\n"
2742 " Module: <name> Asterisk module name (not including extension)\n"
2743 "\n"
2744 "Will return Success/Failure\n"
2745 "For success returns, the module revision number is included.\n";
2747 /* Manager function to check if module is loaded */
2748 static int manager_modulecheck(struct mansession *s, const struct message *m)
2750 int res;
2751 const char *module = astman_get_header(m, "Module");
2752 const char *id = astman_get_header(m, "ActionID");
2753 char idText[256];
2754 #if !defined(LOW_MEMORY)
2755 const char *version;
2756 #endif
2757 char filename[PATH_MAX];
2758 char *cut;
2760 ast_copy_string(filename, module, sizeof(filename));
2761 if ((cut = strchr(filename, '.'))) {
2762 *cut = '\0';
2763 } else {
2764 cut = filename + strlen(filename);
2766 snprintf(cut, (sizeof(filename) - strlen(filename)) - 1, ".so");
2767 ast_log(LOG_DEBUG, "**** ModuleCheck .so file %s\n", filename);
2768 res = ast_module_check(filename);
2769 if (!res) {
2770 astman_send_error(s, m, "Module not loaded");
2771 return 0;
2773 snprintf(cut, (sizeof(filename) - strlen(filename)) - 1, ".c");
2774 ast_log(LOG_DEBUG, "**** ModuleCheck .c file %s\n", filename);
2775 #if !defined(LOW_MEMORY)
2776 version = ast_file_version_find(filename);
2777 #endif
2779 if (!ast_strlen_zero(id))
2780 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
2781 else
2782 idText[0] = '\0';
2783 astman_append(s, "Response: Success\r\n%s", idText);
2784 #if !defined(LOW_MEMORY)
2785 astman_append(s, "Version: %s\r\n\r\n", version ? version : "");
2786 #endif
2787 return 0;
2790 static char mandescr_moduleload[] =
2791 "Description: Loads, unloads or reloads an Asterisk module in a running system.\n"
2792 "Variables: \n"
2793 " ActionID: <id> Action ID for this transaction. Will be returned.\n"
2794 " Module: <name> Asterisk module name (including .so extension)\n"
2795 " or subsystem identifier:\n"
2796 " cdr, enum, dnsmgr, extconfig, manager, rtp, http\n"
2797 " LoadType: load | unload | reload\n"
2798 " The operation to be done on module\n"
2799 " If no module is specified for a reload loadtype, all modules are reloaded";
2801 static int manager_moduleload(struct mansession *s, const struct message *m)
2803 int res;
2804 const char *module = astman_get_header(m, "Module");
2805 const char *loadtype = astman_get_header(m, "LoadType");
2807 if (!loadtype || strlen(loadtype) == 0)
2808 astman_send_error(s, m, "Incomplete ModuleLoad action.");
2809 if ((!module || strlen(module) == 0) && strcasecmp(loadtype, "reload") != 0)
2810 astman_send_error(s, m, "Need module name");
2812 if (!strcasecmp(loadtype, "load")) {
2813 res = ast_load_resource(module);
2814 if (res)
2815 astman_send_error(s, m, "Could not load module.");
2816 else
2817 astman_send_ack(s, m, "Module loaded.");
2818 } else if (!strcasecmp(loadtype, "unload")) {
2819 res = ast_unload_resource(module, AST_FORCE_SOFT);
2820 if (res)
2821 astman_send_error(s, m, "Could not unload module.");
2822 else
2823 astman_send_ack(s, m, "Module unloaded.");
2824 } else if (!strcasecmp(loadtype, "reload")) {
2825 if (module != NULL) {
2826 res = ast_module_reload(module);
2827 if (res == 0)
2828 astman_send_error(s, m, "No such module.");
2829 else if (res == 1)
2830 astman_send_error(s, m, "Module does not support reload action.");
2831 else
2832 astman_send_ack(s, m, "Module reloaded.");
2833 } else {
2834 ast_module_reload(NULL); /* Reload all modules */
2835 astman_send_ack(s, m, "All modules reloaded");
2837 } else
2838 astman_send_error(s, m, "Incomplete ModuleLoad action.");
2839 return 0;
2843 * Done with the action handlers here, we start with the code in charge
2844 * of accepting connections and serving them.
2845 * accept_thread() forks a new thread for each connection, session_do(),
2846 * which in turn calls get_input() repeatedly until a full message has
2847 * been accumulated, and then invokes process_message() to pass it to
2848 * the appropriate handler.
2852 * Process an AMI message, performing desired action.
2853 * Return 0 on success, -1 on error that require the session to be destroyed.
2855 static int process_message(struct mansession *s, const struct message *m)
2857 char action[80] = "";
2858 int ret = 0;
2859 struct manager_action *tmp;
2860 const char *user = astman_get_header(m, "Username");
2862 ast_copy_string(action, __astman_get_header(m, "Action", GET_HEADER_SKIP_EMPTY), sizeof(action));
2863 ast_debug(1, "Manager received command '%s'\n", action);
2865 if (ast_strlen_zero(action)) {
2866 ast_mutex_lock(&s->__lock);
2867 astman_send_error(s, m, "Missing action in request");
2868 ast_mutex_unlock(&s->__lock);
2869 return 0;
2872 if (!s->authenticated && strcasecmp(action, "Login") && strcasecmp(action, "Logoff") && strcasecmp(action, "Challenge")) {
2873 ast_mutex_lock(&s->__lock);
2874 astman_send_error(s, m, "Permission denied");
2875 ast_mutex_unlock(&s->__lock);
2876 return 0;
2879 if (!allowmultiplelogin && !s->authenticated && user &&
2880 (!strcasecmp(action, "Login") || !strcasecmp(action, "Challenge"))) {
2881 if (check_manager_session_inuse(user)) {
2882 sleep(1);
2883 ast_mutex_lock(&s->__lock);
2884 astman_send_error(s, m, "Login Already In Use");
2885 ast_mutex_unlock(&s->__lock);
2886 return -1;
2890 AST_RWLIST_RDLOCK(&actions);
2891 AST_RWLIST_TRAVERSE(&actions, tmp, list) {
2892 if (strcasecmp(action, tmp->action))
2893 continue;
2894 if (s->writeperm & tmp->authority || tmp->authority == 0)
2895 ret = tmp->func(s, m);
2896 else
2897 astman_send_error(s, m, "Permission denied");
2898 break;
2900 AST_RWLIST_UNLOCK(&actions);
2902 if (!tmp) {
2903 char buf[512];
2904 snprintf(buf, sizeof(buf), "Invalid/unknown command: %s. Use Action: ListCommands to show available commands.", action);
2905 ast_mutex_lock(&s->__lock);
2906 astman_send_error(s, m, buf);
2907 ast_mutex_unlock(&s->__lock);
2909 if (ret)
2910 return ret;
2911 /* Once done with our message, deliver any pending events */
2912 return process_events(s);
2916 * Read one full line (including crlf) from the manager socket.
2917 * \note \verbatim
2918 * \r\n is the only valid terminator for the line.
2919 * (Note that, later, '\0' will be considered as the end-of-line marker,
2920 * so everything between the '\0' and the '\r\n' will not be used).
2921 * Also note that we assume output to have at least "maxlen" space.
2922 * \endverbatim
2924 static int get_input(struct mansession *s, char *output)
2926 int res, x;
2927 int maxlen = sizeof(s->inbuf) - 1;
2928 char *src = s->inbuf;
2931 * Look for \r\n within the buffer. If found, copy to the output
2932 * buffer and return, trimming the \r\n (not used afterwards).
2934 for (x = 0; x < s->inlen; x++) {
2935 int cr; /* set if we have \r */
2936 if (src[x] == '\r' && x+1 < s->inlen && src[x+1] == '\n')
2937 cr = 2; /* Found. Update length to include \r\n */
2938 else if (src[x] == '\n')
2939 cr = 1; /* also accept \n only */
2940 else
2941 continue;
2942 memmove(output, src, x); /*... but trim \r\n */
2943 output[x] = '\0'; /* terminate the string */
2944 x += cr; /* number of bytes used */
2945 s->inlen -= x; /* remaining size */
2946 memmove(src, src + x, s->inlen); /* remove used bytes */
2947 return 1;
2949 if (s->inlen >= maxlen) {
2950 /* no crlf found, and buffer full - sorry, too long for us */
2951 ast_log(LOG_WARNING, "Dumping long line with no return from %s: %s\n", ast_inet_ntoa(s->sin.sin_addr), src);
2952 s->inlen = 0;
2954 res = 0;
2955 while (res == 0) {
2956 /* XXX do we really need this locking ? */
2957 ast_mutex_lock(&s->__lock);
2958 if (s->pending_event) {
2959 s->pending_event = 0;
2960 ast_mutex_unlock(&s->__lock);
2961 return 0;
2963 s->waiting_thread = pthread_self();
2964 ast_mutex_unlock(&s->__lock);
2966 res = ast_wait_for_input(s->fd, -1); /* return 0 on timeout ? */
2968 ast_mutex_lock(&s->__lock);
2969 s->waiting_thread = AST_PTHREADT_NULL;
2970 ast_mutex_unlock(&s->__lock);
2972 if (res < 0) {
2973 /* If we get a signal from some other thread (typically because
2974 * there are new events queued), return 0 to notify the caller.
2976 if (errno == EINTR || errno == EAGAIN)
2977 return 0;
2978 ast_log(LOG_WARNING, "poll() returned error: %s\n", strerror(errno));
2979 return -1;
2981 ast_mutex_lock(&s->__lock);
2982 res = fread(src + s->inlen, 1, maxlen - s->inlen, s->f);
2983 if (res < 1)
2984 res = -1; /* error return */
2985 else {
2986 s->inlen += res;
2987 src[s->inlen] = '\0';
2988 res = 0;
2990 ast_mutex_unlock(&s->__lock);
2991 return res;
2994 static int do_message(struct mansession *s)
2996 struct message m = { 0 };
2997 char header_buf[sizeof(s->inbuf)] = { '\0' };
2998 int res;
3000 for (;;) {
3001 /* Check if any events are pending and do them if needed */
3002 if (process_events(s))
3003 return -1;
3004 res = get_input(s, header_buf);
3005 if (res == 0) {
3006 continue;
3007 } else if (res > 0) {
3008 if (ast_strlen_zero(header_buf))
3009 return process_message(s, &m) ? -1 : 0;
3010 else if (m.hdrcount < (AST_MAX_MANHEADERS - 1))
3011 m.headers[m.hdrcount++] = ast_strdupa(header_buf);
3012 } else {
3013 return res;
3018 /*! \brief The body of the individual manager session.
3019 * Call get_input() to read one line at a time
3020 * (or be woken up on new events), collect the lines in a
3021 * message until found an empty line, and execute the request.
3022 * In any case, deliver events asynchronously through process_events()
3023 * (called from here if no line is available, or at the end of
3024 * process_message(). )
3026 static void *session_do(void *data)
3028 struct ast_tcptls_session_instance *ser = data;
3029 struct mansession *s = ast_calloc(1, sizeof(*s));
3030 int flags;
3031 int res;
3033 if (s == NULL)
3034 goto done;
3036 s->writetimeout = 100;
3037 s->waiting_thread = AST_PTHREADT_NULL;
3039 flags = fcntl(ser->fd, F_GETFL);
3040 if (!block_sockets) /* make sure socket is non-blocking */
3041 flags |= O_NONBLOCK;
3042 else
3043 flags &= ~O_NONBLOCK;
3044 fcntl(ser->fd, F_SETFL, flags);
3046 ast_mutex_init(&s->__lock);
3047 s->send_events = -1;
3048 /* these fields duplicate those in the 'ser' structure */
3049 s->fd = ser->fd;
3050 s->f = ser->f;
3051 s->sin = ser->requestor;
3053 AST_LIST_LOCK(&sessions);
3054 AST_LIST_INSERT_HEAD(&sessions, s, list);
3055 ast_atomic_fetchadd_int(&num_sessions, 1);
3056 AST_LIST_UNLOCK(&sessions);
3057 /* Hook to the tail of the event queue */
3058 s->last_ev = grab_last();
3059 s->f = ser->f;
3060 astman_append(s, "Asterisk Call Manager/%s\r\n", AMI_VERSION); /* welcome prompt */
3061 for (;;) {
3062 if ((res = do_message(s)) < 0)
3063 break;
3065 /* session is over, explain why and terminate */
3066 if (s->authenticated) {
3067 if (manager_displayconnects(s))
3068 ast_verb(2, "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3069 ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3070 } else {
3071 if (displayconnects)
3072 ast_verb(2, "Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr));
3073 ast_log(LOG_EVENT, "Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr));
3076 /* It is possible under certain circumstances for this session thread
3077 to complete its work and exit *before* the thread that created it
3078 has finished executing the ast_pthread_create_background() function.
3079 If this occurs, some versions of glibc appear to act in a buggy
3080 fashion and attempt to write data into memory that it thinks belongs
3081 to the thread but is in fact not owned by the thread (or may have
3082 been freed completely).
3084 Causing this thread to yield to other threads at least one time
3085 appears to work around this bug.
3087 usleep(1);
3089 destroy_session(s);
3091 done:
3092 ser = ast_tcptls_session_instance_destroy(ser);
3093 return NULL;
3096 /*! \brief remove at most n_max stale session from the list. */
3097 static void purge_sessions(int n_max)
3099 struct mansession *s;
3100 time_t now = time(NULL);
3102 AST_LIST_LOCK(&sessions);
3103 AST_LIST_TRAVERSE_SAFE_BEGIN(&sessions, s, list) {
3104 if (s->sessiontimeout && (now > s->sessiontimeout) && !s->inuse) {
3105 AST_LIST_REMOVE_CURRENT(list);
3106 ast_atomic_fetchadd_int(&num_sessions, -1);
3107 if (s->authenticated && (VERBOSITY_ATLEAST(2)) && manager_displayconnects(s)) {
3108 ast_verb(2, "HTTP Manager '%s' timed out from %s\n",
3109 s->username, ast_inet_ntoa(s->sin.sin_addr));
3111 free_session(s); /* XXX outside ? */
3112 if (--n_max <= 0)
3113 break;
3116 AST_LIST_TRAVERSE_SAFE_END;
3117 AST_LIST_UNLOCK(&sessions);
3121 * events are appended to a queue from where they
3122 * can be dispatched to clients.
3124 static int append_event(const char *str, int category)
3126 struct eventqent *tmp = ast_malloc(sizeof(*tmp) + strlen(str));
3127 static int seq; /* sequence number */
3129 if (!tmp)
3130 return -1;
3132 /* need to init all fields, because ast_malloc() does not */
3133 tmp->usecount = 0;
3134 tmp->category = category;
3135 tmp->seq = ast_atomic_fetchadd_int(&seq, 1);
3136 AST_LIST_NEXT(tmp, eq_next) = NULL;
3137 strcpy(tmp->eventdata, str);
3139 AST_LIST_LOCK(&all_events);
3140 AST_LIST_INSERT_TAIL(&all_events, tmp, eq_next);
3141 AST_LIST_UNLOCK(&all_events);
3143 return 0;
3146 /* XXX see if can be moved inside the function */
3147 AST_THREADSTORAGE(manager_event_buf);
3148 #define MANAGER_EVENT_BUF_INITSIZE 256
3150 /*! \brief manager_event: Send AMI event to client */
3151 int __manager_event(int category, const char *event,
3152 const char *file, int line, const char *func, const char *fmt, ...)
3154 struct mansession *s;
3155 struct manager_custom_hook *hook;
3156 struct ast_str *auth = ast_str_alloca(80);
3157 const char *cat_str;
3158 va_list ap;
3159 struct timeval now;
3160 struct ast_str *buf;
3162 /* Abort if there aren't any manager sessions */
3163 if (!num_sessions)
3164 return 0;
3166 if (!(buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE)))
3167 return -1;
3169 cat_str = authority_to_str(category, &auth);
3170 ast_str_set(&buf, 0,
3171 "Event: %s\r\nPrivilege: %s\r\n",
3172 event, cat_str);
3174 if (timestampevents) {
3175 now = ast_tvnow();
3176 ast_str_append(&buf, 0,
3177 "Timestamp: %ld.%06lu\r\n",
3178 (long)now.tv_sec, (unsigned long) now.tv_usec);
3180 if (manager_debug) {
3181 static int seq;
3182 ast_str_append(&buf, 0,
3183 "SequenceNumber: %d\r\n",
3184 ast_atomic_fetchadd_int(&seq, 1));
3185 ast_str_append(&buf, 0,
3186 "File: %s\r\nLine: %d\r\nFunc: %s\r\n", file, line, func);
3189 va_start(ap, fmt);
3190 ast_str_append_va(&buf, 0, fmt, ap);
3191 va_end(ap);
3193 ast_str_append(&buf, 0, "\r\n");
3195 append_event(buf->str, category);
3197 /* Wake up any sleeping sessions */
3198 AST_LIST_LOCK(&sessions);
3199 AST_LIST_TRAVERSE(&sessions, s, list) {
3200 ast_mutex_lock(&s->__lock);
3201 if (s->waiting_thread != AST_PTHREADT_NULL)
3202 pthread_kill(s->waiting_thread, SIGURG);
3203 else
3204 /* We have an event to process, but the mansession is
3205 * not waiting for it. We still need to indicate that there
3206 * is an event waiting so that get_input processes the pending
3207 * event instead of polling.
3209 s->pending_event = 1;
3210 ast_mutex_unlock(&s->__lock);
3212 AST_LIST_UNLOCK(&sessions);
3214 AST_RWLIST_RDLOCK(&manager_hooks);
3215 AST_RWLIST_TRAVERSE(&manager_hooks, hook, list) {
3216 hook->helper(category, event, buf->str);
3218 AST_RWLIST_UNLOCK(&manager_hooks);
3220 return 0;
3224 * support functions to register/unregister AMI action handlers,
3226 int ast_manager_unregister(char *action)
3228 struct manager_action *cur;
3230 AST_RWLIST_WRLOCK(&actions);
3231 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&actions, cur, list) {
3232 if (!strcasecmp(action, cur->action)) {
3233 AST_RWLIST_REMOVE_CURRENT(list);
3234 ast_free(cur);
3235 ast_verb(2, "Manager unregistered action %s\n", action);
3236 break;
3239 AST_RWLIST_TRAVERSE_SAFE_END;
3240 AST_RWLIST_UNLOCK(&actions);
3242 return 0;
3245 static int manager_state_cb(char *context, char *exten, int state, void *data)
3247 /* Notify managers of change */
3248 char hint[512];
3249 ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten);
3251 manager_event(EVENT_FLAG_CALL, "ExtensionStatus", "Exten: %s\r\nContext: %s\r\nHint: %s\r\nStatus: %d\r\n", exten, context, hint, state);
3252 return 0;
3255 static int ast_manager_register_struct(struct manager_action *act)
3257 struct manager_action *cur, *prev = NULL;
3259 AST_RWLIST_WRLOCK(&actions);
3260 AST_RWLIST_TRAVERSE(&actions, cur, list) {
3261 int ret = strcasecmp(cur->action, act->action);
3262 if (ret == 0) {
3263 ast_log(LOG_WARNING, "Manager: Action '%s' already registered\n", act->action);
3264 AST_RWLIST_UNLOCK(&actions);
3265 return -1;
3267 if (ret > 0) { /* Insert these alphabetically */
3268 prev = cur;
3269 break;
3273 if (prev)
3274 AST_RWLIST_INSERT_AFTER(&actions, prev, act, list);
3275 else
3276 AST_RWLIST_INSERT_HEAD(&actions, act, list);
3278 ast_verb(2, "Manager registered action %s\n", act->action);
3280 AST_RWLIST_UNLOCK(&actions);
3282 return 0;
3285 /*! \brief register a new command with manager, including online help. This is
3286 the preferred way to register a manager command */
3287 int ast_manager_register2(const char *action, int auth, int (*func)(struct mansession *s, const struct message *m), const char *synopsis, const char *description)
3289 struct manager_action *cur = NULL;
3291 if (!(cur = ast_calloc(1, sizeof(*cur))))
3292 return -1;
3294 cur->action = action;
3295 cur->authority = auth;
3296 cur->func = func;
3297 cur->synopsis = synopsis;
3298 cur->description = description;
3300 ast_manager_register_struct(cur);
3302 return 0;
3304 /*! @}
3305 END Doxygen group */
3308 * The following are support functions for AMI-over-http.
3309 * The common entry point is generic_http_callback(),
3310 * which extracts HTTP header and URI fields and reformats
3311 * them into AMI messages, locates a proper session
3312 * (using the mansession_id Cookie or GET variable),
3313 * and calls process_message() as for regular AMI clients.
3314 * When done, the output (which goes to a temporary file)
3315 * is read back into a buffer and reformatted as desired,
3316 * then fed back to the client over the original socket.
3319 enum output_format {
3320 FORMAT_RAW,
3321 FORMAT_HTML,
3322 FORMAT_XML,
3325 static char *contenttype[] = {
3326 [FORMAT_RAW] = "plain",
3327 [FORMAT_HTML] = "html",
3328 [FORMAT_XML] = "xml",
3332 * locate an http session in the list. The search key (ident) is
3333 * the value of the mansession_id cookie (0 is not valid and means
3334 * a session on the AMI socket).
3336 static struct mansession *find_session(uint32_t ident, int incinuse)
3338 struct mansession *s;
3340 if (ident == 0)
3341 return NULL;
3343 AST_LIST_LOCK(&sessions);
3344 AST_LIST_TRAVERSE(&sessions, s, list) {
3345 ast_mutex_lock(&s->__lock);
3346 if (s->managerid == ident && !s->needdestroy) {
3347 ast_atomic_fetchadd_int(&s->inuse, incinuse ? 1 : 0);
3348 break;
3350 ast_mutex_unlock(&s->__lock);
3352 AST_LIST_UNLOCK(&sessions);
3354 return s;
3357 int astman_is_authed(uint32_t ident)
3359 int authed;
3360 struct mansession *s;
3362 if (!(s = find_session(ident, 0)))
3363 return 0;
3365 authed = (s->authenticated != 0);
3367 ast_mutex_unlock(&s->__lock);
3369 return authed;
3372 int astman_verify_session_readpermissions(uint32_t ident, int perm)
3374 int result = 0;
3375 struct mansession *s;
3377 AST_LIST_LOCK(&sessions);
3378 AST_LIST_TRAVERSE(&sessions, s, list) {
3379 ast_mutex_lock(&s->__lock);
3380 if ((s->managerid == ident) && (s->readperm & perm)) {
3381 result = 1;
3382 ast_mutex_unlock(&s->__lock);
3383 break;
3385 ast_mutex_unlock(&s->__lock);
3387 AST_LIST_UNLOCK(&sessions);
3388 return result;
3391 int astman_verify_session_writepermissions(uint32_t ident, int perm)
3393 int result = 0;
3394 struct mansession *s;
3396 AST_LIST_LOCK(&sessions);
3397 AST_LIST_TRAVERSE(&sessions, s, list) {
3398 ast_mutex_lock(&s->__lock);
3399 if ((s->managerid == ident) && (s->writeperm & perm)) {
3400 result = 1;
3401 ast_mutex_unlock(&s->__lock);
3402 break;
3404 ast_mutex_unlock(&s->__lock);
3406 AST_LIST_UNLOCK(&sessions);
3407 return result;
3411 * convert to xml with various conversion:
3412 * mode & 1 -> lowercase;
3413 * mode & 2 -> replace non-alphanumeric chars with underscore
3415 static void xml_copy_escape(struct ast_str **out, const char *src, int mode)
3417 /* store in a local buffer to avoid calling ast_str_append too often */
3418 char buf[256];
3419 char *dst = buf;
3420 int space = sizeof(buf);
3421 /* repeat until done and nothing to flush */
3422 for ( ; *src || dst != buf ; src++) {
3423 if (*src == '\0' || space < 10) { /* flush */
3424 *dst++ = '\0';
3425 ast_str_append(out, 0, "%s", buf);
3426 dst = buf;
3427 space = sizeof(buf);
3428 if (*src == '\0')
3429 break;
3432 if ( (mode & 2) && !isalnum(*src)) {
3433 *dst++ = '_';
3434 space--;
3435 continue;
3437 switch (*src) {
3438 case '<':
3439 strcpy(dst, "&lt;");
3440 dst += 4;
3441 space -= 4;
3442 break;
3443 case '>':
3444 strcpy(dst, "&gt;");
3445 dst += 4;
3446 space -= 4;
3447 break;
3448 case '\"':
3449 strcpy(dst, "&quot;");
3450 dst += 6;
3451 space -= 6;
3452 break;
3453 case '\'':
3454 strcpy(dst, "&apos;");
3455 dst += 6;
3456 space -= 6;
3457 break;
3458 case '&':
3459 strcpy(dst, "&amp;");
3460 dst += 5;
3461 space -= 5;
3462 break;
3464 default:
3465 *dst++ = mode ? tolower(*src) : *src;
3466 space--;
3471 struct variable_count {
3472 char *varname;
3473 int count;
3476 static int compress_char(char c)
3478 c &= 0x7f;
3479 if (c < 32)
3480 return 0;
3481 else if (c >= 'a' && c <= 'z')
3482 return c - 64;
3483 else if (c > 'z')
3484 return '_';
3485 else
3486 return c - 32;
3489 static int variable_count_hash_fn(const void *vvc, const int flags)
3491 const struct variable_count *vc = vvc;
3492 int res = 0, i;
3493 for (i = 0; i < 5; i++) {
3494 if (vc->varname[i] == '\0')
3495 break;
3496 res += compress_char(vc->varname[i]) << (i * 6);
3498 return res;
3501 static int variable_count_cmp_fn(void *obj, void *vstr, int flags)
3503 /* Due to the simplicity of struct variable_count, it makes no difference
3504 * if you pass in objects or strings, the same operation applies. This is
3505 * due to the fact that the hash occurs on the first element, which means
3506 * the address of both the struct and the string are exactly the same. */
3507 struct variable_count *vc = obj;
3508 char *str = vstr;
3509 return !strcmp(vc->varname, str) ? CMP_MATCH : 0;
3512 /*! \brief Convert the input into XML or HTML.
3513 * The input is supposed to be a sequence of lines of the form
3514 * Name: value
3515 * optionally followed by a blob of unformatted text.
3516 * A blank line is a section separator. Basically, this is a
3517 * mixture of the format of Manager Interface and CLI commands.
3518 * The unformatted text is considered as a single value of a field
3519 * named 'Opaque-data'.
3521 * At the moment the output format is the following (but it may
3522 * change depending on future requirements so don't count too
3523 * much on it when writing applications):
3525 * General: the unformatted text is used as a value of
3526 * XML output: to be completed
3528 * \verbatim
3529 * Each section is within <response type="object" id="xxx">
3530 * where xxx is taken from ajaxdest variable or defaults to unknown
3531 * Each row is reported as an attribute Name="value" of an XML
3532 * entity named from the variable ajaxobjtype, default to "generic"
3533 * \endverbatim
3535 * HTML output:
3536 * each Name-value pair is output as a single row of a two-column table.
3537 * Sections (blank lines in the input) are separated by a <HR>
3540 static void xml_translate(struct ast_str **out, char *in, struct ast_variable *vars, enum output_format format)
3542 struct ast_variable *v;
3543 const char *dest = NULL;
3544 char *var, *val;
3545 const char *objtype = NULL;
3546 int in_data = 0; /* parsing data */
3547 int inobj = 0;
3548 int xml = (format == FORMAT_XML);
3549 struct variable_count *vc = NULL;
3550 struct ao2_container *vco = NULL;
3552 for (v = vars; v; v = v->next) {
3553 if (!dest && !strcasecmp(v->name, "ajaxdest"))
3554 dest = v->value;
3555 else if (!objtype && !strcasecmp(v->name, "ajaxobjtype"))
3556 objtype = v->value;
3558 if (!dest)
3559 dest = "unknown";
3560 if (!objtype)
3561 objtype = "generic";
3563 /* we want to stop when we find an empty line */
3564 while (in && *in) {
3565 val = strsep(&in, "\r\n"); /* mark start and end of line */
3566 if (in && *in == '\n') /* remove trailing \n if any */
3567 in++;
3568 ast_trim_blanks(val);
3569 ast_debug(5, "inobj %d in_data %d line <%s>\n", inobj, in_data, val);
3570 if (ast_strlen_zero(val)) {
3571 if (in_data) { /* close data */
3572 ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
3573 in_data = 0;
3575 if (inobj) {
3576 ast_str_append(out, 0, xml ? " /></response>\n" :
3577 "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
3578 inobj = 0;
3579 ao2_ref(vco, -1);
3580 vco = NULL;
3582 continue;
3585 /* we expect Name: value lines */
3586 if (in_data) {
3587 var = NULL;
3588 } else {
3589 var = strsep(&val, ":");
3590 if (val) { /* found the field name */
3591 val = ast_skip_blanks(val);
3592 ast_trim_blanks(var);
3593 } else { /* field name not found, move to opaque mode */
3594 val = var;
3595 var = "Opaque-data";
3599 if (!inobj) {
3600 if (xml)
3601 ast_str_append(out, 0, "<response type='object' id='%s'><%s", dest, objtype);
3602 else
3603 ast_str_append(out, 0, "<body>\n");
3604 vco = ao2_container_alloc(37, variable_count_hash_fn, variable_count_cmp_fn);
3605 inobj = 1;
3608 if (!in_data) { /* build appropriate line start */
3609 ast_str_append(out, 0, xml ? " " : "<tr><td>");
3610 if ((vc = ao2_find(vco, var, 0)))
3611 vc->count++;
3612 else {
3613 /* Create a new entry for this one */
3614 vc = ao2_alloc(sizeof(*vc), NULL);
3615 vc->varname = var;
3616 vc->count = 1;
3617 ao2_link(vco, vc);
3619 xml_copy_escape(out, var, xml ? 1 | 2 : 0);
3620 if (vc->count > 1)
3621 ast_str_append(out, 0, "-%d", vc->count);
3622 ao2_ref(vc, -1);
3623 ast_str_append(out, 0, xml ? "='" : "</td><td>");
3624 if (!strcmp(var, "Opaque-data"))
3625 in_data = 1;
3627 xml_copy_escape(out, val, 0); /* data field */
3628 if (!in_data)
3629 ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
3630 else
3631 ast_str_append(out, 0, xml ? "\n" : "<br>\n");
3633 if (inobj) {
3634 ast_str_append(out, 0, xml ? " /></response>\n" :
3635 "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
3636 ao2_ref(vco, -1);
3640 static struct ast_str *generic_http_callback(enum output_format format,
3641 struct sockaddr_in *requestor, const char *uri, enum ast_http_method method,
3642 struct ast_variable *params, int *status,
3643 char **title, int *contentlength)
3645 struct mansession *s = NULL;
3646 uint32_t ident = 0;
3647 int blastaway = 0;
3648 struct ast_variable *v;
3649 char template[] = "/tmp/ast-http-XXXXXX"; /* template for temporary file */
3650 struct ast_str *out = NULL;
3651 struct message m = { 0 };
3652 unsigned int x;
3653 size_t hdrlen;
3655 for (v = params; v; v = v->next) {
3656 if (!strcasecmp(v->name, "mansession_id")) {
3657 sscanf(v->value, "%x", &ident);
3658 break;
3662 if (!(s = find_session(ident, 1))) {
3663 /* Create new session.
3664 * While it is not in the list we don't need any locking
3666 if (!(s = ast_calloc(1, sizeof(*s)))) {
3667 *status = 500;
3668 goto generic_callback_out;
3670 s->sin = *requestor;
3671 s->fd = -1;
3672 s->waiting_thread = AST_PTHREADT_NULL;
3673 s->send_events = 0;
3674 ast_mutex_init(&s->__lock);
3675 ast_mutex_lock(&s->__lock);
3676 s->inuse = 1;
3677 /*!\note There is approximately a 1 in 1.8E19 chance that the following
3678 * calculation will produce 0, which is an invalid ID, but due to the
3679 * properties of the rand() function (and the constantcy of s), that
3680 * won't happen twice in a row.
3682 while ((s->managerid = rand() ^ (unsigned long) s) == 0);
3683 s->last_ev = grab_last();
3684 AST_LIST_LOCK(&sessions);
3685 AST_LIST_INSERT_HEAD(&sessions, s, list);
3686 ast_atomic_fetchadd_int(&num_sessions, 1);
3687 AST_LIST_UNLOCK(&sessions);
3690 ast_mutex_unlock(&s->__lock);
3692 if (!(out = ast_str_create(1024))) {
3693 *status = 500;
3694 goto generic_callback_out;
3697 s->fd = mkstemp(template); /* create a temporary file for command output */
3698 unlink(template);
3699 s->f = fdopen(s->fd, "w+");
3701 for (x = 0, v = params; v && (x < AST_MAX_MANHEADERS); x++, v = v->next) {
3702 hdrlen = strlen(v->name) + strlen(v->value) + 3;
3703 m.headers[m.hdrcount] = alloca(hdrlen);
3704 snprintf((char *) m.headers[m.hdrcount], hdrlen, "%s: %s", v->name, v->value);
3705 ast_verb(4, "HTTP Manager add header %s\n", m.headers[m.hdrcount]);
3706 m.hdrcount = x + 1;
3709 if (process_message(s, &m)) {
3710 if (s->authenticated) {
3711 if (manager_displayconnects(s)) {
3712 ast_verb(2, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3714 ast_log(LOG_EVENT, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3715 } else {
3716 if (displayconnects) {
3717 ast_verb(2, "HTTP Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr));
3719 ast_log(LOG_EVENT, "HTTP Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr));
3721 s->needdestroy = 1;
3724 ast_str_append(&out, 0,
3725 "Content-type: text/%s\r\n"
3726 "Cache-Control: no-cache;\r\n"
3727 "Set-Cookie: mansession_id=\"%08x\"; Version=\"1\"; Max-Age=%d\r\n"
3728 "\r\n",
3729 contenttype[format],
3730 s->managerid, httptimeout);
3732 if (format == FORMAT_XML) {
3733 ast_str_append(&out, 0, "<ajax-response>\n");
3734 } else if (format == FORMAT_HTML) {
3736 * When handling AMI-over-HTTP in HTML format, we provide a simple form for
3737 * debugging purposes. This HTML code should not be here, we
3738 * should read from some config file...
3741 #define ROW_FMT "<tr><td colspan=\"2\" bgcolor=\"#f1f1ff\">%s</td></tr>\r\n"
3742 #define TEST_STRING \
3743 "<form action=\"manager\">\n\
3744 Action: <select name=\"action\">\n\
3745 <option value=\"\">-----&gt;</option>\n\
3746 <option value=\"login\">login</option>\n\
3747 <option value=\"command\">Command</option>\n\
3748 <option value=\"waitevent\">waitevent</option>\n\
3749 <option value=\"listcommands\">listcommands</option>\n\
3750 </select>\n\
3751 or <input name=\"action\"><br/>\n\
3752 CLI Command <input name=\"command\"><br>\n\
3753 user <input name=\"username\"> pass <input type=\"password\" name=\"secret\"><br>\n\
3754 <input type=\"submit\">\n</form>\n"
3756 ast_str_append(&out, 0, "<title>Asterisk&trade; Manager Interface</title>");
3757 ast_str_append(&out, 0, "<body bgcolor=\"#ffffff\"><table align=center bgcolor=\"#f1f1f1\" width=\"500\">\r\n");
3758 ast_str_append(&out, 0, ROW_FMT, "<h1>Manager Tester</h1>");
3759 ast_str_append(&out, 0, ROW_FMT, TEST_STRING);
3762 if (s->f != NULL) { /* have temporary output */
3763 char *buf;
3764 size_t l = ftell(s->f);
3766 if (l) {
3767 if ((buf = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_SHARED, s->fd, 0))) {
3768 if (format == FORMAT_XML || format == FORMAT_HTML)
3769 xml_translate(&out, buf, params, format);
3770 else
3771 ast_str_append(&out, 0, "%s", buf);
3772 munmap(buf, l);
3774 } else if (format == FORMAT_XML || format == FORMAT_HTML) {
3775 xml_translate(&out, "", params, format);
3777 fclose(s->f);
3778 s->f = NULL;
3779 s->fd = -1;
3782 if (format == FORMAT_XML) {
3783 ast_str_append(&out, 0, "</ajax-response>\n");
3784 } else if (format == FORMAT_HTML)
3785 ast_str_append(&out, 0, "</table></body>\r\n");
3787 ast_mutex_lock(&s->__lock);
3788 /* Reset HTTP timeout. If we're not authenticated, keep it extremely short */
3789 s->sessiontimeout = time(NULL) + ((s->authenticated || httptimeout < 5) ? httptimeout : 5);
3791 if (s->needdestroy) {
3792 if (s->inuse == 1) {
3793 ast_debug(1, "Need destroy, doing it now!\n");
3794 blastaway = 1;
3795 } else {
3796 ast_debug(1, "Need destroy, but can't do it yet!\n");
3797 if (s->waiting_thread != AST_PTHREADT_NULL)
3798 pthread_kill(s->waiting_thread, SIGURG);
3799 s->inuse--;
3801 } else
3802 s->inuse--;
3803 ast_mutex_unlock(&s->__lock);
3805 if (blastaway)
3806 destroy_session(s);
3807 generic_callback_out:
3808 if (*status != 200)
3809 return ast_http_error(500, "Server Error", NULL, "Internal Server Error (out of memory)\n");
3810 return out;
3813 static struct ast_str *manager_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *params, struct ast_variable *headers, int *status, char **title, int *contentlength)
3815 return generic_http_callback(FORMAT_HTML, &ser->requestor, uri, method, params, status, title, contentlength);
3818 static struct ast_str *mxml_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *params, struct ast_variable *headers, int *status, char **title, int *contentlength)
3820 return generic_http_callback(FORMAT_XML, &ser->requestor, uri, method, params, status, title, contentlength);
3823 static struct ast_str *rawman_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *params, struct ast_variable *headers, int *status, char **title, int *contentlength)
3825 return generic_http_callback(FORMAT_RAW, &ser->requestor, uri, method, params, status, title, contentlength);
3828 struct ast_http_uri rawmanuri = {
3829 .description = "Raw HTTP Manager Event Interface",
3830 .uri = "rawman",
3831 .callback = rawman_http_callback,
3832 .supports_get = 1,
3833 .data = NULL,
3834 .key = __FILE__,
3837 struct ast_http_uri manageruri = {
3838 .description = "HTML Manager Event Interface",
3839 .uri = "manager",
3840 .callback = manager_http_callback,
3841 .supports_get = 1,
3842 .data = NULL,
3843 .key = __FILE__,
3846 struct ast_http_uri managerxmluri = {
3847 .description = "XML Manager Event Interface",
3848 .uri = "mxml",
3849 .callback = mxml_http_callback,
3850 .supports_get = 1,
3851 .data = NULL,
3852 .key = __FILE__,
3855 static int registered = 0;
3856 static int webregged = 0;
3858 /*! \brief cleanup code called at each iteration of server_root,
3859 * guaranteed to happen every 5 seconds at most
3861 static void purge_old_stuff(void *data)
3863 purge_sessions(1);
3864 purge_events();
3867 struct ast_tls_config ami_tls_cfg;
3868 static struct server_args ami_desc = {
3869 .accept_fd = -1,
3870 .master = AST_PTHREADT_NULL,
3871 .tls_cfg = NULL,
3872 .poll_timeout = 5000, /* wake up every 5 seconds */
3873 .periodic_fn = purge_old_stuff,
3874 .name = "AMI server",
3875 .accept_fn = ast_tcptls_server_root, /* thread doing the accept() */
3876 .worker_fn = session_do, /* thread handling the session */
3879 static struct server_args amis_desc = {
3880 .accept_fd = -1,
3881 .master = AST_PTHREADT_NULL,
3882 .tls_cfg = &ami_tls_cfg,
3883 .poll_timeout = -1, /* the other does the periodic cleanup */
3884 .name = "AMI TLS server",
3885 .accept_fn = ast_tcptls_server_root, /* thread doing the accept() */
3886 .worker_fn = session_do, /* thread handling the session */
3889 static int __init_manager(int reload)
3891 struct ast_config *ucfg = NULL, *cfg = NULL;
3892 const char *val;
3893 char *cat = NULL;
3894 int newhttptimeout = 60;
3895 int have_sslbindaddr = 0;
3896 struct hostent *hp;
3897 struct ast_hostent ahp;
3898 struct ast_manager_user *user = NULL;
3899 struct ast_variable *var;
3900 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
3902 manager_enabled = 0;
3904 if (!registered) {
3905 /* Register default actions */
3906 ast_manager_register2("Ping", 0, action_ping, "Keepalive command", mandescr_ping);
3907 ast_manager_register2("Events", 0, action_events, "Control Event Flow", mandescr_events);
3908 ast_manager_register2("Logoff", 0, action_logoff, "Logoff Manager", mandescr_logoff);
3909 ast_manager_register2("Login", 0, action_login, "Login Manager", NULL);
3910 ast_manager_register2("Challenge", 0, action_challenge, "Generate Challenge for MD5 Auth", NULL);
3911 ast_manager_register2("Hangup", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, action_hangup, "Hangup Channel", mandescr_hangup);
3912 ast_manager_register2("Status", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_status, "Lists channel status", mandescr_status);
3913 ast_manager_register2("Setvar", EVENT_FLAG_CALL, action_setvar, "Set Channel Variable", mandescr_setvar);
3914 ast_manager_register2("Getvar", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_getvar, "Gets a Channel Variable", mandescr_getvar);
3915 ast_manager_register2("GetConfig", EVENT_FLAG_SYSTEM | EVENT_FLAG_CONFIG, action_getconfig, "Retrieve configuration", mandescr_getconfig);
3916 ast_manager_register2("GetConfigJSON", EVENT_FLAG_SYSTEM | EVENT_FLAG_CONFIG, action_getconfigjson, "Retrieve configuration (JSON format)", mandescr_getconfigjson);
3917 ast_manager_register2("UpdateConfig", EVENT_FLAG_CONFIG, action_updateconfig, "Update basic configuration", mandescr_updateconfig);
3918 ast_manager_register2("CreateConfig", EVENT_FLAG_CONFIG, action_createconfig, "Creates an empty file in the configuration directory", mandescr_createconfig);
3919 ast_manager_register2("ListCategories", EVENT_FLAG_CONFIG, action_listcategories, "List categories in configuration file", mandescr_listcategories);
3920 ast_manager_register2("Redirect", EVENT_FLAG_CALL, action_redirect, "Redirect (transfer) a call", mandescr_redirect );
3921 ast_manager_register2("Atxfer", EVENT_FLAG_CALL, action_atxfer, "Attended transfer", mandescr_atxfer);
3922 ast_manager_register2("Originate", EVENT_FLAG_ORIGINATE, action_originate, "Originate Call", mandescr_originate);
3923 ast_manager_register2("Command", EVENT_FLAG_COMMAND, action_command, "Execute Asterisk CLI Command", mandescr_command );
3924 ast_manager_register2("ExtensionState", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstate, "Check Extension Status", mandescr_extensionstate );
3925 ast_manager_register2("AbsoluteTimeout", EVENT_FLAG_SYSTEM | EVENT_FLAG_CALL, action_timeout, "Set Absolute Timeout", mandescr_timeout );
3926 ast_manager_register2("MailboxStatus", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxstatus, "Check Mailbox", mandescr_mailboxstatus );
3927 ast_manager_register2("MailboxCount", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_mailboxcount, "Check Mailbox Message Count", mandescr_mailboxcount );
3928 ast_manager_register2("ListCommands", 0, action_listcommands, "List available manager commands", mandescr_listcommands);
3929 ast_manager_register2("SendText", EVENT_FLAG_CALL, action_sendtext, "Send text message to channel", mandescr_sendtext);
3930 ast_manager_register2("UserEvent", EVENT_FLAG_USER, action_userevent, "Send an arbitrary event", mandescr_userevent);
3931 ast_manager_register2("WaitEvent", 0, action_waitevent, "Wait for an event to occur", mandescr_waitevent);
3932 ast_manager_register2("CoreSettings", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_coresettings, "Show PBX core settings (version etc)", mandescr_coresettings);
3933 ast_manager_register2("CoreStatus", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_corestatus, "Show PBX core status variables", mandescr_corestatus);
3934 ast_manager_register2("Reload", EVENT_FLAG_CONFIG | EVENT_FLAG_SYSTEM, action_reload, "Send a reload event", mandescr_reload);
3935 ast_manager_register2("CoreShowChannels", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, action_coreshowchannels, "List currently active channels", mandescr_coreshowchannels);
3936 ast_manager_register2("ModuleLoad", EVENT_FLAG_SYSTEM, manager_moduleload, "Module management", mandescr_moduleload);
3937 ast_manager_register2("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck, "Check if module is loaded", mandescr_modulecheck);
3939 ast_cli_register_multiple(cli_manager, sizeof(cli_manager) / sizeof(struct ast_cli_entry));
3940 ast_extension_state_add(NULL, NULL, manager_state_cb, NULL);
3941 registered = 1;
3942 /* Append placeholder event so master_eventq never runs dry */
3943 append_event("Event: Placeholder\r\n\r\n", 0);
3945 if ((cfg = ast_config_load2("manager.conf", "manager", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
3946 return 0;
3948 displayconnects = 1;
3949 if (!cfg) {
3950 ast_log(LOG_NOTICE, "Unable to open AMI configuration manager.conf. Asterisk management interface (AMI) disabled.\n");
3951 return 0;
3954 /* default values */
3955 memset(&ami_desc.sin, 0, sizeof(struct sockaddr_in));
3956 memset(&amis_desc.sin, 0, sizeof(amis_desc.sin));
3957 amis_desc.sin.sin_port = htons(5039);
3958 ami_desc.sin.sin_port = htons(DEFAULT_MANAGER_PORT);
3960 ami_tls_cfg.enabled = 0;
3961 if (ami_tls_cfg.certfile)
3962 ast_free(ami_tls_cfg.certfile);
3963 ami_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
3964 if (ami_tls_cfg.cipher)
3965 ast_free(ami_tls_cfg.cipher);
3966 ami_tls_cfg.cipher = ast_strdup("");
3968 for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
3969 val = var->value;
3970 if (!strcasecmp(var->name, "sslenable"))
3971 ami_tls_cfg.enabled = ast_true(val);
3972 else if (!strcasecmp(var->name, "sslbindport"))
3973 amis_desc.sin.sin_port = htons(atoi(val));
3974 else if (!strcasecmp(var->name, "sslbindaddr")) {
3975 if ((hp = ast_gethostbyname(val, &ahp))) {
3976 memcpy(&amis_desc.sin.sin_addr, hp->h_addr, sizeof(amis_desc.sin.sin_addr));
3977 have_sslbindaddr = 1;
3978 } else {
3979 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", val);
3981 } else if (!strcasecmp(var->name, "sslcert")) {
3982 ast_free(ami_tls_cfg.certfile);
3983 ami_tls_cfg.certfile = ast_strdup(val);
3984 } else if (!strcasecmp(var->name, "sslcipher")) {
3985 ast_free(ami_tls_cfg.cipher);
3986 ami_tls_cfg.cipher = ast_strdup(val);
3987 } else if (!strcasecmp(var->name, "enabled")) {
3988 manager_enabled = ast_true(val);
3989 } else if (!strcasecmp(var->name, "block-sockets")) {
3990 block_sockets = ast_true(val);
3991 } else if (!strcasecmp(var->name, "webenabled")) {
3992 webmanager_enabled = ast_true(val);
3993 } else if (!strcasecmp(var->name, "port")) {
3994 ami_desc.sin.sin_port = htons(atoi(val));
3995 } else if (!strcasecmp(var->name, "bindaddr")) {
3996 if (!inet_aton(val, &ami_desc.sin.sin_addr)) {
3997 ast_log(LOG_WARNING, "Invalid address '%s' specified, using 0.0.0.0\n", val);
3998 memset(&ami_desc.sin.sin_addr, 0, sizeof(ami_desc.sin.sin_addr));
4000 } else if (!strcasecmp(var->name, "allowmultiplelogin")) {
4001 allowmultiplelogin = ast_true(val);
4002 } else if (!strcasecmp(var->name, "displayconnects")) {
4003 displayconnects = ast_true(val);
4004 } else if (!strcasecmp(var->name, "timestampevents")) {
4005 timestampevents = ast_true(val);
4006 } else if (!strcasecmp(var->name, "debug")) {
4007 manager_debug = ast_true(val);
4008 } else if (!strcasecmp(var->name, "httptimeout")) {
4009 newhttptimeout = atoi(val);
4010 } else {
4011 ast_log(LOG_NOTICE, "Invalid keyword <%s> = <%s> in manager.conf [general]\n",
4012 var->name, val);
4016 if (manager_enabled)
4017 ami_desc.sin.sin_family = AF_INET;
4018 if (!have_sslbindaddr)
4019 amis_desc.sin.sin_addr = ami_desc.sin.sin_addr;
4020 if (ami_tls_cfg.enabled)
4021 amis_desc.sin.sin_family = AF_INET;
4024 AST_RWLIST_WRLOCK(&users);
4026 /* First, get users from users.conf */
4027 ucfg = ast_config_load2("users.conf", "manager", config_flags);
4028 if (ucfg && (ucfg != CONFIG_STATUS_FILEUNCHANGED)) {
4029 const char *hasmanager;
4030 int genhasmanager = ast_true(ast_variable_retrieve(ucfg, "general", "hasmanager"));
4032 while ((cat = ast_category_browse(ucfg, cat))) {
4033 if (!strcasecmp(cat, "general"))
4034 continue;
4036 hasmanager = ast_variable_retrieve(ucfg, cat, "hasmanager");
4037 if ((!hasmanager && genhasmanager) || ast_true(hasmanager)) {
4038 const char *user_secret = ast_variable_retrieve(ucfg, cat, "secret");
4039 const char *user_read = ast_variable_retrieve(ucfg, cat, "read");
4040 const char *user_write = ast_variable_retrieve(ucfg, cat, "write");
4041 const char *user_displayconnects = ast_variable_retrieve(ucfg, cat, "displayconnects");
4042 const char *user_writetimeout = ast_variable_retrieve(ucfg, cat, "writetimeout");
4044 /* Look for an existing entry,
4045 * if none found - create one and add it to the list
4047 if (!(user = get_manager_by_name_locked(cat))) {
4048 if (!(user = ast_calloc(1, sizeof(*user))))
4049 break;
4051 /* Copy name over */
4052 ast_copy_string(user->username, cat, sizeof(user->username));
4053 /* Insert into list */
4054 AST_LIST_INSERT_TAIL(&users, user, list);
4055 user->ha = NULL;
4056 user->readperm = -1;
4057 user->writeperm = -1;
4058 /* Default displayconnect from [general] */
4059 user->displayconnects = displayconnects;
4060 user->writetimeout = 100;
4063 if (!user_secret)
4064 user_secret = ast_variable_retrieve(ucfg, "general", "secret");
4065 if (!user_read)
4066 user_read = ast_variable_retrieve(ucfg, "general", "read");
4067 if (!user_write)
4068 user_write = ast_variable_retrieve(ucfg, "general", "write");
4069 if (!user_displayconnects)
4070 user_displayconnects = ast_variable_retrieve(ucfg, "general", "displayconnects");
4071 if (!user_writetimeout)
4072 user_writetimeout = ast_variable_retrieve(ucfg, "general", "writetimeout");
4074 if (!ast_strlen_zero(user_secret)) {
4075 if (user->secret)
4076 ast_free(user->secret);
4077 user->secret = ast_strdup(user_secret);
4080 if (user_read)
4081 user->readperm = get_perm(user_read);
4082 if (user_write)
4083 user->writeperm = get_perm(user_write);
4084 if (user_displayconnects)
4085 user->displayconnects = ast_true(user_displayconnects);
4087 if (user_writetimeout) {
4088 int val = atoi(user_writetimeout);
4089 if (val < 100)
4090 ast_log(LOG_WARNING, "Invalid writetimeout value '%s' at users.conf line %d\n", var->value, var->lineno);
4091 else
4092 user->writetimeout = val;
4096 ast_config_destroy(ucfg);
4099 /* cat is NULL here in any case */
4101 while ((cat = ast_category_browse(cfg, cat))) {
4102 struct ast_ha *oldha;
4104 if (!strcasecmp(cat, "general"))
4105 continue;
4107 /* Look for an existing entry, if none found - create one and add it to the list */
4108 if (!(user = get_manager_by_name_locked(cat))) {
4109 if (!(user = ast_calloc(1, sizeof(*user))))
4110 break;
4111 /* Copy name over */
4112 ast_copy_string(user->username, cat, sizeof(user->username));
4114 user->ha = NULL;
4115 user->readperm = 0;
4116 user->writeperm = 0;
4117 /* Default displayconnect from [general] */
4118 user->displayconnects = displayconnects;
4119 user->writetimeout = 100;
4121 /* Insert into list */
4122 AST_RWLIST_INSERT_TAIL(&users, user, list);
4125 /* Make sure we keep this user and don't destroy it during cleanup */
4126 user->keep = 1;
4127 oldha = user->ha;
4128 user->ha = NULL;
4130 var = ast_variable_browse(cfg, cat);
4131 for (; var; var = var->next) {
4132 if (!strcasecmp(var->name, "secret")) {
4133 if (user->secret)
4134 ast_free(user->secret);
4135 user->secret = ast_strdup(var->value);
4136 } else if (!strcasecmp(var->name, "deny") ||
4137 !strcasecmp(var->name, "permit")) {
4138 user->ha = ast_append_ha(var->name, var->value, user->ha, NULL);
4139 } else if (!strcasecmp(var->name, "read") ) {
4140 user->readperm = get_perm(var->value);
4141 } else if (!strcasecmp(var->name, "write") ) {
4142 user->writeperm = get_perm(var->value);
4143 } else if (!strcasecmp(var->name, "displayconnects") ) {
4144 user->displayconnects = ast_true(var->value);
4145 } else if (!strcasecmp(var->name, "writetimeout")) {
4146 int val = atoi(var->value);
4147 if (val < 100)
4148 ast_log(LOG_WARNING, "Invalid writetimeout value '%s' at line %d\n", var->value, var->lineno);
4149 else
4150 user->writetimeout = val;
4151 } else
4152 ast_debug(1, "%s is an unknown option.\n", var->name);
4154 ast_free_ha(oldha);
4156 ast_config_destroy(cfg);
4158 /* Perform cleanup - essentially prune out old users that no longer exist */
4159 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&users, user, list) {
4160 if (user->keep) { /* valid record. clear flag for the next round */
4161 user->keep = 0;
4162 continue;
4164 /* We do not need to keep this user so take them out of the list */
4165 AST_RWLIST_REMOVE_CURRENT(list);
4166 /* Free their memory now */
4167 if (user->secret)
4168 ast_free(user->secret);
4169 ast_free_ha(user->ha);
4170 ast_free(user);
4172 AST_RWLIST_TRAVERSE_SAFE_END;
4174 AST_RWLIST_UNLOCK(&users);
4176 if (webmanager_enabled && manager_enabled) {
4177 if (!webregged) {
4178 ast_http_uri_link(&rawmanuri);
4179 ast_http_uri_link(&manageruri);
4180 ast_http_uri_link(&managerxmluri);
4181 webregged = 1;
4183 } else {
4184 if (webregged) {
4185 ast_http_uri_unlink(&rawmanuri);
4186 ast_http_uri_unlink(&manageruri);
4187 ast_http_uri_unlink(&managerxmluri);
4188 webregged = 0;
4192 if (newhttptimeout > 0)
4193 httptimeout = newhttptimeout;
4195 manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: Manager\r\nStatus: %s\r\nMessage: Manager reload Requested\r\n", manager_enabled ? "Enabled" : "Disabled");
4197 ast_tcptls_server_start(&ami_desc);
4198 if (ast_ssl_setup(amis_desc.tls_cfg))
4199 ast_tcptls_server_start(&amis_desc);
4200 return 0;
4203 int init_manager(void)
4205 return __init_manager(0);
4208 int reload_manager(void)
4210 return __init_manager(1);