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/module.h"
59 #include "asterisk/config.h"
60 #include "asterisk/callerid.h"
61 #include "asterisk/lock.h"
62 #include "asterisk/cli.h"
63 #include "asterisk/app.h"
64 #include "asterisk/pbx.h"
65 #include "asterisk/md5.h"
66 #include "asterisk/acl.h"
67 #include "asterisk/utils.h"
68 #include "asterisk/tcptls.h"
69 #include "asterisk/http.h"
70 #include "asterisk/ast_version.h"
71 #include "asterisk/threadstorage.h"
72 #include "asterisk/linkedlists.h"
73 #include "asterisk/version.h"
74 #include "asterisk/term.h"
75 #include "asterisk/astobj2.h"
76 #include "asterisk/features.h"
93 * Linked list of events.
94 * Global events are appended to the list by append_event().
95 * The usecount is the number of stored pointers to the element,
96 * excluding the list pointers. So an element that is only in
97 * the list has a usecount of 0, not 1.
99 * Clients have a pointer to the last event processed, and for each
100 * of these clients we track the usecount of the elements.
101 * If we have a pointer to an entry in the list, it is safe to navigate
102 * it forward because elements will not be deleted, but only appended.
103 * The worst that can happen is seeing the pointer still NULL.
105 * When the usecount of an element drops to 0, and the element is the
106 * first in the list, we can remove it. Removal is done within the
107 * main thread, which is woken up for the purpose.
109 * For simplicity of implementation, we make sure the list is never empty.
112 int usecount; /*!< # of clients who still need the event */
114 unsigned int seq; /*!< sequence number */
115 AST_LIST_ENTRY(eventqent) eq_next;
116 char eventdata[1]; /*!< really variable size, allocated by append_event() */
119 static AST_LIST_HEAD_STATIC(all_events, eventqent);
121 static int displayconnects = 1;
122 static int allowmultiplelogin = 1;
123 static int timestampevents;
124 static int httptimeout = 60;
125 static int manager_enabled = 0;
126 static int webmanager_enabled = 0;
128 static int block_sockets;
129 static int num_sessions;
131 static int manager_debug; /*!< enable some debugging code in the manager */
134 * Descriptor for a manager session, either on the AMI socket or over HTTP.
137 * AMI session have managerid == 0; the entry is created upon a connect,
138 * and destroyed with the socket.
139 * HTTP sessions have managerid != 0, the value is used as a search key
140 * to lookup sessions (using the mansession_id cookie).
142 static const char *command_blacklist[] = {
148 pthread_t ms_t; /*!< Execution thread, basically useless */
149 ast_mutex_t __lock; /*!< Thread lock -- don't use in action callbacks, it's already taken care of */
150 /* XXX need to document which fields it is protecting */
151 struct sockaddr_in sin; /*!< address we are connecting from */
152 FILE *f; /*!< fdopen() on the underlying fd */
153 int fd; /*!< descriptor used for output. Either the socket (AMI) or a temporary file (HTTP) */
154 int inuse; /*!< number of HTTP sessions using this entry */
155 int needdestroy; /*!< Whether an HTTP session should be destroyed */
156 pthread_t waiting_thread; /*!< Sleeping thread using this descriptor */
157 uint32_t managerid; /*!< Unique manager identifier, 0 for AMI sessions */
158 time_t sessionstart; /*!< Session start time */
159 time_t sessiontimeout; /*!< Session timeout if HTTP */
160 char username[80]; /*!< Logged in username */
161 char challenge[10]; /*!< Authentication challenge */
162 int authenticated; /*!< Authentication status */
163 int readperm; /*!< Authorization for reading */
164 int writeperm; /*!< Authorization for writing */
165 char inbuf[1025]; /*!< Buffer */
166 /* we use the extra byte to add a '\0' and simplify parsing */
167 int inlen; /*!< number of buffered bytes */
168 int send_events; /*!< XXX what ? */
169 struct eventqent *last_ev; /*!< last event processed. */
170 int writetimeout; /*!< Timeout for ast_carefulwrite() */
171 int pending_event; /*!< Pending events indicator in case when waiting_thread is NULL */
172 AST_LIST_ENTRY(mansession) list;
175 #define NEW_EVENT(m) (AST_LIST_NEXT(m->last_ev, eq_next))
177 static AST_LIST_HEAD_STATIC(sessions, mansession);
179 /*! \brief user descriptor, as read from the config file.
181 * \note It is still missing some fields -- e.g. we can have multiple permit and deny
182 * lines which are not supported here, and readperm/writeperm/writetimeout
185 struct ast_manager_user {
188 struct ast_ha *ha; /*!< ACL setting */
189 int readperm; /*! Authorization for reading */
190 int writeperm; /*! Authorization for writing */
191 int writetimeout; /*! Per user Timeout for ast_carefulwrite() */
192 int displayconnects; /*!< XXX unused */
193 int keep; /*!< mark entries created on a reload */
194 AST_RWLIST_ENTRY(ast_manager_user) list;
197 /*! \brief list of users found in the config file */
198 static AST_RWLIST_HEAD_STATIC(users, ast_manager_user);
200 /*! \brief list of actions registered */
201 static AST_RWLIST_HEAD_STATIC(actions, manager_action);
203 /*! \brief list of hooks registered */
204 static AST_RWLIST_HEAD_STATIC(manager_hooks, manager_custom_hook);
206 /*! \brief Add a custom hook to be called when an event is fired */
207 void ast_manager_register_hook(struct manager_custom_hook *hook)
209 AST_RWLIST_WRLOCK(&manager_hooks);
210 AST_RWLIST_INSERT_TAIL(&manager_hooks, hook, list);
211 AST_RWLIST_UNLOCK(&manager_hooks);
215 /*! \brief Delete a custom hook to be called when an event is fired */
216 void ast_manager_unregister_hook(struct manager_custom_hook *hook)
218 AST_RWLIST_WRLOCK(&manager_hooks);
219 AST_RWLIST_REMOVE(&manager_hooks, hook, list);
220 AST_RWLIST_UNLOCK(&manager_hooks);
225 * Event list management functions.
226 * We assume that the event list always has at least one element,
227 * and the delete code will not remove the last entry even if the
231 static time_t __deb(time_t start, const char *msg)
233 time_t now = time(NULL);
234 ast_verbose("%4d th %p %s\n", (int)(now % 3600), pthread_self(), msg);
235 if (start != 0 && now - start > 5)
236 ast_verbose("+++ WOW, %s took %d seconds\n", msg, (int)(now - start));
240 static void LOCK_EVENTS(void)
242 time_t start = __deb(0, "about to lock events");
243 AST_LIST_LOCK(&all_events);
244 __deb(start, "done lock events");
247 static void UNLOCK_EVENTS(void)
249 __deb(0, "about to unlock events");
250 AST_LIST_UNLOCK(&all_events);
253 static void LOCK_SESS(void)
255 time_t start = __deb(0, "about to lock sessions");
256 AST_LIST_LOCK(&sessions);
257 __deb(start, "done lock sessions");
260 static void UNLOCK_SESS(void)
262 __deb(0, "about to unlock sessions");
263 AST_LIST_UNLOCK(&sessions);
267 int check_manager_enabled()
269 return manager_enabled;
272 int check_webmanager_enabled()
274 return (webmanager_enabled && manager_enabled);
278 * Grab a reference to the last event, update usecount as needed.
279 * Can handle a NULL pointer.
281 static struct eventqent *grab_last(void)
283 struct eventqent *ret;
285 AST_LIST_LOCK(&all_events);
286 ret = AST_LIST_LAST(&all_events);
287 /* the list is never empty now, but may become so when
288 * we optimize it in the future, so be prepared.
291 ast_atomic_fetchadd_int(&ret->usecount, 1);
292 AST_LIST_UNLOCK(&all_events);
297 * Purge unused events. Remove elements from the head
298 * as long as their usecount is 0 and there is a next element.
300 static void purge_events(void)
302 struct eventqent *ev;
304 AST_LIST_LOCK(&all_events);
305 while ( (ev = AST_LIST_FIRST(&all_events)) &&
306 ev->usecount == 0 && AST_LIST_NEXT(ev, eq_next)) {
307 AST_LIST_REMOVE_HEAD(&all_events, eq_next);
310 AST_LIST_UNLOCK(&all_events);
314 * helper functions to convert back and forth between
315 * string and numeric representation of set of flags
317 static struct permalias {
321 { EVENT_FLAG_SYSTEM, "system" },
322 { EVENT_FLAG_CALL, "call" },
323 { EVENT_FLAG_LOG, "log" },
324 { EVENT_FLAG_VERBOSE, "verbose" },
325 { EVENT_FLAG_COMMAND, "command" },
326 { EVENT_FLAG_AGENT, "agent" },
327 { EVENT_FLAG_USER, "user" },
328 { EVENT_FLAG_CONFIG, "config" },
329 { EVENT_FLAG_DTMF, "dtmf" },
330 { EVENT_FLAG_REPORTING, "reporting" },
331 { EVENT_FLAG_CDR, "cdr" },
332 { EVENT_FLAG_DIALPLAN, "dialplan" },
333 { EVENT_FLAG_ORIGINATE, "originate" },
338 /*! \brief Convert authority code to a list of options */
339 static char *authority_to_str(int authority, struct ast_str **res)
345 for (i = 0; i < (sizeof(perms) / sizeof(perms[0])) - 1; i++) {
346 if (authority & perms[i].num) {
347 ast_str_append(res, 0, "%s%s", sep, perms[i].label);
352 if ((*res)->used == 0) /* replace empty string with something sensible */
353 ast_str_append(res, 0, "<none>");
358 /*! Tells you if smallstr exists inside bigstr
359 which is delim by delim and uses no buf or stringsep
360 ast_instring("this|that|more","this",'|') == 1;
362 feel free to move this to app.c -anthm */
363 static int ast_instring(const char *bigstr, const char *smallstr, const char delim)
365 const char *val = bigstr, *next;
368 if ((next = strchr(val, delim))) {
369 if (!strncmp(val, smallstr, (next - val)))
374 return !strcmp(smallstr, val);
375 } while (*(val = (next + 1)));
380 static int get_perm(const char *instr)
387 for (x = 0; x < (sizeof(perms) / sizeof(perms[0])); x++) {
388 if (ast_instring(instr, perms[x].label, ','))
396 * A number returns itself, false returns 0, true returns all flags,
397 * other strings return the flags that are set.
399 static int strings_to_mask(const char *string)
403 if (ast_strlen_zero(string))
406 for (p = string; *p; p++)
407 if (*p < '0' || *p > '9')
409 if (!p) /* all digits */
411 if (ast_false(string))
413 if (ast_true(string)) { /* all permissions */
415 for (x = 0; x<sizeof(perms) / sizeof(perms[0]); x++)
419 return get_perm(string);
422 static int check_manager_session_inuse(const char *name)
424 struct mansession *session = NULL;
426 AST_LIST_LOCK(&sessions);
427 AST_LIST_TRAVERSE(&sessions, session, list) {
428 if (!strcasecmp(session->username, name))
431 AST_LIST_UNLOCK(&sessions);
433 return session ? 1 : 0;
438 * lookup an entry in the list of registered users.
439 * must be called with the list lock held.
441 static struct ast_manager_user *get_manager_by_name_locked(const char *name)
443 struct ast_manager_user *user = NULL;
445 AST_RWLIST_TRAVERSE(&users, user, list)
446 if (!strcasecmp(user->username, name))
451 /*! \brief Get displayconnects config option.
452 * \param s manager session to get parameter from.
453 * \return displayconnects config option value.
455 static int manager_displayconnects (struct mansession *s)
457 struct ast_manager_user *user = NULL;
460 AST_RWLIST_RDLOCK(&users);
461 if ((user = get_manager_by_name_locked (s->username)))
462 ret = user->displayconnects;
463 AST_RWLIST_UNLOCK(&users);
468 static char *handle_showmancmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
470 struct manager_action *cur;
471 struct ast_str *authority;
476 e->command = "manager show command";
478 "Usage: manager show command <actionname>\n"
479 " Shows the detailed description for a specific Asterisk manager interface command.\n";
484 AST_RWLIST_RDLOCK(&actions);
485 AST_RWLIST_TRAVERSE(&actions, cur, list) {
486 if (!strncasecmp(a->word, cur->action, l) && ++which > a->n) {
487 ret = ast_strdup(cur->action);
488 break; /* make sure we exit even if ast_strdup() returns NULL */
491 AST_RWLIST_UNLOCK(&actions);
494 authority = ast_str_alloca(80);
496 return CLI_SHOWUSAGE;
498 AST_RWLIST_RDLOCK(&actions);
499 AST_RWLIST_TRAVERSE(&actions, cur, list) {
500 for (num = 3; num < a->argc; num++) {
501 if (!strcasecmp(cur->action, a->argv[num])) {
502 ast_cli(a->fd, "Action: %s\nSynopsis: %s\nPrivilege: %s\n%s\n",
503 cur->action, cur->synopsis,
504 authority_to_str(cur->authority, &authority),
505 S_OR(cur->description, ""));
509 AST_RWLIST_UNLOCK(&actions);
514 static char *handle_mandebug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
518 e->command = "manager debug [on|off]";
519 e->usage = "Usage: manager debug [on|off]\n Show, enable, disable debugging of the manager code.\n";
525 ast_cli(a->fd, "manager debug is %s\n", manager_debug? "on" : "off");
526 else if (a->argc == 3) {
527 if (!strcasecmp(a->argv[2], "on"))
529 else if (!strcasecmp(a->argv[2], "off"))
532 return CLI_SHOWUSAGE;
537 static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
539 struct ast_manager_user *user = NULL;
542 struct ast_str *rauthority = ast_str_alloca(80);
543 struct ast_str *wauthority = ast_str_alloca(80);
547 e->command = "manager show user";
549 " Usage: manager show user <user>\n"
550 " Display all information related to the manager user specified.\n";
557 AST_RWLIST_RDLOCK(&users);
558 AST_RWLIST_TRAVERSE(&users, user, list) {
559 if ( !strncasecmp(a->word, user->username, l) && ++which > a->n ) {
560 ret = ast_strdup(user->username);
564 AST_RWLIST_UNLOCK(&users);
569 return CLI_SHOWUSAGE;
571 AST_RWLIST_RDLOCK(&users);
573 if (!(user = get_manager_by_name_locked(a->argv[3]))) {
574 ast_cli(a->fd, "There is no manager called %s\n", a->argv[3]);
575 AST_RWLIST_UNLOCK(&users);
579 ast_cli(a->fd, "\n");
586 "displayconnects: %s\n",
587 (user->username ? user->username : "(N/A)"),
588 (user->secret ? "<Set>" : "(N/A)"),
589 (user->ha ? "yes" : "no"),
590 authority_to_str(user->readperm, &rauthority),
591 authority_to_str(user->writeperm, &wauthority),
592 (user->displayconnects ? "yes" : "no"));
594 AST_RWLIST_UNLOCK(&users);
600 static char *handle_showmanagers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
602 struct ast_manager_user *user = NULL;
606 e->command = "manager show users";
608 "Usage: manager show users\n"
609 " Prints a listing of all managers that are currently configured on that\n"
616 return CLI_SHOWUSAGE;
618 AST_RWLIST_RDLOCK(&users);
620 /* If there are no users, print out something along those lines */
621 if (AST_RWLIST_EMPTY(&users)) {
622 ast_cli(a->fd, "There are no manager users.\n");
623 AST_RWLIST_UNLOCK(&users);
627 ast_cli(a->fd, "\nusername\n--------\n");
629 AST_RWLIST_TRAVERSE(&users, user, list) {
630 ast_cli(a->fd, "%s\n", user->username);
634 AST_RWLIST_UNLOCK(&users);
636 ast_cli(a->fd, "-------------------\n");
637 ast_cli(a->fd, "%d manager users configured.\n", count_amu);
643 /*! \brief CLI command manager list commands */
644 static char *handle_showmancmds(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
646 struct manager_action *cur;
647 struct ast_str *authority;
648 #define HSMC_FORMAT " %-15.15s %-15.15s %-55.55s\n"
651 e->command = "manager show commands";
653 "Usage: manager show commands\n"
654 " Prints a listing of all the available Asterisk manager interface commands.\n";
659 authority = ast_str_alloca(80);
660 ast_cli(a->fd, HSMC_FORMAT, "Action", "Privilege", "Synopsis");
661 ast_cli(a->fd, HSMC_FORMAT, "------", "---------", "--------");
663 AST_RWLIST_RDLOCK(&actions);
664 AST_RWLIST_TRAVERSE(&actions, cur, list)
665 ast_cli(a->fd, HSMC_FORMAT, cur->action, authority_to_str(cur->authority, &authority), cur->synopsis);
666 AST_RWLIST_UNLOCK(&actions);
671 /*! \brief CLI command manager list connected */
672 static char *handle_showmanconn(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
674 struct mansession *s;
675 time_t now = time(NULL);
676 #define HSMCONN_FORMAT1 " %-15.15s %-15.15s %-10.10s %-10.10s %-8.8s %-8.8s %-5.5s %-5.5s\n"
677 #define HSMCONN_FORMAT2 " %-15.15s %-15.15s %-10d %-10d %-8d %-8d %-5.5d %-5.5d\n"
681 e->command = "manager show connected";
683 "Usage: manager show connected\n"
684 " Prints a listing of the users that are currently connected to the\n"
685 "Asterisk manager interface.\n";
691 ast_cli(a->fd, HSMCONN_FORMAT1, "Username", "IP Address", "Start", "Elapsed", "FileDes", "HttpCnt", "Read", "Write");
693 AST_LIST_LOCK(&sessions);
694 AST_LIST_TRAVERSE(&sessions, s, list) {
695 ast_cli(a->fd, HSMCONN_FORMAT2, s->username, ast_inet_ntoa(s->sin.sin_addr), (int)(s->sessionstart), (int)(now - s->sessionstart), s->fd, s->inuse, s->readperm, s->writeperm);
698 AST_LIST_UNLOCK(&sessions);
700 ast_cli(a->fd, "%d users connected.\n", count);
705 /*! \brief CLI command manager list eventq */
706 /* Should change to "manager show connected" */
707 static char *handle_showmaneventq(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
712 e->command = "manager show eventq";
714 "Usage: manager show eventq\n"
715 " Prints a listing of all events pending in the Asterisk manger\n"
721 AST_LIST_LOCK(&all_events);
722 AST_LIST_TRAVERSE(&all_events, s, eq_next) {
723 ast_cli(a->fd, "Usecount: %d\n", s->usecount);
724 ast_cli(a->fd, "Category: %d\n", s->category);
725 ast_cli(a->fd, "Event:\n%s", s->eventdata);
727 AST_LIST_UNLOCK(&all_events);
732 /*! \brief CLI command manager reload */
733 static char *handle_manager_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
737 e->command = "manager reload";
739 "Usage: manager reload\n"
740 " Reloads the manager configuration.\n";
746 return CLI_SHOWUSAGE;
752 static struct ast_cli_entry cli_manager[] = {
753 AST_CLI_DEFINE(handle_showmancmd, "Show a manager interface command"),
754 AST_CLI_DEFINE(handle_showmancmds, "List manager interface commands"),
755 AST_CLI_DEFINE(handle_showmanconn, "List connected manager interface users"),
756 AST_CLI_DEFINE(handle_showmaneventq, "List manager interface queued events"),
757 AST_CLI_DEFINE(handle_showmanagers, "List configured manager users"),
758 AST_CLI_DEFINE(handle_showmanager, "Display information on a specific manager user"),
759 AST_CLI_DEFINE(handle_mandebug, "Show, enable, disable debugging of the manager code"),
760 AST_CLI_DEFINE(handle_manager_reload, "Reload manager configurations"),
764 * Decrement the usecount for the event; if it goes to zero,
765 * (why check for e->next ?) wakeup the
766 * main thread, which is in charge of freeing the record.
767 * Returns the next record.
769 static struct eventqent *unref_event(struct eventqent *e)
771 ast_atomic_fetchadd_int(&e->usecount, -1);
772 return AST_LIST_NEXT(e, eq_next);
775 static void ref_event(struct eventqent *e)
777 ast_atomic_fetchadd_int(&e->usecount, 1);
781 * destroy a session, leaving the usecount
783 static void free_session(struct mansession *s)
785 struct eventqent *eqe = s->last_ev;
788 ast_mutex_destroy(&s->__lock);
793 static void destroy_session(struct mansession *s)
795 AST_LIST_LOCK(&sessions);
796 AST_LIST_REMOVE(&sessions, s, list);
797 ast_atomic_fetchadd_int(&num_sessions, -1);
799 AST_LIST_UNLOCK(&sessions);
802 const char *astman_get_header(const struct message *m, char *var)
804 int x, l = strlen(var);
806 for (x = 0; x < m->hdrcount; x++) {
807 const char *h = m->headers[x];
808 if (!strncasecmp(var, h, l) && h[l] == ':' && h[l+1] == ' ')
815 struct ast_variable *astman_get_variables(const struct message *m)
818 struct ast_variable *head = NULL, *cur;
820 AST_DECLARE_APP_ARGS(args,
821 AST_APP_ARG(vars)[32];
824 varlen = strlen("Variable: ");
826 for (x = 0; x < m->hdrcount; x++) {
827 char *parse, *var, *val;
829 if (strncasecmp("Variable: ", m->headers[x], varlen))
831 parse = ast_strdupa(m->headers[x] + varlen);
833 AST_STANDARD_APP_ARGS(args, parse);
836 for (y = 0; y < args.argc; y++) {
839 var = val = ast_strdupa(args.vars[y]);
841 if (!val || ast_strlen_zero(var))
843 cur = ast_variable_new(var, val, "");
853 * helper function to send a string to the socket.
854 * Return -1 on error (e.g. buffer full).
856 static int send_string(struct mansession *s, char *string)
858 int len = strlen(string); /* residual length */
860 struct timeval start = ast_tvnow();
866 n = fwrite(src, 1, len, s->f); /* try to write the string, non blocking */
867 if (n == len /* ok */ || n < 0 /* error */)
869 len -= n; /* skip already written data */
873 n = -1; /* error marker */
874 elapsed = ast_tvdiff_ms(ast_tvnow(), start);
875 if (elapsed > s->writetimeout)
877 if (poll(&fd, 1, s->writetimeout - elapsed) < 1)
881 return n < 0 ? -1 : 0;
885 * \brief thread local buffer for astman_append
887 * \note This can not be defined within the astman_append() function
888 * because it declares a couple of functions that get used to
889 * initialize the thread local storage key.
891 AST_THREADSTORAGE(astman_append_buf);
892 /*! \brief initial allocated size for the astman_append_buf */
893 #define ASTMAN_APPEND_BUF_INITSIZE 256
896 * utility functions for creating AMI replies
898 void astman_append(struct mansession *s, const char *fmt, ...)
903 if (!(buf = ast_str_thread_get(&astman_append_buf, ASTMAN_APPEND_BUF_INITSIZE)))
907 ast_str_set_va(&buf, 0, fmt, ap);
911 send_string(s, buf->str);
913 ast_verbose("fd == -1 in astman_append, should not happen\n");
916 /*! \note NOTE: XXX this comment is unclear and possibly wrong.
917 Callers of astman_send_error(), astman_send_response() or astman_send_ack() must EITHER
918 hold the session lock _or_ be running in an action callback (in which case s->busy will
919 be non-zero). In either of these cases, there is no need to lock-protect the session's
920 fd, since no other output will be sent (events will be queued), and no input will
921 be read until either the current action finishes or get_input() obtains the session
925 /*! \brief send a response with an optional message,
926 * and terminate it with an empty line.
927 * m is used only to grab the 'ActionID' field.
929 * Use the explicit constant MSG_MOREDATA to remove the empty line.
930 * XXX MSG_MOREDATA should go to a header file.
932 #define MSG_MOREDATA ((char *)astman_send_response)
933 static void astman_send_response_full(struct mansession *s, const struct message *m, char *resp, char *msg, char *listflag)
935 const char *id = astman_get_header(m, "ActionID");
937 astman_append(s, "Response: %s\r\n", resp);
938 if (!ast_strlen_zero(id))
939 astman_append(s, "ActionID: %s\r\n", id);
941 astman_append(s, "Eventlist: %s\r\n", listflag); /* Start, complete, cancelled */
942 if (msg == MSG_MOREDATA)
945 astman_append(s, "Message: %s\r\n\r\n", msg);
947 astman_append(s, "\r\n");
950 void astman_send_response(struct mansession *s, const struct message *m, char *resp, char *msg)
952 astman_send_response_full(s, m, resp, msg, NULL);
955 void astman_send_error(struct mansession *s, const struct message *m, char *error)
957 astman_send_response_full(s, m, "Error", error, NULL);
960 void astman_send_ack(struct mansession *s, const struct message *m, char *msg)
962 astman_send_response_full(s, m, "Success", msg, NULL);
965 static void astman_start_ack(struct mansession *s, const struct message *m)
967 astman_send_response_full(s, m, "Success", MSG_MOREDATA, NULL);
970 void astman_send_listack(struct mansession *s, const struct message *m, char *msg, char *listflag)
972 astman_send_response_full(s, m, "Success", msg, listflag);
977 Rather than braindead on,off this now can also accept a specific int mask value
978 or a ',' delim list of mask strings (the same as manager.conf) -anthm
980 static int set_eventmask(struct mansession *s, const char *eventmask)
982 int maskint = strings_to_mask(eventmask);
984 ast_mutex_lock(&s->__lock);
986 s->send_events = maskint;
987 ast_mutex_unlock(&s->__lock);
993 * Here we start with action_ handlers for AMI actions,
994 * and the internal functions used by them.
995 * Generally, the handlers are called action_foo()
998 /* helper function for action_login() */
999 static int authenticate(struct mansession *s, const struct message *m)
1001 const char *username = astman_get_header(m, "Username");
1002 const char *password = astman_get_header(m, "Secret");
1004 struct ast_manager_user *user = NULL;
1006 if (ast_strlen_zero(username)) /* missing username */
1009 /* locate user in locked state */
1010 AST_RWLIST_WRLOCK(&users);
1012 if (!(user = get_manager_by_name_locked(username))) {
1013 ast_log(LOG_NOTICE, "%s tried to authenticate with nonexistent user '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
1014 } else if (user->ha && !ast_apply_ha(user->ha, &(s->sin))) {
1015 ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
1016 } else if (!strcasecmp(astman_get_header(m, "AuthType"), "MD5")) {
1017 const char *key = astman_get_header(m, "Key");
1018 if (!ast_strlen_zero(key) && !ast_strlen_zero(s->challenge) && user->secret) {
1021 char md5key[256] = "";
1022 struct MD5Context md5;
1023 unsigned char digest[16];
1026 MD5Update(&md5, (unsigned char *) s->challenge, strlen(s->challenge));
1027 MD5Update(&md5, (unsigned char *) user->secret, strlen(user->secret));
1028 MD5Final(digest, &md5);
1029 for (x = 0; x < 16; x++)
1030 len += sprintf(md5key + len, "%2.2x", digest[x]);
1031 if (!strcmp(md5key, key))
1034 ast_debug(1, "MD5 authentication is not possible. challenge: '%s'\n",
1035 S_OR(s->challenge, ""));
1037 } else if (password && user->secret && !strcmp(password, user->secret))
1041 ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", ast_inet_ntoa(s->sin.sin_addr), username);
1042 AST_RWLIST_UNLOCK(&users);
1048 ast_copy_string(s->username, username, sizeof(s->username));
1049 s->readperm = user->readperm;
1050 s->writeperm = user->writeperm;
1051 s->writetimeout = user->writetimeout;
1052 s->sessionstart = time(NULL);
1053 set_eventmask(s, astman_get_header(m, "Events"));
1055 AST_RWLIST_UNLOCK(&users);
1059 /*! \brief Manager PING */
1060 static char mandescr_ping[] =
1061 "Description: A 'Ping' action will ellicit a 'Pong' response. Used to keep the\n"
1062 " manager connection open.\n"
1063 "Variables: NONE\n";
1065 static int action_ping(struct mansession *s, const struct message *m)
1067 astman_append(s, "Response: Success\r\n"
1072 static char mandescr_getconfig[] =
1073 "Description: A 'GetConfig' action will dump the contents of a configuration\n"
1074 "file by category and contents or optionally by specified category only.\n"
1075 "Variables: (Names marked with * are required)\n"
1076 " *Filename: Configuration filename (e.g. foo.conf)\n"
1077 " Category: Category in configuration file\n";
1079 static int action_getconfig(struct mansession *s, const struct message *m)
1081 struct ast_config *cfg;
1082 const char *fn = astman_get_header(m, "Filename");
1083 const char *category = astman_get_header(m, "Category");
1086 char *cur_category = NULL;
1087 struct ast_variable *v;
1088 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1090 if (ast_strlen_zero(fn)) {
1091 astman_send_error(s, m, "Filename not specified");
1094 if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
1095 astman_send_error(s, m, "Config file not found");
1099 astman_start_ack(s, m);
1100 while ((cur_category = ast_category_browse(cfg, cur_category))) {
1101 if (ast_strlen_zero(category) || (!ast_strlen_zero(category) && !strcmp(category, cur_category))) {
1103 astman_append(s, "Category-%06d: %s\r\n", catcount, cur_category);
1104 for (v = ast_variable_browse(cfg, cur_category); v; v = v->next)
1105 astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
1109 if (!ast_strlen_zero(category) && catcount == 0) /* TODO: actually, a config with no categories doesn't even get loaded */
1110 astman_append(s, "No categories found");
1111 ast_config_destroy(cfg);
1112 astman_append(s, "\r\n");
1117 static char mandescr_listcategories[] =
1118 "Description: A 'ListCategories' action will dump the categories in\n"
1121 " Filename: Configuration filename (e.g. foo.conf)\n";
1123 static int action_listcategories(struct mansession *s, const struct message *m)
1125 struct ast_config *cfg;
1126 const char *fn = astman_get_header(m, "Filename");
1127 char *category = NULL;
1128 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1131 if (ast_strlen_zero(fn)) {
1132 astman_send_error(s, m, "Filename not specified");
1135 if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
1136 astman_send_error(s, m, "Config file not found or file has invalid syntax");
1139 astman_start_ack(s, m);
1140 while ((category = ast_category_browse(cfg, category))) {
1141 astman_append(s, "Category-%06d: %s\r\n", catcount, category);
1144 if (catcount == 0) /* TODO: actually, a config with no categories doesn't even get loaded */
1145 astman_append(s, "Error: no categories found");
1146 ast_config_destroy(cfg);
1147 astman_append(s, "\r\n");
1155 /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */
1156 static void json_escape(char *out, const char *in)
1159 if (*in == '\\' || *in == '\"')
1166 static char mandescr_getconfigjson[] =
1167 "Description: A 'GetConfigJSON' action will dump the contents of a configuration\n"
1168 "file by category and contents in JSON format. This only makes sense to be used\n"
1169 "using rawman over the HTTP interface.\n"
1171 " Filename: Configuration filename (e.g. foo.conf)\n";
1173 static int action_getconfigjson(struct mansession *s, const struct message *m)
1175 struct ast_config *cfg;
1176 const char *fn = astman_get_header(m, "Filename");
1177 char *category = NULL;
1178 struct ast_variable *v;
1181 unsigned int buf_len = 0;
1182 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1184 if (ast_strlen_zero(fn)) {
1185 astman_send_error(s, m, "Filename not specified");
1189 if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
1190 astman_send_error(s, m, "Config file not found");
1195 buf = alloca(buf_len);
1197 astman_start_ack(s, m);
1198 astman_append(s, "JSON: {");
1199 while ((category = ast_category_browse(cfg, category))) {
1201 if (buf_len < 2 * strlen(category) + 1) {
1203 buf = alloca(buf_len);
1205 json_escape(buf, category);
1206 astman_append(s, "%s\"%s\":[", comma1 ? "," : "", buf);
1209 for (v = ast_variable_browse(cfg, category); v; v = v->next) {
1211 astman_append(s, ",");
1212 if (buf_len < 2 * strlen(v->name) + 1) {
1214 buf = alloca(buf_len);
1216 json_escape(buf, v->name);
1217 astman_append(s, "\"%s", buf);
1218 if (buf_len < 2 * strlen(v->value) + 1) {
1220 buf = alloca(buf_len);
1222 json_escape(buf, v->value);
1223 astman_append(s, "%s\"", buf);
1227 astman_append(s, "]");
1229 astman_append(s, "}\r\n\r\n");
1231 ast_config_destroy(cfg);
1236 /* helper function for action_updateconfig */
1237 static enum error_type handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg, const char *dfn)
1241 const char *action, *cat, *var, *value, *match, *line;
1242 struct ast_category *category;
1243 struct ast_variable *v;
1245 for (x = 0; x < 100000; x++) {
1246 unsigned int object = 0;
1248 snprintf(hdr, sizeof(hdr), "Action-%06d", x);
1249 action = astman_get_header(m, hdr);
1250 if (ast_strlen_zero(action))
1252 snprintf(hdr, sizeof(hdr), "Cat-%06d", x);
1253 cat = astman_get_header(m, hdr);
1254 snprintf(hdr, sizeof(hdr), "Var-%06d", x);
1255 var = astman_get_header(m, hdr);
1256 snprintf(hdr, sizeof(hdr), "Value-%06d", x);
1257 value = astman_get_header(m, hdr);
1258 if (!ast_strlen_zero(value) && *value == '>') {
1262 snprintf(hdr, sizeof(hdr), "Match-%06d", x);
1263 match = astman_get_header(m, hdr);
1264 snprintf(hdr, sizeof(hdr), "Line-%06d", x);
1265 line = astman_get_header(m, hdr);
1266 if (!strcasecmp(action, "newcat")) {
1267 if (ast_strlen_zero(cat))
1268 return UNSPECIFIED_CATEGORY;
1269 if (!(category = ast_category_new(cat, dfn, -1)))
1270 return FAILURE_ALLOCATION;
1271 if (ast_strlen_zero(match)) {
1272 ast_category_append(cfg, category);
1274 ast_category_insert(cfg, category, match);
1275 } else if (!strcasecmp(action, "renamecat")) {
1276 if (ast_strlen_zero(cat) || ast_strlen_zero(value))
1277 return UNSPECIFIED_ARGUMENT;
1278 if (!(category = ast_category_get(cfg, cat)))
1279 return UNKNOWN_CATEGORY;
1280 ast_category_rename(category, value);
1281 } else if (!strcasecmp(action, "delcat")) {
1282 if (ast_strlen_zero(cat))
1283 return UNSPECIFIED_CATEGORY;
1284 if (ast_category_delete(cfg, cat))
1285 return FAILURE_DELCAT;
1286 } else if (!strcasecmp(action, "emptycat")) {
1287 if (ast_strlen_zero(cat))
1288 return UNSPECIFIED_CATEGORY;
1289 if (ast_category_empty(cfg, cat))
1290 return FAILURE_EMPTYCAT;
1291 } else if (!strcasecmp(action, "update")) {
1292 if (ast_strlen_zero(cat) || ast_strlen_zero(var))
1293 return UNSPECIFIED_ARGUMENT;
1294 if (!(category = ast_category_get(cfg,cat)))
1295 return UNKNOWN_CATEGORY;
1296 if (ast_variable_update(category, var, value, match, object))
1297 return FAILURE_UPDATE;
1298 } else if (!strcasecmp(action, "delete")) {
1299 if (ast_strlen_zero(cat) || (ast_strlen_zero(var) && ast_strlen_zero(line)))
1300 return UNSPECIFIED_ARGUMENT;
1301 if (!(category = ast_category_get(cfg, cat)))
1302 return UNKNOWN_CATEGORY;
1303 if (ast_variable_delete(category, var, match, line))
1304 return FAILURE_DELETE;
1305 } else if (!strcasecmp(action, "append")) {
1306 if (ast_strlen_zero(cat) || ast_strlen_zero(var))
1307 return UNSPECIFIED_ARGUMENT;
1308 if (!(category = ast_category_get(cfg, cat)))
1309 return UNKNOWN_CATEGORY;
1310 if (!(v = ast_variable_new(var, value, dfn)))
1311 return FAILURE_ALLOCATION;
1312 if (object || (match && !strcasecmp(match, "object")))
1314 ast_variable_append(category, v);
1315 } else if (!strcasecmp(action, "insert")) {
1316 if (ast_strlen_zero(cat) || ast_strlen_zero(var) || ast_strlen_zero(line))
1317 return UNSPECIFIED_ARGUMENT;
1318 if (!(category = ast_category_get(cfg, cat)))
1319 return UNKNOWN_CATEGORY;
1320 if (!(v = ast_variable_new(var, value, dfn)))
1321 return FAILURE_ALLOCATION;
1322 ast_variable_insert(category, v, line);
1325 ast_log(LOG_WARNING, "Action-%06d: %s not handled\n", x, action);
1326 return UNKNOWN_ACTION;
1332 static char mandescr_updateconfig[] =
1333 "Description: A 'UpdateConfig' action will modify, create, or delete\n"
1334 "configuration elements in Asterisk configuration files.\n"
1335 "Variables (X's represent 6 digit number beginning with 000000):\n"
1336 " SrcFilename: Configuration filename to read(e.g. foo.conf)\n"
1337 " DstFilename: Configuration filename to write(e.g. foo.conf)\n"
1338 " Reload: Whether or not a reload should take place (or name of specific module)\n"
1339 " Action-XXXXXX: Action to Take (NewCat,RenameCat,DelCat,EmptyCat,Update,Delete,Append,Insert)\n"
1340 " Cat-XXXXXX: Category to operate on\n"
1341 " Var-XXXXXX: Variable to work on\n"
1342 " Value-XXXXXX: Value to work on\n"
1343 " Match-XXXXXX: Extra match required to match line\n"
1344 " Line-XXXXXX: Line in category to operate on (used with delete and insert actions)\n";
1346 static int action_updateconfig(struct mansession *s, const struct message *m)
1348 struct ast_config *cfg;
1349 const char *sfn = astman_get_header(m, "SrcFilename");
1350 const char *dfn = astman_get_header(m, "DstFilename");
1352 const char *rld = astman_get_header(m, "Reload");
1353 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
1354 enum error_type result;
1356 if (ast_strlen_zero(sfn) || ast_strlen_zero(dfn)) {
1357 astman_send_error(s, m, "Filename not specified");
1360 if (!(cfg = ast_config_load2(sfn, "manager", config_flags))) {
1361 astman_send_error(s, m, "Config file not found");
1364 result = handle_updates(s, m, cfg, dfn);
1366 ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
1367 res = config_text_file_save(dfn, cfg, "Manager");
1368 ast_config_destroy(cfg);
1370 astman_send_error(s, m, "Save of config failed");
1373 astman_send_ack(s, m, NULL);
1374 if (!ast_strlen_zero(rld)) {
1377 ast_module_reload(rld);
1380 ast_config_destroy(cfg);
1382 case UNKNOWN_ACTION:
1383 astman_send_error(s, m, "Unknown action command");
1385 case UNKNOWN_CATEGORY:
1386 astman_send_error(s, m, "Given category does not exist");
1388 case UNSPECIFIED_CATEGORY:
1389 astman_send_error(s, m, "Category not specified");
1391 case UNSPECIFIED_ARGUMENT:
1392 astman_send_error(s, m, "Problem with category, value, or line (if required)");
1394 case FAILURE_ALLOCATION:
1395 astman_send_error(s, m, "Memory allocation failure, this should not happen");
1397 case FAILURE_DELCAT:
1398 astman_send_error(s, m, "Delete category did not complete successfully");
1400 case FAILURE_EMPTYCAT:
1401 astman_send_error(s, m, "Empty category did not complete successfully");
1403 case FAILURE_UPDATE:
1404 astman_send_error(s, m, "Update did not complete successfully");
1406 case FAILURE_DELETE:
1407 astman_send_error(s, m, "Delete did not complete successfully");
1409 case FAILURE_APPEND:
1410 astman_send_error(s, m, "Append did not complete successfully");
1417 static char mandescr_createconfig[] =
1418 "Description: A 'CreateConfig' action will create an empty file in the\n"
1419 "configuration directory. This action is intended to be used before an\n"
1420 "UpdateConfig action.\n"
1422 " Filename: The configuration filename to create (e.g. foo.conf)\n";
1424 static int action_createconfig(struct mansession *s, const struct message *m)
1427 const char *fn = astman_get_header(m, "Filename");
1428 struct ast_str *filepath = ast_str_alloca(PATH_MAX);
1429 ast_str_set(&filepath, 0, "%s/", ast_config_AST_CONFIG_DIR);
1430 ast_str_append(&filepath, 0, "%s", fn);
1432 if ((fd = open(filepath->str, O_CREAT | O_EXCL, AST_FILE_MODE)) != -1) {
1434 astman_send_ack(s, m, "New configuration file created successfully");
1436 astman_send_error(s, m, strerror(errno));
1441 /*! \brief Manager WAITEVENT */
1442 static char mandescr_waitevent[] =
1443 "Description: A 'WaitEvent' action will ellicit a 'Success' response. Whenever\n"
1444 "a manager event is queued. Once WaitEvent has been called on an HTTP manager\n"
1445 "session, events will be generated and queued.\n"
1447 " Timeout: Maximum time (in seconds) to wait for events, -1 means forever.\n";
1449 static int action_waitevent(struct mansession *s, const struct message *m)
1451 const char *timeouts = astman_get_header(m, "Timeout");
1455 const char *id = astman_get_header(m, "ActionID");
1458 if (!ast_strlen_zero(id))
1459 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
1463 if (!ast_strlen_zero(timeouts)) {
1464 sscanf(timeouts, "%i", &timeout);
1467 /* XXX maybe put an upper bound, or prevent the use of 0 ? */
1470 ast_mutex_lock(&s->__lock);
1471 if (s->waiting_thread != AST_PTHREADT_NULL)
1472 pthread_kill(s->waiting_thread, SIGURG);
1474 if (s->managerid) { /* AMI-over-HTTP session */
1476 * Make sure the timeout is within the expire time of the session,
1477 * as the client will likely abort the request if it does not see
1478 * data coming after some amount of time.
1480 time_t now = time(NULL);
1481 int max = s->sessiontimeout - now - 10;
1483 if (max < 0) /* We are already late. Strange but possible. */
1485 if (timeout < 0 || timeout > max)
1487 if (!s->send_events) /* make sure we record events */
1488 s->send_events = -1;
1490 ast_mutex_unlock(&s->__lock);
1492 /* XXX should this go inside the lock ? */
1493 s->waiting_thread = pthread_self(); /* let new events wake up this thread */
1494 ast_debug(1, "Starting waiting for an event!\n");
1496 for (x = 0; x < timeout || timeout < 0; x++) {
1497 ast_mutex_lock(&s->__lock);
1500 /* We can have multiple HTTP session point to the same mansession entry.
1501 * The way we deal with it is not very nice: newcomers kick out the previous
1502 * HTTP session. XXX this needs to be improved.
1504 if (s->waiting_thread != pthread_self())
1508 ast_mutex_unlock(&s->__lock);
1511 if (s->managerid == 0) { /* AMI session */
1512 if (ast_wait_for_input(s->fd, 1000))
1514 } else { /* HTTP session */
1518 ast_debug(1, "Finished waiting for an event!\n");
1519 ast_mutex_lock(&s->__lock);
1520 if (s->waiting_thread == pthread_self()) {
1521 struct eventqent *eqe;
1522 astman_send_response(s, m, "Success", "Waiting for Event completed.");
1523 while ( (eqe = NEW_EVENT(s)) ) {
1525 if (((s->readperm & eqe->category) == eqe->category) &&
1526 ((s->send_events & eqe->category) == eqe->category)) {
1527 astman_append(s, "%s", eqe->eventdata);
1529 s->last_ev = unref_event(s->last_ev);
1532 "Event: WaitEventComplete\r\n"
1535 s->waiting_thread = AST_PTHREADT_NULL;
1537 ast_debug(1, "Abandoning event request!\n");
1539 ast_mutex_unlock(&s->__lock);
1543 static char mandescr_listcommands[] =
1544 "Description: Returns the action name and synopsis for every\n"
1545 " action that is available to the user\n"
1546 "Variables: NONE\n";
1548 /*! \note The actionlock is read-locked by the caller of this function */
1549 static int action_listcommands(struct mansession *s, const struct message *m)
1551 struct manager_action *cur;
1552 struct ast_str *temp = ast_str_alloca(BUFSIZ); /* XXX very large ? */
1554 astman_start_ack(s, m);
1555 AST_RWLIST_TRAVERSE(&actions, cur, list) {
1556 if (s->writeperm & cur->authority || cur->authority == 0)
1557 astman_append(s, "%s: %s (Priv: %s)\r\n",
1558 cur->action, cur->synopsis, authority_to_str(cur->authority, &temp));
1560 astman_append(s, "\r\n");
1565 static char mandescr_events[] =
1566 "Description: Enable/Disable sending of events to this manager\n"
1569 " EventMask: 'on' if all events should be sent,\n"
1570 " 'off' if no events should be sent,\n"
1571 " 'system,call,log' to select which flags events should have to be sent.\n";
1573 static int action_events(struct mansession *s, const struct message *m)
1575 const char *mask = astman_get_header(m, "EventMask");
1578 res = set_eventmask(s, mask);
1580 astman_append(s, "Response: Success\r\n"
1583 astman_append(s, "Response: Success\r\n"
1588 static char mandescr_logoff[] =
1589 "Description: Logoff this manager session\n"
1590 "Variables: NONE\n";
1592 static int action_logoff(struct mansession *s, const struct message *m)
1594 astman_send_response(s, m, "Goodbye", "Thanks for all the fish.");
1598 static int action_login(struct mansession *s, const struct message *m)
1600 if (authenticate(s, m)) {
1602 astman_send_error(s, m, "Authentication failed");
1605 s->authenticated = 1;
1606 if (manager_displayconnects(s))
1607 ast_verb(2, "%sManager '%s' logged on from %s\n", (s->managerid ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr));
1608 ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->managerid ? "HTTP " : ""), s->username, ast_inet_ntoa(s->sin.sin_addr));
1609 astman_send_ack(s, m, "Authentication accepted");
1613 static int action_challenge(struct mansession *s, const struct message *m)
1615 const char *authtype = astman_get_header(m, "AuthType");
1617 if (!strcasecmp(authtype, "MD5")) {
1618 if (ast_strlen_zero(s->challenge))
1619 snprintf(s->challenge, sizeof(s->challenge), "%ld", ast_random());
1620 ast_mutex_lock(&s->__lock);
1621 astman_start_ack(s, m);
1622 astman_append(s, "Challenge: %s\r\n\r\n", s->challenge);
1623 ast_mutex_unlock(&s->__lock);
1625 astman_send_error(s, m, "Must specify AuthType");
1630 static char mandescr_hangup[] =
1631 "Description: Hangup a channel\n"
1633 " Channel: The channel name to be hungup\n";
1635 static int action_hangup(struct mansession *s, const struct message *m)
1637 struct ast_channel *c = NULL;
1638 const char *name = astman_get_header(m, "Channel");
1639 if (ast_strlen_zero(name)) {
1640 astman_send_error(s, m, "No channel specified");
1643 c = ast_get_channel_by_name_locked(name);
1645 astman_send_error(s, m, "No such channel");
1648 ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
1649 ast_channel_unlock(c);
1650 astman_send_ack(s, m, "Channel Hungup");
1654 static char mandescr_setvar[] =
1655 "Description: Set a global or local channel variable.\n"
1656 "Variables: (Names marked with * are required)\n"
1657 " Channel: Channel to set variable for\n"
1658 " *Variable: Variable name\n"
1661 static int action_setvar(struct mansession *s, const struct message *m)
1663 struct ast_channel *c = NULL;
1664 const char *name = astman_get_header(m, "Channel");
1665 const char *varname = astman_get_header(m, "Variable");
1666 const char *varval = astman_get_header(m, "Value");
1668 if (ast_strlen_zero(varname)) {
1669 astman_send_error(s, m, "No variable specified");
1673 if (!ast_strlen_zero(name)) {
1674 c = ast_get_channel_by_name_locked(name);
1676 astman_send_error(s, m, "No such channel");
1681 pbx_builtin_setvar_helper(c, varname, S_OR(varval, ""));
1684 ast_channel_unlock(c);
1686 astman_send_ack(s, m, "Variable Set");
1691 static char mandescr_getvar[] =
1692 "Description: Get the value of a global or local channel variable.\n"
1693 "Variables: (Names marked with * are required)\n"
1694 " Channel: Channel to read variable from\n"
1695 " *Variable: Variable name\n"
1696 " ActionID: Optional Action id for message matching.\n";
1698 static int action_getvar(struct mansession *s, const struct message *m)
1700 struct ast_channel *c = NULL;
1701 const char *name = astman_get_header(m, "Channel");
1702 const char *varname = astman_get_header(m, "Variable");
1704 char workspace[1024] = "";
1706 if (ast_strlen_zero(varname)) {
1707 astman_send_error(s, m, "No variable specified");
1711 if (!ast_strlen_zero(name)) {
1712 c = ast_get_channel_by_name_locked(name);
1714 astman_send_error(s, m, "No such channel");
1719 if (varname[strlen(varname) - 1] == ')') {
1720 ast_func_read(c, (char *) varname, workspace, sizeof(workspace));
1723 pbx_retrieve_variable(c, varname, &varval, workspace, sizeof(workspace), NULL);
1727 ast_channel_unlock(c);
1728 astman_start_ack(s, m);
1729 astman_append(s, "Variable: %s\r\nValue: %s\r\n\r\n", varname, varval);
1735 /*! \brief Manager "status" command to show channels */
1736 /* Needs documentation... */
1737 static int action_status(struct mansession *s, const struct message *m)
1739 const char *name = astman_get_header(m, "Channel");
1740 struct ast_channel *c;
1742 struct timeval now = ast_tvnow();
1743 long elapsed_seconds = 0;
1745 int all = ast_strlen_zero(name); /* set if we want all channels */
1746 const char *id = astman_get_header(m, "ActionID");
1749 if (!ast_strlen_zero(id))
1750 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
1755 c = ast_channel_walk_locked(NULL);
1757 c = ast_get_channel_by_name_locked(name);
1759 astman_send_error(s, m, "No such channel");
1763 astman_send_ack(s, m, "Channel status will follow");
1765 /* if we look by name, we break after the first iteration */
1769 snprintf(bridge, sizeof(bridge), "BridgedChannel: %s\r\nBridgedUniqueid: %s\r\n", c->_bridge->name, c->_bridge->uniqueid);
1774 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
1778 "Privilege: Call\r\n"
1780 "CallerIDNum: %s\r\n"
1781 "CallerIDName: %s\r\n"
1782 "Accountcode: %s\r\n"
1783 "ChannelState: %d\r\n"
1784 "ChannelStateDesc: %s\r\n"
1794 S_OR(c->cid.cid_num, ""),
1795 S_OR(c->cid.cid_name, ""),
1798 ast_state2str(c->_state), c->context,
1799 c->exten, c->priority, (long)elapsed_seconds, bridge, c->uniqueid, idText);
1803 "Privilege: Call\r\n"
1805 "CallerIDNum: %s\r\n"
1806 "CallerIDName: %s\r\n"
1814 S_OR(c->cid.cid_num, "<unknown>"),
1815 S_OR(c->cid.cid_name, "<unknown>"),
1817 ast_state2str(c->_state), bridge, c->uniqueid, idText);
1819 ast_channel_unlock(c);
1822 c = ast_channel_walk_locked(c);
1825 "Event: StatusComplete\r\n"
1828 "\r\n", idText, channels);
1832 static char mandescr_sendtext[] =
1833 "Description: Sends A Text Message while in a call.\n"
1834 "Variables: (Names marked with * are required)\n"
1835 " *Channel: Channel to send message to\n"
1836 " *Message: Message to send\n"
1837 " ActionID: Optional Action id for message matching.\n";
1839 static int action_sendtext(struct mansession *s, const struct message *m)
1841 struct ast_channel *c = NULL;
1842 const char *name = astman_get_header(m, "Channel");
1843 const char *textmsg = astman_get_header(m, "Message");
1846 if (ast_strlen_zero(name)) {
1847 astman_send_error(s, m, "No channel specified");
1851 if (ast_strlen_zero(textmsg)) {
1852 astman_send_error(s, m, "No Message specified");
1856 c = ast_get_channel_by_name_locked(name);
1858 astman_send_error(s, m, "No such channel");
1862 res = ast_sendtext(c, textmsg);
1863 ast_channel_unlock(c);
1866 astman_send_ack(s, m, "Success");
1868 astman_send_error(s, m, "Failure");
1873 static char mandescr_redirect[] =
1874 "Description: Redirect (transfer) a call.\n"
1875 "Variables: (Names marked with * are required)\n"
1876 " *Channel: Channel to redirect\n"
1877 " ExtraChannel: Second call leg to transfer (optional)\n"
1878 " *Exten: Extension to transfer to\n"
1879 " *Context: Context to transfer to\n"
1880 " *Priority: Priority to transfer to\n"
1881 " ActionID: Optional Action id for message matching.\n";
1883 /*! \brief action_redirect: The redirect manager command */
1884 static int action_redirect(struct mansession *s, const struct message *m)
1886 const char *name = astman_get_header(m, "Channel");
1887 const char *name2 = astman_get_header(m, "ExtraChannel");
1888 const char *exten = astman_get_header(m, "Exten");
1889 const char *context = astman_get_header(m, "Context");
1890 const char *priority = astman_get_header(m, "Priority");
1891 struct ast_channel *chan, *chan2 = NULL;
1895 if (ast_strlen_zero(name)) {
1896 astman_send_error(s, m, "Channel not specified");
1899 if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) {
1900 if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
1901 astman_send_error(s, m, "Invalid priority\n");
1905 /* XXX watch out, possible deadlock - we are trying to get two channels!!! */
1906 chan = ast_get_channel_by_name_locked(name);
1909 snprintf(buf, sizeof(buf), "Channel does not exist: %s", name);
1910 astman_send_error(s, m, buf);
1913 if (ast_check_hangup(chan)) {
1914 astman_send_error(s, m, "Redirect failed, channel not up.\n");
1915 ast_channel_unlock(chan);
1918 if (!ast_strlen_zero(name2))
1919 chan2 = ast_get_channel_by_name_locked(name2);
1920 if (chan2 && ast_check_hangup(chan2)) {
1921 astman_send_error(s, m, "Redirect failed, extra channel not up.\n");
1922 ast_channel_unlock(chan);
1923 ast_channel_unlock(chan2);
1926 res = ast_async_goto(chan, context, exten, pi);
1928 if (!ast_strlen_zero(name2)) {
1930 res = ast_async_goto(chan2, context, exten, pi);
1934 astman_send_ack(s, m, "Dual Redirect successful");
1936 astman_send_error(s, m, "Secondary redirect failed");
1938 astman_send_ack(s, m, "Redirect successful");
1940 astman_send_error(s, m, "Redirect failed");
1942 ast_channel_unlock(chan);
1944 ast_channel_unlock(chan2);
1948 static char mandescr_atxfer[] =
1949 "Description: Attended transfer.\n"
1950 "Variables: (Names marked with * are required)\n"
1951 " *Channel: Transferer's channel\n"
1952 " *Exten: Extension to transfer to\n"
1953 " *Context: Context to transfer to\n"
1954 " *Priority: Priority to transfer to\n"
1955 " ActionID: Optional Action id for message matching.\n";
1957 static int action_atxfer(struct mansession *s, const struct message *m)
1959 const char *name = astman_get_header(m, "Channel");
1960 const char *exten = astman_get_header(m, "Exten");
1961 const char *context = astman_get_header(m, "Context");
1962 const char *priority = astman_get_header(m, "Priority");
1963 struct ast_channel *chan = NULL;
1964 struct ast_call_feature *atxfer_feature = NULL;
1965 char *feature_code = NULL;
1966 int priority_int = 0;
1968 if (ast_strlen_zero(name)) {
1969 astman_send_error(s, m, "No channel specified\n");
1972 if (ast_strlen_zero(exten)) {
1973 astman_send_error(s, m, "No extension specified\n");
1976 if (ast_strlen_zero(context)) {
1977 astman_send_error(s, m, "No context specified\n");
1980 if (ast_strlen_zero(priority)) {
1981 astman_send_error(s, m, "No priority specified\n");
1985 if (sscanf(priority, "%d", &priority_int) != 1 && (priority_int = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
1986 astman_send_error(s, m, "Invalid Priority\n");
1990 if (!(atxfer_feature = ast_find_call_feature("atxfer"))) {
1991 astman_send_error(s, m, "No attended transfer feature found\n");
1995 if (!(chan = ast_get_channel_by_name_locked(name))) {
1996 astman_send_error(s, m, "Channel specified does not exist\n");
2000 for (feature_code = atxfer_feature->exten; feature_code && *feature_code; ++feature_code) {
2001 struct ast_frame f = {AST_FRAME_DTMF, *feature_code};
2002 ast_queue_frame(chan, &f);
2005 for (feature_code = (char *)exten; feature_code && *feature_code; ++feature_code) {
2006 struct ast_frame f = {AST_FRAME_DTMF, *feature_code};
2007 ast_queue_frame(chan, &f);
2010 astman_send_ack(s, m, "Atxfer successfully queued\n");
2011 ast_channel_unlock(chan);
2016 static char mandescr_command[] =
2017 "Description: Run a CLI command.\n"
2018 "Variables: (Names marked with * are required)\n"
2019 " *Command: Asterisk CLI command to run\n"
2020 " ActionID: Optional Action id for message matching.\n";
2022 /*! \brief Manager command "command" - execute CLI command */
2023 static int action_command(struct mansession *s, const struct message *m)
2025 const char *cmd = astman_get_header(m, "Command");
2026 const char *id = astman_get_header(m, "ActionID");
2027 char *buf, *final_buf;
2028 char template[] = "/tmp/ast-ami-XXXXXX"; /* template for temporary file */
2029 int fd = mkstemp(template), i = 0;
2032 for (i = 0; i < sizeof(command_blacklist) / sizeof(command_blacklist[0]); i++) {
2033 if (!strncmp(cmd, command_blacklist[i], strlen(command_blacklist[i]))) {
2034 astman_send_error(s, m, "Command blacklisted");
2039 astman_append(s, "Response: Follows\r\nPrivilege: Command\r\n");
2040 if (!ast_strlen_zero(id))
2041 astman_append(s, "ActionID: %s\r\n", id);
2042 /* FIXME: Wedge a ActionID response in here, waiting for later changes */
2043 ast_cli_command(fd, cmd); /* XXX need to change this to use a FILE * */
2044 l = lseek(fd, 0, SEEK_END); /* how many chars available */
2046 /* This has a potential to overflow the stack. Hence, use the heap. */
2047 buf = ast_calloc(1, l + 1);
2048 final_buf = ast_calloc(1, l + 1);
2050 lseek(fd, 0, SEEK_SET);
2054 term_strip(final_buf, buf, l);
2055 final_buf[l] = '\0';
2057 astman_append(s, "%s", S_OR(final_buf, buf));
2062 astman_append(s, "--END COMMAND--\r\n\r\n");
2064 ast_free(final_buf);
2068 /* helper function for originate */
2069 struct fast_originate_helper {
2070 char tech[AST_MAX_EXTENSION];
2071 char data[AST_MAX_EXTENSION];
2073 char app[AST_MAX_APP];
2074 char appdata[AST_MAX_EXTENSION];
2075 char cid_name[AST_MAX_EXTENSION];
2076 char cid_num[AST_MAX_EXTENSION];
2077 char context[AST_MAX_CONTEXT];
2078 char exten[AST_MAX_EXTENSION];
2079 char idtext[AST_MAX_EXTENSION];
2080 char account[AST_MAX_ACCOUNT_CODE];
2082 struct ast_variable *vars;
2085 static void *fast_originate(void *data)
2087 struct fast_originate_helper *in = data;
2090 struct ast_channel *chan = NULL;
2091 char requested_channel[AST_CHANNEL_NAME];
2093 if (!ast_strlen_zero(in->app)) {
2094 res = ast_pbx_outgoing_app(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->app, in->appdata, &reason, 1,
2095 S_OR(in->cid_num, NULL),
2096 S_OR(in->cid_name, NULL),
2097 in->vars, in->account, &chan);
2099 res = ast_pbx_outgoing_exten(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->context, in->exten, in->priority, &reason, 1,
2100 S_OR(in->cid_num, NULL),
2101 S_OR(in->cid_name, NULL),
2102 in->vars, in->account, &chan);
2106 snprintf(requested_channel, AST_CHANNEL_NAME, "%s/%s", in->tech, in->data);
2107 /* Tell the manager what happened with the channel */
2108 manager_event(EVENT_FLAG_CALL, "OriginateResponse",
2116 "CallerIDNum: %s\r\n"
2117 "CallerIDName: %s\r\n",
2118 in->idtext, res ? "Failure" : "Success", chan ? chan->name : requested_channel, in->context, in->exten, reason,
2119 chan ? chan->uniqueid : "<null>",
2120 S_OR(in->cid_num, "<unknown>"),
2121 S_OR(in->cid_name, "<unknown>")
2124 /* Locked by ast_pbx_outgoing_exten or ast_pbx_outgoing_app */
2126 ast_channel_unlock(chan);
2131 static char mandescr_originate[] =
2132 "Description: Generates an outgoing call to a Extension/Context/Priority or\n"
2133 " Application/Data\n"
2134 "Variables: (Names marked with * are required)\n"
2135 " *Channel: Channel name to call\n"
2136 " Exten: Extension to use (requires 'Context' and 'Priority')\n"
2137 " Context: Context to use (requires 'Exten' and 'Priority')\n"
2138 " Priority: Priority to use (requires 'Exten' and 'Context')\n"
2139 " Application: Application to use\n"
2140 " Data: Data to use (requires 'Application')\n"
2141 " Timeout: How long to wait for call to be answered (in ms)\n"
2142 " CallerID: Caller ID to be set on the outgoing channel\n"
2143 " Variable: Channel variable to set, multiple Variable: headers are allowed\n"
2144 " Account: Account code\n"
2145 " Async: Set to 'true' for fast origination\n";
2147 static int action_originate(struct mansession *s, const struct message *m)
2149 const char *name = astman_get_header(m, "Channel");
2150 const char *exten = astman_get_header(m, "Exten");
2151 const char *context = astman_get_header(m, "Context");
2152 const char *priority = astman_get_header(m, "Priority");
2153 const char *timeout = astman_get_header(m, "Timeout");
2154 const char *callerid = astman_get_header(m, "CallerID");
2155 const char *account = astman_get_header(m, "Account");
2156 const char *app = astman_get_header(m, "Application");
2157 const char *appdata = astman_get_header(m, "Data");
2158 const char *async = astman_get_header(m, "Async");
2159 const char *id = astman_get_header(m, "ActionID");
2160 struct ast_variable *vars = astman_get_variables(m);
2162 char *l = NULL, *n = NULL;
2172 astman_send_error(s, m, "Channel not specified");
2175 if (!ast_strlen_zero(priority) && (sscanf(priority, "%d", &pi) != 1)) {
2176 if ((pi = ast_findlabel_extension(NULL, context, exten, priority, NULL)) < 1) {
2177 astman_send_error(s, m, "Invalid priority\n");
2181 if (!ast_strlen_zero(timeout) && (sscanf(timeout, "%d", &to) != 1)) {
2182 astman_send_error(s, m, "Invalid timeout\n");
2185 ast_copy_string(tmp, name, sizeof(tmp));
2187 data = strchr(tmp, '/');
2189 astman_send_error(s, m, "Invalid channel\n");
2193 ast_copy_string(tmp2, callerid, sizeof(tmp2));
2194 ast_callerid_parse(tmp2, &n, &l);
2196 if (ast_strlen_zero(n))
2200 ast_shrink_phone_number(l);
2201 if (ast_strlen_zero(l))
2204 if (ast_true(async)) {
2205 struct fast_originate_helper *fast = ast_calloc(1, sizeof(*fast));
2209 if (!ast_strlen_zero(id))
2210 snprintf(fast->idtext, sizeof(fast->idtext), "ActionID: %s\r\n", id);
2211 ast_copy_string(fast->tech, tech, sizeof(fast->tech));
2212 ast_copy_string(fast->data, data, sizeof(fast->data));
2213 ast_copy_string(fast->app, app, sizeof(fast->app));
2214 ast_copy_string(fast->appdata, appdata, sizeof(fast->appdata));
2216 ast_copy_string(fast->cid_num, l, sizeof(fast->cid_num));
2218 ast_copy_string(fast->cid_name, n, sizeof(fast->cid_name));
2220 ast_copy_string(fast->context, context, sizeof(fast->context));
2221 ast_copy_string(fast->exten, exten, sizeof(fast->exten));
2222 ast_copy_string(fast->account, account, sizeof(fast->account));
2224 fast->priority = pi;
2225 if (ast_pthread_create_detached(&th, NULL, fast_originate, fast)) {
2231 } else if (!ast_strlen_zero(app)) {
2232 /* To run the System application (or anything else that goes to shell), you must have the additional System privilege */
2233 if (!(s->writeperm & EVENT_FLAG_SYSTEM)
2235 strcasestr(app, "system") == 0 || /* System(rm -rf /)
2236 TrySystem(rm -rf /) */
2237 strcasestr(app, "exec") || /* Exec(System(rm -rf /))
2238 TryExec(System(rm -rf /)) */
2239 strcasestr(app, "agi") || /* AGI(/bin/rm,-rf /)
2240 EAGI(/bin/rm,-rf /) */
2241 strstr(appdata, "SHELL") || /* NoOp(${SHELL(rm -rf /)}) */
2242 strstr(appdata, "EVAL") /* NoOp(${EVAL(${some_var_containing_SHELL})}) */
2244 astman_send_error(s, m, "Originate with certain 'Application' arguments requires the additional System privilege, which you do not have.");
2247 res = ast_pbx_outgoing_app(tech, AST_FORMAT_SLINEAR, data, to, app, appdata, &reason, 1, l, n, vars, account, NULL);
2249 if (exten && context && pi)
2250 res = ast_pbx_outgoing_exten(tech, AST_FORMAT_SLINEAR, data, to, context, exten, pi, &reason, 1, l, n, vars, account, NULL);
2252 astman_send_error(s, m, "Originate with 'Exten' requires 'Context' and 'Priority'");
2257 astman_send_ack(s, m, "Originate successfully queued");
2259 astman_send_error(s, m, "Originate failed");
2263 /*! \brief Help text for manager command mailboxstatus
2265 static char mandescr_mailboxstatus[] =
2266 "Description: Checks a voicemail account for status.\n"
2267 "Variables: (Names marked with * are required)\n"
2268 " *Mailbox: Full mailbox ID <mailbox>@<vm-context>\n"
2269 " ActionID: Optional ActionID for message matching.\n"
2270 "Returns number of messages.\n"
2271 " Message: Mailbox Status\n"
2272 " Mailbox: <mailboxid>\n"
2273 " Waiting: <count>\n"
2276 static int action_mailboxstatus(struct mansession *s, const struct message *m)
2278 const char *mailbox = astman_get_header(m, "Mailbox");
2281 if (ast_strlen_zero(mailbox)) {
2282 astman_send_error(s, m, "Mailbox not specified");
2285 ret = ast_app_has_voicemail(mailbox, NULL);
2286 astman_start_ack(s, m);
2287 astman_append(s, "Message: Mailbox Status\r\n"
2289 "Waiting: %d\r\n\r\n", mailbox, ret);
2293 static char mandescr_mailboxcount[] =
2294 "Description: Checks a voicemail account for new messages.\n"
2295 "Variables: (Names marked with * are required)\n"
2296 " *Mailbox: Full mailbox ID <mailbox>@<vm-context>\n"
2297 " ActionID: Optional ActionID for message matching.\n"
2298 "Returns number of new and old messages.\n"
2299 " Message: Mailbox Message Count\n"
2300 " Mailbox: <mailboxid>\n"
2301 " NewMessages: <count>\n"
2302 " OldMessages: <count>\n"
2304 static int action_mailboxcount(struct mansession *s, const struct message *m)
2306 const char *mailbox = astman_get_header(m, "Mailbox");
2307 int newmsgs = 0, oldmsgs = 0;
2309 if (ast_strlen_zero(mailbox)) {
2310 astman_send_error(s, m, "Mailbox not specified");
2313 ast_app_inboxcount(mailbox, &newmsgs, &oldmsgs);
2314 astman_start_ack(s, m);
2315 astman_append(s, "Message: Mailbox Message Count\r\n"
2317 "NewMessages: %d\r\n"
2318 "OldMessages: %d\r\n"
2320 mailbox, newmsgs, oldmsgs);
2324 static char mandescr_extensionstate[] =
2325 "Description: Report the extension state for given extension.\n"
2326 " If the extension has a hint, will use devicestate to check\n"
2327 " the status of the device connected to the extension.\n"
2328 "Variables: (Names marked with * are required)\n"
2329 " *Exten: Extension to check state on\n"
2330 " *Context: Context for extension\n"
2331 " ActionId: Optional ID for this transaction\n"
2332 "Will return an \"Extension Status\" message.\n"
2333 "The response will include the hint for the extension and the status.\n";
2335 static int action_extensionstate(struct mansession *s, const struct message *m)
2337 const char *exten = astman_get_header(m, "Exten");
2338 const char *context = astman_get_header(m, "Context");
2339 char hint[256] = "";
2341 if (ast_strlen_zero(exten)) {
2342 astman_send_error(s, m, "Extension not specified");
2345 if (ast_strlen_zero(context))
2346 context = "default";
2347 status = ast_extension_state(NULL, context, exten);
2348 ast_get_hint(hint, sizeof(hint) - 1, NULL, 0, NULL, context, exten);
2349 astman_start_ack(s, m);
2350 astman_append(s, "Message: Extension Status\r\n"
2354 "Status: %d\r\n\r\n",
2355 exten, context, hint, status);
2359 static char mandescr_timeout[] =
2360 "Description: Hangup a channel after a certain time.\n"
2361 "Variables: (Names marked with * are required)\n"
2362 " *Channel: Channel name to hangup\n"
2363 " *Timeout: Maximum duration of the call (sec)\n"
2364 "Acknowledges set time with 'Timeout Set' message\n";
2366 static int action_timeout(struct mansession *s, const struct message *m)
2368 struct ast_channel *c;
2369 const char *name = astman_get_header(m, "Channel");
2370 double timeout = atof(astman_get_header(m, "Timeout"));
2371 struct timeval tv = { timeout, 0 };
2373 if (ast_strlen_zero(name)) {
2374 astman_send_error(s, m, "No channel specified");
2377 if (!timeout || timeout < 0) {
2378 astman_send_error(s, m, "No timeout specified");
2381 c = ast_get_channel_by_name_locked(name);
2383 astman_send_error(s, m, "No such channel");
2387 tv.tv_usec = (timeout - tv.tv_sec) * 1000000.0;
2388 ast_channel_setwhentohangup_tv(c, tv);
2389 ast_channel_unlock(c);
2390 astman_send_ack(s, m, "Timeout Set");
2395 * Send any applicable events to the client listening on this socket.
2396 * Wait only for a finite time on each event, and drop all events whether
2397 * they are successfully sent or not.
2399 static int process_events(struct mansession *s)
2403 ast_mutex_lock(&s->__lock);
2405 struct eventqent *eqe;
2407 while ( (eqe = NEW_EVENT(s)) ) {
2409 if (!ret && s->authenticated &&
2410 (s->readperm & eqe->category) == eqe->category &&
2411 (s->send_events & eqe->category) == eqe->category) {
2412 if (send_string(s, eqe->eventdata) < 0)
2413 ret = -1; /* don't send more */
2415 s->last_ev = unref_event(s->last_ev);
2418 ast_mutex_unlock(&s->__lock);
2422 static char mandescr_userevent[] =
2423 "Description: Send an event to manager sessions.\n"
2424 "Variables: (Names marked with * are required)\n"
2425 " *UserEvent: EventStringToSend\n"
2426 " Header1: Content1\n"
2427 " HeaderN: ContentN\n";
2429 static int action_userevent(struct mansession *s, const struct message *m)
2431 const char *event = astman_get_header(m, "UserEvent");
2432 char body[2048] = "";
2434 for (x = 0; x < m->hdrcount; x++) {
2435 if (strncasecmp("UserEvent:", m->headers[x], strlen("UserEvent:"))) {
2436 ast_copy_string(body + bodylen, m->headers[x], sizeof(body) - bodylen - 3);
2437 bodylen += strlen(m->headers[x]);
2438 ast_copy_string(body + bodylen, "\r\n", 3);
2443 manager_event(EVENT_FLAG_USER, "UserEvent", "UserEvent: %s\r\n%s", event, body);
2447 static char mandescr_coresettings[] =
2448 "Description: Query for Core PBX settings.\n"
2449 "Variables: (Names marked with * are optional)\n"
2450 " *ActionID: ActionID of this transaction\n";
2452 /*! \brief Show PBX core settings information */
2453 static int action_coresettings(struct mansession *s, const struct message *m)
2455 const char *actionid = astman_get_header(m, "ActionID");
2458 if (!ast_strlen_zero(actionid))
2459 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
2463 astman_append(s, "Response: Success\r\n"
2465 "AMIversion: %s\r\n"
2466 "AsteriskVersion: %s\r\n"
2467 "SystemName: %s\r\n"
2468 "CoreMaxCalls: %d\r\n"
2469 "CoreMaxLoadAvg: %f\r\n"
2470 "CoreRunUser: %s\r\n"
2471 "CoreRunGroup: %s\r\n"
2472 "CoreMaxFilehandles: %d\r\n"
2473 "CoreRealTimeEnabled: %s\r\n"
2474 "CoreCDRenabled: %s\r\n"
2475 "CoreHTTPenabled: %s\r\n"
2480 ast_config_AST_SYSTEM_NAME,
2483 ast_config_AST_RUN_USER,
2484 ast_config_AST_RUN_GROUP,
2486 ast_realtime_enabled() ? "Yes" : "No",
2487 check_cdr_enabled() ? "Yes" : "No",
2488 check_webmanager_enabled() ? "Yes" : "No"
2493 static char mandescr_corestatus[] =
2494 "Description: Query for Core PBX status.\n"
2495 "Variables: (Names marked with * are optional)\n"
2496 " *ActionID: ActionID of this transaction\n";
2498 /*! \brief Show PBX core status information */
2499 static int action_corestatus(struct mansession *s, const struct message *m)
2501 const char *actionid = astman_get_header(m, "ActionID");
2503 char startuptime[150];
2504 char reloadtime[150];
2507 if (!ast_strlen_zero(actionid))
2508 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", actionid);
2512 ast_localtime(&ast_startuptime, &tm, NULL);
2513 ast_strftime(startuptime, sizeof(startuptime), "%H:%M:%S", &tm);
2514 ast_localtime(&ast_lastreloadtime, &tm, NULL);
2515 ast_strftime(reloadtime, sizeof(reloadtime), "%H:%M:%S", &tm);
2517 astman_append(s, "Response: Success\r\n"
2519 "CoreStartupTime: %s\r\n"
2520 "CoreReloadTime: %s\r\n"
2521 "CoreCurrentCalls: %d\r\n"
2526 ast_active_channels()
2531 static char mandescr_reload[] =
2532 "Description: Send a reload event.\n"
2533 "Variables: (Names marked with * are optional)\n"
2534 " *ActionID: ActionID of this transaction\n"
2535 " *Module: Name of the module to reload\n";
2537 /*! \brief Send a reload event */
2538 static int action_reload(struct mansession *s, const struct message *m)
2540 const char *module = astman_get_header(m, "Module");
2541 int res = ast_module_reload(S_OR(module, NULL));
2544 astman_send_ack(s, m, "Module Reloaded");
2546 astman_send_error(s, m, s == 0 ? "No such module" : "Module does not support reload");
2550 static char mandescr_coreshowchannels[] =
2551 "Description: List currently defined channels and some information\n"
2554 " ActionID: Optional Action id for message matching.\n";
2556 /*! \brief Manager command "CoreShowChannels" - List currently defined channels
2557 * and some information about them. */
2558 static int action_coreshowchannels(struct mansession *s, const struct message *m)
2560 const char *actionid = astman_get_header(m, "ActionID");
2561 char actionidtext[256];
2562 struct ast_channel *c = NULL;
2564 int duration, durh, durm, durs;
2566 if (!ast_strlen_zero(actionid))
2567 snprintf(actionidtext, sizeof(actionidtext), "ActionID: %s\r\n", actionid);
2569 actionidtext[0] = '\0';
2571 astman_send_listack(s, m, "Channels will follow", "start");
2573 while ((c = ast_channel_walk_locked(c)) != NULL) {
2574 struct ast_channel *bc = ast_bridged_channel(c);
2575 char durbuf[10] = "";
2577 if (c->cdr && !ast_tvzero(c->cdr->start)) {
2578 duration = (int)(ast_tvdiff_ms(ast_tvnow(), c->cdr->start) / 1000);
2579 durh = duration / 3600;
2580 durm = (duration % 3600) / 60;
2581 durs = duration % 60;
2582 snprintf(durbuf, sizeof(durbuf), "%02d:%02d:%02d", durh, durm, durs);
2591 "ChannelState: %d\r\n"
2592 "ChannelStateDesc: %s\r\n"
2593 "Application: %s\r\n"
2594 "ApplicationData: %s\r\n"
2595 "CallerIDnum: %s\r\n"
2597 "AccountCode: %s\r\n"
2598 "BridgedChannel: %s\r\n"
2599 "BridgedUniqueID: %s\r\n"
2600 "\r\n", c->name, c->uniqueid, c->context, c->exten, c->priority, c->_state, ast_state2str(c->_state),
2601 c->appl ? c->appl : "", c->data ? S_OR(c->data, ""): "",
2602 S_OR(c->cid.cid_num, ""), durbuf, S_OR(c->accountcode, ""), bc ? bc->name : "", bc ? bc->uniqueid : "");
2603 ast_channel_unlock(c);
2608 "Event: CoreShowChannelsComplete\r\n"
2609 "EventList: Complete\r\n"
2612 "\r\n", numchans, actionidtext);
2617 static char mandescr_modulecheck[] =
2618 "Description: Checks if Asterisk module is loaded\n"
2620 " ActionID: <id> Action ID for this transaction. Will be returned.\n"
2621 " Module: <name> Asterisk module name (not including extension)\n"
2623 "Will return Success/Failure\n"
2624 "For success returns, the module revision number is included.\n";
2626 /* Manager function to check if module is loaded */
2627 static int manager_modulecheck(struct mansession *s, const struct message *m)
2630 const char *module = astman_get_header(m, "Module");
2631 const char *id = astman_get_header(m, "ActionID");
2633 #if !defined(LOW_MEMORY)
2634 const char *version;
2636 char filename[PATH_MAX];
2639 ast_copy_string(filename, module, sizeof(filename));
2640 if ((cut = strchr(filename, '.'))) {
2643 cut = filename + strlen(filename);
2645 snprintf(cut, (sizeof(filename) - strlen(filename)) - 1, ".so");
2646 ast_log(LOG_DEBUG, "**** ModuleCheck .so file %s\n", filename);
2647 res = ast_module_check(filename);
2649 astman_send_error(s, m, "Module not loaded");
2652 snprintf(cut, (sizeof(filename) - strlen(filename)) - 1, ".c");
2653 ast_log(LOG_DEBUG, "**** ModuleCheck .c file %s\n", filename);
2654 #if !defined(LOW_MEMORY)
2655 version = ast_file_version_find(filename);
2658 if (!ast_strlen_zero(id))
2659 snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
2662 astman_append(s, "Response: Success\r\n%s", idText);
2663 #if !defined(LOW_MEMORY)
2664 astman_append(s, "Version: %s\r\n\r\n", version ? version : "");
2669 static char mandescr_moduleload[] =
2670 "Description: Loads, unloads or reloads an Asterisk module in a running system.\n"
2672 " ActionID: <id> Action ID for this transaction. Will be returned.\n"
2673 " Module: <name> Asterisk module name (including .so extension)\n"
2674 " or subsystem identifier:\n"
2675 " cdr, enum, dnsmgr, extconfig, manager, rtp, http\n"
2676 " LoadType: load | unload | reload\n"
2677 " The operation to be done on module\n"
2678 " If no module is specified for a reload loadtype, all modules are reloaded";
2680 static int manager_moduleload(struct mansession *s, const struct message *m)
2683 const char *module = astman_get_header(m, "Module");
2684 const char *loadtype = astman_get_header(m, "LoadType");
2686 if (!loadtype || strlen(loadtype) == 0)
2687 astman_send_error(s, m, "Incomplete ModuleLoad action.");
2688 if ((!module || strlen(module) == 0) && strcasecmp(loadtype, "reload") != 0)
2689 astman_send_error(s, m, "Need module name");
2691 if (!strcasecmp(loadtype, "load")) {
2692 res = ast_load_resource(module);
2694 astman_send_error(s, m, "Could not load module.");
2696 astman_send_ack(s, m, "Module loaded.");
2697 } else if (!strcasecmp(loadtype, "unload")) {
2698 res = ast_unload_resource(module, AST_FORCE_SOFT);
2700 astman_send_error(s, m, "Could not unload module.");
2702 astman_send_ack(s, m, "Module unloaded.");
2703 } else if (!strcasecmp(loadtype, "reload")) {
2704 if (module != NULL) {
2705 res = ast_module_reload(module);
2707 astman_send_error(s, m, "No such module.");
2709 astman_send_error(s, m, "Module does not support reload action.");
2711 astman_send_ack(s, m, "Module reloaded.");
2713 ast_module_reload(NULL); /* Reload all modules */
2714 astman_send_ack(s, m, "All modules reloaded");
2717 astman_send_error(s, m, "Incomplete ModuleLoad action.");
2722 * Done with the action handlers here, we start with the code in charge
2723 * of accepting connections and serving them.
2724 * accept_thread() forks a new thread for each connection, session_do(),
2725 * which in turn calls get_input() repeatedly until a full message has
2726 * been accumulated, and then invokes process_message() to pass it to
2727 * the appropriate handler.
2731 * Process an AMI message, performing desired action.
2732 * Return 0 on success, -1 on error that require the session to be destroyed.
2734 static int process_message(struct mansession *s, const struct message *m)
2736 char action[80] = "";
2738 struct manager_action *tmp;
2739 const char *user = astman_get_header(m, "Username");
2741 ast_copy_string(action, astman_get_header(m, "Action"), sizeof(action));
2742 ast_debug(1, "Manager received command '%s'\n", action);
2744 if (ast_strlen_zero(action)) {
2745 ast_mutex_lock(&s->__lock);
2746 astman_send_error(s, m, "Missing action in request");
2747 ast_mutex_unlock(&s->__lock);
2751 if (!s->authenticated && strcasecmp(action, "Login") && strcasecmp(action, "Logoff") && strcasecmp(action, "Challenge")) {
2752 ast_mutex_lock(&s->__lock);
2753 astman_send_error(s, m, "Permission denied");
2754 ast_mutex_unlock(&s->__lock);
2758 if (!allowmultiplelogin && !s->authenticated && user &&
2759 (!strcasecmp(action, "Login") || !strcasecmp(action, "Challenge"))) {
2760 if (check_manager_session_inuse(user)) {
2762 ast_mutex_lock(&s->__lock);
2763 astman_send_error(s, m, "Login Already In Use");
2764 ast_mutex_unlock(&s->__lock);
2769 AST_RWLIST_RDLOCK(&actions);
2770 AST_RWLIST_TRAVERSE(&actions, tmp, list) {
2771 if (strcasecmp(action, tmp->action))
2773 if (s->writeperm & tmp->authority || tmp->authority == 0)
2774 ret = tmp->func(s, m);
2776 astman_send_error(s, m, "Permission denied");
2779 AST_RWLIST_UNLOCK(&actions);
2783 snprintf(buf, sizeof(buf), "Invalid/unknown command: %s. Use Action: ListCommands to show available commands.", action);
2784 ast_mutex_lock(&s->__lock);
2785 astman_send_error(s, m, buf);
2786 ast_mutex_unlock(&s->__lock);
2790 /* Once done with our message, deliver any pending events */
2791 return process_events(s);
2795 * Read one full line (including crlf) from the manager socket.
2797 * \r\n is the only valid terminator for the line.
2798 * (Note that, later, '\0' will be considered as the end-of-line marker,
2799 * so everything between the '\0' and the '\r\n' will not be used).
2800 * Also note that we assume output to have at least "maxlen" space.
2803 static int get_input(struct mansession *s, char *output)
2806 int maxlen = sizeof(s->inbuf) - 1;
2807 char *src = s->inbuf;
2810 * Look for \r\n within the buffer. If found, copy to the output
2811 * buffer and return, trimming the \r\n (not used afterwards).
2813 for (x = 0; x < s->inlen; x++) {
2814 int cr; /* set if we have \r */
2815 if (src[x] == '\r' && x+1 < s->inlen && src[x+1] == '\n')
2816 cr = 2; /* Found. Update length to include \r\n */
2817 else if (src[x] == '\n')
2818 cr = 1; /* also accept \n only */
2821 memmove(output, src, x); /*... but trim \r\n */
2822 output[x] = '\0'; /* terminate the string */
2823 x += cr; /* number of bytes used */
2824 s->inlen -= x; /* remaining size */
2825 memmove(src, src + x, s->inlen); /* remove used bytes */
2828 if (s->inlen >= maxlen) {
2829 /* no crlf found, and buffer full - sorry, too long for us */
2830 ast_log(LOG_WARNING, "Dumping long line with no return from %s: %s\n", ast_inet_ntoa(s->sin.sin_addr), src);
2835 /* XXX do we really need this locking ? */
2836 ast_mutex_lock(&s->__lock);
2837 if (s->pending_event) {
2838 s->pending_event = 0;
2839 ast_mutex_unlock(&s->__lock);
2842 s->waiting_thread = pthread_self();
2843 ast_mutex_unlock(&s->__lock);
2845 res = ast_wait_for_input(s->fd, -1); /* return 0 on timeout ? */
2847 ast_mutex_lock(&s->__lock);
2848 s->waiting_thread = AST_PTHREADT_NULL;
2849 ast_mutex_unlock(&s->__lock);
2852 /* If we get a signal from some other thread (typically because
2853 * there are new events queued), return 0 to notify the caller.
2855 if (errno == EINTR || errno == EAGAIN)
2857 ast_log(LOG_WARNING, "poll() returned error: %s\n", strerror(errno));
2860 ast_mutex_lock(&s->__lock);
2861 res = fread(src + s->inlen, 1, maxlen - s->inlen, s->f);
2863 res = -1; /* error return */
2866 src[s->inlen] = '\0';
2869 ast_mutex_unlock(&s->__lock);
2873 static int do_message(struct mansession *s)
2875 struct message m = { 0 };
2876 char header_buf[sizeof(s->inbuf)] = { '\0' };
2880 /* Check if any events are pending and do them if needed */
2881 if (process_events(s))
2883 res = get_input(s, header_buf);
2886 } else if (res > 0) {
2887 if (ast_strlen_zero(header_buf))
2888 return process_message(s, &m) ? -1 : 0;
2889 else if (m.hdrcount < (AST_MAX_MANHEADERS - 1))
2890 m.headers[m.hdrcount++] = ast_strdupa(header_buf);
2897 /*! \brief The body of the individual manager session.
2898 * Call get_input() to read one line at a time
2899 * (or be woken up on new events), collect the lines in a
2900 * message until found an empty line, and execute the request.
2901 * In any case, deliver events asynchronously through process_events()
2902 * (called from here if no line is available, or at the end of
2903 * process_message(). )
2905 static void *session_do(void *data)
2907 struct ast_tcptls_session_instance *ser = data;
2908 struct mansession *s = ast_calloc(1, sizeof(*s));
2915 s->writetimeout = 100;
2916 s->waiting_thread = AST_PTHREADT_NULL;
2918 flags = fcntl(ser->fd, F_GETFL);
2919 if (!block_sockets) /* make sure socket is non-blocking */
2920 flags |= O_NONBLOCK;
2922 flags &= ~O_NONBLOCK;
2923 fcntl(ser->fd, F_SETFL, flags);
2925 ast_mutex_init(&s->__lock);
2926 s->send_events = -1;
2927 /* these fields duplicate those in the 'ser' structure */
2930 s->sin = ser->requestor;
2932 AST_LIST_LOCK(&sessions);
2933 AST_LIST_INSERT_HEAD(&sessions, s, list);
2934 ast_atomic_fetchadd_int(&num_sessions, 1);
2935 AST_LIST_UNLOCK(&sessions);
2936 /* Hook to the tail of the event queue */
2937 s->last_ev = grab_last();
2939 astman_append(s, "Asterisk Call Manager/%s\r\n", AMI_VERSION); /* welcome prompt */
2941 if ((res = do_message(s)) < 0)
2944 /* session is over, explain why and terminate */
2945 if (s->authenticated) {
2946 if (manager_displayconnects(s))
2947 ast_verb(2, "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
2948 ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
2950 if (displayconnects)
2951 ast_verb(2, "Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr));
2952 ast_log(LOG_EVENT, "Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr));
2955 /* It is possible under certain circumstances for this session thread
2956 to complete its work and exit *before* the thread that created it
2957 has finished executing the ast_pthread_create_background() function.
2958 If this occurs, some versions of glibc appear to act in a buggy
2959 fashion and attempt to write data into memory that it thinks belongs
2960 to the thread but is in fact not owned by the thread (or may have
2961 been freed completely).
2963 Causing this thread to yield to other threads at least one time
2964 appears to work around this bug.
2971 ser = ast_tcptls_session_instance_destroy(ser);
2975 /*! \brief remove at most n_max stale session from the list. */
2976 static void purge_sessions(int n_max)
2978 struct mansession *s;
2979 time_t now = time(NULL);
2981 AST_LIST_LOCK(&sessions);
2982 AST_LIST_TRAVERSE_SAFE_BEGIN(&sessions, s, list) {
2983 if (s->sessiontimeout && (now > s->sessiontimeout) && !s->inuse) {
2984 AST_LIST_REMOVE_CURRENT(list);
2985 ast_atomic_fetchadd_int(&num_sessions, -1);
2986 if (s->authenticated && (VERBOSITY_ATLEAST(2)) && manager_displayconnects(s)) {
2987 ast_verb(2, "HTTP Manager '%s' timed out from %s\n",
2988 s->username, ast_inet_ntoa(s->sin.sin_addr));
2990 free_session(s); /* XXX outside ? */
2995 AST_LIST_TRAVERSE_SAFE_END;
2996 AST_LIST_UNLOCK(&sessions);
3000 * events are appended to a queue from where they
3001 * can be dispatched to clients.
3003 static int append_event(const char *str, int category)
3005 struct eventqent *tmp = ast_malloc(sizeof(*tmp) + strlen(str));
3006 static int seq; /* sequence number */
3011 /* need to init all fields, because ast_malloc() does not */
3013 tmp->category = category;
3014 tmp->seq = ast_atomic_fetchadd_int(&seq, 1);
3015 AST_LIST_NEXT(tmp, eq_next) = NULL;
3016 strcpy(tmp->eventdata, str);
3018 AST_LIST_LOCK(&all_events);
3019 AST_LIST_INSERT_TAIL(&all_events, tmp, eq_next);
3020 AST_LIST_UNLOCK(&all_events);
3025 /* XXX see if can be moved inside the function */
3026 AST_THREADSTORAGE(manager_event_buf);
3027 #define MANAGER_EVENT_BUF_INITSIZE 256
3029 /*! \brief manager_event: Send AMI event to client */
3030 int __manager_event(int category, const char *event,
3031 const char *file, int line, const char *func, const char *fmt, ...)
3033 struct mansession *s;
3034 struct manager_custom_hook *hook;
3035 struct ast_str *auth = ast_str_alloca(80);
3036 const char *cat_str;
3039 struct ast_str *buf;
3041 /* Abort if there aren't any manager sessions */
3045 if (!(buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE)))
3048 cat_str = authority_to_str(category, &auth);
3049 ast_str_set(&buf, 0,
3050 "Event: %s\r\nPrivilege: %s\r\n",
3053 if (timestampevents) {
3055 ast_str_append(&buf, 0,
3056 "Timestamp: %ld.%06lu\r\n",
3057 now.tv_sec, (unsigned long) now.tv_usec);
3059 if (manager_debug) {
3061 ast_str_append(&buf, 0,
3062 "SequenceNumber: %d\r\n",
3063 ast_atomic_fetchadd_int(&seq, 1));
3064 ast_str_append(&buf, 0,
3065 "File: %s\r\nLine: %d\r\nFunc: %s\r\n", file, line, func);
3069 ast_str_append_va(&buf, 0, fmt, ap);
3072 ast_str_append(&buf, 0, "\r\n");
3074 append_event(buf->str, category);
3076 /* Wake up any sleeping sessions */
3077 AST_LIST_LOCK(&sessions);
3078 AST_LIST_TRAVERSE(&sessions, s, list) {
3079 ast_mutex_lock(&s->__lock);
3080 if (s->waiting_thread != AST_PTHREADT_NULL)
3081 pthread_kill(s->waiting_thread, SIGURG);
3083 /* We have an event to process, but the mansession is
3084 * not waiting for it. We still need to indicate that there
3085 * is an event waiting so that get_input processes the pending
3086 * event instead of polling.
3088 s->pending_event = 1;
3089 ast_mutex_unlock(&s->__lock);
3091 AST_LIST_UNLOCK(&sessions);
3093 AST_RWLIST_RDLOCK(&manager_hooks);
3094 AST_RWLIST_TRAVERSE(&manager_hooks, hook, list) {
3095 hook->helper(category, event, buf->str);
3097 AST_RWLIST_UNLOCK(&manager_hooks);
3103 * support functions to register/unregister AMI action handlers,
3105 int ast_manager_unregister(char *action)
3107 struct manager_action *cur;
3109 AST_RWLIST_WRLOCK(&actions);
3110 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&actions, cur, list) {
3111 if (!strcasecmp(action, cur->action)) {
3112 AST_RWLIST_REMOVE_CURRENT(list);
3114 ast_verb(2, "Manager unregistered action %s\n", action);
3118 AST_RWLIST_TRAVERSE_SAFE_END;
3119 AST_RWLIST_UNLOCK(&actions);
3124 static int manager_state_cb(char *context, char *exten, int state, void *data)
3126 /* Notify managers of change */
3128 ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten);
3130 manager_event(EVENT_FLAG_CALL, "ExtensionStatus", "Exten: %s\r\nContext: %s\r\nHint: %s\r\nStatus: %d\r\n", exten, context, hint, state);
3134 static int ast_manager_register_struct(struct manager_action *act)
3136 struct manager_action *cur, *prev = NULL;
3138 AST_RWLIST_WRLOCK(&actions);
3139 AST_RWLIST_TRAVERSE(&actions, cur, list) {
3140 int ret = strcasecmp(cur->action, act->action);
3142 ast_log(LOG_WARNING, "Manager: Action '%s' already registered\n", act->action);
3143 AST_RWLIST_UNLOCK(&actions);
3146 if (ret > 0) { /* Insert these alphabetically */
3153 AST_RWLIST_INSERT_AFTER(&actions, prev, act, list);
3155 AST_RWLIST_INSERT_HEAD(&actions, act, list);
3157 ast_verb(2, "Manager registered action %s\n", act->action);
3159 AST_RWLIST_UNLOCK(&actions);
3164 /*! \brief register a new command with manager, including online help. This is
3165 the preferred way to register a manager command */
3166 int ast_manager_register2(const char *action, int auth, int (*func)(struct mansession *s, const struct message *m), const char *synopsis, const char *description)
3168 struct manager_action *cur = NULL;
3170 if (!(cur = ast_calloc(1, sizeof(*cur))))
3173 cur->action = action;
3174 cur->authority = auth;
3176 cur->synopsis = synopsis;
3177 cur->description = description;
3179 ast_manager_register_struct(cur);
3184 END Doxygen group */
3187 * The following are support functions for AMI-over-http.
3188 * The common entry point is generic_http_callback(),
3189 * which extracts HTTP header and URI fields and reformats
3190 * them into AMI messages, locates a proper session
3191 * (using the mansession_id Cookie or GET variable),
3192 * and calls process_message() as for regular AMI clients.
3193 * When done, the output (which goes to a temporary file)
3194 * is read back into a buffer and reformatted as desired,
3195 * then fed back to the client over the original socket.
3198 enum output_format {
3204 static char *contenttype[] = {
3205 [FORMAT_RAW] = "plain",
3206 [FORMAT_HTML] = "html",
3207 [FORMAT_XML] = "xml",
3211 * locate an http session in the list. The search key (ident) is
3212 * the value of the mansession_id cookie (0 is not valid and means
3213 * a session on the AMI socket).
3215 static struct mansession *find_session(uint32_t ident)
3217 struct mansession *s;
3222 AST_LIST_LOCK(&sessions);
3223 AST_LIST_TRAVERSE(&sessions, s, list) {
3224 ast_mutex_lock(&s->__lock);
3225 if (s->managerid == ident && !s->needdestroy) {
3226 ast_atomic_fetchadd_int(&s->inuse, 1);
3229 ast_mutex_unlock(&s->__lock);
3231 AST_LIST_UNLOCK(&sessions);
3236 int astman_verify_session_readpermissions(uint32_t ident, int perm)
3239 struct mansession *s;
3241 AST_LIST_LOCK(&sessions);
3242 AST_LIST_TRAVERSE(&sessions, s, list) {
3243 ast_mutex_lock(&s->__lock);
3244 if ((s->managerid == ident) && (s->readperm & perm)) {
3246 ast_mutex_unlock(&s->__lock);
3249 ast_mutex_unlock(&s->__lock);
3251 AST_LIST_UNLOCK(&sessions);
3255 int astman_verify_session_writepermissions(uint32_t ident, int perm)
3258 struct mansession *s;
3260 AST_LIST_LOCK(&sessions);
3261 AST_LIST_TRAVERSE(&sessions, s, list) {
3262 ast_mutex_lock(&s->__lock);
3263 if ((s->managerid == ident) && (s->writeperm & perm)) {
3265 ast_mutex_unlock(&s->__lock);
3268 ast_mutex_unlock(&s->__lock);
3270 AST_LIST_UNLOCK(&sessions);
3275 * convert to xml with various conversion:
3276 * mode & 1 -> lowercase;
3277 * mode & 2 -> replace non-alphanumeric chars with underscore
3279 static void xml_copy_escape(struct ast_str **out, const char *src, int mode)
3281 /* store in a local buffer to avoid calling ast_str_append too often */
3284 int space = sizeof(buf);
3285 /* repeat until done and nothing to flush */
3286 for ( ; *src || dst != buf ; src++) {
3287 if (*src == '\0' || space < 10) { /* flush */
3289 ast_str_append(out, 0, "%s", buf);
3291 space = sizeof(buf);
3296 if ( (mode & 2) && !isalnum(*src)) {
3303 strcpy(dst, "<");
3308 strcpy(dst, ">");
3313 strcpy(dst, """);
3318 strcpy(dst, "'");
3323 strcpy(dst, "&");
3329 *dst++ = mode ? tolower(*src) : *src;
3335 struct variable_count {
3340 static int compress_char(char c)
3345 else if (c >= 'a' && c <= 'z')
3353 static int variable_count_hash_fn(const void *vvc, const int flags)
3355 const struct variable_count *vc = vvc;
3357 for (i = 0; i < 5; i++) {
3358 if (vc->varname[i] == '\0')
3360 res += compress_char(vc->varname[i]) << (i * 6);
3365 static int variable_count_cmp_fn(void *obj, void *vstr, int flags)
3367 /* Due to the simplicity of struct variable_count, it makes no difference
3368 * if you pass in objects or strings, the same operation applies. This is
3369 * due to the fact that the hash occurs on the first element, which means
3370 * the address of both the struct and the string are exactly the same. */
3371 struct variable_count *vc = obj;
3373 return !strcmp(vc->varname, str) ? CMP_MATCH : 0;
3376 /*! \brief Convert the input into XML or HTML.
3377 * The input is supposed to be a sequence of lines of the form
3379 * optionally followed by a blob of unformatted text.
3380 * A blank line is a section separator. Basically, this is a
3381 * mixture of the format of Manager Interface and CLI commands.
3382 * The unformatted text is considered as a single value of a field
3383 * named 'Opaque-data'.
3385 * At the moment the output format is the following (but it may
3386 * change depending on future requirements so don't count too
3387 * much on it when writing applications):
3389 * General: the unformatted text is used as a value of
3390 * XML output: to be completed
3393 * Each section is within <response type="object" id="xxx">
3394 * where xxx is taken from ajaxdest variable or defaults to unknown
3395 * Each row is reported as an attribute Name="value" of an XML
3396 * entity named from the variable ajaxobjtype, default to "generic"
3400 * each Name-value pair is output as a single row of a two-column table.
3401 * Sections (blank lines in the input) are separated by a <HR>
3404 static void xml_translate(struct ast_str **out, char *in, struct ast_variable *vars, enum output_format format)
3406 struct ast_variable *v;
3407 const char *dest = NULL;
3409 const char *objtype = NULL;
3410 int in_data = 0; /* parsing data */
3412 int xml = (format == FORMAT_XML);
3413 struct variable_count *vc = NULL;
3414 struct ao2_container *vco = NULL;
3416 for (v = vars; v; v = v->next) {
3417 if (!dest && !strcasecmp(v->name, "ajaxdest"))
3419 else if (!objtype && !strcasecmp(v->name, "ajaxobjtype"))
3425 objtype = "generic";
3427 /* we want to stop when we find an empty line */
3429 val = strsep(&in, "\r\n"); /* mark start and end of line */
3430 if (in && *in == '\n') /* remove trailing \n if any */
3432 ast_trim_blanks(val);
3433 ast_debug(5, "inobj %d in_data %d line <%s>\n", inobj, in_data, val);
3434 if (ast_strlen_zero(val)) {
3435 if (in_data) { /* close data */
3436 ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
3440 ast_str_append(out, 0, xml ? " /></response>\n" :
3441 "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
3449 /* we expect Name: value lines */
3453 var = strsep(&val, ":");
3454 if (val) { /* found the field name */
3455 val = ast_skip_blanks(val);
3456 ast_trim_blanks(var);
3457 } else { /* field name not found, move to opaque mode */
3459 var = "Opaque-data";
3465 ast_str_append(out, 0, "<response type='object' id='%s'><%s", dest, objtype);
3467 ast_str_append(out, 0, "<body>\n");
3468 vco = ao2_container_alloc(37, variable_count_hash_fn, variable_count_cmp_fn);
3472 if (!in_data) { /* build appropriate line start */
3473 ast_str_append(out, 0, xml ? " " : "<tr><td>");
3474 if ((vc = ao2_find(vco, var, 0)))
3477 /* Create a new entry for this one */
3478 vc = ao2_alloc(sizeof(*vc), NULL);
3483 xml_copy_escape(out, var, xml ? 1 | 2 : 0);
3485 ast_str_append(out, 0, "-%d", vc->count);
3487 ast_str_append(out, 0, xml ? "='" : "</td><td>");
3488 if (!strcmp(var, "Opaque-data"))
3491 xml_copy_escape(out, val, 0); /* data field */
3493 ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
3495 ast_str_append(out, 0, xml ? "\n" : "<br>\n");
3498 ast_str_append(out, 0, xml ? " /></response>\n" :
3499 "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
3504 static struct ast_str *generic_http_callback(enum output_format format,
3505 struct sockaddr_in *requestor, const char *uri, enum ast_http_method method,
3506 struct ast_variable *params, int *status,
3507 char **title, int *contentlength)
3509 struct mansession *s = NULL;
3512 struct ast_variable *v;
3513 char template[] = "/tmp/ast-http-XXXXXX"; /* template for temporary file */
3514 struct ast_str *out = NULL;
3515 struct message m = { 0 };
3519 for (v = params; v; v = v->next) {
3520 if (!strcasecmp(v->name, "mansession_id")) {
3521 sscanf(v->value, "%x", &ident);
3526 if (!(s = find_session(ident))) {
3527 /* Create new session.
3528 * While it is not in the list we don't need any locking
3530 if (!(s = ast_calloc(1, sizeof(*s)))) {
3532 goto generic_callback_out;
3534 s->sin = *requestor;
3536 s->waiting_thread = AST_PTHREADT_NULL;
3538 ast_mutex_init(&s->__lock);
3539 ast_mutex_lock(&s->__lock);
3541 /*!\note There is approximately a 1 in 1.8E19 chance that the following
3542 * calculation will produce 0, which is an invalid ID, but due to the
3543 * properties of the rand() function (and the constantcy of s), that
3544 * won't happen twice in a row.
3546 while ((s->managerid = rand() ^ (unsigned long) s) == 0);
3547 s->last_ev = grab_last();
3548 AST_LIST_LOCK(&sessions);
3549 AST_LIST_INSERT_HEAD(&sessions, s, list);
3550 ast_atomic_fetchadd_int(&num_sessions, 1);
3551 AST_LIST_UNLOCK(&sessions);
3554 ast_mutex_unlock(&s->__lock);
3556 if (!(out = ast_str_create(1024))) {
3558 goto generic_callback_out;
3561 s->fd = mkstemp(template); /* create a temporary file for command output */
3563 s->f = fdopen(s->fd, "w+");
3565 for (x = 0, v = params; v && (x < AST_MAX_MANHEADERS); x++, v = v->next) {
3566 hdrlen = strlen(v->name) + strlen(v->value) + 3;
3567 m.headers[m.hdrcount] = alloca(hdrlen);
3568 snprintf((char *) m.headers[m.hdrcount], hdrlen, "%s: %s", v->name, v->value);
3572 if (process_message(s, &m)) {
3573 if (s->authenticated) {
3574 if (manager_displayconnects(s))
3575 ast_verb(2, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3576 ast_log(LOG_EVENT, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
3578 if (displayconnects)
3579 ast_verb(2, "HTTP Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr));
3580 ast_log(LOG_EVENT, "HTTP Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr));
3585 ast_str_append(&out, 0,
3586 "Content-type: text/%s\r\n"
3587 "Cache-Control: no-cache;\r\n"
3588 "Set-Cookie: mansession_id=\"%08x\"; Version=\"1\"; Max-Age=%d\r\n"
3590 contenttype[format],
3591 s->managerid, httptimeout);
3593 if (format == FORMAT_XML) {
3594 ast_str_append(&out, 0, "<ajax-response>\n");
3595 } else if (format == FORMAT_HTML) {
3597 #define ROW_FMT "<tr><td colspan=\"2\" bgcolor=\"#f1f1ff\">%s</td></tr>\r\n"
3598 #define TEST_STRING \
3599 "<form action=\"manager\">action: <input name=\"action\"> cmd <input name=\"command\"><br> \
3600 user <input name=\"username\"> pass <input type=\"password\" name=\"secret\"><br> \
3601 <input type=\"submit\"></form>"
3603 ast_str_append(&out, 0, "<title>Asterisk™ Manager Interface</title>");
3604 ast_str_append(&out, 0, "<body bgcolor=\"#ffffff\"><table align=center bgcolor=\"#f1f1f1\" width=\"500\">\r\n");
3605 ast_str_append(&out, 0, ROW_FMT, "<h1>Manager Tester</h1>");
3606 ast_str_append(&out, 0, ROW_FMT, TEST_STRING);
3609 if (s->f != NULL) { /* have temporary output */
3611 size_t l = ftell(s->f);
3614 if ((buf = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_SHARED, s->fd, 0))) {
3615 if (format == FORMAT_XML || format == FORMAT_HTML)
3616 xml_translate(&out, buf, params, format);
3618 ast_str_append(&out, 0, "%s", buf);
3621 } else if (format == FORMAT_XML || format == FORMAT_HTML) {
3622 xml_translate(&out, "", params, format);
3629 if (format == FORMAT_XML) {
3630 ast_str_append(&out, 0, "</ajax-response>\n");
3631 } else if (format == FORMAT_HTML)
3632 ast_str_append(&out, 0, "</table></body>\r\n");
3634 ast_mutex_lock(&s->__lock);
3635 /* Reset HTTP timeout. If we're not authenticated, keep it extremely short */
3636 s->sessiontimeout = time(NULL) + ((s->authenticated || httptimeout < 5) ? httptimeout : 5);
3638 if (s->needdestroy) {
3639 if (s->inuse == 1) {
3640 ast_debug(1, "Need destroy, doing it now!\n");
3643 ast_debug(1, "Need destroy, but can't do it yet!\n");
3644 if (s->waiting_thread != AST_PTHREADT_NULL)
3645 pthread_kill(s->waiting_thread, SIGURG);
3650 ast_mutex_unlock(&s->__lock);
3654 generic_callback_out:
3656 return ast_http_error(500, "Server Error", NULL, "Internal Server Error (out of memory)\n");
3660 static struct ast_str *manager_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *params, struct ast_variable *headers, int *status, char **title, int *contentlength)
3662 return generic_http_callback(FORMAT_HTML, &ser->requestor, uri, method, params, status, title, contentlength);
3665 static struct ast_str *mxml_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *params, struct ast_variable *headers, int *status, char **title, int *contentlength)
3667 return generic_http_callback(FORMAT_XML, &ser->requestor, uri, method, params, status, title, contentlength);
3670 static struct ast_str *rawman_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *params, struct ast_variable *headers, int *status, char **title, int *contentlength)
3672 return generic_http_callback(FORMAT_RAW, &ser->requestor, uri, method, params, status, title, contentlength);
3675 struct ast_http_uri rawmanuri = {
3676 .description = "Raw HTTP Manager Event Interface",
3678 .callback = rawman_http_callback,
3684 struct ast_http_uri manageruri = {
3685 .description = "HTML Manager Event Interface",
3687 .callback = manager_http_callback,
3693 struct ast_http_uri managerxmluri = {
3694 .description = "XML Manager Event Interface",
3696 .callback = mxml_http_callback,
3702 static int registered = 0;
3703 static int webregged = 0;
3705 /*! \brief cleanup code called at each iteration of server_root,
3706 * guaranteed to happen every 5 seconds at most
3708 static void purge_old_stuff(void *data)
3714 struct ast_tls_config ami_tls_cfg;
3715 static struct server_args ami_desc = {
3717 .master = AST_PTHREADT_NULL,
3719 .poll_timeout = 5000, /* wake up every 5 seconds */
3720 .periodic_fn = purge_old_stuff,
3721 .name = "AMI server",
3722 .accept_fn = ast_tcptls_server_root, /* thread doing the accept() */
3723 .worker_fn = session_do, /* thread handling the session */
3726 static struct server_args amis_desc = {
3728 .master = AST_PTHREADT_NULL,
3729 .tls_cfg = &ami_tls_cfg,
3730 .poll_timeout = -1, /* the other does the periodic cleanup */
3731 .name = "AMI TLS server",
3732 .accept_fn = ast_tcptls_server_root, /* thread doing the accept() */
3733 .worker_fn = session_do, /* thread handling the session */
3736 static int __init_manager(int reload)
3738 struct ast_config *ucfg = NULL, *cfg = NULL;
3741 int newhttptimeout = 60;
3742 int have_sslbindaddr = 0;
3744 struct ast_hostent ahp;
3745 struct ast_manager_user *user = NULL;
3746 struct ast_variable *var;
3747 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
3749 manager_enabled = 0;