diff options
Diffstat (limited to 'main/asterisk.c')
-rw-r--r-- | main/asterisk.c | 2727 |
1 files changed, 2727 insertions, 0 deletions
diff --git a/main/asterisk.c b/main/asterisk.c new file mode 100644 index 000000000..84df3b024 --- /dev/null +++ b/main/asterisk.c @@ -0,0 +1,2727 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + + +/* Doxygenified Copyright Header */ +/*! + * \mainpage Asterisk -- An Open Source Telephony Toolkit + * + * \par Developer Documentation for Asterisk + * This is the main developer documentation for Asterisk. It is + * generated by running "make progdocs". + * \par Additional documentation + * \arg \ref DevDoc + * \arg \ref ConfigFiles + * + * \section copyright Copyright and author + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * Asterisk is a trade mark registered by Digium, Inc. + * + * \author Mark Spencer <markster@digium.com> + * Also see \ref AstCREDITS + * + * \section license License + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + * + * \verbinclude LICENSE + * + */ + +/*! \file + \brief Top level source file for Asterisk - the Open Source PBX. Implementation + of PBX core functions and CLI interface. + + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <unistd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <fcntl.h> +#include <stdio.h> +#include <signal.h> +#include <sched.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <sys/resource.h> +#include <grp.h> +#include <pwd.h> +#include <sys/stat.h> +#ifdef linux +#include <sys/prctl.h> +#endif +#include <regex.h> + +#ifdef linux +#include <sys/prctl.h> +#endif + +#if defined(__FreeBSD__) || defined( __NetBSD__ ) || defined(SOLARIS) +#include <netdb.h> +#if defined(SOLARIS) +int daemon(int, int); /* defined in libresolv of all places */ +#endif +#endif + +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/cli.h" +#include "asterisk/channel.h" +#include "asterisk/ulaw.h" +#include "asterisk/alaw.h" +#include "asterisk/callerid.h" +#include "asterisk/image.h" +#include "asterisk/tdd.h" +#include "asterisk/term.h" +#include "asterisk/manager.h" +#include "asterisk/cdr.h" +#include "asterisk/pbx.h" +#include "asterisk/enum.h" +#include "asterisk/rtp.h" +#include "asterisk/http.h" +#include "asterisk/udptl.h" +#include "asterisk/app.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/file.h" +#include "asterisk/io.h" +#include "asterisk/lock.h" +#include "editline/histedit.h" +#include "asterisk/config.h" +#include "asterisk/version.h" +#include "asterisk/linkedlists.h" +#include "asterisk/devicestate.h" + +#include "asterisk/doxyref.h" /* Doxygen documentation */ + +#include "../defaults.h" + +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#define PF_LOCAL PF_UNIX +#endif + +#define AST_MAX_CONNECTS 128 +#define NUM_MSGS 64 + +/*! \brief Welcome message when starting a CLI interface */ +#define WELCOME_MESSAGE \ + ast_verbose("Asterisk " ASTERISK_VERSION ", Copyright (C) 1999 - 2006 Digium, Inc. and others.\n"); \ + ast_verbose("Created by Mark Spencer <markster@digium.com>\n"); \ + ast_verbose("Asterisk comes with ABSOLUTELY NO WARRANTY; type 'show warranty' for details.\n"); \ + ast_verbose("This is free software, with components licensed under the GNU General Public\n"); \ + ast_verbose("License version 2 and other licenses; you are welcome to redistribute it under\n"); \ + ast_verbose("certain conditions. Type 'show license' for details.\n"); \ + ast_verbose("=========================================================================\n") + +/*! \defgroup main_options + \brief Main configuration options from \ref Config_ast "asterisk.conf" or + the operating system command line when starting Asterisk + Some of them can be changed in the CLI + */ +/*! @{ */ + +extern int ast_language_is_prefix; /* XXX move to some header */ + +struct ast_flags ast_options = { AST_DEFAULT_OPTIONS }; + +int option_verbose = 0; /*!< Verbosity level */ +int option_debug = 0; /*!< Debug level */ + +double option_maxload = 0.0; /*!< Max load avg on system */ +int option_maxcalls = 0; /*!< Max number of active calls */ + +/*! @} */ + +char record_cache_dir[AST_CACHE_DIR_LEN] = AST_TMP_DIR; +char debug_filename[AST_FILENAME_MAX] = ""; + +static int ast_socket = -1; /*!< UNIX Socket for allowing remote control */ +static int ast_consock = -1; /*!< UNIX Socket for controlling another asterisk */ +pid_t ast_mainpid; +struct console { + int fd; /*!< File descriptor */ + int p[2]; /*!< Pipe */ + pthread_t t; /*!< Thread of handler */ + int mute; /*!< Is the console muted for logs */ +}; + +struct ast_atexit { + void (*func)(void); + AST_LIST_ENTRY(ast_atexit) list; +}; + +static AST_LIST_HEAD_STATIC(atexits, ast_atexit); + +time_t ast_startuptime; +time_t ast_lastreloadtime; + +static History *el_hist = NULL; +static EditLine *el = NULL; +static char *remotehostname; + +struct console consoles[AST_MAX_CONNECTS]; + +char defaultlanguage[MAX_LANGUAGE] = DEFAULT_LANGUAGE; + +static int ast_el_add_history(char *); +static int ast_el_read_history(char *); +static int ast_el_write_history(char *); + +char ast_config_AST_CONFIG_DIR[PATH_MAX]; +char ast_config_AST_CONFIG_FILE[PATH_MAX]; +char ast_config_AST_MODULE_DIR[PATH_MAX]; +char ast_config_AST_SPOOL_DIR[PATH_MAX]; +char ast_config_AST_MONITOR_DIR[PATH_MAX]; +char ast_config_AST_VAR_DIR[PATH_MAX]; +char ast_config_AST_DATA_DIR[PATH_MAX]; +char ast_config_AST_LOG_DIR[PATH_MAX]; +char ast_config_AST_AGI_DIR[PATH_MAX]; +char ast_config_AST_DB[PATH_MAX]; +char ast_config_AST_KEY_DIR[PATH_MAX]; +char ast_config_AST_PID[PATH_MAX]; +char ast_config_AST_SOCKET[PATH_MAX]; +char ast_config_AST_RUN_DIR[PATH_MAX]; +char ast_config_AST_RUN_USER[PATH_MAX]; +char ast_config_AST_RUN_GROUP[PATH_MAX]; +char ast_config_AST_CTL_PERMISSIONS[PATH_MAX]; +char ast_config_AST_CTL_OWNER[PATH_MAX] = "\0"; +char ast_config_AST_CTL_GROUP[PATH_MAX] = "\0"; +char ast_config_AST_CTL[PATH_MAX] = "asterisk.ctl"; +char ast_config_AST_SYSTEM_NAME[20] = ""; + +extern const char *ast_build_hostname; +extern const char *ast_build_kernel; +extern const char *ast_build_machine; +extern const char *ast_build_os; +extern const char *ast_build_date; +extern const char *ast_build_user; + +static char *_argv[256]; +static int shuttingdown = 0; +static int restartnow = 0; +static pthread_t consolethread = AST_PTHREADT_NULL; + +static char randompool[256]; + +#if !defined(LOW_MEMORY) +struct file_version { + AST_LIST_ENTRY(file_version) list; + const char *file; + char *version; +}; + +static AST_LIST_HEAD_STATIC(file_versions, file_version); + +void ast_register_file_version(const char *file, const char *version) +{ + struct file_version *new; + char *work; + size_t version_length; + + work = ast_strdupa(version); + work = ast_strip(ast_strip_quoted(work, "$", "$")); + version_length = strlen(work) + 1; + + if (!(new = ast_calloc(1, sizeof(*new) + version_length))) + return; + + new->file = file; + new->version = (char *) new + sizeof(*new); + memcpy(new->version, work, version_length); + AST_LIST_LOCK(&file_versions); + AST_LIST_INSERT_HEAD(&file_versions, new, list); + AST_LIST_UNLOCK(&file_versions); +} + +void ast_unregister_file_version(const char *file) +{ + struct file_version *find; + + AST_LIST_LOCK(&file_versions); + AST_LIST_TRAVERSE_SAFE_BEGIN(&file_versions, find, list) { + if (!strcasecmp(find->file, file)) { + AST_LIST_REMOVE_CURRENT(&file_versions, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&file_versions); + if (find) + free(find); +} + +struct thread_list_t { + AST_LIST_ENTRY(thread_list_t) list; + char *name; + pthread_t id; +}; + +static AST_LIST_HEAD_STATIC(thread_list, thread_list_t); + +static char show_threads_help[] = +"Usage: show threads\n" +" List threads currently active in the system.\n"; + +void ast_register_thread(char *name) +{ + struct thread_list_t *new = ast_calloc(1, sizeof(*new)); + + if (!new) + return; + new->id = pthread_self(); + new->name = name; /* this was a copy already */ + AST_LIST_LOCK(&thread_list); + AST_LIST_INSERT_HEAD(&thread_list, new, list); + AST_LIST_UNLOCK(&thread_list); +} + +void ast_unregister_thread(void *id) +{ + struct thread_list_t *x; + + AST_LIST_LOCK(&thread_list); + AST_LIST_TRAVERSE_SAFE_BEGIN(&thread_list, x, list) { + if ((void *)x->id == id) { + AST_LIST_REMOVE_CURRENT(&thread_list, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&thread_list); + if (x) { + free(x->name); + free(x); + } +} + +static int handle_show_threads(int fd, int argc, char *argv[]) +{ + int count = 0; + struct thread_list_t *cur; + + AST_LIST_LOCK(&thread_list); + AST_LIST_TRAVERSE(&thread_list, cur, list) { + ast_cli(fd, "%p %s\n", (void *)cur->id, cur->name); + count++; + } + AST_LIST_UNLOCK(&thread_list); + ast_cli(fd, "%d threads listed.\n", count); + return 0; +} + +struct profile_entry { + const char *name; + uint64_t scale; /* if non-zero, values are scaled by this */ + int64_t mark; + int64_t value; + int64_t events; +}; + +struct profile_data { + int entries; + int max_size; + struct profile_entry e[0]; +}; + +static struct profile_data *prof_data; + +/*! \brief allocates a counter with a given name and scale. + * \return Returns the identifier of the counter. + */ +int ast_add_profile(const char *name, uint64_t scale) +{ + int l = sizeof(struct profile_data); + int n = 10; /* default entries */ + + if (prof_data == NULL) { + prof_data = ast_calloc(1, l + n*sizeof(struct profile_entry)); + if (prof_data == NULL) + return -1; + prof_data->entries = 0; + prof_data->max_size = n; + } + if (prof_data->entries >= prof_data->max_size) { + void *p; + n = prof_data->max_size + 20; + p = ast_realloc(prof_data, l + n*sizeof(struct profile_entry)); + if (p == NULL) + return -1; + prof_data = p; + prof_data->max_size = n; + } + n = prof_data->entries++; + prof_data->e[n].name = ast_strdup(name); + prof_data->e[n].value = 0; + prof_data->e[n].events = 0; + prof_data->e[n].mark = 0; + prof_data->e[n].scale = scale; + return n; +} + +int64_t ast_profile(int i, int64_t delta) +{ + if (!prof_data || i < 0 || i > prof_data->entries) /* invalid index */ + return 0; + if (prof_data->e[i].scale > 1) + delta /= prof_data->e[i].scale; + prof_data->e[i].value += delta; + prof_data->e[i].events++; + return prof_data->e[i].value; +} + +#if defined ( __i386__) && (defined(__FreeBSD__) || defined(linux)) +#if defined(__FreeBSD__) +#include <machine/cpufunc.h> +#elif defined(linux) +static __inline u_int64_t +rdtsc(void) +{ + uint64_t rv; + + __asm __volatile(".byte 0x0f, 0x31" : "=A" (rv)); + return (rv); +} +#endif +#else /* supply a dummy function on other platforms */ +static __inline u_int64_t +rdtsc(void) +{ + return 0; +} +#endif + +int64_t ast_mark(int i, int startstop) +{ + if (!prof_data || i < 0 || i > prof_data->entries) /* invalid index */ + return 0; + if (startstop == 1) + prof_data->e[i].mark = rdtsc(); + else { + prof_data->e[i].mark = (rdtsc() - prof_data->e[i].mark); + if (prof_data->e[i].scale > 1) + prof_data->e[i].mark /= prof_data->e[i].scale; + prof_data->e[i].value += prof_data->e[i].mark; + prof_data->e[i].events++; + } + return prof_data->e[i].mark; +} + +static int handle_show_profile(int fd, int argc, char *argv[]) +{ + int i, min, max; + char *search = NULL; + + if (prof_data == NULL) + return 0; + + min = 0; + max = prof_data->entries; + if (argc >= 3) { /* specific entries */ + if (isdigit(argv[2][0])) { + min = atoi(argv[2]); + if (argc == 4 && strcmp(argv[3], "-")) + max = atoi(argv[3]); + } else + search = argv[2]; + } + if (max > prof_data->entries) + max = prof_data->entries; + if (!strcmp(argv[0], "clear")) { + for (i= min; i < max; i++) { + if (!search || strstr(prof_data->e[i].name, search)) { + prof_data->e[i].value = 0; + prof_data->e[i].events = 0; + } + } + return 0; + } + ast_cli(fd, "profile values (%d, allocated %d)\n-------------------\n", + prof_data->entries, prof_data->max_size); + for (i = min; i < max; i++) { + struct profile_entry *e = &prof_data->e[i]; + if (!search || strstr(prof_data->e[i].name, search)) + ast_cli(fd, "%6d: [%8ld] %10ld %12lld %12lld %s\n", + i, + (long)e->scale, + (long)e->events, (long long)e->value, + (long long)(e->events ? e->value / e->events : e->value), + e->name); + } + return 0; +} + +static char show_version_files_help[] = +"Usage: show version files [like <pattern>]\n" +" Shows the revision numbers of the files used to build this copy of Asterisk.\n" +" Optional regular expression pattern is used to filter the file list.\n"; + +/*! \brief CLI command to list module versions */ +static int handle_show_version_files(int fd, int argc, char *argv[]) +{ +#define FORMAT "%-25.25s %-40.40s\n" + struct file_version *iterator; + regex_t regexbuf; + int havepattern = 0; + int havename = 0; + int count_files = 0; + + switch (argc) { + case 5: + if (!strcasecmp(argv[3], "like")) { + if (regcomp(®exbuf, argv[4], REG_EXTENDED | REG_NOSUB)) + return RESULT_SHOWUSAGE; + havepattern = 1; + } else + return RESULT_SHOWUSAGE; + break; + case 4: + havename = 1; + break; + case 3: + break; + default: + return RESULT_SHOWUSAGE; + } + + ast_cli(fd, FORMAT, "File", "Revision"); + ast_cli(fd, FORMAT, "----", "--------"); + AST_LIST_LOCK(&file_versions); + AST_LIST_TRAVERSE(&file_versions, iterator, list) { + if (havename && strcasecmp(iterator->file, argv[3])) + continue; + + if (havepattern && regexec(®exbuf, iterator->file, 0, NULL, 0)) + continue; + + ast_cli(fd, FORMAT, iterator->file, iterator->version); + count_files++; + if (havename) + break; + } + AST_LIST_UNLOCK(&file_versions); + if (!havename) { + ast_cli(fd, "%d files listed.\n", count_files); + } + + if (havepattern) + regfree(®exbuf); + + return RESULT_SUCCESS; +#undef FORMAT +} + +static char *complete_show_version_files(const char *line, const char *word, int pos, int state) +{ + struct file_version *find; + int which = 0; + char *ret = NULL; + int matchlen = strlen(word); + + if (pos != 3) + return NULL; + + AST_LIST_LOCK(&file_versions); + AST_LIST_TRAVERSE(&file_versions, find, list) { + if (!strncasecmp(word, find->file, matchlen) && ++which > state) { + ret = ast_strdup(find->file); + break; + } + } + AST_LIST_UNLOCK(&file_versions); + + return ret; +} +#endif /* ! LOW_MEMORY */ + +int ast_register_atexit(void (*func)(void)) +{ + int res = -1; + struct ast_atexit *ae; + ast_unregister_atexit(func); + AST_LIST_LOCK(&atexits); + if ((ae = ast_calloc(1, sizeof(*ae)))) { + AST_LIST_INSERT_HEAD(&atexits, ae, list); + ae->func = func; + res = 0; + } + AST_LIST_UNLOCK(&atexits); + return res; +} + +void ast_unregister_atexit(void (*func)(void)) +{ + struct ast_atexit *ae; + AST_LIST_LOCK(&atexits); + AST_LIST_TRAVERSE_SAFE_BEGIN(&atexits, ae, list) { + if (ae->func == func) { + AST_LIST_REMOVE_CURRENT(&atexits, list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&atexits); +} + +static int fdprint(int fd, const char *s) +{ + return write(fd, s, strlen(s) + 1); +} + +/*! \brief NULL handler so we can collect the child exit status */ +static void null_sig_handler(int signal) +{ + +} + +AST_MUTEX_DEFINE_STATIC(safe_system_lock); +/*! \brief Keep track of how many threads are currently trying to wait*() on + * a child process */ +static unsigned int safe_system_level = 0; +static void *safe_system_prev_handler; + +void ast_replace_sigchld(void) +{ + unsigned int level; + + ast_mutex_lock(&safe_system_lock); + level = safe_system_level++; + + /* only replace the handler if it has not already been done */ + if (level == 0) + safe_system_prev_handler = signal(SIGCHLD, null_sig_handler); + + ast_mutex_unlock(&safe_system_lock); +} + +void ast_unreplace_sigchld(void) +{ + unsigned int level; + + ast_mutex_lock(&safe_system_lock); + level = --safe_system_level; + + /* only restore the handler if we are the last one */ + if (level == 0) + signal(SIGCHLD, safe_system_prev_handler); + + ast_mutex_unlock(&safe_system_lock); +} + +int ast_safe_system(const char *s) +{ + pid_t pid; + int x; + int res; + struct rusage rusage; + int status; + + ast_replace_sigchld(); + + pid = fork(); + + if (pid == 0) { + if (ast_opt_high_priority) + ast_set_priority(0); + /* Close file descriptors and launch system command */ + for (x = STDERR_FILENO + 1; x < 4096; x++) + close(x); + execl("/bin/sh", "/bin/sh", "-c", s, (char *) NULL); + exit(1); + } else if (pid > 0) { + for(;;) { + res = wait4(pid, &status, 0, &rusage); + if (res > -1) { + res = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + break; + } else if (errno != EINTR) + break; + } + } else { + ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno)); + res = -1; + } + + ast_unreplace_sigchld(); + + return res; +} + +/*! + * \brief mute or unmute a console from logging + */ +void ast_console_toggle_mute(int fd) { + int x; + for (x = 0;x < AST_MAX_CONNECTS; x++) { + if (fd == consoles[x].fd) { + if (consoles[x].mute) { + consoles[x].mute = 0; + ast_cli(fd, "Console is not muted anymore.\n"); + } else { + consoles[x].mute = 1; + ast_cli(fd, "Console is muted.\n"); + } + return; + } + } + ast_cli(fd, "Couldn't find remote console.\n"); +} + +/*! + * \brief log the string to all attached console clients + */ +static void ast_network_puts_mutable(const char *string) +{ + int x; + for (x = 0;x < AST_MAX_CONNECTS; x++) { + if (consoles[x].mute) + continue; + if (consoles[x].fd > -1) + fdprint(consoles[x].p[1], string); + } +} + +/*! + * \brief log the string to the console, and all attached + * console clients + */ +void ast_console_puts_mutable(const char *string) +{ + fputs(string, stdout); + fflush(stdout); + ast_network_puts_mutable(string); +} + +/*! + * \brief write the string to all attached console clients + */ +static void ast_network_puts(const char *string) +{ + int x; + for (x=0; x < AST_MAX_CONNECTS; x++) { + if (consoles[x].fd > -1) + fdprint(consoles[x].p[1], string); + } +} + +/*! + * write the string to the console, and all attached + * console clients + */ +void ast_console_puts(const char *string) +{ + fputs(string, stdout); + fflush(stdout); + ast_network_puts(string); +} + +static void network_verboser(const char *s) +{ + ast_network_puts_mutable(s); +} + +static pthread_t lthread; + +static void *netconsole(void *vconsole) +{ + struct console *con = vconsole; + char hostname[MAXHOSTNAMELEN] = ""; + char tmp[512]; + int res; + struct pollfd fds[2]; + + if (gethostname(hostname, sizeof(hostname)-1)) + ast_copy_string(hostname, "<Unknown>", sizeof(hostname)); + snprintf(tmp, sizeof(tmp), "%s/%ld/%s\n", hostname, (long)ast_mainpid, ASTERISK_VERSION); + fdprint(con->fd, tmp); + for(;;) { + fds[0].fd = con->fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = con->p[0]; + fds[1].events = POLLIN; + fds[1].revents = 0; + + res = poll(fds, 2, -1); + if (res < 0) { + if (errno != EINTR) + ast_log(LOG_WARNING, "poll returned < 0: %s\n", strerror(errno)); + continue; + } + if (fds[0].revents) { + res = read(con->fd, tmp, sizeof(tmp)); + if (res < 1) { + break; + } + tmp[res] = 0; + ast_cli_command(con->fd, tmp); + } + if (fds[1].revents) { + res = read(con->p[0], tmp, sizeof(tmp)); + if (res < 1) { + ast_log(LOG_ERROR, "read returned %d\n", res); + break; + } + res = write(con->fd, tmp, res); + if (res < 1) + break; + } + } + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Remote UNIX connection disconnected\n"); + close(con->fd); + close(con->p[0]); + close(con->p[1]); + con->fd = -1; + + return NULL; +} + +static void *listener(void *unused) +{ + struct sockaddr_un sunaddr; + int s; + socklen_t len; + int x; + int flags; + struct pollfd fds[1]; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + for (;;) { + if (ast_socket < 0) + return NULL; + fds[0].fd = ast_socket; + fds[0].events = POLLIN; + s = poll(fds, 1, -1); + pthread_testcancel(); + if (s < 0) { + if (errno != EINTR) + ast_log(LOG_WARNING, "poll returned error: %s\n", strerror(errno)); + continue; + } + len = sizeof(sunaddr); + s = accept(ast_socket, (struct sockaddr *)&sunaddr, &len); + if (s < 0) { + if (errno != EINTR) + ast_log(LOG_WARNING, "Accept returned %d: %s\n", s, strerror(errno)); + } else { + for (x = 0; x < AST_MAX_CONNECTS; x++) { + if (consoles[x].fd < 0) { + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, consoles[x].p)) { + ast_log(LOG_ERROR, "Unable to create pipe: %s\n", strerror(errno)); + consoles[x].fd = -1; + fdprint(s, "Server failed to create pipe\n"); + close(s); + break; + } + flags = fcntl(consoles[x].p[1], F_GETFL); + fcntl(consoles[x].p[1], F_SETFL, flags | O_NONBLOCK); + consoles[x].fd = s; + consoles[x].mute = ast_opt_mute; + if (ast_pthread_create(&consoles[x].t, &attr, netconsole, &consoles[x])) { + ast_log(LOG_ERROR, "Unable to spawn thread to handle connection: %s\n", strerror(errno)); + close(consoles[x].p[0]); + close(consoles[x].p[1]); + consoles[x].fd = -1; + fdprint(s, "Server failed to spawn thread\n"); + close(s); + } + break; + } + } + if (x >= AST_MAX_CONNECTS) { + fdprint(s, "No more connections allowed\n"); + ast_log(LOG_WARNING, "No more connections allowed\n"); + close(s); + } else if (consoles[x].fd > -1) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Remote UNIX connection\n"); + } + } + } + return NULL; +} + +static int ast_makesocket(void) +{ + struct sockaddr_un sunaddr; + int res; + int x; + uid_t uid = -1; + gid_t gid = -1; + + for (x = 0; x < AST_MAX_CONNECTS; x++) + consoles[x].fd = -1; + unlink(ast_config_AST_SOCKET); + ast_socket = socket(PF_LOCAL, SOCK_STREAM, 0); + if (ast_socket < 0) { + ast_log(LOG_WARNING, "Unable to create control socket: %s\n", strerror(errno)); + return -1; + } + memset(&sunaddr, 0, sizeof(sunaddr)); + sunaddr.sun_family = AF_LOCAL; + ast_copy_string(sunaddr.sun_path, ast_config_AST_SOCKET, sizeof(sunaddr.sun_path)); + res = bind(ast_socket, (struct sockaddr *)&sunaddr, sizeof(sunaddr)); + if (res) { + ast_log(LOG_WARNING, "Unable to bind socket to %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + close(ast_socket); + ast_socket = -1; + return -1; + } + res = listen(ast_socket, 2); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to listen on socket %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + close(ast_socket); + ast_socket = -1; + return -1; + } + ast_register_verbose(network_verboser); + ast_pthread_create(<hread, NULL, listener, NULL); + + if (!ast_strlen_zero(ast_config_AST_CTL_OWNER)) { + struct passwd *pw; + if ((pw = getpwnam(ast_config_AST_CTL_OWNER)) == NULL) { + ast_log(LOG_WARNING, "Unable to find uid of user %s\n", ast_config_AST_CTL_OWNER); + } else { + uid = pw->pw_uid; + } + } + + if (!ast_strlen_zero(ast_config_AST_CTL_GROUP)) { + struct group *grp; + if ((grp = getgrnam(ast_config_AST_CTL_GROUP)) == NULL) { + ast_log(LOG_WARNING, "Unable to find gid of group %s\n", ast_config_AST_CTL_GROUP); + } else { + gid = grp->gr_gid; + } + } + + if (chown(ast_config_AST_SOCKET, uid, gid) < 0) + ast_log(LOG_WARNING, "Unable to change ownership of %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + + if (!ast_strlen_zero(ast_config_AST_CTL_PERMISSIONS)) { + int p1; + mode_t p; + sscanf(ast_config_AST_CTL_PERMISSIONS, "%o", &p1); + p = p1; + if ((chmod(ast_config_AST_SOCKET, p)) < 0) + ast_log(LOG_WARNING, "Unable to change file permissions of %s: %s\n", ast_config_AST_SOCKET, strerror(errno)); + } + + return 0; +} + +static int ast_tryconnect(void) +{ + struct sockaddr_un sunaddr; + int res; + ast_consock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (ast_consock < 0) { + ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno)); + return 0; + } + memset(&sunaddr, 0, sizeof(sunaddr)); + sunaddr.sun_family = AF_LOCAL; + ast_copy_string(sunaddr.sun_path, (char *)ast_config_AST_SOCKET, sizeof(sunaddr.sun_path)); + res = connect(ast_consock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)); + if (res) { + close(ast_consock); + ast_consock = -1; + return 0; + } else + return 1; +} + +/*! \brief Urgent handler + + Called by soft_hangup to interrupt the poll, read, or other + system call. We don't actually need to do anything though. + Remember: Cannot EVER ast_log from within a signal handler + */ +static void urg_handler(int num) +{ + signal(num, urg_handler); + return; +} + +static void hup_handler(int num) +{ + if (option_verbose > 1) + printf("Received HUP signal -- Reloading configs\n"); + if (restartnow) + execvp(_argv[0], _argv); + /* XXX This could deadlock XXX */ + ast_module_reload(NULL); + signal(num, hup_handler); +} + +static void child_handler(int sig) +{ + /* Must not ever ast_log or ast_verbose within signal handler */ + int n, status; + + /* + * Reap all dead children -- not just one + */ + for (n = 0; wait4(-1, &status, WNOHANG, NULL) > 0; n++) + ; + if (n == 0 && option_debug) + printf("Huh? Child handler, but nobody there?\n"); + signal(sig, child_handler); +} + +/*! \brief Set an X-term or screen title */ +static void set_title(char *text) +{ + if (getenv("TERM") && strstr(getenv("TERM"), "xterm")) + fprintf(stdout, "\033]2;%s\007", text); +} + +static void set_icon(char *text) +{ + if (getenv("TERM") && strstr(getenv("TERM"), "xterm")) + fprintf(stdout, "\033]1;%s\007", text); +} + +/*! \brief We set ourselves to a high priority, that we might pre-empt everything + else. If your PBX has heavy activity on it, this is a good thing. */ +int ast_set_priority(int pri) +{ + struct sched_param sched; + memset(&sched, 0, sizeof(sched)); +#ifdef __linux__ + if (pri) { + sched.sched_priority = 10; + if (sched_setscheduler(0, SCHED_RR, &sched)) { + ast_log(LOG_WARNING, "Unable to set high priority\n"); + return -1; + } else + if (option_verbose) + ast_verbose("Set to realtime thread\n"); + } else { + sched.sched_priority = 0; + if (sched_setscheduler(0, SCHED_OTHER, &sched)) { + ast_log(LOG_WARNING, "Unable to set normal priority\n"); + return -1; + } + } +#else + if (pri) { + if (setpriority(PRIO_PROCESS, 0, -10) == -1) { + ast_log(LOG_WARNING, "Unable to set high priority\n"); + return -1; + } else + if (option_verbose) + ast_verbose("Set to high priority\n"); + } else { + if (setpriority(PRIO_PROCESS, 0, 0) == -1) { + ast_log(LOG_WARNING, "Unable to set normal priority\n"); + return -1; + } + } +#endif + return 0; +} + +static void ast_run_atexits(void) +{ + struct ast_atexit *ae; + AST_LIST_LOCK(&atexits); + AST_LIST_TRAVERSE(&atexits, ae, list) { + if (ae->func) + ae->func(); + } + AST_LIST_UNLOCK(&atexits); +} + +static void quit_handler(int num, int nice, int safeshutdown, int restart) +{ + char filename[80] = ""; + time_t s,e; + int x; + /* Try to get as many CDRs as possible submitted to the backend engines (if in batch mode) */ + ast_cdr_engine_term(); + if (safeshutdown) { + shuttingdown = 1; + if (!nice) { + /* Begin shutdown routine, hanging up active channels */ + ast_begin_shutdown(1); + if (option_verbose && ast_opt_console) + ast_verbose("Beginning asterisk %s....\n", restart ? "restart" : "shutdown"); + time(&s); + for (;;) { + time(&e); + /* Wait up to 15 seconds for all channels to go away */ + if ((e - s) > 15) + break; + if (!ast_active_channels()) + break; + if (!shuttingdown) + break; + /* Sleep 1/10 of a second */ + usleep(100000); + } + } else { + if (nice < 2) + ast_begin_shutdown(0); + if (option_verbose && ast_opt_console) + ast_verbose("Waiting for inactivity to perform %s...\n", restart ? "restart" : "halt"); + for (;;) { + if (!ast_active_channels()) + break; + if (!shuttingdown) + break; + sleep(1); + } + } + + if (!shuttingdown) { + if (option_verbose && ast_opt_console) + ast_verbose("Asterisk %s cancelled.\n", restart ? "restart" : "shutdown"); + return; + } + } + if (ast_opt_console || ast_opt_remote) { + if (getenv("HOME")) + snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); + if (!ast_strlen_zero(filename)) + ast_el_write_history(filename); + if (el != NULL) + el_end(el); + if (el_hist != NULL) + history_end(el_hist); + } + if (option_verbose) + ast_verbose("Executing last minute cleanups\n"); + ast_run_atexits(); + /* Called on exit */ + if (option_verbose && ast_opt_console) + ast_verbose("Asterisk %s ending (%d).\n", ast_active_channels() ? "uncleanly" : "cleanly", num); + else if (option_debug) + ast_log(LOG_DEBUG, "Asterisk ending (%d).\n", num); + manager_event(EVENT_FLAG_SYSTEM, "Shutdown", "Shutdown: %s\r\nRestart: %s\r\n", ast_active_channels() ? "Uncleanly" : "Cleanly", restart ? "True" : "False"); + if (ast_socket > -1) { + pthread_cancel(lthread); + close(ast_socket); + ast_socket = -1; + unlink(ast_config_AST_SOCKET); + } + if (ast_consock > -1) + close(ast_consock); + if (!ast_opt_remote) + unlink(ast_config_AST_PID); + printf(term_quit()); + if (restart) { + if (option_verbose || ast_opt_console) + ast_verbose("Preparing for Asterisk restart...\n"); + /* Mark all FD's for closing on exec */ + for (x=3; x < 32768; x++) { + fcntl(x, F_SETFD, FD_CLOEXEC); + } + if (option_verbose || ast_opt_console) + ast_verbose("Restarting Asterisk NOW...\n"); + restartnow = 1; + + /* close logger */ + close_logger(); + + /* If there is a consolethread running send it a SIGHUP + so it can execvp, otherwise we can do it ourselves */ + if ((consolethread != AST_PTHREADT_NULL) && (consolethread != pthread_self())) { + pthread_kill(consolethread, SIGHUP); + /* Give the signal handler some time to complete */ + sleep(2); + } else + execvp(_argv[0], _argv); + + } else { + /* close logger */ + close_logger(); + } + exit(0); +} + +static void __quit_handler(int num) +{ + quit_handler(num, 0, 1, 0); +} + +static const char *fix_header(char *outbuf, int maxout, const char *s, char *cmp) +{ + const char *c; + if (!strncmp(s, cmp, strlen(cmp))) { + c = s + strlen(cmp); + term_color(outbuf, cmp, COLOR_GRAY, 0, maxout); + return c; + } + return NULL; +} + +static void console_verboser(const char *s) +{ + char tmp[80]; + const char *c = NULL; + + if ((c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_4)) || + (c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_3)) || + (c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_2)) || + (c = fix_header(tmp, sizeof(tmp), s, VERBOSE_PREFIX_1))) { + fputs(tmp, stdout); + fputs(c, stdout); + } else + fputs(s, stdout); + + fflush(stdout); + + /* Wake up a poll()ing console */ + if (ast_opt_console && consolethread != AST_PTHREADT_NULL) + pthread_kill(consolethread, SIGURG); +} + +static int ast_all_zeros(char *s) +{ + while (*s) { + if (*s > 32) + return 0; + s++; + } + return 1; +} + +static void consolehandler(char *s) +{ + printf(term_end()); + fflush(stdout); + + /* Called when readline data is available */ + if (!ast_all_zeros(s)) + ast_el_add_history(s); + /* The real handler for bang */ + if (s[0] == '!') { + if (s[1]) + ast_safe_system(s+1); + else + ast_safe_system(getenv("SHELL") ? getenv("SHELL") : "/bin/sh"); + } else + ast_cli_command(STDOUT_FILENO, s); +} + +static int remoteconsolehandler(char *s) +{ + int ret = 0; + + /* Called when readline data is available */ + if (!ast_all_zeros(s)) + ast_el_add_history(s); + /* The real handler for bang */ + if (s[0] == '!') { + if (s[1]) + ast_safe_system(s+1); + else + ast_safe_system(getenv("SHELL") ? getenv("SHELL") : "/bin/sh"); + ret = 1; + } + if ((strncasecmp(s, "quit", 4) == 0 || strncasecmp(s, "exit", 4) == 0) && + (s[4] == '\0' || isspace(s[4]))) { + quit_handler(0, 0, 0, 0); + ret = 1; + } + + return ret; +} + +static char abort_halt_help[] = +"Usage: abort shutdown\n" +" Causes Asterisk to abort an executing shutdown or restart, and resume normal\n" +" call operations.\n"; + +static char shutdown_now_help[] = +"Usage: stop now\n" +" Shuts down a running Asterisk immediately, hanging up all active calls .\n"; + +static char shutdown_gracefully_help[] = +"Usage: stop gracefully\n" +" Causes Asterisk to not accept new calls, and exit when all\n" +" active calls have terminated normally.\n"; + +static char shutdown_when_convenient_help[] = +"Usage: stop when convenient\n" +" Causes Asterisk to perform a shutdown when all active calls have ended.\n"; + +static char restart_now_help[] = +"Usage: restart now\n" +" Causes Asterisk to hangup all calls and exec() itself performing a cold\n" +" restart.\n"; + +static char restart_gracefully_help[] = +"Usage: restart gracefully\n" +" Causes Asterisk to stop accepting new calls and exec() itself performing a cold\n" +" restart when all active calls have ended.\n"; + +static char restart_when_convenient_help[] = +"Usage: restart when convenient\n" +" Causes Asterisk to perform a cold restart when all active calls have ended.\n"; + +static char bang_help[] = +"Usage: !<command>\n" +" Executes a given shell command\n"; + +static char show_warranty_help[] = +"Usage: show warranty\n" +" Shows the warranty (if any) for this copy of Asterisk.\n"; + +static char show_license_help[] = +"Usage: show license\n" +" Shows the license(s) for this copy of Asterisk.\n"; + +static char version_help[] = +"Usage: show version\n" +" Shows Asterisk version information.\n"; + +static int handle_version(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + ast_cli(fd, "Asterisk %s built by %s @ %s on a %s running %s on %s\n", + ASTERISK_VERSION, ast_build_user, ast_build_hostname, + ast_build_machine, ast_build_os, ast_build_date); + return RESULT_SUCCESS; +} + +#if 0 +static int handle_quit(int fd, int argc, char *argv[]) +{ + if (argc != 1) + return RESULT_SHOWUSAGE; + quit_handler(0, 0, 1, 0); + return RESULT_SUCCESS; +} +#endif + +static int handle_shutdown_now(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 0 /* Not nice */, 1 /* safely */, 0 /* not restart */); + return RESULT_SUCCESS; +} + +static int handle_shutdown_gracefully(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 1 /* nicely */, 1 /* safely */, 0 /* no restart */); + return RESULT_SUCCESS; +} + +static int handle_shutdown_when_convenient(int fd, int argc, char *argv[]) +{ + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_cli(fd, "Waiting for inactivity to perform halt\n"); + quit_handler(0, 2 /* really nicely */, 1 /* safely */, 0 /* don't restart */); + return RESULT_SUCCESS; +} + +static int handle_restart_now(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 0 /* not nicely */, 1 /* safely */, 1 /* restart */); + return RESULT_SUCCESS; +} + +static int handle_restart_gracefully(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + quit_handler(0, 1 /* nicely */, 1 /* safely */, 1 /* restart */); + return RESULT_SUCCESS; +} + +static int handle_restart_when_convenient(int fd, int argc, char *argv[]) +{ + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_cli(fd, "Waiting for inactivity to perform restart\n"); + quit_handler(0, 2 /* really nicely */, 1 /* safely */, 1 /* restart */); + return RESULT_SUCCESS; +} + +static int handle_abort_halt(int fd, int argc, char *argv[]) +{ + if (argc != 2) + return RESULT_SHOWUSAGE; + ast_cancel_shutdown(); + shuttingdown = 0; + return RESULT_SUCCESS; +} + +static int handle_bang(int fd, int argc, char *argv[]) +{ + return RESULT_SUCCESS; +} +static const char *warranty_lines[] = { + "\n", + " NO WARRANTY\n", + "\n", + "BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\n", + "FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN\n", + "OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\n", + "PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\n", + "OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n", + "MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS\n", + "TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE\n", + "PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\n", + "REPAIR OR CORRECTION.\n", + "\n", + "IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n", + "WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\n", + "REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\n", + "INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\n", + "OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\n", + "TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\n", + "YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n", + "PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n", + "POSSIBILITY OF SUCH DAMAGES.\n", +}; + +static int show_warranty(int fd, int argc, char *argv[]) +{ + int x; + + for (x = 0; x < sizeof(warranty_lines) / sizeof(warranty_lines[0]); x++) + ast_cli(fd, (char *) warranty_lines[x]); + + return RESULT_SUCCESS; +} + +static const char *license_lines[] = { + "\n", + "This program is free software; you can redistribute it and/or modify\n", + "it under the terms of the GNU General Public License version 2 as\n", + "published by the Free Software Foundation.\n", + "\n", + "This program also contains components licensed under other licenses.\n", + "They include:\n", + "\n", + "This program is distributed in the hope that it will be useful,\n", + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n", + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n", + "GNU General Public License for more details.\n", + "\n", + "You should have received a copy of the GNU General Public License\n", + "along with this program; if not, write to the Free Software\n", + "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n", +}; + +static int show_license(int fd, int argc, char *argv[]) +{ + int x; + + for (x = 0; x < sizeof(license_lines) / sizeof(license_lines[0]); x++) + ast_cli(fd, (char *) license_lines[x]); + + return RESULT_SUCCESS; +} + +#define ASTERISK_PROMPT "*CLI> " + +#define ASTERISK_PROMPT2 "%s*CLI> " + +static struct ast_cli_entry core_cli[] = { + { { "abort", "halt", NULL }, handle_abort_halt, + "Cancel a running halt", abort_halt_help }, + { { "stop", "now", NULL }, handle_shutdown_now, + "Shut down Asterisk immediately", shutdown_now_help }, + { { "stop", "gracefully", NULL }, handle_shutdown_gracefully, + "Gracefully shut down Asterisk", shutdown_gracefully_help }, + { { "stop", "when","convenient", NULL }, handle_shutdown_when_convenient, + "Shut down Asterisk at empty call volume", shutdown_when_convenient_help }, + { { "restart", "now", NULL }, handle_restart_now, + "Restart Asterisk immediately", restart_now_help }, + { { "restart", "gracefully", NULL }, handle_restart_gracefully, + "Restart Asterisk gracefully", restart_gracefully_help }, + { { "restart", "when", "convenient", NULL }, handle_restart_when_convenient, + "Restart Asterisk at empty call volume", restart_when_convenient_help }, + { { "show", "warranty", NULL }, show_warranty, + "Show the warranty (if any) for this copy of Asterisk", show_warranty_help }, + { { "show", "license", NULL }, show_license, + "Show the license(s) for this copy of Asterisk", show_license_help }, + { { "show", "version", NULL }, handle_version, + "Display version info", version_help }, + { { "!", NULL }, handle_bang, + "Execute a shell command", bang_help }, +#if !defined(LOW_MEMORY) + { { "show", "version", "files", NULL }, handle_show_version_files, + "Show versions of files used to build Asterisk", show_version_files_help, complete_show_version_files }, + { { "show", "threads", NULL }, handle_show_threads, + "Show running threads", show_threads_help, NULL }, + { { "show", "profile", NULL }, handle_show_profile, + "Show profiling info"}, + { { "clear", "profile", NULL }, handle_show_profile, + "Clear profiling info"}, +#endif /* ! LOW_MEMORY */ +}; + +static int ast_el_read_char(EditLine *el, char *cp) +{ + int num_read = 0; + int lastpos = 0; + struct pollfd fds[2]; + int res; + int max; + char buf[512]; + + for (;;) { + max = 1; + fds[0].fd = ast_consock; + fds[0].events = POLLIN; + if (!ast_opt_exec) { + fds[1].fd = STDIN_FILENO; + fds[1].events = POLLIN; + max++; + } + res = poll(fds, max, -1); + if (res < 0) { + if (errno == EINTR) + continue; + ast_log(LOG_ERROR, "poll failed: %s\n", strerror(errno)); + break; + } + + if (!ast_opt_exec && fds[1].revents) { + num_read = read(STDIN_FILENO, cp, 1); + if (num_read < 1) { + break; + } else + return (num_read); + } + if (fds[0].revents) { + res = read(ast_consock, buf, sizeof(buf) - 1); + /* if the remote side disappears exit */ + if (res < 1) { + fprintf(stderr, "\nDisconnected from Asterisk server\n"); + if (!ast_opt_reconnect) { + quit_handler(0, 0, 0, 0); + } else { + int tries; + int reconnects_per_second = 20; + fprintf(stderr, "Attempting to reconnect for 30 seconds\n"); + for (tries=0; tries < 30 * reconnects_per_second; tries++) { + if (ast_tryconnect()) { + fprintf(stderr, "Reconnect succeeded after %.3f seconds\n", 1.0 / reconnects_per_second * tries); + printf(term_quit()); + WELCOME_MESSAGE; + break; + } else { + usleep(1000000 / reconnects_per_second); + } + } + if (tries >= 30 * reconnects_per_second) { + fprintf(stderr, "Failed to reconnect for 30 seconds. Quitting.\n"); + quit_handler(0, 0, 0, 0); + } + } + } + + buf[res] = '\0'; + + if (!ast_opt_exec && !lastpos) + write(STDOUT_FILENO, "\r", 1); + write(STDOUT_FILENO, buf, res); + if ((buf[res-1] == '\n') || (buf[res-2] == '\n')) { + *cp = CC_REFRESH; + return(1); + } else { + lastpos = 1; + } + } + } + + *cp = '\0'; + return (0); +} + +static char *cli_prompt(EditLine *el) +{ + static char prompt[200]; + char *pfmt; + int color_used = 0; + char term_code[20]; + + if ((pfmt = getenv("ASTERISK_PROMPT"))) { + char *t = pfmt, *p = prompt; + memset(prompt, 0, sizeof(prompt)); + while (*t != '\0' && *p < sizeof(prompt)) { + if (*t == '%') { + char hostname[MAXHOSTNAMELEN]=""; + int i; + time_t ts; + struct tm tm; +#ifdef linux + FILE *LOADAVG; +#endif + int fgcolor = COLOR_WHITE, bgcolor = COLOR_BLACK; + + t++; + switch (*t) { + case 'C': /* color */ + t++; + if (sscanf(t, "%d;%d%n", &fgcolor, &bgcolor, &i) == 2) { + strncat(p, term_color_code(term_code, fgcolor, bgcolor, sizeof(term_code)),sizeof(prompt) - strlen(prompt) - 1); + t += i - 1; + } else if (sscanf(t, "%d%n", &fgcolor, &i) == 1) { + strncat(p, term_color_code(term_code, fgcolor, 0, sizeof(term_code)),sizeof(prompt) - strlen(prompt) - 1); + t += i - 1; + } + + /* If the color has been reset correctly, then there's no need to reset it later */ + if ((fgcolor == COLOR_WHITE) && (bgcolor == COLOR_BLACK)) { + color_used = 0; + } else { + color_used = 1; + } + break; + case 'd': /* date */ + memset(&tm, 0, sizeof(tm)); + time(&ts); + if (localtime_r(&ts, &tm)) { + strftime(p, sizeof(prompt) - strlen(prompt), "%Y-%m-%d", &tm); + } + break; + case 'h': /* hostname */ + if (!gethostname(hostname, sizeof(hostname) - 1)) { + strncat(p, hostname, sizeof(prompt) - strlen(prompt) - 1); + } else { + strncat(p, "localhost", sizeof(prompt) - strlen(prompt) - 1); + } + break; + case 'H': /* short hostname */ + if (!gethostname(hostname, sizeof(hostname) - 1)) { + for (i = 0; i < sizeof(hostname); i++) { + if (hostname[i] == '.') { + hostname[i] = '\0'; + break; + } + } + strncat(p, hostname, sizeof(prompt) - strlen(prompt) - 1); + } else { + strncat(p, "localhost", sizeof(prompt) - strlen(prompt) - 1); + } + break; +#ifdef linux + case 'l': /* load avg */ + t++; + if ((LOADAVG = fopen("/proc/loadavg", "r"))) { + float avg1, avg2, avg3; + int actproc, totproc, npid, which; + fscanf(LOADAVG, "%f %f %f %d/%d %d", + &avg1, &avg2, &avg3, &actproc, &totproc, &npid); + if (sscanf(t, "%d", &which) == 1) { + switch (which) { + case 1: + snprintf(p, sizeof(prompt) - strlen(prompt), "%.2f", avg1); + break; + case 2: + snprintf(p, sizeof(prompt) - strlen(prompt), "%.2f", avg2); + break; + case 3: + snprintf(p, sizeof(prompt) - strlen(prompt), "%.2f", avg3); + break; + case 4: + snprintf(p, sizeof(prompt) - strlen(prompt), "%d/%d", actproc, totproc); + break; + case 5: + snprintf(p, sizeof(prompt) - strlen(prompt), "%d", npid); + break; + } + } + } + break; +#endif + case 's': /* Asterisk system name (from asterisk.conf) */ + strncat(p, ast_config_AST_SYSTEM_NAME, sizeof(prompt) - strlen(prompt) - 1); + break; + case 't': /* time */ + memset(&tm, 0, sizeof(tm)); + time(&ts); + if (localtime_r(&ts, &tm)) { + strftime(p, sizeof(prompt) - strlen(prompt), "%H:%M:%S", &tm); + } + break; + case '#': /* process console or remote? */ + if (!ast_opt_remote) { + strncat(p, "#", sizeof(prompt) - strlen(prompt) - 1); + } else { + strncat(p, ">", sizeof(prompt) - strlen(prompt) - 1); + } + break; + case '%': /* literal % */ + strncat(p, "%", sizeof(prompt) - strlen(prompt) - 1); + break; + case '\0': /* % is last character - prevent bug */ + t--; + break; + } + while (*p != '\0') { + p++; + } + t++; + } else { + *p = *t; + p++; + t++; + } + } + if (color_used) { + /* Force colors back to normal at end */ + term_color_code(term_code, COLOR_WHITE, COLOR_BLACK, sizeof(term_code)); + if (strlen(term_code) > sizeof(prompt) - strlen(prompt)) { + strncat(prompt + sizeof(prompt) - strlen(term_code) - 1, term_code, strlen(term_code)); + } else { + strncat(p, term_code, sizeof(term_code)); + } + } + } else if (remotehostname) + snprintf(prompt, sizeof(prompt), ASTERISK_PROMPT2, remotehostname); + else + snprintf(prompt, sizeof(prompt), ASTERISK_PROMPT); + + return(prompt); +} + +static char **ast_el_strtoarr(char *buf) +{ + char **match_list = NULL, *retstr; + size_t match_list_len; + int matches = 0; + + match_list_len = 1; + while ( (retstr = strsep(&buf, " ")) != NULL) { + + if (!strcmp(retstr, AST_CLI_COMPLETE_EOF)) + break; + if (matches + 1 >= match_list_len) { + match_list_len <<= 1; + if (!(match_list = ast_realloc(match_list, match_list_len * sizeof(char *)))) { + /* TODO: Handle memory allocation failure */ + } + } + + match_list[matches++] = strdup(retstr); + } + + if (!match_list) + return (char **) NULL; + + if (matches >= match_list_len) { + if (!(match_list = ast_realloc(match_list, (match_list_len + 1) * sizeof(char *)))) { + /* TODO: Handle memory allocation failure */ + } + } + + match_list[matches] = (char *) NULL; + + return match_list; +} + +static int ast_el_sort_compare(const void *i1, const void *i2) +{ + char *s1, *s2; + + s1 = ((char **)i1)[0]; + s2 = ((char **)i2)[0]; + + return strcasecmp(s1, s2); +} + +static int ast_cli_display_match_list(char **matches, int len, int max) +{ + int i, idx, limit, count; + int screenwidth = 0; + int numoutput = 0, numoutputline = 0; + + screenwidth = ast_get_termcols(STDOUT_FILENO); + + /* find out how many entries can be put on one line, with two spaces between strings */ + limit = screenwidth / (max + 2); + if (limit == 0) + limit = 1; + + /* how many lines of output */ + count = len / limit; + if (count * limit < len) + count++; + + idx = 1; + + qsort(&matches[0], (size_t)(len), sizeof(char *), ast_el_sort_compare); + + for (; count > 0; count--) { + numoutputline = 0; + for (i=0; i < limit && matches[idx]; i++, idx++) { + + /* Don't print dupes */ + if ( (matches[idx+1] != NULL && strcmp(matches[idx], matches[idx+1]) == 0 ) ) { + i--; + free(matches[idx]); + matches[idx] = NULL; + continue; + } + + numoutput++; + numoutputline++; + fprintf(stdout, "%-*s ", max, matches[idx]); + free(matches[idx]); + matches[idx] = NULL; + } + if (numoutputline > 0) + fprintf(stdout, "\n"); + } + + return numoutput; +} + + +static char *cli_complete(EditLine *el, int ch) +{ + int len = 0; + char *ptr; + int nummatches = 0; + char **matches; + int retval = CC_ERROR; + char buf[2048]; + int res; + + LineInfo *lf = (LineInfo *)el_line(el); + + *(char *)lf->cursor = '\0'; + ptr = (char *)lf->cursor; + if (ptr) { + while (ptr > lf->buffer) { + if (isspace(*ptr)) { + ptr++; + break; + } + ptr--; + } + } + + len = lf->cursor - ptr; + + if (ast_opt_remote) { + snprintf(buf, sizeof(buf),"_COMMAND NUMMATCHES \"%s\" \"%s\"", lf->buffer, ptr); + fdprint(ast_consock, buf); + res = read(ast_consock, buf, sizeof(buf)); + buf[res] = '\0'; + nummatches = atoi(buf); + + if (nummatches > 0) { + char *mbuf; + int mlen = 0, maxmbuf = 2048; + /* Start with a 2048 byte buffer */ + if (!(mbuf = ast_malloc(maxmbuf))) + return (char *)(CC_ERROR); + snprintf(buf, sizeof(buf),"_COMMAND MATCHESARRAY \"%s\" \"%s\"", lf->buffer, ptr); + fdprint(ast_consock, buf); + res = 0; + mbuf[0] = '\0'; + while (!strstr(mbuf, AST_CLI_COMPLETE_EOF) && res != -1) { + if (mlen + 1024 > maxmbuf) { + /* Every step increment buffer 1024 bytes */ + maxmbuf += 1024; + if (!(mbuf = ast_realloc(mbuf, maxmbuf))) + return (char *)(CC_ERROR); + } + /* Only read 1024 bytes at a time */ + res = read(ast_consock, mbuf + mlen, 1024); + if (res > 0) + mlen += res; + } + mbuf[mlen] = '\0'; + + matches = ast_el_strtoarr(mbuf); + free(mbuf); + } else + matches = (char **) NULL; + } else { + char **p, *oldbuf=NULL; + nummatches = 0; + matches = ast_cli_completion_matches((char *)lf->buffer,ptr); + for (p = matches; p && *p; p++) { + if (!oldbuf || strcmp(*p,oldbuf)) + nummatches++; + oldbuf = *p; + } + } + + if (matches) { + int i; + int matches_num, maxlen, match_len; + + if (matches[0][0] != '\0') { + el_deletestr(el, (int) len); + el_insertstr(el, matches[0]); + retval = CC_REFRESH; + } + + if (nummatches == 1) { + /* Found an exact match */ + el_insertstr(el, " "); + retval = CC_REFRESH; + } else { + /* Must be more than one match */ + for (i=1, maxlen=0; matches[i]; i++) { + match_len = strlen(matches[i]); + if (match_len > maxlen) + maxlen = match_len; + } + matches_num = i - 1; + if (matches_num >1) { + fprintf(stdout, "\n"); + ast_cli_display_match_list(matches, nummatches, maxlen); + retval = CC_REDISPLAY; + } else { + el_insertstr(el," "); + retval = CC_REFRESH; + } + } + free(matches); + } + + return (char *)(long)retval; +} + +static int ast_el_initialize(void) +{ + HistEvent ev; + char *editor = getenv("AST_EDITOR"); + + if (el != NULL) + el_end(el); + if (el_hist != NULL) + history_end(el_hist); + + el = el_init("asterisk", stdin, stdout, stderr); + el_set(el, EL_PROMPT, cli_prompt); + + el_set(el, EL_EDITMODE, 1); + el_set(el, EL_EDITOR, editor ? editor : "emacs"); + el_hist = history_init(); + if (!el || !el_hist) + return -1; + + /* setup history with 100 entries */ + history(el_hist, &ev, H_SETSIZE, 100); + + el_set(el, EL_HIST, history, el_hist); + + el_set(el, EL_ADDFN, "ed-complete", "Complete argument", cli_complete); + /* Bind <tab> to command completion */ + el_set(el, EL_BIND, "^I", "ed-complete", NULL); + /* Bind ? to command completion */ + el_set(el, EL_BIND, "?", "ed-complete", NULL); + /* Bind ^D to redisplay */ + el_set(el, EL_BIND, "^D", "ed-redisplay", NULL); + + return 0; +} + +static int ast_el_add_history(char *buf) +{ + HistEvent ev; + + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + if (strlen(buf) > 256) + return 0; + return (history(el_hist, &ev, H_ENTER, buf)); +} + +static int ast_el_write_history(char *filename) +{ + HistEvent ev; + + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + return (history(el_hist, &ev, H_SAVE, filename)); +} + +static int ast_el_read_history(char *filename) +{ + char buf[256]; + FILE *f; + int ret = -1; + + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + if ((f = fopen(filename, "r")) == NULL) + return ret; + + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if (!strcmp(buf, "_HiStOrY_V2_\n")) + continue; + if (ast_all_zeros(buf)) + continue; + if ((ret = ast_el_add_history(buf)) == -1) + break; + } + fclose(f); + + return ret; +} + +static void ast_remotecontrol(char * data) +{ + char buf[80]; + int res; + char filename[80] = ""; + char *hostname; + char *cpid; + char *version; + int pid; + char tmp[80]; + char *stringp = NULL; + + char *ebuf; + int num = 0; + + read(ast_consock, buf, sizeof(buf)); + if (data) + write(ast_consock, data, strlen(data) + 1); + stringp = buf; + hostname = strsep(&stringp, "/"); + cpid = strsep(&stringp, "/"); + version = strsep(&stringp, "\n"); + if (!version) + version = "<Version Unknown>"; + stringp = hostname; + strsep(&stringp, "."); + if (cpid) + pid = atoi(cpid); + else + pid = -1; + snprintf(tmp, sizeof(tmp), "set verbose atleast %d", option_verbose); + fdprint(ast_consock, tmp); + snprintf(tmp, sizeof(tmp), "set debug atleast %d", option_debug); + fdprint(ast_consock, tmp); + if (ast_opt_mute) { + snprintf(tmp, sizeof(tmp), "log and verbose output currently muted ('logger unmute' to unmute)"); + fdprint(ast_consock, tmp); + } + ast_verbose("Connected to Asterisk %s currently running on %s (pid = %d)\n", version, hostname, pid); + remotehostname = hostname; + if (getenv("HOME")) + snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + el_set(el, EL_GETCFN, ast_el_read_char); + + if (!ast_strlen_zero(filename)) + ast_el_read_history(filename); + + if (ast_opt_exec && data) { /* hack to print output then exit if asterisk -rx is used */ + char tempchar; + struct pollfd fds; + fds.fd = ast_consock; + fds.events = POLLIN; + fds.revents = 0; + while (poll(&fds, 1, 100) > 0) + ast_el_read_char(el, &tempchar); + return; + } + for (;;) { + ebuf = (char *)el_gets(el, &num); + + if (!ast_strlen_zero(ebuf)) { + if (ebuf[strlen(ebuf)-1] == '\n') + ebuf[strlen(ebuf)-1] = '\0'; + if (!remoteconsolehandler(ebuf)) { + res = write(ast_consock, ebuf, strlen(ebuf) + 1); + if (res < 1) { + ast_log(LOG_WARNING, "Unable to write: %s\n", strerror(errno)); + break; + } + } + } + } + printf("\nDisconnected from Asterisk server\n"); +} + +static int show_version(void) +{ + printf("Asterisk " ASTERISK_VERSION "\n"); + return 0; +} + +static int show_cli_help(void) { + printf("Asterisk " ASTERISK_VERSION ", Copyright (C) 1999 - 2006, Digium, Inc. and others.\n"); + printf("Usage: asterisk [OPTIONS]\n"); + printf("Valid Options:\n"); + printf(" -V Display version number and exit\n"); + printf(" -C <configfile> Use an alternate configuration file\n"); + printf(" -G <group> Run as a group other than the caller\n"); + printf(" -U <user> Run as a user other than the caller\n"); + printf(" -c Provide console CLI\n"); + printf(" -d Enable extra debugging\n"); + printf(" -f Do not fork\n"); + printf(" -g Dump core in case of a crash\n"); + printf(" -h This help screen\n"); + printf(" -i Initialize crypto keys at startup\n"); + printf(" -I Enable internal timing if Zaptel timer is available\n"); + printf(" -L <load> Limit the maximum load average before rejecting new calls\n"); + printf(" -M <value> Limit the maximum number of calls to the specified value\n"); + printf(" -m Mute the console from debugging and verbose output\n"); + printf(" -n Disable console colorization\n"); + printf(" -p Run as pseudo-realtime thread\n"); + printf(" -q Quiet mode (suppress output)\n"); + printf(" -r Connect to Asterisk on this machine\n"); + printf(" -R Connect to Asterisk, and attempt to reconnect if disconnected\n"); + printf(" -t Record soundfiles in /var/tmp and move them where they belong after they are done.\n"); + printf(" -T Display the time in [Mmm dd hh:mm:ss] format for each line of output to the CLI.\n"); + printf(" -v Increase verbosity (multiple v's = more verbose)\n"); + printf(" -x <cmd> Execute command <cmd> (only valid with -r)\n"); + printf("\n"); + return 0; +} + +static void ast_readconfig(void) +{ + struct ast_config *cfg; + struct ast_variable *v; + char *config = AST_CONFIG_FILE; + + if (ast_opt_override_config) { + cfg = ast_config_load(ast_config_AST_CONFIG_FILE); + if (!cfg) + ast_log(LOG_WARNING, "Unable to open specified master config file '%s', using built-in defaults\n", ast_config_AST_CONFIG_FILE); + } else { + cfg = ast_config_load(config); + } + + /* init with buildtime config */ + ast_copy_string(ast_config_AST_CONFIG_DIR, AST_CONFIG_DIR, sizeof(ast_config_AST_CONFIG_DIR)); + ast_copy_string(ast_config_AST_SPOOL_DIR, AST_SPOOL_DIR, sizeof(ast_config_AST_SPOOL_DIR)); + ast_copy_string(ast_config_AST_MODULE_DIR, AST_MODULE_DIR, sizeof(ast_config_AST_MODULE_DIR)); + snprintf(ast_config_AST_MONITOR_DIR, sizeof(ast_config_AST_MONITOR_DIR) - 1, "%s/monitor", ast_config_AST_SPOOL_DIR); + ast_copy_string(ast_config_AST_VAR_DIR, AST_VAR_DIR, sizeof(ast_config_AST_VAR_DIR)); + ast_copy_string(ast_config_AST_DATA_DIR, AST_DATA_DIR, sizeof(ast_config_AST_DATA_DIR)); + ast_copy_string(ast_config_AST_LOG_DIR, AST_LOG_DIR, sizeof(ast_config_AST_LOG_DIR)); + ast_copy_string(ast_config_AST_AGI_DIR, AST_AGI_DIR, sizeof(ast_config_AST_AGI_DIR)); + ast_copy_string(ast_config_AST_DB, AST_DB, sizeof(ast_config_AST_DB)); + ast_copy_string(ast_config_AST_KEY_DIR, AST_KEY_DIR, sizeof(ast_config_AST_KEY_DIR)); + ast_copy_string(ast_config_AST_PID, AST_PID, sizeof(ast_config_AST_PID)); + ast_copy_string(ast_config_AST_SOCKET, AST_SOCKET, sizeof(ast_config_AST_SOCKET)); + ast_copy_string(ast_config_AST_RUN_DIR, AST_RUN_DIR, sizeof(ast_config_AST_RUN_DIR)); + + /* no asterisk.conf? no problem, use buildtime config! */ + if (!cfg) { + return; + } + + for (v = ast_variable_browse(cfg, "files"); v; v = v->next) { + if (!strcasecmp(v->name, "astctlpermissions")) { + ast_copy_string(ast_config_AST_CTL_PERMISSIONS, v->value, sizeof(ast_config_AST_CTL_PERMISSIONS)); + } else if (!strcasecmp(v->name, "astctlowner")) { + ast_copy_string(ast_config_AST_CTL_OWNER, v->value, sizeof(ast_config_AST_CTL_OWNER)); + } else if (!strcasecmp(v->name, "astctlgroup")) { + ast_copy_string(ast_config_AST_CTL_GROUP, v->value, sizeof(ast_config_AST_CTL_GROUP)); + } else if (!strcasecmp(v->name, "astctl")) { + ast_copy_string(ast_config_AST_CTL, v->value, sizeof(ast_config_AST_CTL)); + } + } + + for (v = ast_variable_browse(cfg, "directories"); v; v = v->next) { + if (!strcasecmp(v->name, "astetcdir")) { + ast_copy_string(ast_config_AST_CONFIG_DIR, v->value, sizeof(ast_config_AST_CONFIG_DIR)); + } else if (!strcasecmp(v->name, "astspooldir")) { + ast_copy_string(ast_config_AST_SPOOL_DIR, v->value, sizeof(ast_config_AST_SPOOL_DIR)); + snprintf(ast_config_AST_MONITOR_DIR, sizeof(ast_config_AST_MONITOR_DIR) - 1, "%s/monitor", v->value); + } else if (!strcasecmp(v->name, "astvarlibdir")) { + ast_copy_string(ast_config_AST_VAR_DIR, v->value, sizeof(ast_config_AST_VAR_DIR)); + snprintf(ast_config_AST_DB, sizeof(ast_config_AST_DB), "%s/astdb", v->value); + } else if (!strcasecmp(v->name, "astdatadir")) { + ast_copy_string(ast_config_AST_DATA_DIR, v->value, sizeof(ast_config_AST_DATA_DIR)); + snprintf(ast_config_AST_KEY_DIR, sizeof(ast_config_AST_KEY_DIR), "%s/keys", v->value); + } else if (!strcasecmp(v->name, "astlogdir")) { + ast_copy_string(ast_config_AST_LOG_DIR, v->value, sizeof(ast_config_AST_LOG_DIR)); + } else if (!strcasecmp(v->name, "astagidir")) { + ast_copy_string(ast_config_AST_AGI_DIR, v->value, sizeof(ast_config_AST_AGI_DIR)); + } else if (!strcasecmp(v->name, "astrundir")) { + snprintf(ast_config_AST_PID, sizeof(ast_config_AST_PID), "%s/%s", v->value, "asterisk.pid"); + snprintf(ast_config_AST_SOCKET, sizeof(ast_config_AST_SOCKET), "%s/%s", v->value, ast_config_AST_CTL); + ast_copy_string(ast_config_AST_RUN_DIR, v->value, sizeof(ast_config_AST_RUN_DIR)); + } else if (!strcasecmp(v->name, "astmoddir")) { + ast_copy_string(ast_config_AST_MODULE_DIR, v->value, sizeof(ast_config_AST_MODULE_DIR)); + } + } + + for (v = ast_variable_browse(cfg, "options"); v; v = v->next) { + /* verbose level (-v at startup) */ + if (!strcasecmp(v->name, "verbose")) { + option_verbose = atoi(v->value); + /* whether or not to force timestamping in CLI verbose output. (-T at startup) */ + } else if (!strcasecmp(v->name, "timestamp")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_TIMESTAMP); + /* whether or not to support #exec in config files */ + } else if (!strcasecmp(v->name, "execincludes")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_EXEC_INCLUDES); + /* debug level (-d at startup) */ + } else if (!strcasecmp(v->name, "debug")) { + option_debug = 0; + if (sscanf(v->value, "%d", &option_debug) != 1) { + option_debug = ast_true(v->value); + } + /* Disable forking (-f at startup) */ + } else if (!strcasecmp(v->name, "nofork")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_NO_FORK); + /* Always fork, even if verbose or debug are enabled (-F at startup) */ + } else if (!strcasecmp(v->name, "alwaysfork")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_ALWAYS_FORK); + /* Run quietly (-q at startup ) */ + } else if (!strcasecmp(v->name, "quiet")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_QUIET); + /* Run as console (-c at startup, implies nofork) */ + } else if (!strcasecmp(v->name, "console")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_CONSOLE); + /* Run with high priority if the O/S permits (-p at startup) */ + } else if (!strcasecmp(v->name, "highpriority")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_HIGH_PRIORITY); + /* Initialize RSA auth keys (IAX2) (-i at startup) */ + } else if (!strcasecmp(v->name, "initcrypto")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_INIT_KEYS); + /* Disable ANSI colors for console (-c at startup) */ + } else if (!strcasecmp(v->name, "nocolor")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_NO_COLOR); + /* Disable some usage warnings for picky people :p */ + } else if (!strcasecmp(v->name, "dontwarn")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_DONT_WARN); + /* Dump core in case of crash (-g) */ + } else if (!strcasecmp(v->name, "dumpcore")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_DUMP_CORE); + /* Cache recorded sound files to another directory during recording */ + } else if (!strcasecmp(v->name, "cache_record_files")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_CACHE_RECORD_FILES); + /* Specify cache directory */ + } else if (!strcasecmp(v->name, "record_cache_dir")) { + ast_copy_string(record_cache_dir, v->value, AST_CACHE_DIR_LEN); + /* Build transcode paths via SLINEAR, instead of directly */ + } else if (!strcasecmp(v->name, "transcode_via_sln")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_TRANSCODE_VIA_SLIN); + /* Transmit SLINEAR silence while a channel is being recorded */ + } else if (!strcasecmp(v->name, "transmit_silence_during_record")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_TRANSMIT_SILENCE); + /* Enable internal timing */ + } else if (!strcasecmp(v->name, "internal_timing")) { + ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_INTERNAL_TIMING); + } else if (!strcasecmp(v->name, "maxcalls")) { + if ((sscanf(v->value, "%d", &option_maxcalls) != 1) || (option_maxcalls < 0)) { + option_maxcalls = 0; + } + } else if (!strcasecmp(v->name, "maxload")) { + double test[1]; + + if (getloadavg(test, 1) == -1) { + ast_log(LOG_ERROR, "Cannot obtain load average on this system. 'maxload' option disabled.\n"); + option_maxload = 0.0; + } else if ((sscanf(v->value, "%lf", &option_maxload) != 1) || (option_maxload < 0.0)) { + option_maxload = 0.0; + } + /* What user to run as */ + } else if (!strcasecmp(v->name, "runuser")) { + ast_copy_string(ast_config_AST_RUN_USER, v->value, sizeof(ast_config_AST_RUN_USER)); + /* What group to run as */ + } else if (!strcasecmp(v->name, "rungroup")) { + ast_copy_string(ast_config_AST_RUN_GROUP, v->value, sizeof(ast_config_AST_RUN_GROUP)); + } else if (!strcasecmp(v->name, "systemname")) { + ast_copy_string(ast_config_AST_SYSTEM_NAME, v->value, sizeof(ast_config_AST_SYSTEM_NAME)); + } else if (!strcasecmp(v->name, "languageprefix")) { + ast_language_is_prefix = ast_true(v->value); + } + } + ast_config_destroy(cfg); +} + +int main(int argc, char *argv[]) +{ + int c; + char filename[80] = ""; + char hostname[MAXHOSTNAMELEN] = ""; + char tmp[80]; + char * xarg = NULL; + int x; + FILE *f; + sigset_t sigs; + int num; + int is_child_of_nonroot = 0; + char *buf; + char *runuser = NULL, *rungroup = NULL; + + /* Remember original args for restart */ + if (argc > sizeof(_argv) / sizeof(_argv[0]) - 1) { + fprintf(stderr, "Truncating argument size to %d\n", (int)(sizeof(_argv) / sizeof(_argv[0])) - 1); + argc = sizeof(_argv) / sizeof(_argv[0]) - 1; + } + for (x=0; x<argc; x++) + _argv[x] = argv[x]; + _argv[x] = NULL; + + /* if the progname is rasterisk consider it a remote console */ + if (argv[0] && (strstr(argv[0], "rasterisk")) != NULL) { + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE); + } + if (gethostname(hostname, sizeof(hostname)-1)) + ast_copy_string(hostname, "<Unknown>", sizeof(hostname)); + ast_mainpid = getpid(); + ast_ulaw_init(); + ast_alaw_init(); + callerid_init(); + ast_builtins_init(); + ast_utils_init(); + tdd_init(); + /* When Asterisk restarts after it has dropped the root privileges, + * it can't issue setuid(), setgid(), setgroups() or set_priority() + */ + if (getenv("ASTERISK_ALREADY_NONROOT")) + is_child_of_nonroot=1; + if (getenv("HOME")) + snprintf(filename, sizeof(filename), "%s/.asterisk_history", getenv("HOME")); + /* Check for options */ + while ((c = getopt(argc, argv, "mtThfdvVqprRgciInx:U:G:C:L:M:")) != -1) { + switch (c) { + case 'F': + ast_set_flag(&ast_options, AST_OPT_FLAG_ALWAYS_FORK); + break; + case 'd': + option_debug++; + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK); + break; + case 'c': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_CONSOLE); + break; + case 'f': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK); + break; + case 'n': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_COLOR); + break; + case 'r': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE); + break; + case 'R': + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK | AST_OPT_FLAG_REMOTE | AST_OPT_FLAG_RECONNECT); + break; + case 'p': + ast_set_flag(&ast_options, AST_OPT_FLAG_HIGH_PRIORITY); + break; + case 'v': + option_verbose++; + ast_set_flag(&ast_options, AST_OPT_FLAG_NO_FORK); + break; + case 'm': + ast_set_flag(&ast_options, AST_OPT_FLAG_MUTE); + break; + case 'M': + if ((sscanf(optarg, "%d", &option_maxcalls) != 1) || (option_maxcalls < 0)) + option_maxcalls = 0; + break; + case 'L': + if ((sscanf(optarg, "%lf", &option_maxload) != 1) || (option_maxload < 0.0)) + option_maxload = 0.0; + break; + case 'q': + ast_set_flag(&ast_options, AST_OPT_FLAG_QUIET); + break; + case 't': + ast_set_flag(&ast_options, AST_OPT_FLAG_CACHE_RECORD_FILES); + break; + case 'T': + ast_set_flag(&ast_options, AST_OPT_FLAG_TIMESTAMP); + break; + case 'x': + ast_set_flag(&ast_options, AST_OPT_FLAG_EXEC); + xarg = optarg; + break; + case 'C': + ast_copy_string(ast_config_AST_CONFIG_FILE, optarg, sizeof(ast_config_AST_CONFIG_FILE)); + ast_set_flag(&ast_options, AST_OPT_FLAG_OVERRIDE_CONFIG); + break; + case 'I': + ast_set_flag(&ast_options, AST_OPT_FLAG_INTERNAL_TIMING); + break; + case 'i': + ast_set_flag(&ast_options, AST_OPT_FLAG_INIT_KEYS); + break; + case 'g': + ast_set_flag(&ast_options, AST_OPT_FLAG_DUMP_CORE); + break; + case 'h': + show_cli_help(); + exit(0); + case 'V': + show_version(); + exit(0); + case 'U': + runuser = optarg; + break; + case 'G': + rungroup = optarg; + break; + case '?': + exit(1); + } + } + + if (ast_opt_console || option_verbose || (ast_opt_remote && !ast_opt_exec)) { + ast_register_verbose(console_verboser); + WELCOME_MESSAGE; + } + + if (ast_opt_console && !option_verbose) + ast_verbose("[ Booting...\n"); + + if (ast_opt_always_fork && (ast_opt_remote || ast_opt_console)) { + ast_log(LOG_WARNING, "'alwaysfork' is not compatible with console or remote console mode; ignored\n"); + ast_clear_flag(&ast_options, AST_OPT_FLAG_ALWAYS_FORK); + } + + /* For remote connections, change the name of the remote connection. + * We do this for the benefit of init scripts (which need to know if/when + * the main asterisk process has died yet). */ + if (ast_opt_remote) { + strcpy(argv[0], "rasterisk"); + for (x = 1; x < argc; x++) { + argv[x] = argv[0] + 10; + } + } + + if (ast_opt_console && !option_verbose) + ast_verbose("[ Reading Master Configuration ]\n"); + ast_readconfig(); + + if (ast_opt_dump_core) { + struct rlimit l; + memset(&l, 0, sizeof(l)); + l.rlim_cur = RLIM_INFINITY; + l.rlim_max = RLIM_INFINITY; + if (setrlimit(RLIMIT_CORE, &l)) { + ast_log(LOG_WARNING, "Unable to disable core size resource limit: %s\n", strerror(errno)); + } + } + + if ((!rungroup) && !ast_strlen_zero(ast_config_AST_RUN_GROUP)) + rungroup = ast_config_AST_RUN_GROUP; + if ((!runuser) && !ast_strlen_zero(ast_config_AST_RUN_USER)) + runuser = ast_config_AST_RUN_USER; + +#ifndef __CYGWIN__ + + if (!is_child_of_nonroot) + ast_set_priority(ast_opt_high_priority); + + if (!is_child_of_nonroot && rungroup) { + struct group *gr; + gr = getgrnam(rungroup); + if (!gr) { + ast_log(LOG_WARNING, "No such group '%s'!\n", rungroup); + exit(1); + } + if (setgid(gr->gr_gid)) { + ast_log(LOG_WARNING, "Unable to setgid to %d (%s)\n", (int)gr->gr_gid, rungroup); + exit(1); + } + if (setgroups(0, NULL)) { + ast_log(LOG_WARNING, "Unable to drop unneeded groups\n"); + exit(1); + } + if (option_verbose) + ast_verbose("Running as group '%s'\n", rungroup); + } + + if (!is_child_of_nonroot && runuser) { + struct passwd *pw; + pw = getpwnam(runuser); + if (!pw) { + ast_log(LOG_WARNING, "No such user '%s'!\n", runuser); + exit(1); + } + if (!rungroup) { + if (setgid(pw->pw_gid)) { + ast_log(LOG_WARNING, "Unable to setgid to %d!\n", (int)pw->pw_gid); + exit(1); + } + if (initgroups(pw->pw_name, pw->pw_gid)) { + ast_log(LOG_WARNING, "Unable to init groups for '%s'\n", runuser); + exit(1); + } + } + if (setuid(pw->pw_uid)) { + ast_log(LOG_WARNING, "Unable to setuid to %d (%s)\n", (int)pw->pw_uid, runuser); + exit(1); + } + setenv("ASTERISK_ALREADY_NONROOT", "yes", 1); + if (option_verbose) + ast_verbose("Running as user '%s'\n", runuser); + } + +#endif /* __CYGWIN__ */ + +#ifdef linux + if (geteuid() && ast_opt_dump_core) { + if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) { + ast_log(LOG_WARNING, "Unable to set the process for core dumps after changing to a non-root user. %s\n", strerror(errno)); + } + } +#endif + + term_init(); + printf(term_end()); + fflush(stdout); + + if (ast_opt_console && !option_verbose) + ast_verbose("[ Initializing Custom Configuration Options ]\n"); + /* custom config setup */ + register_config_cli(); + read_config_maps(); + + if (ast_opt_console) { + if (el_hist == NULL || el == NULL) + ast_el_initialize(); + + if (!ast_strlen_zero(filename)) + ast_el_read_history(filename); + } + + if (ast_tryconnect()) { + /* One is already running */ + if (ast_opt_remote) { + if (ast_opt_exec) { + ast_remotecontrol(xarg); + quit_handler(0, 0, 0, 0); + exit(0); + } + printf(term_quit()); + ast_remotecontrol(NULL); + quit_handler(0, 0, 0, 0); + exit(0); + } else { + ast_log(LOG_ERROR, "Asterisk already running on %s. Use 'asterisk -r' to connect.\n", ast_config_AST_SOCKET); + printf(term_quit()); + exit(1); + } + } else if (ast_opt_remote || ast_opt_exec) { + ast_log(LOG_ERROR, "Unable to connect to remote asterisk (does %s exist?)\n", ast_config_AST_SOCKET); + printf(term_quit()); + exit(1); + } + /* Blindly write pid file since we couldn't connect */ + unlink(ast_config_AST_PID); + f = fopen(ast_config_AST_PID, "w"); + if (f) { + fprintf(f, "%ld\n", (long)getpid()); + fclose(f); + } else + ast_log(LOG_WARNING, "Unable to open pid file '%s': %s\n", ast_config_AST_PID, strerror(errno)); + + if (ast_opt_always_fork || !ast_opt_no_fork) { + daemon(0, 0); + ast_mainpid = getpid(); + /* Blindly re-write pid file since we are forking */ + unlink(ast_config_AST_PID); + f = fopen(ast_config_AST_PID, "w"); + if (f) { + fprintf(f, "%ld\n", (long)ast_mainpid); + fclose(f); + } else + ast_log(LOG_WARNING, "Unable to open pid file '%s': %s\n", ast_config_AST_PID, strerror(errno)); + } + + /* Test recursive mutex locking. */ + if (test_for_thread_safety()) + ast_verbose("Warning! Asterisk is not thread safe.\n"); + + ast_makesocket(); + sigemptyset(&sigs); + sigaddset(&sigs, SIGHUP); + sigaddset(&sigs, SIGTERM); + sigaddset(&sigs, SIGINT); + sigaddset(&sigs, SIGPIPE); + sigaddset(&sigs, SIGWINCH); + pthread_sigmask(SIG_BLOCK, &sigs, NULL); + signal(SIGURG, urg_handler); + signal(SIGINT, __quit_handler); + signal(SIGTERM, __quit_handler); + signal(SIGHUP, hup_handler); + signal(SIGCHLD, child_handler); + signal(SIGPIPE, SIG_IGN); + + /* ensure that the random number generators are seeded with a different value every time + Asterisk is started + */ + srand((unsigned int) getpid() + (unsigned int) time(NULL)); + initstate((unsigned int) getpid() * 65536 + (unsigned int) time(NULL), randompool, sizeof(randompool)); + + if (init_logger()) { + printf(term_quit()); + exit(1); + } + if (load_modules()) { + printf(term_quit()); + exit(1); + } + + if (dnsmgr_init()) { + printf(term_quit()); + exit(1); + } + + ast_http_init(); + + ast_channels_init(); + + if (init_manager()) { + printf(term_quit()); + exit(1); + } + + if (ast_cdr_engine_init()) { + printf(term_quit()); + exit(1); + } + + if (ast_device_state_engine_init()) { + printf(term_quit()); + exit(1); + } + + ast_rtp_init(); + + ast_udptl_init(); + + if (ast_image_init()) { + printf(term_quit()); + exit(1); + } + + if (ast_file_init()) { + printf(term_quit()); + exit(1); + } + + if (load_pbx()) { + printf(term_quit()); + exit(1); + } + + if (init_framer()) { + printf(term_quit()); + exit(1); + } + + if (astdb_init()) { + printf(term_quit()); + exit(1); + } + + if (ast_enum_init()) { + printf(term_quit()); + exit(1); + } + + dnsmgr_start_refresh(); + + /* We might have the option of showing a console, but for now just + do nothing... */ + if (ast_opt_console && !option_verbose) + ast_verbose(" ]\n"); + if (option_verbose || ast_opt_console) + ast_verbose(term_color(tmp, "Asterisk Ready.\n", COLOR_BRWHITE, COLOR_BLACK, sizeof(tmp))); + if (ast_opt_no_fork) + consolethread = pthread_self(); + + ast_set_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED); + pthread_sigmask(SIG_UNBLOCK, &sigs, NULL); + +#ifdef __AST_DEBUG_MALLOC + __ast_mm_init(); +#endif + + time(&ast_startuptime); + ast_cli_register_multiple(core_cli, sizeof(core_cli) / sizeof(core_cli[0])); + + if (ast_opt_console) { + /* Console stuff now... */ + /* Register our quit function */ + char title[256]; + set_icon("Asterisk"); + snprintf(title, sizeof(title), "Asterisk Console on '%s' (pid %ld)", hostname, (long)ast_mainpid); + set_title(title); + + for (;;) { + buf = (char *)el_gets(el, &num); + if (buf) { + if (buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = '\0'; + + consolehandler((char *)buf); + } else { + if (write(STDOUT_FILENO, "\nUse EXIT or QUIT to exit the asterisk console\n", + strlen("\nUse EXIT or QUIT to exit the asterisk console\n")) < 0) { + /* Whoa, stdout disappeared from under us... Make /dev/null's */ + int fd; + fd = open("/dev/null", O_RDWR); + if (fd > -1) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDIN_FILENO); + } else + ast_log(LOG_WARNING, "Failed to open /dev/null to recover from dead console. Bad things will happen!\n"); + break; + } + } + } + + } + /* Do nothing */ + for(;;) { /* apparently needed for the MACos */ + struct pollfd p = { -1 /* no descriptor */, 0, 0 }; + poll(&p, 0, -1); + } + return 0; +} |