/* * app_networkclid * Network Caller ID * * Copyright (C) 2008 Thralling Penguin LLC. All rights reserved. * http://www.ThrallingPenguin.com/ * * This program may be modified and distributed under the * terms of the GNU Public License V2. * */ #include #include #include #include #include #include "asterisk.h" #include "asterisk/lock.h" #include "asterisk/file.h" #include "asterisk/logger.h" #include "asterisk/options.h" #include "asterisk/channel.h" #include "asterisk/pbx.h" #include "asterisk/cli.h" #include "asterisk/module.h" #include "asterisk/callerid.h" #define APP_NETWORKCLID_VERSION "0.1" ASTERISK_FILE_VERSION( __FILE__, APP_NETWORKCLID_VERSION ); static char *tdesc = "Network Caller ID Application (" APP_NETWORKCLID_VERSION ")"; static char *app = "NetworkCLID"; static char *synopsis = "Network Caller ID (" APP_NETWORKCLID_VERSION ")"; static char *descrip = "NetworkCLID([type]|[clidName]|[clidNumber]|[hostname[:port][,hostname[:port]]])\n" "\n" "All arguments are optional, if specified, they override any configuration\n" "which may be available. 'type' specifies the type of network clid protocol\n" "to use. 'clidName' is the caller id name. 'clidNumber' is the caller id\n" "number. 'hostname' is a multiple argument item which specifies a list\n" "of client network clid clients to notify. Each 'hostname' may have an\n" "optional port number to use, if the hostname is specified in host:port\n" "notation. If a port number is not specified, the default port for the\n" "type/protocol choosen is used.\n" "\n" "Available network caller id type/protocols:\n" "\n" " yac - Yet Another Caller ID (tcp port 10629)\n" " http://sunflowerhead.com/software/yac/\n" "\n"; AST_MUTEX_DEFINE_STATIC(conf_lock); STANDARD_LOCAL_USER; LOCAL_USER_DECL; struct nclid_user { char exten[AST_MAX_EXTENSION]; char context[AST_MAX_CONTEXT]; char type[4]; char remote_clients[128]; struct nclid_user *next; }; static struct nclid_user *users = NULL; static int app_networkclid_main(struct ast_channel *chan, void *data); static int do_lookup(const char *exten, const char *context, char **type, char **remote_clients); static int load_config(void); static int send_yac_notification(struct ast_channel *chan, const char *clid_name, const char *clid_number, const char *remote_clients); int unload_module(void) { struct nclid_user *cur, *l; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Unloading NetworkCLID module\n"); STANDARD_HANGUP_LOCALUSERS; ast_mutex_lock(&conf_lock); cur = users; while (cur) { l = cur; cur = cur->next; free(l); } users = NULL; ast_mutex_unlock(&conf_lock); return ast_unregister_application(app); } int load_module(void) { int res = 0; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Loading NetworkCLID module\n"); res = load_config(); res |= ast_register_application(app, app_networkclid_main, synopsis, descrip); return res; } int reload(void) { return load_config(); } char *description(void) { return tdesc; } int usecount(void) { int res; STANDARD_USECOUNT(res); return res; } char *key() { return ASTERISK_GPL_KEY; } int app_networkclid_main(struct ast_channel *chan, void *data) { int res = 0, need_to_free_remote_clients = 0, need_to_free_type = 0; struct localuser *u; char *info, *type = NULL, *clid_name = NULL, *clid_number = NULL, *remote_clients = NULL; LOCAL_USER_ADD(u); info = ast_strdupa(data); if (!info) { ast_log(LOG_ERROR, "Out of memory\n"); LOCAL_USER_REMOVE(u); return -1; } if ((type = strsep(&info, "|")) == NULL) { type = ""; } if ((clid_name = strsep(&info, "|")) == NULL) { clid_name = (chan->cid.cid_name ? chan->cid.cid_name : ""); } if ((clid_number = strsep(&info, "|")) == NULL) { clid_number = (chan->cid.cid_num ? chan->cid.cid_num : ""); } remote_clients = info; if (ast_strlen_zero(remote_clients)) { res = do_lookup(chan->exten, chan->context, &type, &remote_clients); if (res == -1) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Could not find any remote clients for: %s\n", chan->exten); LOCAL_USER_REMOVE(u); return 0; } need_to_free_remote_clients = 1; need_to_free_type = 1; } if (!strcasecmp(type, "yac")) { res = send_yac_notification(chan, clid_name, clid_number, remote_clients); } else { ast_log(LOG_WARNING, "Unknown remote caller id notification type: %s\n", type); } if (need_to_free_type && type) { free(type); type = NULL; } if (need_to_free_remote_clients && remote_clients) { free(remote_clients); remote_clients = NULL; } LOCAL_USER_REMOVE(u); return res; } static int do_lookup(const char *exten, const char *context, char **type, char **remote_clients) { int res = -1; struct nclid_user *user; ast_mutex_lock(&conf_lock); if (!context) context = "default"; user = users; while (user) { if ((!strcasecmp(user->context, context) || !strcasecmp(user->context, "default")) && !strcasecmp(user->exten, exten)) { res = 0; *type = calloc(1, strlen(user->type) + 1); ast_copy_string(*type, user->type, strlen(user->type) + 1); *remote_clients = calloc(1, strlen(user->remote_clients) + 1); ast_copy_string(*remote_clients, user->remote_clients, strlen(user->remote_clients) + 1); break; } user = user->next; } ast_mutex_unlock(&conf_lock); return res; } static int load_config(void) { struct ast_config *cfg; struct ast_variable *var; char *cat; char conf_file[AST_CONFIG_MAX_PATH]; struct nclid_user *cur, *l; snprintf((char *) conf_file, sizeof(conf_file) - 1, "%s/networkclid.conf", (char *) ast_config_AST_CONFIG_DIR); cfg = ast_config_load(conf_file); ast_mutex_lock(&conf_lock); /* Free the current list */ cur = users; while (cur) { l = cur; cur = cur->next; free(l); } users = NULL; if (cfg) { cat = ast_category_browse(cfg, NULL); while (cat) { if (strcasecmp(cat, "general")) { /* not general settings */ var = ast_variable_browse(cfg, cat); while (var) { struct nclid_user *user = calloc(1, sizeof(struct nclid_user)); if (user) { char *buf, *type, *remote_clients; buf = ast_strdupa(var->value); if (buf) { type = strsep(&buf, ","); remote_clients = buf; ast_copy_string(user->exten, var->name, sizeof(user->exten)); ast_copy_string(user->context, cat, sizeof(user->context)); if (!ast_strlen_zero(type)) ast_copy_string(user->type, type, sizeof(user->type)); if (!ast_strlen_zero(remote_clients)) ast_copy_string(user->remote_clients, remote_clients, sizeof(user->remote_clients)); if (option_debug > 2) ast_log(LOG_DEBUG, "Added '%s' clients '%s', for '%s' in context '%s'.\n", user->type, user->remote_clients, user->exten, user->context ); user->next = users; users = user; } else { ast_log(LOG_ERROR, "Out of memory.\n"); ast_mutex_unlock(&conf_lock); ast_config_destroy(cfg); return -1; } } else { ast_log(LOG_ERROR, "Out of memory.\n"); ast_mutex_unlock(&conf_lock); ast_config_destroy(cfg); return -1; } var = var->next; } } cat = ast_category_browse(cfg, cat); } } ast_mutex_unlock(&conf_lock); ast_config_destroy(cfg); return 0; } static int send_yac_notification(struct ast_channel *chan, const char *clid_name, const char *clid_number, const char *remote_clients) { char *hosts, *tmp, *host; int res = 0, port, sock; struct sockaddr_in addr; struct pollfd connectFD; int err; unsigned int errlen = sizeof(err); struct hostent *h; struct ast_hostent ahp; char message[300]; /* Maximum of 300 characters in a YAC message */ memset(message, 0, sizeof(message)); snprintf(message, sizeof(message) - 1, "@CALL%s~%s", clid_name, clid_number); hosts = ast_strdupa(remote_clients); if (!hosts) { ast_log(LOG_ERROR, "Out of memory.\n"); return -1; } while ((host = strsep(&hosts, "|"))) { if (strchr(host, ':')) { tmp = strsep(&host, ":"); port = atoi(strsep(&host, "\0")); host = tmp; } else { port = 10629; /* Default YAC port */ } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Sending YAC notification to '%s' on port %d.\n", host, port); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { ast_log(LOG_WARNING, "socket call failed: %s\n", strerror(errno)); return -1; } if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { ast_log(LOG_WARNING, "fcntl call failed: %s\n", strerror(errno)); close(sock); continue; } h = ast_gethostbyname(host, &ahp); if (!h) { ast_log(LOG_WARNING, "Unknown host: %s\n", host); close(sock); continue; } addr.sin_family = h->h_addrtype; memcpy(&addr.sin_addr, h->h_addr, sizeof(addr.sin_addr)); addr.sin_port = htons(port); if (connect(sock, (struct sockaddr *) &addr, sizeof(addr))) { if (errno == EINPROGRESS) { connectFD.fd = sock; connectFD.events = POLLOUT; res = poll(&connectFD, 1, 1000); if (res <= 0) { ast_log(LOG_WARNING, "poll() returned %d: %s\n", res, strerror(errno)); close(sock); continue; } if (!(connectFD.revents & POLLOUT)) { ast_log(LOG_WARNING, "client will block, skipping\n"); close(sock); continue; } res = getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &errlen); if (err != 0) { ast_log(LOG_WARNING, "connect() shows error in SO_ERROR: %d\n", err); close(sock); continue; } } else { ast_log(LOG_WARNING, "connect() failed: %s\n", strerror(errno)); close(sock); continue; } } if (send(sock, message, strlen(message), 0) == -1) { ast_log(LOG_WARNING, "send() failed: %s\n", strerror(errno)); } close(sock); } return 0; }