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.
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
39 /*! \addtogroup Group_AMI AMI functions
46 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
48 #include "asterisk/_private.h"
49 #include "asterisk/paths.h" /* use various ast_config_AST_* */
55 #include "asterisk/channel.h"
56 #include "asterisk/file.h"
57 #include "asterisk/manager.h"
58 #include "asterisk/config.h"
59 #include "asterisk/callerid.h"
60 #include "asterisk/lock.h"
61 #include "asterisk/cli.h"
62 #include "asterisk/app.h"
63 #include "asterisk/pbx.h"
64 #include "asterisk/md5.h"
65 #include "asterisk/acl.h"
66 #include "asterisk/utils.h"
67 #include "asterisk/http.h"
68 #include "asterisk/version.h"
69 #include "asterisk/threadstorage.h"
70 #include "asterisk/linkedlists.h"
71 #include "asterisk/version.h"
72 #include "asterisk/term.h"
73 #include "asterisk/astobj2.h"
76 * Linked list of events.
77 * Global events are appended to the list by append_event().
78 * The usecount is the number of stored pointers to the element,
79 * excluding the list pointers. So an element that is only in
80 * the list has a usecount of 0, not 1.
82 * Clients have a pointer to the last event processed, and for each
83 * of these clients we track the usecount of the elements.
84 * If we have a pointer to an entry in the list, it is safe to navigate
85 * it forward because elements will not be deleted, but only appended.
86 * The worst that can happen is seeing the pointer still NULL.
88 * When the usecount of an element drops to 0, and the element is the
89 * first in the list, we can remove it. Removal is done within the
90 * main thread, which is woken up for the purpose.
92 * For simplicity of implementation, we make sure the list is never empty.
95 int usecount; /*!< # of clients who still need the event */
97 unsigned int seq; /*!< sequence number */
98 AST_LIST_ENTRY(eventqent) eq_next;
99 char eventdata[1]; /*!< really variable size, allocated by append_event() */
102 static AST_LIST_HEAD_STATIC(all_events, eventqent);
104 static int displayconnects = 1;
105 static int allowmultiplelogin = 1;
106 static int timestampevents;
107 static int httptimeout = 60;
108 static int manager_enabled = 0;
109 static int webmanager_enabled = 0;
111 static int block_sockets;
112 static int num_sessions;
114 static int manager_debug; /*!< enable some debugging code in the manager */
117 * Descriptor for a manager session, either on the AMI socket or over HTTP.
120 * AMI session have managerid == 0; the entry is created upon a connect,
121 * and destroyed with the socket.
122 * HTTP sessions have managerid != 0, the value is used as a search key
123 * to lookup sessions (using the mansession_id cookie).
125 static const char *command_blacklist[] = {
131 pthread_t ms_t; /*!< Execution thread, basically useless */
132 ast_mutex_t __lock; /*!< Thread lock -- don't use in action callbacks, it's already taken care of */
133 /* XXX need to document which fields it is protecting */
134 struct sockaddr_in sin; /*!< address we are connecting from */
135 FILE *f; /*!< fdopen() on the underlying fd */
136 int fd; /*!< descriptor used for output. Either the socket (AMI) or a temporary file (HTTP) */
137 int inuse; /*!< number of HTTP sessions using this entry */
138 int needdestroy; /*!< Whether an HTTP session should be destroyed */
139 pthread_t waiting_thread; /*!< Sleeping thread using this descriptor */
140 unsigned long managerid; /*!< Unique manager identifier, 0 for AMI sessions */
141 time_t sessionstart; /*!< Session start time */
142 time_t sessiontimeout; /*!< Session timeout if HTTP */
143 char username[80]; /*!< Logged in username */
144 char challenge[10]; /*!< Authentication challenge */
145 int authenticated; /*!< Authentication status */
146 int readperm; /*!< Authorization for reading */
147 int writeperm; /*!< Authorization for writing */
148 char inbuf[1025]; /*!< Buffer */
149 /* we use the extra byte to add a '\0' and simplify parsing */
150 int inlen; /*!< number of buffered bytes */
151 int send_events; /*!< XXX what ? */
152 struct eventqent *last_ev; /*!< last event processed. */
153 int writetimeout; /*!< Timeout for ast_carefulwrite() */
154 AST_LIST_ENTRY(mansession) list;
157 #define NEW_EVENT(m) (AST_LIST_NEXT(m->last_ev, eq_next))
159 static AST_LIST_HEAD_STATIC(sessions, mansession);
161 /*! \brief user descriptor, as read from the config file.
163 * \note It is still missing some fields -- e.g. we can have multiple permit and deny
164 * lines which are not supported here, and readperm/writeperm/writetimeout
167 struct ast_manager_user {
170 struct ast_ha *ha; /*!< ACL setting */
171 int readperm; /*! Authorization for reading */
172 int writeperm; /*! Authorization for writing */
173 int writetimeout; /*! Per user Timeout for ast_carefulwrite() */
174 int displayconnects; /*!< XXX unused */
175 int keep; /*!< mark entries created on a reload */
176 AST_RWLIST_ENTRY(ast_manager_user) list;
179 /*! \brief list of users found in the config file */
180 static AST_RWLIST_HEAD_STATIC(users, ast_manager_user);
182 /*! \brief list of actions registered */
183 static AST_RWLIST_HEAD_STATIC(actions, manager_action);
185 /*! \brief list of hooks registered */
186 static AST_RWLIST_HEAD_STATIC(manager_hooks, manager_custom_hook);
188 /*! \brief Add a custom hook to be called when an event is fired */
189 void ast_manager_register_hook(struct manager_custom_hook *hook)
191 AST_RWLIST_WRLOCK(&manager_hooks);
192 AST_RWLIST_INSERT_TAIL(&manager_hooks, hook, list);
193 AST_RWLIST_UNLOCK(&manager_hooks);
197 /*! \brief Delete a custom hook to be called when an event is fired */
198 void ast_manager_unregister_hook(struct manager_custom_hook *hook)
200 AST_RWLIST_WRLOCK(&manager_hooks);
201 AST_RWLIST_REMOVE(&manager_hooks, hook, list);
202 AST_RWLIST_UNLOCK(&manager_hooks);
207 * Event list management functions.
208 * We assume that the event list always has at least one element,
209 * and the delete code will not remove the last entry even if the
213 static time_t __deb(time_t start, const char *msg)
215 time_t now = time(NULL);
216 ast_verbose("%4d th %p %s\n", (int)(now % 3600), pthread_self(), msg);
217 if (start != 0 && now - start > 5)
218 ast_verbose("+++ WOW, %s took %d seconds\n", msg, (int)(now - start));
222 static void LOCK_EVENTS(void)
224 time_t start = __deb(0, "about to lock events");
225 AST_LIST_LOCK(&all_events);
226 __deb(start, "done lock events");
229 static void UNLOCK_EVENTS(void)
231 __deb(0, "about to unlock events");
232 AST_LIST_UNLOCK(&all_events);
235 static void LOCK_SESS(void)
237 time_t start = __deb(0, "about to lock sessions");
238 AST_LIST_LOCK(&sessions);
239 __deb(start, "done lock sessions");
242 static void UNLOCK_SESS(void)
244 __deb(0, "about to unlock sessions");
245 AST_LIST_UNLOCK(&sessions);
249 int check_manager_enabled()
251 return manager_enabled;
254 int check_webmanager_enabled()
256 return (webmanager_enabled && manager_enabled);
260 * Grab a reference to the last event, update usecount as needed.
261 * Can handle a NULL pointer.
263 static struct eventqent *grab_last(void)
265 struct eventqent *ret;
267 AST_LIST_LOCK(&all_events);
268 ret = AST_LIST_LAST(&all_events);
269 /* the list is never empty now, but may become so when
270 * we optimize it in the future, so be prepared.
273 ast_atomic_fetchadd_int(&ret->usecount, 1);
274 AST_LIST_UNLOCK(&all_events);
279 * Purge unused events. Remove elements from the head
280 * as long as their usecount is 0 and there is a next element.
282 static void purge_events(void)
284 struct eventqent *ev;
286 AST_LIST_LOCK(&all_events);
287 while ( (ev = AST_LIST_FIRST(&all_events)) &&
288 ev->usecount == 0 && AST_LIST_NEXT(ev, eq_next)) {
289 AST_LIST_REMOVE_HEAD(&all_events, eq_next);
292 AST_LIST_UNLOCK(&all_events);
296 * helper functions to convert back and forth between
297 * string and numeric representation of set of flags
299 static struct permalias {
303 { EVENT_FLAG_SYSTEM, "system" },
304 { EVENT_FLAG_CALL, "call" },
305 { EVENT_FLAG_LOG, "log" },
306 { EVENT_FLAG_VERBOSE, "verbose" },
307 { EVENT_FLAG_COMMAND, "command" },
308 { EVENT_FLAG_AGENT, "agent" },
309 { EVENT_FLAG_USER, "user" },
310 { EVENT_FLAG_CONFIG, "config" },
311 { EVENT_FLAG_DTMF, "dtmf" },
312 { EVENT_FLAG_REPORTING, "reporting" },
313 { EVENT_FLAG_CDR, "cdr" },
318 /*! \brief Convert authority code to a list of options */
319 static char *authority_to_str(int authority, struct ast_str **res)
325 for (i = 0; i < (sizeof(perms) / sizeof(perms[0])) - 1; i++) {
326 if (authority & perms[i].num) {
327 ast_str_append(res, 0, "%s%s", sep, perms[i].label);
332 if ((*res)->used == 0) /* replace empty string with something sensible */
333 ast_str_append(res, 0, "<none>");
338 /*! Tells you if smallstr exists inside bigstr
339 which is delim by delim and uses no buf or stringsep
340 ast_instring("this|that|more","this",'|') == 1;
342 feel free to move this to app.c -anthm */
343 static int ast_instring(const char *bigstr, const char *smallstr, const char delim)
345 const char *val = bigstr, *next;
348 if ((next = strchr(val, delim))) {
349 if (!strncmp(val, smallstr, (next - val)))
354 return !strcmp(smallstr, val);
355 } while (*(val = (next + 1)));
360 static int get_perm(const char *instr)
367 for (x = 0; x < (sizeof(perms) / sizeof(perms[0])); x++) {
368 if (ast_instring(instr, perms[x].label, ','))
376 * A number returns itself, false returns 0, true returns all flags,
377 * other strings return the flags that are set.
379 static int strings_to_mask(const char *string)
383 if (ast_strlen_zero(string))
386 for (p = string; *p; p++)
387 if (*p < '0' || *p > '9')
389 if (!p) /* all digits */
391 if (ast_false(string))
393 if (ast_true(string)) { /* all permissions */
395 for (x=0; x<sizeof(perms) / sizeof(perms[0]); x++)
399 return get_perm(string);
402 static int check_manager_session_inuse(const char *name)
404 struct mansession *session = NULL;
406 AST_LIST_LOCK(&sessions);
407 AST_LIST_TRAVERSE(&sessions, session, list) {
408 if (!strcasecmp(session->username, name))
411 AST_LIST_UNLOCK(&sessions);
413 return session ? 1 : 0;
418 * lookup an entry in the list of registered users.
419 * must be called with the list lock held.
421 static struct ast_manager_user *get_manager_by_name_locked(const char *name)
423 struct ast_manager_user *user = NULL;
425 AST_RWLIST_TRAVERSE(&users, user, list)
426 if (!strcasecmp(user->username, name))
431 /*! \brief Get displayconnects config option.
432 * \param s manager session to get parameter from.
433 * \return displayconnects config option value.
435 static int manager_displayconnects (struct mansession *s)
437 struct ast_manager_user *user = NULL;
440 AST_RWLIST_RDLOCK(&users);
441 if ((user = get_manager_by_name_locked (s->username)))
442 ret = user->displayconnects;
443 AST_RWLIST_UNLOCK(&users);
448 static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
450 struct manager_action *cur;
451 struct ast_str *authority;
456 e->command = "manager show command";
458 "Usage: manager show command <actionname>\n"
459 " Shows the detailed description for a specific Asterisk manager interface command.\n";
464 AST_RWLIST_RDLOCK(&actions);
465 AST_RWLIST_TRAVERSE(&actions, cur, list) {
466 if (!strncasecmp(a->word, cur->action, l) && ++which > a->n) {
467 ret = ast_strdup(cur->action);
468 break; /* make sure we exit even if ast_strdup() returns NULL */
471 AST_RWLIST_UNLOCK(&actions);
474 authority = ast_str_alloca(80);
476 return CLI_SHOWUSAGE;
478 AST_RWLIST_RDLOCK(&actions);
479 AST_RWLIST_TRAVERSE(&actions, cur, list) {
480 for (num = 3; num < a->argc; num++) {
481 if (!strcasecmp(cur->action, a->argv[num])) {
482 ast_cli(a->fd, "Action: %s\nSynopsis: %s\nPrivilege: %s\n%s\n",
483 cur->action, cur->synopsis,
484 authority_to_str(cur->authority, &authority),
485 S_OR(cur->description, ""));
489 AST_RWLIST_UNLOCK(&actions);
494 static char *handle_mandebug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
498 e->command = "manager debug [on|off]";
499 e->usage = "Usage: manager debug [on|off]\n Show, enable, disable debugging of the manager code.\n";
505 ast_cli(a->fd, "manager debug is %s\n", manager_debug? "on" : "off");
506 else if (a->argc == 3) {
507 if (!strcasecmp(a->argv[2], "on"))
509 else if (!strcasecmp(a->argv[2], "off"))
512 return CLI_SHOWUSAGE;
517 static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
519 struct ast_manager_user *user = NULL;
522 struct ast_str *rauthority = ast_str_alloca(80);
523 struct ast_str *wauthority = ast_str_alloca(80);
527 e->command = "manager show user";
529 " Usage: manager show user <user>\n"
530 " Display all information related to the manager user specified.\n";
537 AST_RWLIST_RDLOCK(&users);
538 AST_RWLIST_TRAVERSE(&users, user, list) {
539 if ( !strncasecmp(a->word, user->username, l) && ++which > a->n ) {
540 ret = ast_strdup(user->username);
544 AST_RWLIST_UNLOCK(&users);
549 return CLI_SHOWUSAGE;
551 AST_RWLIST_RDLOCK(&users);
553 if (!(user = get_manager_by_name_locked(a->argv[3]))) {
554 ast_cli(a->fd, "There is no manager called %s\n", a->argv[3]);
555 AST_RWLIST_UNLOCK(&users);
566 "displayconnects: %s\n",
567 (user->username ? user->username : "(N/A)"),
568 (user->secret ? "<Set>" : "(N/A)"),
569 (user->ha ? "yes" : "no"),
570 authority_to_str(user->readperm, &rauthority),
571 authority_to_str(user->writeperm, &wauthority),
572 (user->displayconnects ? "yes" : "no"));
574 AST_RWLIST_UNLOCK(&users);
580 static char *handle_showmanagers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
582 struct ast_manager_user *user = NULL;
586 e->command = "manager show users";
588 "Usage: manager show users\n"
589 " Prints a listing of all managers that are currently configured on that\n"
596 return CLI_SHOWUSAGE;
598 AST_RWLIST_RDLOCK(&users);
600 /* If there are no users, print out something along those lines */
601 if (AST_RWLIST_EMPTY(&users)) {
602 ast_cli(a->fd, "There are no manager users.\n");
603 AST_RWLIST_UNLOCK(&users);
607 ast_cli(a->fd, "\nusername\n--------\n");
609 AST_RWLIST_TRAVERSE(&users, user, list) {
610 ast_cli(a->fd, "%s\n", user->username);
614 AST_RWLIST_UNLOCK(&users);
616 ast_cli(a->fd,"-------------------\n");
617 ast_cli(a->fd,"%d manager users configured.\n", count_amu);
623 /*! \brief CLI command manager list commands */
624 static char *handle_showmancmds(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
626 struct manager_action *cur;
627 struct ast_str *authority;
628 static const char *format = " %-15.15s %-15.15s %-55.55s\n";
631 e->command = "manager show commands";
633 "Usage: manager show commands\n"
634 " Prints a listing of all the available Asterisk manager interface commands.\n";
639 authority = ast_str_alloca(80);
640 ast_cli(a->fd, format, "Action", "Privilege", "Synopsis");
641 ast_cli(a->fd, format, "------", "---------", "--------");
643 AST_RWLIST_RDLOCK(&actions);
644 AST_RWLIST_TRAVERSE(&actions, cur, list)
645 ast_cli(a->fd, format, cur->action, authority_to_str(cur->authority, &authority), cur->synopsis);
646 AST_RWLIST_UNLOCK(&actions);
651 /*! \brief CLI command manager list connected */
652 static char *handle_showmanconn(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
654 struct mansession *s;
655 time_t now = time(NULL);
656 static const char *format = " %-15.15s %-15.15s %-10.10s %-10.10s %-8.8s %-8.8s %-5.5s %-5.5s\n";
657 static const char *format2 = " %-15.15s %-15.15s %-10d %-10d %-8d %-8d %-5.5d %-5.5d\n";
661 e->command = "manager show connected";
663 "Usage: manager show connected\n"
664 " Prints a listing of the users that are currently connected to the\n"
665 "Asterisk manager interface.\n";
671 ast_cli(a->fd, format, "Username", "IP Address", "Start", "Elapsed", "FileDes", "HttpCnt", "Read", "Write");
673 AST_LIST_LOCK(&sessions);
674 AST_LIST_TRAVERSE(&sessions, s, list) {
675 ast_cli(a->fd, 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);
678 AST_LIST_UNLOCK(&sessions);
680 ast_cli(a->fd, "%d users connected.\n", count);
685 /*! \brief CLI command manager list eventq */
686 /* Should change to "manager show connected" */
687 static char *handle_showmaneventq(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
692 e->command = "manager show eventq";
694 "Usage: manager show eventq\n"
695 " Prints a listing of all events pending in the Asterisk manger\n"
701 AST_LIST_LOCK(&all_events);
702 AST_LIST_TRAVERSE(&all_events, s, eq_next) {
703 ast_cli(a->fd, "Usecount: %d\n",s->usecount);
704 ast_cli(a->fd, "Category: %d\n", s->category);
705 ast_cli(a->fd, "Event:\n%s", s->eventdata);
707 AST_LIST_UNLOCK(&all_events);
712 /*! \brief CLI command manager reload */
713 static char *handle_manager_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
717 e->command = "manager reload";
719 "Usage: manager reload\n"
720 " Reloads the manager configuration.\n";
726 return CLI_SHOWUSAGE;
732 static struct ast_cli_entry cli_manager[] = {
733 AST_CLI_DEFINE(handle_showmancmd, "Show a manager interface command"),
734 AST_CLI_DEFINE(handle_showmancmds, "List manager interface commands"),
735 AST_CLI_DEFINE(handle_showmanconn, "List connected manager interface users"),
736 AST_CLI_DEFINE(handle_showmaneventq, "List manager interface queued events"),
737 AST_CLI_DEFINE(handle_showmanagers, "List configured manager users"),
738 AST_CLI_DEFINE(handle_showmanager, "Display information on a specific manager user"),
739 AST_CLI_DEFINE(handle_mandebug, "Show, enable, disable debugging of the manager code"),
740 AST_CLI_DEFINE(handle_manager_reload, "Reload manager configurations"),
744 * Decrement the usecount for the event; if it goes to zero,
745 * (why check for e->next ?) wakeup the
746 * main thread, which is in charge of freeing the record.
747 * Returns the next record.
749 static struct eventqent *unref_event(struct eventqent *e)
751 ast_atomic_fetchadd_int(&e->usecount, -1);
752 return AST_LIST_NEXT(e, eq_next);
755 static void ref_event(struct eventqent *e)
757 ast_atomic_fetchadd_int(&e->usecount, 1);
761 * destroy a session, leaving the usecount
763 static void free_session(struct mansession *s)
765 struct eventqent *eqe = s->last_ev;
768 ast_mutex_destroy(&s->__lock);
773 static void destroy_session(struct mansession *s)
775 AST_LIST_LOCK(&sessions);
776 AST_LIST_REMOVE(&sessions, s, list);
777 ast_atomic_fetchadd_int(&num_sessions, -1);
779 AST_LIST_UNLOCK(&sessions);
782 const char *astman_get_header(const struct message *m, char *var)
784 int x, l = strlen(var);
786 for (x = 0; x < m->hdrcount; x++) {
787 const char *h = m->headers[x];
788 if (!strncasecmp(var, h, l) && h[l] == ':' && h[l+1] == ' ')
795 struct ast_variable *astman_get_variables(const struct message *m)
798 struct ast_variable *head = NULL, *cur;
800 AST_DECLARE_APP_ARGS(args,
801 AST_APP_ARG(vars)[32];
804 varlen = strlen("Variable: ");
806 for (x = 0; x < m->hdrcount; x++) {
807 char *parse, *var, *val;
809 if (strncasecmp("Variable: ", m->headers[x], varlen))
811 parse = ast_strdupa(m->headers[x] + varlen);
813 AST_STANDARD_APP_ARGS(args, parse);
816 for (y = 0; y < args.argc; y++) {
819 var = val = ast_strdupa(args.vars[y]);
821 if (!val || ast_strlen_zero(var))
823 cur = ast_variable_new(var, val, "");
833 * helper function to send a string to the socket.
834 * Return -1 on error (e.g. buffer full).
836 static int send_string(struct mansession *s, char *string)
838 int len = strlen(string); /* residual length */
840 struct timeval start = ast_tvnow();
846 n = fwrite(src, 1, len, s->f); /* try to write the string, non blocking */
847 if (n == len /* ok */ || n < 0 /* error */)
849 len -= n; /* skip already written data */
853 n = -1; /* error marker */
854 elapsed = ast_tvdiff_ms(ast_tvnow(), start);
855 if (elapsed > s->writetimeout)
857 if (poll(&fd, 1, s->writetimeout - elapsed) < 1)
861 return n < 0 ? -1 : 0;
865 * \brief thread local buffer for astman_append
867 * \note This can not be defined within the astman_append() function
868 * because it declares a couple of functions that get used to
869 * initialize the thread local storage key.
871 AST_THREADSTORAGE(astman_append_buf);
872 /*! \brief initial allocated size for the astman_append_buf */
873 #define ASTMAN_APPEND_BUF_INITSIZE 256
876 * utility functions for creating AMI replies
878 void astman_append(struct mansession *s, const char *fmt, ...)
883 if (!(buf = ast_str_thread_get(&astman_append_buf, ASTMAN_APPEND_BUF_INITSIZE)))
887 ast_str_set_va(&buf, 0, fmt, ap);
891 send_string(s, buf->str);
893 ast_verbose("fd == -1 in astman_append, should not happen\n");
896 /*! \note NOTE: XXX this comment is unclear and possibly wrong.
897 Callers of astman_send_error(), astman_send_response() or astman_send_ack() must EITHER
898 hold the session lock _or_ be running in an action callback (in which case s->busy will
899 be non-zero). In either of these cases, there is no need to lock-protect the session's
900 fd, since no other output will be sent (events will be queued), and no input will
901 be read until either the current action finishes or get_input() obtains the session
905 /*! \brief send a response with an optional message,
906 * and terminate it with an empty line.
907 * m is used only to grab the 'ActionID' field.
909 * Use the explicit constant MSG_MOREDATA to remove the empty line.
910 * XXX MSG_MOREDATA should go to a header file.
912 #define MSG_MOREDATA ((char *)astman_send_response)
913 static void astman_send_response_full(struct mansession *s, const struct message *m, char *resp, char *msg, char *listflag)
915 const char *id = astman_get_header(m,"ActionID");
917 astman_append(s, "Response: %s\r\n", resp);
918 if (!ast_strlen_zero(id))
919 astman_append(s, "ActionID: %s\r\n", id);
921 astman_append(s, "Eventlist: %s\r\n", listflag); /* Start, complete, cancelled */
922 if (msg == MSG_MOREDATA)
925 astman_append(s, "Message: %s\r\n\r\n", msg);
927 astman_append(s, "\r\n");
930 void astman_send_response(struct mansession *s, const struct message *m, char *resp, char *msg)
932 astman_send_response_full(s, m, resp, msg, NULL);
935 void astman_send_error(struct mansession *s, const struct message *m, char *error)
937 astman_send_response_full(s, m, "Error", error, NULL);
940 void astman_send_ack(struct mansession *s, const struct message *m, char *msg)
942 astman_send_response_full(s, m, "Success", msg, NULL);
945 static void astman_start_ack(struct mansession *s, const struct message *m)
947 astman_send_response_full(s, m, "Success", MSG_MOREDATA, NULL);
950 void astman_send_listack(struct mansession *s, const struct message *m, char *msg, char *listflag)
952 astman_send_response_full(s, m, "Success", msg, listflag);
957 Rather than braindead on,off this now can also accept a specific int mask value
958 or a ',' delim list of mask strings (the same as manager.conf) -anthm
960 static int set_eventmask(struct mansession *s, const char *eventmask)
962 int maskint = strings_to_mask(eventmask);
964 ast_mutex_lock(&s->__lock);
966 s->send_events = maskint;
967 ast_mutex_unlock(&s->__lock);
973 * Here we start with action_ handlers for AMI actions,
974 * and the internal functions used by them.
975 * Generally, the handlers are called action_foo()
978 /* helper function for action_login() */
979 static int authenticate(struct mansession *s, const struct message *m)
981 const char *username = astman_get_header(m, "Username");
982 const char *password = astman_get_header(m, "Secret");
984 struct ast_manager_user *user = NULL;
986 if (ast_strlen_zero(username)) /* missing username */
989 /* locate user in locked state */
990 AST_RWLIST_WRLOCK(&users);
992 if (!(user = get_manager_by_name_locked(username))) {
993 ast_log(LOG_NOTICE, "%s tried to authenticate with nonexistent user '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
994 } else if (user->ha && !ast_apply_ha(user->ha, &(s->sin))) {
995 ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
996 } else if (!strcasecmp(astman_get_header(m, "AuthType"), "MD5")) {
997 const char *key = astman_get_header(m, "Key");
998 if (!ast_strlen_zero(key) && !ast_strlen_zero(s->challenge) &&
999 !ast_strlen_zero(password)) {
1002 char md5key[256] = "";
1003 struct MD5Context md5;
1004 unsigned char digest[16];
1007 MD5Update(&md5, (unsigned char *) s->challenge, strlen(s->challenge));
1008 MD5Update(&md5, (unsigned char *) password, strlen(password));
1009 MD5Final(digest, &md5);
1010 for (x=0; x<16; x++)
1011 len += sprintf(md5key + len, "%2.2x", digest[x]);
1012 if (!strcmp(md5key, key))
1015 ast_debug(1, "MD5 authentication is not possible. challenge: '%s'\n",
1016 S_OR(s->challenge, ""));
1018 } else if (password && user->secret && !strcmp(password, user->secret))
1022 ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
1023 AST_RWLIST_UNLOCK(&users);
1029 ast_copy_string(s->username, username, sizeof(s->username));
1030 s->readperm = user->readperm;
1031 s->writeperm = user->writeperm;
1032 s->writetimeout = user->writetimeout;
1033 s->sessionstart = time(NULL);
1034 set_eventmask(s, astman_get_header(m, "Events"));
1036 AST_RWLIST_UNLOCK(&users);
1040 /*! \brief Manager PING */
1041 static char mandescr_ping[] =
1042 "Description: A 'Ping' action will ellicit a 'Pong' response. Used to keep the\n"
1043 " manager connection open.\n"
1044 "Variables: NONE\n";
1046 static int action_ping(struct mansession *s, const struct message *m)
1048 astman_send_response(s, m, "Success", "Ping: Pong\r\n");
1052 static char mandescr_getconfig[] =
1053 "Description: A 'GetConfig' action will dump the contents of a configuration\n"
1054 "file by category and contents.\n"
1056 " Filename: Configuration filename (e.g. foo.conf)\n";
1058 static int action_getconfig(struct mansession *s, const struct message *m)
1060 struct ast_config *cfg;
1061 const char *fn = astman_get_header(m, "Filename");
1064 char *category=NULL;
1065 struct ast_variable *v;
1066 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1068 if (ast_strlen_zero(fn)) {
1069 astman_send_error(s, m, "Filename not specified");
1072 if (!(cfg = ast_config_load(fn, config_flags))) {
1073 astman_send_error(s, m, "Config file not found");
1076 astman_start_ack(s, m);
1077 while ((category = ast_category_browse(cfg, category))) {
1079 astman_append(s, "Category-%06d: %s\r\n", catcount, category);
1080 for (v = ast_variable_browse(cfg, category); v; v = v->next)
1081 astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
1084 ast_config_destroy(cfg);
1085 astman_append(s, "\r\n");
1090 /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */
1091 static void json_escape(char *out, const char *in)
1094 if (*in == '\\' || *in == '\"')
1101 static char mandescr_getconfigjson[] =
1102 "Description: A 'GetConfigJSON' action will dump the contents of a configuration\n"
1103 "file by category and contents in JSON format. This only makes sense to be used\n"
1104 "using rawman over the HTTP interface.\n"
1106 " Filename: Configuration filename (e.g. foo.conf)\n";
1108 static int action_getconfigjson(struct mansession *s, const struct message *m)
1110 struct ast_config *cfg;
1111 const char *fn = astman_get_header(m, "Filename");
1112 char *category = NULL;
1113 struct ast_variable *v;
1116 unsigned int buf_len = 0;
1117 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1119 if (ast_strlen_zero(fn)) {
1120 astman_send_error(s, m, "Filename not specified");
1124 if (!(cfg = ast_config_load(fn, config_flags))) {
1125 astman_send_error(s, m, "Config file not found");
1130 buf = alloca(buf_len);
1132 astman_start_ack(s, m);
1133 astman_append(s, "JSON: {");
1134 while ((category = ast_category_browse(cfg, category))) {
1136 if (buf_len < 2 * strlen(category) + 1) {
1138 buf = alloca(buf_len);
1140 json_escape(buf, category);
1141 astman_append(s, "%s\"%s\":[", comma1 ? "," : "", buf);
1144 for (v = ast_variable_browse(cfg, category); v; v = v->next) {
1146 astman_append(s, ",");
1147 if (buf_len < 2 * strlen(v->name) + 1) {
1149 buf = alloca(buf_len);
1151 json_escape(buf, v->name);
1152 astman_append(s, "\"%s", buf);
1153 if (buf_len < 2 * strlen(v->value) + 1) {
1155 buf = alloca(buf_len);
1157 json_escape(buf, v->value);
1158 astman_append(s, "%s\"", buf);
1162 astman_append(s, "]");
1164 astman_append(s, "}\r\n\r\n");
1166 ast_config_destroy(cfg);
1171 /* helper function for action_updateconfig */
1172 static void handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg, const char *dfn)
1176 const char *action, *cat, *var, *value, *match;
1177 struct ast_category *category;
1178 struct ast_variable *v;
1180 for (x = 0; x < 100000; x++) {
1181 unsigned int object = 0;
1183 snprintf(hdr, sizeof(hdr), "Action-%06d", x);
1184 action = astman_get_header(m, hdr);
1185 if (ast_strlen_zero(action))
1187 snprintf(hdr, sizeof(hdr), "Cat-%06d", x);
1188 cat = astman_get_header(m, hdr);
1189 snprintf(hdr, sizeof(hdr), "Var-%06d", x);
1190 var = astman_get_header(m, hdr);
1191 snprintf(hdr, sizeof(hdr), "Value-%06d", x);
1192 value = astman_get_header(m, hdr);
1193 if (!ast_strlen_zero(value) && *value == '>') {
1197 snprintf(hdr, sizeof(hdr), "Match-%06d", x);
1198 match = astman_get_header(m, hdr);
1199 if (!strcasecmp(action, "newcat")) {
1200 if (!ast_strlen_zero(cat)) {
1201 category = ast_category_new(cat, dfn, 99999);
1203 ast_category_append(cfg, category);
1206 } else if (!strcasecmp(action, "renamecat")) {
1207 if (!ast_strlen_zero(cat) && !ast_strlen_zero(value)) {
1208 category = ast_category_get(cfg, cat);
1210 ast_category_rename(category, value);
1212 } else if (!strcasecmp(action, "delcat")) {
1213 if (!ast_strlen_zero(cat))
1214 ast_category_delete(cfg, cat);
1215 } else if (!strcasecmp(action, "update")) {
1216 if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) && (category = ast_category_get(cfg, cat)))
1217 ast_variable_update(category, var, value, match, object);
1218 } else if (!strcasecmp(action, "delete")) {
1219 if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) && (category = ast_category_get(cfg, cat)))
1220 ast_variable_delete(category, var, match);
1221 } else if (!strcasecmp(action, "append")) {
1222 if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) &&
1223 (category = ast_category_get(cfg, cat)) &&
1224 (v = ast_variable_new(var, value, dfn))){
1225 if (object || (match && !strcasecmp(match, "object")))
1227 ast_variable_append(category, v);
1233 static char mandescr_updateconfig[] =
1234 "Description: A 'UpdateConfig' action will modify, create, or delete\n"
1235 "configuration elements in Asterisk configuration files.\n"
1236 "Variables (X's represent 6 digit number beginning with 000000):\n"
1237 " SrcFilename: Configuration filename to read(e.g. foo.conf)\n"
1238 " DstFilename: Configuration filename to write(e.g. foo.conf)\n"
1239 " Reload: Whether or not a reload should take place (or name of specific module)\n"
1240 " Action-XXXXXX: Action to Take (NewCat,RenameCat,DelCat,Update,Delete,Append)\n"
1241 " Cat-XXXXXX: Category to operate on\n"
1242 " Var-XXXXXX: Variable to work on\n"
1243 " Value-XXXXXX: Value to work on\n"
1244 " Match-XXXXXX: Extra match required to match line\n";
1246 static int action_updateconfig(struct mansession *s, const struct message *m)
1248 struct ast_config *cfg;
1249 const char *sfn = astman_get_header(m, "SrcFilename");
1250 const char *dfn = astman_get_header(m, "DstFilename");
1252 const char *rld = astman_get_header(m, "Reload");
1253 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1255 if (ast_strlen_zero(sfn) || ast_strlen_zero(dfn)) {
1256 astman_send_error(s, m, "Filename not specified");
1259 if (!(cfg = ast_config_load(sfn, config_flags))) {
1260 astman_send_error(s, m, "Config file not found");
1263 handle_updates(s, m, cfg, dfn);
1264 ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
1265 res = config_text_file_save(dfn, cfg, "Manager");
1266 ast_config_destroy(cfg);
1268 astman_send_error(s, m, "Save of config failed");
1271 astman_send_ack(s, m, NULL);
1272 if (!ast_strlen_zero(rld)) {
1275 ast_module_reload(rld);
1280 /*! \brief Manager WAITEVENT */
1281 static char mandescr_waitevent[] =
1282 "Description: A 'WaitEvent' action will ellicit a 'Success' response. Whenever\n"
1283 "a manager event is queued. Once WaitEvent has been called on an HTTP manager\n"
1284 "session, events will be generated and queued.\n"
1286 " Timeout: Maximum time (in seconds) to wait for events, -1 means forever.\n";
1288 static int action_waitevent(struct mansession *s, const struct message *m)
1290 const char *timeouts = astman_get_header(m, "Timeout");
1294 const char *id = astman_get_header(m,"ActionID");
1295 char idText[256] = "";
1297 if (!ast_strlen_zero(id))
1298 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
1300 if (!ast_strlen_zero(timeouts)) {
1301 sscanf(timeouts, "%i", &timeout);
1304 /* XXX maybe put an upper bound, or prevent the use of 0 ? */
1307 ast_mutex_lock(&s->__lock);
1308 if (s->waiting_thread != AST_PTHREADT_NULL)
1309 pthread_kill(s->waiting_thread, SIGURG);
1311 if (s->managerid) { /* AMI-over-HTTP session */
1313 * Make sure the timeout is within the expire time of the session,
1314 * as the client will likely abort the request if it does not see
1315 * data coming after some amount of time.
1317 time_t now = time(NULL);
1318 int max = s->sessiontimeout - now - 10;
1320 if (max < 0) /* We are already late. Strange but possible. */
1322 if (timeout < 0 || timeout > max)
1324 if (!s->send_events) /* make sure we record events */
1325 s->send_events = -1;
1327 ast_mutex_unlock(&s->__lock);
1329 /* XXX should this go inside the lock ? */
1330 s->waiting_thread = pthread_self(); /* let new events wake up this thread */
1331 ast_debug(1, "Starting waiting for an event!\n");
1333 for (x=0; x < timeout || timeout < 0; x++) {
1334 ast_mutex_lock(&s->__lock);
1337 /* We can have multiple HTTP session point to the same mansession entry.
1338 * The way we deal with it is not very nice: newcomers kick out the previous
1339 * HTTP session. XXX this needs to be improved.
1341 if (s->waiting_thread != pthread_self())
1345 ast_mutex_unlock(&s->__lock);
1348 if (s->managerid == 0) { /* AMI session */
1349 if (ast_wait_for_input(s->fd, 1000))
1351 } else { /* HTTP session */
1355 ast_debug(1, "Finished waiting for an event!\n");
1356 ast_mutex_lock(&s->__lock);
1357 if (s->waiting_thread == pthread_self()) {
1358 struct eventqent *eqe;
1359 astman_send_response(s, m, "Success", "Waiting for Event completed.");
1360 while ( (eqe = NEW_EVENT(s)) ) {
1362 if (((s->readperm & eqe->category) == eqe->category) &&
1363 ((s->send_events & eqe->category) == eqe->category)) {
1364 astman_append(s, "%s", eqe->eventdata);
1366 s->last_ev = unref_event(s->last_ev);
1369 "Event: WaitEventComplete\r\n"
1372 s->waiting_thread = AST_PTHREADT_NULL;
1374 ast_debug(1, "Abandoning event request!\n");
1376 ast_mutex_unlock(&s->__lock);
1380 static char mandescr_listcommands[] =
1381 "Description: Returns the action name and synopsis for every\n"
1382 " action that is available to the user\n"
1383 "Variables: NONE\n";
1385 /*! \note The actionlock is read-locked by the caller of this function */
1386 static int action_listcommands(struct mansession *s, const struct message *m)
1388 struct manager_action *cur;
1389 struct ast_str *temp = ast_str_alloca(BUFSIZ); /* XXX very large ? */
1391 astman_start_ack(s, m);
1392 AST_RWLIST_TRAVERSE(&actions, cur, list) {
1393 if ((s->writeperm & cur->authority) == cur->authority)
1394 astman_append(s, "%s: %s (Priv: %s)\r\n",
1395 cur->action, cur->synopsis, authority_to_str(cur->authority, &temp));
1397 astman_append(s, "\r\n");
1402 static char mandescr_events[] =
1403 "Description: Enable/Disable sending of events to this manager\n"
1406 " EventMask: 'on' if all events should be sent,\n"
1407 " 'off' if no events should be sent,\n"
1408 " 'system,call,log' to select which flags events should have to be sent.\n";
1410 static int action_events(struct mansession *s, const struct message *m)
1412 const char *mask = astman_get_header(m, "EventMask");
1415 res = set_eventmask(s, mask);
1417 astman_send_response(s, m, "Success", "Events: On\r\n");
1419 astman_send_response(s, m, "Success", "Events: Off\r\n");
1424 static char mandescr_logoff[] =
1425 "Description: Logoff this manager session\n"
1426 "Variables: NONE\n";
1428 static int action_logoff(struct mansession *s, const struct message *m)
1430 astman_send_response(s, m, "Goodbye", "Thanks for all the fish.");
1434 static int action_login(struct mansession *s, const struct message *m)
1436 if (authenticate(s, m)) {
1438 astman_send_error(s, m, "Authentication failed");
1441 s->authenticated = 1;
1442 if (manager_displayconnects(s))
1443 ast_verb(2, "%sManager '%s' logged on from %s\n", (s->managerid ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr));
1444 ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->managerid ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr));
1445 astman_send_ack(s, m, "Authentication accepted");
1449 static int action_challenge(struct mansession *s, const struct message *m)
1451 const char *authtype = astman_get_header(m, "AuthType");
1453 if (!strcasecmp(authtype, "MD5")) {
1454 if (ast_strlen_zero(s->challenge))
1455 snprintf(s->challenge, sizeof(s->challenge), "%ld", ast_random());
1456 ast_mutex_lock(&s->__lock);
1457 astman_start_ack(s, m);
1458 astman_append(s, "Challenge: %s\r\n\r\n", s->challenge);
1459 ast_mutex_unlock(&s->__lock);
1461 astman_send_error(s, m, "Must specify AuthType");
1466 static char mandescr_hangup[] =
1467 "Description: Hangup a channel\n"
1469 " Channel: The channel name to be hungup\n";
1471 static int action_hangup(struct mansession *s, const struct message *m)
1473 struct ast_channel *c = NULL;
1474 const char *name = astman_get_header(m, "Channel");
1475 if (ast_strlen_zero(name)) {
1476 astman_send_error(s, m, "No channel specified");
1479 c = ast_get_channel_by_name_locked(name);
1481 astman_send_error(s, m, "No such channel");
1484 ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
1485 ast_channel_unlock(c);
1486 astman_send_ack(s, m, "Channel Hungup");
1490 static char mandescr_setvar[] =
1491 "Description: Set a global or local channel variable.\n"
1492 "Variables: (Names marked with * are required)\n"
1493 " Channel: Channel to set variable for\n"
1494 " *Variable: Variable name\n"
1497 static int action_setvar(struct mansession *s, const struct message *m)
1499 struct ast_channel *c = NULL;
1500 const char *name = astman_get_header(m, "Channel");
1501 const char *varname = astman_get_header(m, "Variable");
1502 const char *varval = astman_get_header(m, "Value");
1504 if (ast_strlen_zero(varname)) {
1505 astman_send_error(s, m, "No variable specified");
1509 if (!ast_strlen_zero(name)) {
1510 c = ast_get_channel_by_name_locked(name);
1512 astman_send_error(s, m, "No such channel");
1517 pbx_builtin_setvar_helper(c, varname, S_OR(varval, ""));
1520 ast_channel_unlock(c);
1522 astman_send_ack(s, m, "Variable Set");
1527 static char mandescr_getvar[] =
1528 "Description: Get the value of a global or local channel variable.\n"
1529 "Variables: (Names marked with * are required)\n"
1530 " Channel: Channel to read variable from\n"
1531 " *Variable: Variable name\n"
1532 " ActionID: Optional Action id for message matching.\n";
1534 static int action_getvar(struct mansession *s, const struct message *m)
1536 struct ast_channel *c = NULL;
1537 const char *name = astman_get_header(m, "Channel");
1538 const char *varname = astman_get_header(m, "Variable");
1540 char workspace[1024] = "";
1542 if (ast_strlen_zero(varname)) {
1543 astman_send_error(s, m, "No variable specified");
1547 if (!ast_strlen_zero(name)) {
1548 c = ast_get_channel_by_name_locked(name);
1550 astman_send_error(s, m, "No such channel");
1555 if (varname[strlen(varname) - 1] == ')') {
1556 ast_func_read(c, (char *) varname, workspace, sizeof(workspace));
1559 pbx_retrieve_variable(c, varname, &varval, workspace, sizeof(workspace), NULL);
1563 ast_channel_unlock(c);
1564 astman_start_ack(s, m);
1565 astman_append(s, "Variable: %s\r\nValue: %s\r\n\r\n", varname, varval);
1571 /*! \brief Manager "status" command to show channels */
1572 /* Needs documentation... */
1573 static int action_status(struct mansession *s, const struct message *m)
1575 const char *name = astman_get_header(m,"Channel");
1576 struct ast_channel *c;
1578 struct timeval now = ast_tvnow();
1579 long elapsed_seconds = 0;
1581 int all = ast_strlen_zero(name); /* set if we want all channels */
1582 const char *id = astman_get_header(m,"ActionID");
1583 char idText[256] = "";
1585 if (!ast_strlen_zero(id))
1586 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
1589 c = ast_channel_walk_locked(NULL);
1591 c = ast_get_channel_by_name_locked(name);
1593 astman_send_error(s, m, "No such channel");
1597 astman_send_ack(s, m, "Channel status will follow");
1599 /* if we look by name, we break after the first iteration */
1603 snprintf(bridge, sizeof(bridge), "BridgedChannel: %s\r\nBridgedUniqueid: %s\r\n", c->_bridge->name, c->_bridge->uniqueid);
1608 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
1612 "Privilege: Call\r\n"
1614 "CallerIDNum: %s\r\n"
1615 "CallerIDName: %s\r\n"
1616 "Accountcode: %s\r\n"
1617 "ChannelState: %d\r\n"
1618 "ChannelStateDesc: %s\r\n"
1628 S_OR(c->cid.cid_num, ""),
1629 S_OR(c->cid.cid_name, ""),
1632 ast_state2str(c->_state), c->context,
1633 c->exten, c->priority, (long)elapsed_seconds, bridge, c->uniqueid, idText);
1637 "Privilege: Call\r\n"
1639 "CallerIDNum: %s\r\n"
1640 "CallerIDName: %s\r\n"
1648 S_OR(c->cid.cid_num, "<unknown>"),
1649 S_OR(c->cid.cid_name, "<unknown>"),
1651 ast_state2str(c->_state), bridge, c->uniqueid, idText);
1653 ast_channel_unlock(c);
1656 c = ast_channel_walk_locked(c);
1659 "Event: StatusComplete\r\n"
1662 "\r\n",idText, channels);
1666 static char mandescr_sendtext[] =
1667 "Description: Sends A Text Message while in a call.\n"
1668 "Variables: (Names marked with * are required)\n"
1669 " *Channel: Channel to send message to\n"
1670 " *Message: Message to send\n"
1671 " ActionID: Optional Action id for message matching.\n";
1673 static int action_sendtext(struct mansession *s, const struct message *m)
1675 struct ast_channel *c = NULL;
1676 const char *name = astman_get_header(m, "Channel");
1677 const char *textmsg = astman_get_header(m, "Message");
1680 if (ast_strlen_zero(name)) {
1681 astman_send_error(s, m, "No channel specified");
1685 if (ast_strlen_zero(textmsg)) {
1686 astman_send_error(s, m, "No Message specified");
1690 c = ast_get_channel_by_name_locked(name);
1692 astman_send_error(s, m, "No such channel");
1696 res = ast_sendtext(c, textmsg);
1697 ast_channel_unlock(c);
1700 astman_send_ack(s, m, "Success");
1702 astman_send_error(s, m, "Failure");
1707 static char mandescr_redirect[] =
1708 "Description: Redirect (transfer) a call.\n"
1709 "Variables: (Names marked with * are required)\n"
1710 " *Channel: Channel to redirect\n"
1711 " ExtraChannel: Second call leg to transfer (optional)\n"
1712 " *Exten: Extension to transfer to\n"
1713 " *Context: Context to transfer to\n"
1714 " *Priority: Priority to transfer to\n"
1715 " ActionID: Optional Action id for message matching.\n";
1717 /*! \brief action_redirect: The redirect manager command */
1718 static int action_redirect(struct mansession *s, const struct message *m)
1720 const char *name = astman_get_header(m, "Channel");
1721 const char *name2 = astman_get_header(m, "ExtraChannel");
1722 const char *exten = astman_get_header(m, "Exten");
1723 const char *context = astman_get_header(m, "Context");
1724 const char *priority = astman_get_header(m, "Priority");
1725 struct ast_channel *chan, *chan2 = NULL;
1729 if (ast_strlen_zero(name)) {
1730 astman_send_error(s, m, "Channel not specified");
1733 if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) {
1734 if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
1735 astman_send_error(s, m, "Invalid priority\n");
1739 /* XXX watch out, possible deadlock - we are trying to get two channels!!! */
1740 chan = ast_get_channel_by_name_locked(name);
1743 snprintf(buf, sizeof(buf), "Channel does not exist: %s", name);
1744 astman_send_error(s, m, buf);
1747 if (ast_check_hangup(chan)) {
1748 astman_send_error(s, m, "Redirect failed, channel not up.\n");
1749 ast_channel_unlock(chan);
1752 if (!ast_strlen_zero(name2))
1753 chan2 = ast_get_channel_by_name_locked(name2);
1754 if (chan2 && ast_check_hangup(chan2)) {
1755 astman_send_error(s, m, "Redirect failed, extra channel not up.\n");
1756 ast_channel_unlock(chan);
1757 ast_channel_unlock(chan2);
1760 res = ast_async_goto(chan, context, exten, pi);
1762 if (!ast_strlen_zero(name2)) {
1764 res = ast_async_goto(chan2, context, exten, pi);
1768 astman_send_ack(s, m, "Dual Redirect successful");
1770 astman_send_error(s, m, "Secondary redirect failed");
1772 astman_send_ack(s, m, "Redirect successful");
1774 astman_send_error(s, m, "Redirect failed");
1776 ast_channel_unlock(chan);
1778 ast_channel_unlock(chan2);
1782 static char mandescr_command[] =
1783 "Description: Run a CLI command.\n"
1784 "Variables: (Names marked with * are required)\n"
1785 " *Command: Asterisk CLI command to run\n"
1786 " ActionID: Optional Action id for message matching.\n";
1788 /*! \brief Manager command "command" - execute CLI command */
1789 static int action_command(struct mansession *s, const struct message *m)
1791 const char *cmd = astman_get_header(m, "Command");
1792 const char *id = astman_get_header(m, "ActionID");
1793 char *buf, *final_buf;
1794 char template[] = "/tmp/ast-ami-XXXXXX"; /* template for temporary file */
1795 int fd = mkstemp(template), i = 0;
1798 for (i = 0; i < sizeof(command_blacklist) / sizeof(command_blacklist[0]); i++) {
1799 if (!strncmp(cmd, command_blacklist[i], strlen(command_blacklist[i]))) {
1800 astman_send_error(s, m, "Command blacklisted");
1805 astman_append(s, "Response: Follows\r\nPrivilege: Command\r\n");
1806 if (!ast_strlen_zero(id))
1807 astman_append(s, "ActionID: %s\r\n", id);
1808 /* FIXME: Wedge a ActionID response in here, waiting for later changes */
1809 ast_cli_command(fd, cmd); /* XXX need to change this to use a FILE * */
1810 l = lseek(fd, 0, SEEK_END); /* how many chars available */
1812 /* This has a potential to overflow the stack. Hence, use the heap. */
1813 buf = ast_calloc(1, l + 1);
1814 final_buf = ast_calloc(1, l + 1);
1816 lseek(fd, 0, SEEK_SET);
1820 term_strip(final_buf, buf, l);
1821 final_buf[l] = '\0';
1823 astman_append(s, S_OR(final_buf, buf));
1828 astman_append(s, "--END COMMAND--\r\n\r\n");
1830 ast_free(final_buf);
1834 /* helper function for originate */
1835 struct fast_originate_helper {
1836 char tech[AST_MAX_EXTENSION];
1837 char data[AST_MAX_EXTENSION];
1839 char app[AST_MAX_APP];
1840 char appdata[AST_MAX_EXTENSION];
1841 char cid_name[AST_MAX_EXTENSION];
1842 char cid_num[AST_MAX_EXTENSION];
1843 char context[AST_MAX_CONTEXT];
1844 char exten[AST_MAX_EXTENSION];
1845 char idtext[AST_MAX_EXTENSION];
1846 char account[AST_MAX_ACCOUNT_CODE];
1848 struct ast_variable *vars;
1851 static void *fast_originate(void *data)
1853 struct fast_originate_helper *in = data;
1856 struct ast_channel *chan = NULL;
1857 char requested_channel[AST_CHANNEL_NAME];
1859 if (!ast_strlen_zero(in->app)) {
1860 res = ast_pbx_outgoing_app(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->app, in->appdata, &reason, 1,
1861 S_OR(in->cid_num, NULL),
1862 S_OR(in->cid_name, NULL),
1863 in->vars, in->account, &chan);
1865 res = ast_pbx_outgoing_exten(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->context, in->exten, in->priority, &reason, 1,
1866 S_OR(in->cid_num, NULL),
1867 S_OR(in->cid_name, NULL),
1868 in->vars, in->account, &chan);
1872 snprintf(requested_channel, AST_CHANNEL_NAME, "%s/%s", in->tech, in->data);
1873 /* Tell the manager what happened with the channel */
1874 manager_event(EVENT_FLAG_CALL, "OriginateResponse",
1882 "CallerIDNum: %s\r\n"
1883 "CallerIDName: %s\r\n",
1884 in->idtext, res ? "Failure" : "Success", chan ? chan->name : requested_channel, in->context, in->exten, reason,
1885 chan ? chan->uniqueid : "<null>",
1886 S_OR(in->cid_num, "<unknown>"),
1887 S_OR(in->cid_name, "<unknown>")
1890 /* Locked by ast_pbx_outgoing_exten or ast_pbx_outgoing_app */
1892 ast_channel_unlock(chan);
1897 static char mandescr_originate[] =
1898 "Description: Generates an outgoing call to a Extension/Context/Priority or\n"
1899 " Application/Data\n"
1900 "Variables: (Names marked with * are required)\n"
1901 " *Channel: Channel name to call\n"
1902 " Exten: Extension to use (requires 'Context' and 'Priority')\n"
1903 " Context: Context to use (requires 'Exten' and 'Priority')\n"
1904 " Priority: Priority to use (requires 'Exten' and 'Context')\n"
1905 " Application: Application to use\n"
1906 " Data: Data to use (requires 'Application')\n"
1907 " Timeout: How long to wait for call to be answered (in ms)\n"
1908 " CallerID: Caller ID to be set on the outgoing channel\n"
1909 " Variable: Channel variable to set, multiple Variable: headers are allowed\n"
1910 " Account: Account code\n"
1911 " Async: Set to 'true' for fast origination\n";
1913 static int action_originate(struct mansession *s, const struct message *m)
1915 const char *name = astman_get_header(m, "Channel");
1916 const char *exten = astman_get_header(m, "Exten");
1917 const char *context = astman_get_header(m, "Context");
1918 const char *priority = astman_get_header(m, "Priority");
1919 const char *timeout = astman_get_header(m, "Timeout");
1920 const char *callerid = astman_get_header(m, "CallerID");
1921 const char *account = astman_get_header(m, "Account");
1922 const char *app = astman_get_header(m, "Application");
1923 const char *appdata = astman_get_header(m, "Data");
1924 const char *async = astman_get_header(m, "Async");
1925 const char *id = astman_get_header(m, "ActionID");
1926 struct ast_variable *vars = astman_get_variables(m);
1928 char *l = NULL, *n = NULL;
1938 astman_send_error(s, m, "Channel not specified");
1941 if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) {
1942 if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
1943 astman_send_error(s, m, "Invalid priority\n");
1947 if (!ast_strlen_zero(timeout) && (sscanf(timeout, "%d", &to) != 1)) {
1948 astman_send_error(s, m, "Invalid timeout\n");
1951 ast_copy_string(tmp, name, sizeof(tmp));
1953 data = strchr(tmp, '/');
1955 astman_send_error(s, m, "Invalid channel\n");
1959 ast_copy_string(tmp2, callerid, sizeof(tmp2));
1960 ast_callerid_parse(tmp2, &n, &l);
1962 if (ast_strlen_zero(n))
1966 ast_shrink_phone_number(l);
1967 if (ast_strlen_zero(l))
1970 if (ast_true(async)) {
1971 struct fast_originate_helper *fast = ast_calloc(1, sizeof(*fast));
1975 if (!ast_strlen_zero(id))
1976 snprintf(fast->idtext, sizeof(fast->idtext), "ActionID: %s\r\n", id);
1977 ast_copy_string(fast->tech, tech, sizeof(fast->tech));
1978 ast_copy_string(fast->data, data, sizeof(fast->data));
1979 ast_copy_string(fast->app, app, sizeof(fast->app));
1980 ast_copy_string(fast->appdata, appdata, sizeof(fast->appdata));
1982 ast_copy_string(fast->cid_num, l, sizeof(fast->cid_num));
1984 ast_copy_string(fast->cid_name, n, sizeof(fast->cid_name));
1986 ast_copy_string(fast->context, context, sizeof(fast->context));
1987 ast_copy_string(fast->exten, exten, sizeof(fast->exten));
1988 ast_copy_string(fast->account, account, sizeof(fast->account));
1990 fast->priority = pi;
1991 if (ast_pthread_create_detached(&th, NULL, fast_originate, fast)) {
1997 } else if (!ast_strlen_zero(app)) {
1998 res = ast_pbx_outgoing_app(tech, AST_FORMAT_SLINEAR, data, to, app, appdata, &reason, 1, l, n, vars, account, NULL);
2000 if (exten && context && pi)
2001 res = ast_pbx_outgoing_exten(tech, AST_FORMAT_SLINEAR, data, to, context, exten, pi, &reason, 1, l, n, vars, account, NULL);
2003 astman_send_error(s, m, "Originate with 'Exten' requires 'Context' and 'Priority'");
2008 astman_send_ack(s, m, "Originate successfully queued");
2010 astman_send_error(s, m, "Originate failed");
2014 /*! \brief Help text for manager command mailboxstatus
2016 static char mandescr_mailboxstatus[] =
2017 "Description: Checks a voicemail account for status.\n"
2018 "Variables: (Names marked with * are required)\n"
2019 " *Mailbox: Full mailbox ID <mailbox>@<vm-context>\n"
2020 " ActionID: Optional ActionID for message matching.\n"
2021 "Returns number of messages.\n"
2022 " Message: Mailbox Status\n"
2023 " Mailbox: <mailboxid>\n"
2024 " Waiting: <count>\n"
2027 static int action_mailboxstatus(struct mansession *s, const struct message *m)
2029 const char *mailbox = astman_get_header(m, "Mailbox");
2032 if (ast_strlen_zero(mailbox)) {
2033 astman_send_error(s, m, "Mailbox not specified");
2036 ret = ast_app_has_voicemail(mailbox, NULL);
2037 astman_start_ack(s, m);
2038 astman_append(s, "Message: Mailbox Status\r\n"
2040 "Waiting: %d\r\n\r\n", mailbox, ret);
2044 static char mandescr_mailboxcount[] =
2045 "Description: Checks a voicemail account for new messages.\n"
2046 "Variables: (Names marked with * are required)\n"
2047 " *Mailbox: Full mailbox ID <mailbox>@<vm-context>\n"
2048 " ActionID: Optional ActionID for message matching.\n"
2049 "Returns number of new and old messages.\n"
2050 " Message: Mailbox Message Count\n"
2051 " Mailbox: <mailboxid>\n"
2052 " NewMessages: <count>\n"
2053 " OldMessages: <count>\n"
2055 static int action_mailboxcount(struct mansession *s, const struct message *m)
2057 const char *mailbox = astman_get_header(m, "Mailbox");
2058 int newmsgs = 0, oldmsgs = 0;
2060 if (ast_strlen_zero(mailbox)) {
2061 astman_send_error(s, m, "Mailbox not specified");
2064 ast_app_inboxcount(mailbox, &newmsgs, &oldmsgs);
2065 astman_start_ack(s, m);
2066 astman_append(s, "Message: Mailbox Message Count\r\n"
2068 "NewMessages: %d\r\n"
2069 "OldMessages: %d\r\n"
2071 mailbox, newmsgs, oldmsgs);
2075 static char mandescr_extensionstate[] =
2076 "Description: Report the extension state for given extension.\n"
2077 " If the extension has a hint, will use devicestate to check\n"
2078 " the status of the device connected to the extension.\n"
2079 "Variables: (Names marked with * are required)\n"
2080 " *Exten: Extension to check state on\n"
2081 " *Context: Context for extension\n"
2082 " ActionId: Optional ID for this transaction\n"
2083 "Will return an \"Extension Status\" message.\n"
2084 "The response will include the hint for the extension and the status.\n";
2086 static int action_extensionstate(struct mansession *s, const struct message *m)
2088 const char *exten = astman_get_header(m, "Exten");
2089 const char *context = astman_get_header(m, "Context");
2090 char hint[256] = "";
2092 if (ast_strlen_zero(exten)) {
2093 astman_send_error(s, m, "Extension not specified");
2096 if (ast_strlen_zero(context))
2097 context = "default";
2098 status = ast_extension_state(NULL, context, exten);
2099 ast_get_hint(hint, sizeof(hint) - 1, NULL, 0, NULL, context, exten);
2100 astman_start_ack(s, m);
2101 astman_append(s, "Message: Extension Status\r\n"
2105 "Status: %d\r\n\r\n",
2106 exten, context, hint, status);
2110 static char mandescr_timeout[] =
2111 "Description: Hangup a channel after a certain time.\n"
2112 "Variables: (Names marked with * are required)\n"
2113 " *Channel: Channel name to hangup\n"
2114 " *Timeout: Maximum duration of the call (sec)\n"
2115 "Acknowledges set time with 'Timeout Set' message\n";
2117 static int action_timeout(struct mansession *s, const struct message *m)
2119 struct ast_channel *c;
2120 const char *name = astman_get_header(m, "Channel");
2121 int timeout = atoi(astman_get_header(m, "Timeout"));
2123 if (ast_strlen_zero(name)) {
2124 astman_send_error(s, m, "No channel specified");
2128 astman_send_error(s, m, "No timeout specified");
2131 c = ast_get_channel_by_name_locked(name);
2133 astman_send_error(s, m, "No such channel");
2136 ast_channel_setwhentohangup(c, timeout);
2137 ast_channel_unlock(c);
2138 astman_send_ack(s, m, "Timeout Set");
2143 * Send any applicable events to the client listening on this socket.
2144 * Wait only for a finite time on each event, and drop all events whether
2145 * they are successfully sent or not.
2147 static int process_events(struct mansession *s)
2151 ast_mutex_lock(&s->__lock);
2153 struct eventqent *eqe;
2155 while ( (eqe = NEW_EVENT(s)) ) {
2157 if (!ret && s->authenticated &&
2158 (s->readperm & eqe->category) == eqe->category &&
2159 (s->send_events & eqe->category) == eqe->category) {
2160 if (send_string(s, eqe->eventdata) < 0)
2161 ret = -1; /* don't send more */
2163 s->last_ev = unref_event(s->last_ev);
2166 ast_mutex_unlock(&s->__lock);
2170 static char mandescr_userevent[] =
2171 "Description: Send an event to manager sessions.\n"
2172 "Variables: (Names marked with * are required)\n"
2173 " *UserEvent: EventStringToSend\n"
2174 " Header1: Content1\n"
2175 " HeaderN: ContentN\n";
2177 static int action_userevent(struct mansession *s, const struct message *m)
2179 const char *event = astman_get_header(m, "UserEvent");
2180 char body[2048] = "";
2182 for (x = 0; x < m->hdrcount; x++) {
2183 if (strncasecmp("UserEvent:", m->headers[x], strlen("UserEvent:"))) {
2184 ast_copy_string(body + bodylen, m->headers[x], sizeof(body) - bodylen - 3);
2185 bodylen += strlen(m->headers[x]);
2186 ast_copy_string(body + bodylen, "\r\n", 3);
2191 manager_event(EVENT_FLAG_USER, "UserEvent", "UserEvent: %s\r\n%s", event, body);
2195 static char mandescr_coresettings[] =
2196 "Description: Query for Core PBX settings.\n"
2197 "Variables: (Names marked with * are optional)\n"
2198 " *ActionID: ActionID of this transaction\n";
2200 /*! \brief Show PBX core settings information */
2201 static int action_coresettings(struct mansession *s, const struct message *m)
2203 const char *actionid = astman_get_header(m, "ActionID");
2204 char idText[150] = "";
2206 if (!ast_strlen_zero(actionid))
2207 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
2209 astman_append(s, "Response: Success\r\n"
2211 "AMIversion: %s\r\n"
2212 "AsteriskVersion: %s\r\n"
2213 "SystemName: %s\r\n"
2214 "CoreMaxCalls: %d\r\n"
2215 "CoreMaxLoadAvg: %f\r\n"
2216 "CoreRunUser: %s\r\n"
2217 "CoreRunGroup: %s\r\n"
2218 "CoreMaxFilehandles: %d\r\n"
2219 "CoreRealTimeEnabled: %s\r\n"
2220 "CoreCDRenabled: %s\r\n"
2221 "CoreHTTPenabled: %s\r\n"
2226 ast_config_AST_SYSTEM_NAME,
2229 ast_config_AST_RUN_USER,
2230 ast_config_AST_RUN_GROUP,
2232 ast_realtime_enabled() ? "Yes" : "No",
2233 check_cdr_enabled() ? "Yes" : "No",
2234 check_webmanager_enabled() ? "Yes" : "No"
2239 static char mandescr_corestatus[] =
2240 "Description: Query for Core PBX status.\n"
2241 "Variables: (Names marked with * are optional)\n"
2242 " *ActionID: ActionID of this transaction\n";
2244 /*! \brief Show PBX core status information */
2245 static int action_corestatus(struct mansession *s, const struct message *m)
2247 const char *actionid = astman_get_header(m, "ActionID");
2249 char startuptime[150];
2250 char reloadtime[150];
2253 if (!ast_strlen_zero(actionid))
2254 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
2256 ast_localtime(&ast_startuptime, &tm, NULL);
2257 ast_strftime(startuptime, sizeof(startuptime), "%H:%M:%S", &tm);
2258 ast_localtime(&ast_lastreloadtime, &tm, NULL);
2259 ast_strftime(reloadtime, sizeof(reloadtime), "%H:%M:%S", &tm);
2261 astman_append(s, "Response: Success\r\n"
2263 "CoreStartupTime: %s\r\n"
2264 "CoreReloadTime: %s\r\n"
2265 "CoreCurrentCalls: %d\r\n"
2270 ast_active_channels()
2275 static char mandescr_reload[] =
2276 "Description: Send a reload event.\n"
2277 "Variables: (Names marked with * are optional)\n"
2278 " *ActionID: ActionID of this transaction\n"
2279 " *Module: Name of the module to reload\n";
2281 /*! \brief Send a reload event */
2282 static int action_reload(struct mansession *s, const struct message *m)
2284 const char *actionid = astman_get_header(m, "ActionID");
2285 const char *module = astman_get_header(m, "Module");
2286 int res = ast_module_reload(S_OR(module, NULL));
2287 char idText[80] = "";
2289 if (!ast_strlen_zero(actionid))
2290 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
2292 astman_append(s, "Response: Success\r\n%s", idText);
2294 astman_send_error(s, m, s == 0 ? "No such module" : "Module does not support reload");
2298 static char mandescr_coreshowchannels[] =
2299 "Description: List currently defined channels and some information\n"
2302 " ActionID: Optional Action id for message matching.\n";
2304 /*! \brief Manager command "CoreShowChannels" - List currently defined channels
2305 * and some information about them. */
2306 static int action_coreshowchannels(struct mansession *s, const struct message *m)
2308 const char *actionid = astman_get_header(m, "ActionID");
2309 char actionidtext[256] = "";
2310 struct ast_channel *c = NULL;
2312 int duration, durh, durm, durs;
2314 if (!ast_strlen_zero(actionid))
2315 snprintf(actionidtext, sizeof(actionidtext), "ActionID: %s\r\n", actionid);
2317 astman_send_listack(s, m, "Channels will follow", "start");
2319 while ((c = ast_channel_walk_locked(c)) != NULL) {
2320 struct ast_channel *bc = ast_bridged_channel(c);
2321 char durbuf[10] = "";
2323 if (c->cdr && !ast_tvzero(c->cdr->start)) {
2324 duration = (int)(ast_tvdiff_ms(ast_tvnow(), c->cdr->start) / 1000);
2325 durh = duration / 3600;
2326 durm = (duration % 3600) / 60;
2327 durs = duration % 60;
2328 snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
2337 "ChannelState: %d\r\n"
2338 "ChannelStateDesc: %s\r\n"
2339 "Application: %s\r\n"
2340 "ApplicationData: %s\r\n"
2341 "CallerIDnum: %s\r\n"
2343 "AccountCode: %s\r\n"
2344 "BridgedChannel: %s\r\n"
2345 "BridgedUniqueID: %s\r\n"
2346 "\r\n", c->name, c->uniqueid, c->context, c->exten, c->priority, c->_state, ast_state2str(c->_state),
2347 c->appl ? c->appl : "", c->data ? S_OR(c->data, ""): "",
2348 S_OR(c->cid.cid_num, ""), durbuf, S_OR(c->accountcode, ""), bc ? bc->name : "", bc ? bc->uniqueid : "");
2349 ast_channel_unlock(c);
2354 "Event: CoreShowChannelsComplete\r\n"
2355 "EventList: Complete\r\n"
2358 "\r\n", numchans, actionidtext);
2365 * Done with the action handlers here, we start with the code in charge
2366 * of accepting connections and serving them.
2367 * accept_thread() forks a new thread for each connection, session_do(),
2368 * which in turn calls get_input() repeatedly until a full message has
2369 * been accumulated, and then invokes process_message() to pass it to
2370 * the appropriate handler.
2374 * Process an AMI message, performing desired action.
2375 * Return 0 on success, -1 on error that require the session to be destroyed.
2377 static int process_message(struct mansession *s, const struct message *m)
2379 char action[80] = "";
2381 struct manager_action *tmp;
2382 const char *user = astman_get_header(m, "Username");
2384 ast_copy_string(action, astman_get_header(m, "Action"), sizeof(action));
2385 ast_debug(1, "Manager received command '%s'\n", action);
2387 if (ast_strlen_zero(action)) {
2388 ast_mutex_lock(&s->__lock);
2389 astman_send_error(s, m, "Missing action in request");
2390 ast_mutex_unlock(&s->__lock);
2394 if (!s->authenticated && strcasecmp(action, "Login") && strcasecmp(action, "Logoff") && strcasecmp(action, "Challenge")) {
2395 ast_mutex_lock(&s->__lock);
2396 astman_send_error(s, m, "Permission denied");
2397 ast_mutex_unlock(&s->__lock);
2401 if (!allowmultiplelogin && !s->authenticated && user &&
2402 (!strcasecmp(action, "Login") || !strcasecmp(action, "Challenge"))) {
2403 if (check_manager_session_inuse(user)) {
2405 ast_mutex_lock(&s->__lock);
2406 astman_send_error(s, m, "Login Already In Use");
2407 ast_mutex_unlock(&s->__lock);
2412 AST_RWLIST_RDLOCK(&actions);
2413 AST_RWLIST_TRAVERSE(&actions, tmp, list) {
2414 if (strcasecmp(action, tmp->action))
2416 if ((s->writeperm & tmp->authority) == tmp->authority)
2417 ret = tmp->func(s, m);
2419 astman_send_error(s, m, "Permission denied");
2422 AST_RWLIST_UNLOCK(&actions);
2426 snprintf(buf, sizeof(buf), "Invalid/unknown command: %s. Use Action: ListCommands to show available commands.", action);
2427 ast_mutex_lock(&s->__lock);
2428 astman_send_error(s, m, buf);
2429 ast_mutex_unlock(&s->__lock);
2433 /* Once done with our message, deliver any pending events */
2434 return process_events(s);
2438 * Read one full line (including crlf) from the manager socket.
2440 * \r\n is the only valid terminator for the line.
2441 * (Note that, later, '\0' will be considered as the end-of-line marker,
2442 * so everything between the '\0' and the '\r\n' will not be used).
2443 * Also note that we assume output to have at least "maxlen" space.
2446 static int get_input(struct mansession *s, char *output)
2449 int maxlen = sizeof(s->inbuf) - 1;
2450 char *src = s->inbuf;
2453 * Look for \r\n within the buffer. If found, copy to the output
2454 * buffer and return, trimming the \r\n (not used afterwards).
2456 for (x = 0; x < s->inlen; x++) {
2457 int cr; /* set if we have \r */
2458 if (src[x] == '\r' && x+1 < s->inlen && src[x+1] == '\n')
2459 cr = 2; /* Found. Update length to include \r\n */
2460 else if (src[x] == '\n')
2461 cr = 1; /* also accept \n only */
2464 memmove(output, src, x); /*... but trim \r\n */
2465 output[x] = '\0'; /* terminate the string */
2466 x += cr; /* number of bytes used */
2467 s->inlen -= x; /* remaining size */
2468 memmove(src, src + x, s->inlen); /* remove used bytes */
2471 if (s->inlen >= maxlen) {
2472 /* no crlf found, and buffer full - sorry, too long for us */
2473 ast_log(LOG_WARNING, "Dumping long line with no return from %s: %s\n", ast_inet_ntoa(s->sin.sin_addr), src);
2478 /* XXX do we really need this locking ? */
2479 ast_mutex_lock(&s->__lock);
2480 s->waiting_thread = pthread_self();
2481 ast_mutex_unlock(&s->__lock);
2483 res = ast_wait_for_input(s->fd, -1); /* return 0 on timeout ? */
2485 ast_mutex_lock(&s->__lock);
2486 s->waiting_thread = AST_PTHREADT_NULL;
2487 ast_mutex_unlock(&s->__lock);
2490 /* If we get a signal from some other thread (typically because
2491 * there are new events queued), return 0 to notify the caller.
2495 ast_log(LOG_WARNING, "poll() returned error: %s\n", strerror(errno));
2498 ast_mutex_lock(&s->__lock);
2499 res = fread(src + s->inlen, 1, maxlen - s->inlen, s->f);
2501 res = -1; /* error return */
2504 src[s->inlen] = '\0';
2507 ast_mutex_unlock(&s->__lock);
2511 static int do_message(struct mansession *s)
2513 struct message m = { 0 };
2514 char header_buf[sizeof(s->inbuf)] = { '\0' };
2518 /* Check if any events are pending and do them if needed */
2519 if (process_events(s))
2521 res = get_input(s, header_buf);
2524 } else if (res > 0) {
2525 if (ast_strlen_zero(header_buf))
2526 return process_message(s, &m) ? -1 : 0;
2527 else if (m.hdrcount < (AST_MAX_MANHEADERS - 1))
2528 m.headers[m.hdrcount++] = ast_strdupa(header_buf);
2535 /*! \brief The body of the individual manager session.
2536 * Call get_input() to read one line at a time
2537 * (or be woken up on new events), collect the lines in a
2538 * message until found an empty line, and execute the request.
2539 * In any case, deliver events asynchronously through process_events()
2540 * (called from here if no line is available, or at the end of
2541 * process_message(). )
2543 static void *session_do(void *data)
2545 struct server_instance *ser = data;
2546 struct mansession *s = ast_calloc(1, sizeof(*s));
2553 s->writetimeout = 100;
2554 s->waiting_thread = AST_PTHREADT_NULL;
2556 flags = fcntl(ser->fd, F_GETFL);
2557 if (!block_sockets) /* make sure socket is non-blocking */
2558 flags |= O_NONBLOCK;
2560 flags &= ~O_NONBLOCK;
2561 fcntl(ser->fd, F_SETFL, flags);
2563 ast_mutex_init(&s->__lock);
2564 s->send_events = -1;
2565 /* these fields duplicate those in the 'ser' structure */
2568 s->sin = ser->requestor;
2570 AST_LIST_LOCK(&sessions);
2571 AST_LIST_INSERT_HEAD(&sessions, s, list);
2572 ast_atomic_fetchadd_int(&num_sessions, 1);
2573 AST_LIST_UNLOCK(&sessions);
2574 /* Hook to the tail of the event queue */
2575 s->last_ev = grab_last();
2577 astman_append(s, "Asterisk Call Manager/%s\r\n", AMI_VERSION); /* welcome prompt */
2579 if ((res = do_message(s)) < 0)
2582 /* session is over, explain why and terminate */
2583 if (s->authenticated) {
2584 if (manager_displayconnects(s))
2585 ast_verb(2, "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
2586 ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
2588 if (displayconnects)
2589 ast_verb(2, "Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr));
2590 ast_log(LOG_EVENT, "Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr));
2593 /* If the entire session occurs in a single context switch, then it's
2594 * possible to get an unsafe memory condition by free()ing the memory
2595 * before letting other threads run at least once. This actually seems
2596 * like a workaround for a glibc bug.
2607 /*! \brief remove at most n_max stale session from the list. */
2608 static void purge_sessions(int n_max)
2610 struct mansession *s;
2611 time_t now = time(NULL);
2613 AST_LIST_LOCK(&sessions);
2614 AST_LIST_TRAVERSE_SAFE_BEGIN(&sessions, s, list) {
2615 if (s->sessiontimeout && (now > s->sessiontimeout) && !s->inuse) {
2616 AST_LIST_REMOVE_CURRENT(list);
2617 ast_atomic_fetchadd_int(&num_sessions, -1);
2618 if (s->authenticated && (VERBOSITY_ATLEAST(2)) && manager_displayconnects(s)) {
2619 ast_verb(2, "HTTP Manager '%s' timed out from %s\n",
2620 s->username, ast_inet_ntoa(s->sin.sin_addr));
2622 free_session(s); /* XXX outside ? */
2627 AST_LIST_TRAVERSE_SAFE_END;
2628 AST_LIST_UNLOCK(&sessions);
2632 * events are appended to a queue from where they
2633 * can be dispatched to clients.
2635 static int append_event(const char *str, int category)
2637 struct eventqent *tmp = ast_malloc(sizeof(*tmp) + strlen(str));
2638 static int seq; /* sequence number */
2643 /* need to init all fields, because ast_malloc() does not */
2645 tmp->category = category;
2646 tmp->seq = ast_atomic_fetchadd_int(&seq, 1);
2647 AST_LIST_NEXT(tmp, eq_next) = NULL;
2648 strcpy(tmp->eventdata, str);
2650 AST_LIST_LOCK(&all_events);
2651 AST_LIST_INSERT_TAIL(&all_events, tmp, eq_next);
2652 AST_LIST_UNLOCK(&all_events);
2657 /* XXX see if can be moved inside the function */
2658 AST_THREADSTORAGE(manager_event_buf);
2659 #define MANAGER_EVENT_BUF_INITSIZE 256
2661 /*! \brief manager_event: Send AMI event to client */
2662 int __manager_event(int category, const char *event,
2663 const char *file, int line, const char *func, const char *fmt, ...)
2665 struct mansession *s;
2666 struct manager_custom_hook *hook;
2667 struct ast_str *auth = ast_str_alloca(80);
2668 const char *cat_str;
2671 struct ast_str *buf;
2673 /* Abort if there aren't any manager sessions */
2677 if (!(buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE)))
2680 cat_str = authority_to_str(category, &auth);
2681 ast_str_set(&buf, 0,
2682 "Event: %s\r\nPrivilege: %s\r\n",
2685 if (timestampevents) {
2687 ast_str_append(&buf, 0,
2688 "Timestamp: %ld.%06lu\r\n",
2689 now.tv_sec, (unsigned long) now.tv_usec);
2691 if (manager_debug) {
2693 ast_str_append(&buf, 0,
2694 "SequenceNumber: %d\r\n",
2695 ast_atomic_fetchadd_int(&seq, 1));
2696 ast_str_append(&buf, 0,
2697 "File: %s\r\nLine: %d\r\nFunc: %s\r\n", file, line, func);
2701 ast_str_append_va(&buf, 0, fmt, ap);
2704 ast_str_append(&buf, 0, "\r\n");
2706 append_event(buf->str, category);
2708 /* Wake up any sleeping sessions */
2709 AST_LIST_LOCK(&sessions);
2710 AST_LIST_TRAVERSE(&sessions, s, list) {
2711 ast_mutex_lock(&s->__lock);
2712 if (s->waiting_thread != AST_PTHREADT_NULL)
2713 pthread_kill(s->waiting_thread, SIGURG);
2714 ast_mutex_unlock(&s->__lock);
2716 AST_LIST_UNLOCK(&sessions);
2718 AST_RWLIST_RDLOCK(&manager_hooks);
2719 AST_RWLIST_TRAVERSE(&manager_hooks, hook, list) {
2720 hook->helper(category, event, buf->str);
2722 AST_RWLIST_UNLOCK(&manager_hooks);
2728 * support functions to register/unregister AMI action handlers,
2730 int ast_manager_unregister(char *action)
2732 struct manager_action *cur;
2734 AST_RWLIST_WRLOCK(&actions);
2735 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&actions, cur, list) {
2736 if (!strcasecmp(action, cur->action)) {
2737 AST_RWLIST_REMOVE_CURRENT(list);
2739 ast_verb(2, "Manager unregistered action %s\n", action);
2743 AST_RWLIST_TRAVERSE_SAFE_END;
2744 AST_RWLIST_UNLOCK(&actions);
2749 static int manager_state_cb(char *context, char *exten, int state, void *data)
2751 /* Notify managers of change */
2753 ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten);
2755 manager_event(EVENT_FLAG_CALL, "ExtensionStatus", "Exten: %s\r\nContext: %s\r\nHint: %s\r\nStatus: %d\r\n", exten, context, hint, state);
2759 static int ast_manager_register_struct(struct manager_action *act)
2761 struct manager_action *cur, *prev = NULL;
2763 AST_RWLIST_WRLOCK(&actions);
2764 AST_RWLIST_TRAVERSE(&actions, cur, list) {
2765 int ret = strcasecmp(cur->action, act->action);
2767 ast_log(LOG_WARNING, "Manager: Action '%s' already registered\n", act->action);
2768 AST_RWLIST_UNLOCK(&actions);
2771 if (ret > 0) { /* Insert these alphabetically */
2778 AST_RWLIST_INSERT_AFTER(&actions, prev, act, list);
2780 AST_RWLIST_INSERT_HEAD(&actions, act, list);
2782 ast_verb(2, "Manager registered action %s\n", act->action);
2784 AST_RWLIST_UNLOCK(&actions);
2789 /*! \brief register a new command with manager, including online help. This is
2790 the preferred way to register a manager command */
2791 int ast_manager_register2(const char *action, int auth, int (*func)(struct mansession *s, const struct message *m), const char *synopsis, const char *description)
2793 struct manager_action *cur = NULL;
2795 if (!(cur = ast_calloc(1, sizeof(*cur))))
2798 cur->action = action;
2799 cur->authority = auth;
2801 cur->synopsis = synopsis;
2802 cur->description = description;
2804 ast_manager_register_struct(cur);
2809 END Doxygen group */
2812 * The following are support functions for AMI-over-http.
2813 * The common entry point is generic_http_callback(),
2814 * which extracts HTTP header and URI fields and reformats
2815 * them into AMI messages, locates a proper session
2816 * (using the mansession_id Cookie or GET variable),
2817 * and calls process_message() as for regular AMI clients.
2818 * When done, the output (which goes to a temporary file)
2819 * is read back into a buffer and reformatted as desired,
2820 * then fed back to the client over the original socket.
2823 enum output_format {
2829 static char *contenttype[] = {
2830 [FORMAT_RAW] = "plain",
2831 [FORMAT_HTML] = "html",
2832 [FORMAT_XML] = "xml",
2836 * locate an http session in the list. The search key (ident) is
2837 * the value of the mansession_id cookie (0 is not valid and means
2838 * a session on the AMI socket).
2840 static struct mansession *find_session(unsigned long ident)
2842 struct mansession *s;
2847 AST_LIST_LOCK(&sessions);
2848 AST_LIST_TRAVERSE(&sessions, s, list) {
2849 ast_mutex_lock(&s->__lock);
2850 if (s->managerid == ident && !s->needdestroy) {
2851 ast_atomic_fetchadd_int(&s->inuse, 1);
2854 ast_mutex_unlock(&s->__lock);
2856 AST_LIST_UNLOCK(&sessions);
2861 int astman_verify_session_readpermissions(unsigned long ident, int perm)
2864 struct mansession *s;
2866 AST_LIST_LOCK(&sessions);
2867 AST_LIST_TRAVERSE(&sessions, s, list) {
2868 ast_mutex_lock(&s->__lock);
2869 if ((s->managerid == ident) && (s->readperm & perm)) {
2871 ast_mutex_unlock(&s->__lock);
2874 ast_mutex_unlock(&s->__lock);
2876 AST_LIST_UNLOCK(&sessions);
2880 int astman_verify_session_writepermissions(unsigned long ident, int perm)
2883 struct mansession *s;
2885 AST_LIST_LOCK(&sessions);
2886 AST_LIST_TRAVERSE(&sessions, s, list) {
2887 ast_mutex_lock(&s->__lock);
2888 if ((s->managerid == ident) && (s->writeperm & perm)) {
2890 ast_mutex_unlock(&s->__lock);
2893 ast_mutex_unlock(&s->__lock);
2895 AST_LIST_UNLOCK(&sessions);
2900 * convert to xml with various conversion:
2901 * mode & 1 -> lowercase;
2902 * mode & 2 -> replace non-alphanumeric chars with underscore
2904 static void xml_copy_escape(struct ast_str **out, const char *src, int mode)
2906 /* store in a local buffer to avoid calling ast_str_append too often */
2909 int space = sizeof(buf);
2910 /* repeat until done and nothing to flush */
2911 for ( ; *src || dst != buf ; src++) {
2912 if (*src == '\0' || space < 10) { /* flush */
2914 ast_str_append(out, 0, "%s", buf);
2916 space = sizeof(buf);
2921 if ( (mode & 2) && !isalnum(*src)) {
2928 strcpy(dst, "<");
2933 strcpy(dst, ">");
2938 strcpy(dst, """);
2943 strcpy(dst, "'");
2948 strcpy(dst, "&");
2954 *dst++ = mode ? tolower(*src) : *src;
2960 struct variable_count {
2965 static int compress_char(char c)
2970 else if (c >= 'a' && c <= 'z')
2978 static int variable_count_hash_fn(const void *vvc, const int flags)
2980 const struct variable_count *vc = vvc;
2982 for (i = 0; i < 5; i++) {
2983 if (vc->varname[i] == '\0')
2985 res += compress_char(vc->varname[i]) << (i * 6);
2990 static int variable_count_cmp_fn(void *obj, void *vstr, int flags)
2992 /* Due to the simplicity of struct variable_count, it makes no difference
2993 * if you pass in objects or strings, the same operation applies. This is
2994 * due to the fact that the hash occurs on the first element, which means
2995 * the address of both the struct and the string are exactly the same. */
2996 struct variable_count *vc = obj;
2998 return !strcmp(vc->varname, str) ? CMP_MATCH : 0;
3001 /*! \brief Convert the input into XML or HTML.
3002 * The input is supposed to be a sequence of lines of the form
3004 * optionally followed by a blob of unformatted text.
3005 * A blank line is a section separator. Basically, this is a
3006 * mixture of the format of Manager Interface and CLI commands.
3007 * The unformatted text is considered as a single value of a field
3008 * named 'Opaque-data'.
3010 * At the moment the output format is the following (but it may
3011 * change depending on future requirements so don't count too
3012 * much on it when writing applications):
3014 * General: the unformatted text is used as a value of
3015 * XML output: to be completed
3018 * Each section is within <response type="object" id="xxx">
3019 * where xxx is taken from ajaxdest variable or defaults to unknown
3020 * Each row is reported as an attribute Name="value" of an XML
3021 * entity named from the variable ajaxobjtype, default to "generic"
3025 * each Name-value pair is output as a single row of a two-column table.
3026 * Sections (blank lines in the input) are separated by a <HR>
3029 static void xml_translate(struct ast_str **out, char *in, struct ast_variable *vars, enum output_format format)
3031 struct ast_variable *v;
3032 const char *dest = NULL;
3034 const char *objtype = NULL;
3035 int in_data = 0; /* parsing data */
3037 int xml = (format == FORMAT_XML);
3038 struct variable_count *vc = NULL;
3039 struct ao2_container *vco = NULL;
3041 for (v = vars; v; v = v->next) {
3042 if (!dest && !strcasecmp(v->name, "ajaxdest"))
3044 else if (!objtype && !strcasecmp(v->name, "ajaxobjtype"))
3050 objtype = "generic";
3052 /* we want to stop when we find an empty line */
3054 val = strsep(&in, "\r\n"); /* mark start and end of line */
3055 if (in && *in == '\n') /* remove trailing \n if any */
3057 ast_trim_blanks(val);
3058 ast_debug(5, "inobj %d in_data %d line <%s>\n", inobj, in_data, val);
3059 if (ast_strlen_zero(val)) {
3060 if (in_data) { /* close data */
3061 ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
3064 ast_str_append(out, 0, xml ? " /></response>\n" :
3065 "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
3072 /* we expect Name: value lines */
3076 var = strsep(&val, ":");
3077 if (val) { /* found the field name */
3078 val = ast_skip_blanks(val);
3079 ast_trim_blanks(var);
3080 } else { /* field name not found, move to opaque mode */
3082 var = "Opaque-data";
3088 ast_str_append(out, 0, "<response type='object' id='%s'><%s", dest, objtype);
3090 ast_str_append(out, 0, "<body>\n");
3091 vco = ao2_container_alloc(37, variable_count_hash_fn, variable_count_cmp_fn);
3095 if (!in_data) { /* build appropriate line start */
3096 ast_str_append(out, 0, xml ? " " : "<tr><td>");
3097 if ((vc = ao2_find(vco, var, 0)))
3100 /* Create a new entry for this one */
3101 vc = ao2_alloc(sizeof(*vc), NULL);
3106 xml_copy_escape(out, var, xml ? 1 | 2 : 0);
3108 ast_str_append(out, 0, "-%d", vc->count);
3110 ast_str_append(out, 0, xml ? "='" : "</td><td>");
3111 if (!strcmp(var, "Opaque-data"))
3114 xml_copy_escape(out, val, 0); /* data field */
3116 ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
3118 ast_str_append(out, 0, xml ? "\n" : "<br>\n");
3121 ast_str_append(out, 0, xml ? " /></response>\n" :
3122 "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
3127 static struct ast_str *generic_http_callback(enum output_format format,
3128 struct sockaddr_in *requestor, const char *uri,
3129 struct ast_variable *params, int *status,
3130 char **title, int *contentlength)
3132 struct mansession *s = NULL;
3133 unsigned long ident = 0; /* invalid, so find_session will fail if not set through the cookie */
3135 struct ast_variable *v;
3136 char template[] = "/tmp/ast-http-XXXXXX"; /* template for temporary file */
3137 struct ast_str *out = NULL;
3138 struct message m = { 0 };
3142 for (v = params; v; v = v->next) {
3143 if (!strcasecmp(v->name, "mansession_id")) {
3144 sscanf(v->value, "%lx", &ident);
3149 if (!(s = find_session(ident))) {
3150 /* Create new session.
3151 * While it is not in the list we don't need any locking
3153 if (!(s = ast_calloc(1, sizeof(*s)))) {
3155 goto generic_callback_out;
3157 s->sin = *requestor;
3159 s->waiting_thread = AST_PTHREADT_NULL;
3161 ast_mutex_init(&s->__lock);
3162 ast_mutex_lock(&s->__lock);
3164 s->managerid = rand() | 1; /* make sure it is non-zero */
3165 s->last_ev = grab_last();
3166 AST_LIST_LOCK(&sessions);
3167 AST_LIST_INSERT_HEAD(&sessions, s, list);
3168 ast_atomic_fetchadd_int(&num_sessions, 1);
3169 AST_LIST_UNLOCK(&sessions);
3172 ast_mutex_unlock(&s->__lock);
3174 if (!(out = ast_str_create(1024))) {
3176 goto generic_callback_out;
3179 s->fd = mkstemp(template); /* create a temporary file for command output */
3181 s->f = fdopen(s->fd, "w+");
3183 for (x = 0, v = params; v && (x < AST_MAX_MANHEADERS); x++, v = v->next) {
3184 hdrlen = strlen(v->name) + strlen(v->value) + 3;
3185 m.headers[m.hdrcount] = alloca(hdrlen);
3186 snprintf((char *) m.headers[m.hdrcount], hdrlen, "%s: %s", v->name, v->value);
3190 if (process_message(s, &m)) {
3191 if (s->authenticated) {
3192 if (manager_displayconnects(s))
3193 ast_verb(2, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3194 ast_log(LOG_EVENT, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3196 if (displayconnects)
3197 ast_verb(2, "HTTP Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr));
3198 ast_log(LOG_EVENT, "HTTP Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr));
3203 ast_str_append(&out, 0,
3204 "Content-type: text/%s\r\n"
3205 "Cache-Control: no-cache;\r\n"
3206 "Set-Cookie: mansession_id=\"%08lx\"; Version=\"1\"; Max-Age=%d\r\n"
3208 contenttype[format],
3209 s->managerid, httptimeout);
3211 if (format == FORMAT_XML) {
3212 ast_str_append(&out, 0, "<ajax-response>\n");
3213 } else if (format == FORMAT_HTML) {
3215 #define ROW_FMT "<tr><td colspan=\"2\" bgcolor=\"#f1f1ff\">%s</td></tr>\r\n"
3216 #define TEST_STRING \
3217 "<form action=\"manager\">action: <input name=\"action\"> cmd <input name=\"command\"><br> \
3218 user <input name=\"username\"> pass <input type=\"password\" name=\"secret\"><br> \
3219 <input type=\"submit\"></form>"
3221 ast_str_append(&out, 0, "<title>Asterisk™ Manager Interface</title>");
3222 ast_str_append(&out, 0, "<body bgcolor=\"#ffffff\"><table align=center bgcolor=\"#f1f1f1\" width=\"500\">\r\n");
3223 ast_str_append(&out, 0, ROW_FMT, "<h1>Manager Tester</h1>");
3224 ast_str_append(&out, 0, ROW_FMT, TEST_STRING);
3227 if (s->f != NULL) { /* have temporary output */
3229 size_t l = ftell(s->f);
3232 if ((buf = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_SHARED, s->fd, 0))) {
3233 if (format == FORMAT_XML || format == FORMAT_HTML)
3234 xml_translate(&out, buf, params, format);
3236 ast_str_append(&out, 0, buf);
3239 } else if (format == FORMAT_XML || format == FORMAT_HTML) {
3240 xml_translate(&out, "", params, format);
3247 if (format == FORMAT_XML) {
3248 ast_str_append(&out, 0, "</ajax-response>\n");
3249 } else if (format == FORMAT_HTML)
3250 ast_str_append(&out, 0, "</table></body>\r\n");
3252 ast_mutex_lock(&s->__lock);
3253 /* Reset HTTP timeout. If we're not authenticated, keep it extremely short */
3254 s->sessiontimeout = time(NULL) + ((s->authenticated || httptimeout < 5) ? httptimeout : 5);
3256 if (s->needdestroy) {
3257 if (s->inuse == 1) {
3258 ast_debug(1, "Need destroy, doing it now!\n");
3261 ast_debug(1, "Need destroy, but can't do it yet!\n");
3262 if (s->waiting_thread != AST_PTHREADT_NULL)
3263 pthread_kill(s->waiting_thread, SIGURG);
3268 ast_mutex_unlock(&s->__lock);
3272 generic_callback_out:
3274 return ast_http_error(500, "Server Error", NULL, "Internal Server Error (out of memory)\n");
3278 static struct ast_str *manager_http_callback(struct server_instance *ser, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
3280 return generic_http_callback(FORMAT_HTML, &ser->requestor, uri, params, status, title, contentlength);
3283 static struct ast_str *mxml_http_callback(struct server_instance *ser, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
3285 return generic_http_callback(FORMAT_XML, &ser->requestor, uri, params, status, title, contentlength);
3288 static struct ast_str *rawman_http_callback(struct server_instance *ser, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
3290 return generic_http_callback(FORMAT_RAW, &ser->requestor, uri, params, status, title, contentlength);
3293 struct ast_http_uri rawmanuri = {
3294 .description = "Raw HTTP Manager Event Interface",
3297 .callback = rawman_http_callback,
3300 struct ast_http_uri manageruri = {
3301 .description = "HTML Manager Event Interface",
3304 .callback = manager_http_callback,
3307 struct ast_http_uri managerxmluri = {
3308 .description = "XML Manager Event Interface",
3311 .callback = mxml_http_callback,
3314 static int registered = 0;
3315 static int webregged = 0;
3317 /*! \brief cleanup code called at each iteration of server_root,
3318 * guaranteed to happen every 5 seconds at most
3320 static void purge_old_stuff(void *data)
3326 struct tls_config ami_tls_cfg;
3327 static struct server_args ami_desc = {
3329 .master = AST_PTHREADT_NULL,
3331 .poll_timeout = 5000, /* wake up every 5 seconds */
3332 .periodic_fn = purge_old_stuff,
3333 .name = "AMI server",
3334 .accept_fn = server_root, /* thread doing the accept() */
3335 .worker_fn = session_do, /* thread handling the session */
3338 static struct server_args amis_desc = {
3340 .master = AST_PTHREADT_NULL,
3341 .tls_cfg = &ami_tls_cfg,
3342 .poll_timeout = -1, /* the other does the periodic cleanup */
3343 .name = "AMI TLS server",
3344 .accept_fn = server_root, /* thread doing the accept() */
3345 .worker_fn = session_do, /* thread handling the session */
3348 static int __init_manager(int reload)
3350 struct ast_config *ucfg = NULL, *cfg = NULL;
3353 int newhttptimeout = 60;
3354 int have_sslbindaddr = 0;
3356 struct ast_hostent ahp;
3357 struct ast_manager_user *user = NULL;
3358 struct ast_variable *var;
3359 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
3362 /* Register default actions */
3363 ast_manager_register2("Ping", 0, action_ping, "Keepalive command", mandescr_ping);
3364 ast_manager_register2("Events", 0, action_events, "Control Event Flow", mandescr_events);
3365 ast_manager_register2("Logoff", 0, action_logoff, "Logoff Manager", mandescr_logoff);
3366 ast_manager_register2("Login", 0, action_login, "Login Manager", NULL);
3367 ast_manager_register2("Challenge", 0, action_challenge, "Generate Challenge for MD5 Auth", NULL);
3368 ast_manager_register2("Hangup", EVENT_FLAG_CALL, action_hangup, "Hangup Channel", mandescr_hangup);
3369 ast_manager_register("Status", EVENT_FLAG_CALL, action_status, "Lists channel status" );
3370 ast_manager_register2("Setvar", EVENT_FLAG_CALL, action_setvar, "Set Channel Variable", mandescr_setvar );
3371 ast_manager_register2("Getvar", EVENT_FLAG_CALL, action_getvar, "Gets a Channel Variable", mandescr_getvar );
3372 ast_manager_register2("GetConfig", EVENT_FLAG_CONFIG, action_getconfig, "Retrieve configuration", mandescr_getconfig);
3373 ast_manager_register2("GetConfigJSON", EVENT_FLAG_CONFIG, action_getconfigjson, "Retrieve configuration (JSON format)", mandescr_getconfigjson);
3374 ast_manager_register2("UpdateConfig", EVENT_FLAG_CONFIG, action_updateconfig, "Update basic configuration", mandescr_updateconfig);
3375 ast_manager_register2("Redirect", EVENT_FLAG_CALL, action_redirect, "Redirect (transfer) a call", mandescr_redirect );
3376 ast_manager_register2("Originate", EVENT_FLAG_CALL, action_originate, "Originate Call", mandescr_originate);
3377 ast_manager_register2("Command", EVENT_FLAG_COMMAND, action_command, "Execute Asterisk CLI Command", mandescr_command );
3378 ast_manager_register2("ExtensionState", EVENT_FLAG_CALL, action_extensionstate, "Check Extension Status", mandescr_extensionstate );
3379 ast_manager_register2("AbsoluteTimeout", EVENT_FLAG_CALL, action_timeout, "Set Absolute Timeout", mandescr_timeout );
3380 ast_manager_register2("MailboxStatus", EVENT_FLAG_CALL, action_mailboxstatus, "Check Mailbox", mandescr_mailboxstatus );
3381 ast_manager_register2("MailboxCount", EVENT_FLAG_CALL, action_mailboxcount, "Check Mailbox Message Count", mandescr_mailboxcount );
3382 ast_manager_register2("ListCommands", 0, action_listcommands, "List available manager commands", mandescr_listcommands);
3383 ast_manager_register2("SendText", EVENT_FLAG_CALL, action_sendtext, "Send text message to channel", mandescr_sendtext);
3384 ast_manager_register2("UserEvent", EVENT_FLAG_USER, action_userevent, "Send an arbitrary event", mandescr_userevent);
3385 ast_manager_register2("WaitEvent", 0, action_waitevent, "Wait for an event to occur", mandescr_waitevent);
3386 ast_manager_register2("CoreSettings", EVENT_FLAG_SYSTEM, action_coresettings, "Show PBX core settings (version etc)", mandescr_coresettings);
3387 ast_manager_register2("CoreStatus", EVENT_FLAG_SYSTEM, action_corestatus, "Show PBX core status variables", mandescr_corestatus);
3388 ast_manager_register2("Reload", EVENT_FLAG_CONFIG, action_reload, "Send a reload event", mandescr_reload);
3389 ast_manager_register2("CoreShowChannels", EVENT_FLAG_SYSTEM, action_coreshowchannels, "List currently active channels", mandescr_coreshowchannels);
3391 ast_cli_register_multiple(cli_manager, sizeof(cli_manager) / sizeof(struct ast_cli_entry));
3392 ast_extension_state_add(NULL, NULL, manager_state_cb, NULL);
3394 /* Append placeholder event so master_eventq never runs dry */
3395 append_event("Event: Placeholder\r\n\r\n", 0);
3397 if ((cfg = ast_config_load("manager.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
3400 displayconnects = 1;
3402 ast_log(LOG_NOTICE, "Unable to open AMI configuration manager.conf. Asterisk management interface (AMI) disabled.\n");
3406 /* default values */
3407 memset(&ami_desc.sin, 0, sizeof(struct sockaddr_in));
3408 memset(&amis_desc.sin, 0, sizeof(amis_desc.sin));
3409 amis_desc.sin.sin_port = htons(5039);
3410 ami_desc.sin.sin_port = htons(DEFAULT_MANAGER_PORT);
3412 ami_tls_cfg.enabled = 0;
3413 if (ami_tls_cfg.certfile)
3414 ast_free(ami_tls_cfg.certfile);
3415 ami_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
3416 if (ami_tls_cfg.cipher)
3417 ast_free(ami_tls_cfg.cipher);
3418 ami_tls_cfg.cipher = ast_strdup("");
3420 for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
3422 if (!strcasecmp(var->name, "sslenable"))
3423 ami_tls_cfg.enabled = ast_true(val);
3424 else if (!strcasecmp(var->name, "sslbindport"))
3425 amis_desc.sin.sin_port = htons(atoi(val));
3426 else if (!strcasecmp(var->name, "sslbindaddr")) {
3427 if ((hp = ast_gethostbyname(val, &ahp))) {
3428 memcpy(&amis_desc.sin.sin_addr, hp->h_addr, sizeof(amis_desc.sin.sin_addr));
3429 have_sslbindaddr = 1;
3431 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", val);
3433 } else if (!strcasecmp(var->name, "sslcert")) {
3434 ast_free(ami_tls_cfg.certfile);
3435 ami_tls_cfg.certfile = ast_strdup(val);
3436 } else if (!strcasecmp(var->name, "sslcipher")) {
3437 ast_free(ami_tls_cfg.cipher);
3438 ami_tls_cfg.cipher = ast_strdup(val);
3439 } else if (!strcasecmp(var->name, "enabled")) {
3440 manager_enabled = ast_true(val);
3441 } else if (!strcasecmp(var->name, "block-sockets")) {
3442 block_sockets = ast_true(val);
3443 } else if (!strcasecmp(var->name, "webenabled")) {
3444 webmanager_enabled = ast_true(val);
3445 } else if (!strcasecmp(var->name, "port")) {
3446 ami_desc.sin.sin_port = htons(atoi(val));
3447 } else if (!strcasecmp(var->name, "bindaddr")) {
3448 if (!inet_aton(val, &ami_desc.sin.sin_addr)) {
3449 ast_log(LOG_WARNING, "Invalid address '%s' specified, using 0.0.0.0\n", val);
3450 memset(&ami_desc.sin.sin_addr, 0, sizeof(ami_desc.sin.sin_addr));
3452 } else if (!strcasecmp(var->name, "allowmultiplelogin")) {
3453 allowmultiplelogin = ast_true(val);
3454 } else if (!strcasecmp(var->name, "displayconnects")) {
3455 displayconnects = ast_true(val);
3456 } else if (!strcasecmp(var->name, "timestampevents")) {
3457 timestampevents = ast_true(val);
3458 } else if (!strcasecmp(var->name, "debug")) {
3459 manager_debug = ast_true(val);
3460 } else if (!strcasecmp(var->name, "httptimeout")) {
3461 newhttptimeout = atoi(val);
3463 ast_log(LOG_NOTICE, "Invalid keyword <%s> = <%s> in manager.conf [general]\n",
3468 if (manager_enabled)
3469 ami_desc.sin.sin_family = AF_INET;
3470 if (!have_sslbindaddr)
3471 amis_desc.sin.sin_addr = ami_desc.sin.sin_addr;
3472 if (ami_tls_cfg.enabled)
3473 amis_desc.sin.sin_family = AF_INET;
3476 AST_RWLIST_WRLOCK(&users);
3478 /* First, get users from users.conf */
3479 ucfg = ast_config_load("users.conf", config_flags);
3480 if (ucfg && (ucfg != CONFIG_STATUS_FILEUNCHANGED)) {
3481 const char *hasmanager;
3482 int genhasmanager = ast_true(ast_variable_retrieve(ucfg, "general", "hasmanager"));
3484 while ((cat = ast_category_browse(ucfg, cat))) {
3485 if (!strcasecmp(cat, "general"))
3488 hasmanager = ast_variable_retrieve(ucfg, cat, "hasmanager");
3489 if ((!hasmanager && genhasmanager) || ast_true(hasmanager)) {
3490 const char *user_secret = ast_variable_retrieve(ucfg, cat, "secret");
3491 const char *user_read = ast_variable_retrieve(ucfg, cat, "read");
3492 const char *user_write = ast_variable_retrieve(ucfg, cat, "write");
3493 const char *user_displayconnects = ast_variable_retrieve(ucfg, cat, "displayconnects");
3494 const char *user_writetimeout = ast_variable_retrieve(ucfg, cat, "writetimeout");
3496 /* Look for an existing entry,
3497 * if none found - create one and add it to the list
3499 if (!(user = get_manager_by_name_locked(cat))) {
3500 if (!(user = ast_calloc(1, sizeof(*user))))
3503 /* Copy name over */
3504 ast_copy_string(user->username, cat, sizeof(user->username));
3505 /* Insert into list */
3506 AST_LIST_INSERT_TAIL(&users, user, list);
3508 user->readperm = -1;
3509 user->writeperm = -1;
3510 /* Default displayconnect from [general] */
3511 user->displayconnects = displayconnects;
3512 user->writetimeout = 100;
3516 user_secret = ast_variable_retrieve(ucfg, "general", "secret");
3518 user_read = ast_variable_retrieve(ucfg, "general", "read");
3520 user_write = ast_variable_retrieve(ucfg, "general", "write");
3521 if (!user_displayconnects)
3522 user_displayconnects = ast_variable_retrieve(ucfg, "general", "displayconnects");
3523 if (!user_writetimeout)
3524 user_writetimeout = ast_variable_retrieve(ucfg, "general", "writetimeout");
3526 if (!ast_strlen_zero(user_secret)) {
3528 ast_free(user->secret);
3529 user->secret = ast_strdup(user_secret);
3533 user->readperm = get_perm(user_read);
3535 user->writeperm = get_perm(user_write);
3536 if (user_displayconnects)
3537 user->displayconnects = ast_true(user_displayconnects);
3539 if (user_writetimeout) {
3540 int val = atoi(user_writetimeout);
3542 ast_log(LOG_WARNING, "Invalid writetimeout value '%s' at users.conf line %d\n", var->value, var->lineno);
3544 user->writetimeout = val;
3548 ast_config_destroy(ucfg);
3551 /* cat is NULL here in any case */
3553 while ((cat = ast_category_browse(cfg, cat))) {
3554 struct ast_ha *oldha;
3556 if (!strcasecmp(cat, "general"))
3559 /* Look for an existing entry, if none found - create one and add it to the list */
3560 if (!(user = get_manager_by_name_locked(cat))) {
3561 if (!(user = ast_calloc(1, sizeof(*user))))
3563 /* Copy name over */
3564 ast_copy_string(user->username, cat, sizeof(user->username));
3568 user->writeperm = 0;
3569 /* Default displayconnect from [general] */
3570 user->displayconnects = displayconnects;
3571 user->writetimeout = 100;
3573 /* Insert into list */
3574 AST_RWLIST_INSERT_TAIL(&users, user, list);
3577 /* Make sure we keep this user and don't destroy it during cleanup */
3582 var = ast_variable_browse(cfg, cat);
3583 for (; var; var = var->next) {
3584 if (!strcasecmp(var->name, "secret")) {
3586 ast_free(user->secret);
3587 user->secret = ast_strdup(var->value);
3588 } else if (!strcasecmp(var->name, "deny") ||
3589 !strcasecmp(var->name, "permit")) {
3590 user->ha = ast_append_ha(var->name, var->value, user->ha, NULL);
3591 } else if (!strcasecmp(var->name, "read") ) {
3592 user->readperm = get_perm(var->value);
3593 } else if (!strcasecmp(var->name, "write") ) {
3594 user->writeperm = get_perm(var->value);
3595 } else if (!strcasecmp(var->name, "displayconnects") ) {
3596 user->displayconnects = ast_true(var->value);
3597 } else if (!strcasecmp(var->name, "writetimeout")) {
3598 int val = atoi(var->value);
3600 ast_log(LOG_WARNING, "Invalid writetimeout value '%s' at line %d\n", var->value, var->lineno);
3602 user->writetimeout = val;
3604 ast_debug(1, "%s is an unknown option.\n", var->name);
3608 ast_config_destroy(cfg);
3610 /* Perform cleanup - essentially prune out old users that no longer exist */
3611 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&users, user, list) {
3612 if (user->keep) { /* valid record. clear flag for the next round */
3616 /* We do not need to keep this user so take them out of the list */
3617 AST_RWLIST_REMOVE_CURRENT(list);
3618 /* Free their memory now */
3620 ast_free(user->secret);
3621 ast_free_ha(user->ha);
3624 AST_RWLIST_TRAVERSE_SAFE_END;
3626 AST_RWLIST_UNLOCK(&users);
3628 if (webmanager_enabled && manager_enabled) {
3630 ast_http_uri_link(&rawmanuri);
3631 ast_http_uri_link(&manageruri);
3632 ast_http_uri_link(&managerxmluri);
3637 ast_http_uri_unlink(&rawmanuri);
3638 ast_http_uri_unlink(&manageruri);
3639 ast_http_uri_unlink(&managerxmluri);
3644 if (newhttptimeout > 0)
3645 httptimeout = newhttptimeout;
3647 manager_event(EVENT_FLAG_SYSTEM, "Reload", "Module: Manager\r\nStatus: %s\r\nMessage: Manager reload Requested\r\n", manager_enabled ? "Enabled" : "Disabled");
3649 server_start(&ami_desc);
3650 if (ssl_setup(amis_desc.tls_cfg))
3651 server_start(&amis_desc);
3655 int init_manager(void)
3657 return __init_manager(0);
3660 int reload_manager(void)
3662 return __init_manager(1);