diff --git a/src/modules/slack/Makefile b/src/modules/slack/Makefile new file mode 100644 index 00000000000..833d438fb64 --- /dev/null +++ b/src/modules/slack/Makefile @@ -0,0 +1,31 @@ +# +# Slack client for Kamailio +# +# +# WARNING: do not run this directly, it should be run by the main Makefile + +include ../../Makefile.defs +auto_gen= +NAME=slack.so + +ifeq ($(CROSS_COMPILE),) +CURL_BUILDER=$(shell \ + if pkg-config --exists libcurl; then \ + echo 'pkg-config libcurl'; \ + else \ + which curl-config; \ + fi) +endif + +ifneq ($(CURL_BUILDER),) + CURLDEFS += $(shell $(CURL_BUILDER) --cflags) + CURLLIBS += $(shell $(CURL_BUILDER) --libs) +else + CURLDEFS+=-I$(LOCALBASE)/include -I$(SYSBASE)/include + CURLLIBS+=-L$(LOCALBASE)/lib -L$(SYSBASE)/lib -lcurl +endif + +DEFS+=$(CURLDEFS) +LIBS=$(CURLLIBS) + +include ../../Makefile.modules diff --git a/src/modules/slack/README b/src/modules/slack/README new file mode 100644 index 00000000000..58203e80fb2 --- /dev/null +++ b/src/modules/slack/README @@ -0,0 +1,154 @@ +Slack Module + +Arsen Semenov + + + + Copyright © 2021 arsperger.com + __________________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1. Overview + 2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + + 3. Parameters + + 3.1. slack url (str) + 3.2. channel (str) + 3.3. username (str) + 3.4. icon_emogi (str) + + 4. Functions + + 4.1. slack_send(format) + + List of Examples + + 1.1. Set slack webhook URL parameter + 1.2. Set channel parameter + 1.3. Set username parameter + 1.4. Set icon_emogi parameter + 1.5. slack_send usage + +Chapter 1. Admin Guide + + Table of Contents + + 1. Overview + 2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + + 3. Parameters + + 3.1. slack url (str) + 3.2. channel (str) + 3.3. username (str) + 3.4. icon_emogi (str) + + 4. Functions + + 4.1. slack_send(format) + +1. Overview + + This module provides integration with Slack over webhooks. Slack + integration (https://api.slack.com/messaging/webhooks) + + It relays on libcurl. library (https://curl.se/libcurl). + +2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + +2.1. Kamailio Modules + + The following modules must be loaded before this module: + * none. + +2.2. External Libraries or Applications + + The following libraries or applications must be installed before + running Kamailio with this module loaded: + * libcurl + +3. Parameters + + 3.1. slack url (str) + 3.2. channel (str) + 3.3. username (str) + 3.4. icon_emogi (str) + +3.1. slack url (str) + + Slack webhook url + + Default value is not set (empty) + + Example 1.1. Set slack webhook URL parameter +... +modparam("slack", "slack_url", "https://hooks.slack.com/services/T00000000/B0000 +0000/XXXXXXXXXXXXXXXXXXXXXXXX") +... + +3.2. channel (str) + + Slack channel name + + Default value is #kamailio + + Example 1.2. Set channel parameter +... +modparam("slack", "channel", "#kamailio") +... + +3.3. username (str) + + Specify the username for the published message + + Default value is webhookbot. + + Example 1.3. Set username parameter +... +modparam("slack", "username", "webhookbot") +... + +3.4. icon_emogi (str) + + specify an emoji (using colon shortcodes, eg. :white_check_mark:) to + use as the profile photo alongside the message. + + Default value is :ghost: + + Example 1.4. Set icon_emogi parameter +... +modparam("slack", "icon_emogi", ":ghost:") +... + +4. Functions + + 4.1. slack_send(format) + +4.1. slack_send(format) + + Send a formatted message to slack channel. + + The parameters are: + * format - The formatted string to be send. + + The parameters can contain pseudo-variables. + + This function can be used from ANY_ROUTE. + + Example 1.5. slack_send usage +... + slack_send("Hello from Kamailio! caller=$fU;callee=$tU;callid=$ci"); +... diff --git a/src/modules/slack/doc/Makefile b/src/modules/slack/doc/Makefile new file mode 100644 index 00000000000..f64776f7230 --- /dev/null +++ b/src/modules/slack/doc/Makefile @@ -0,0 +1,4 @@ +docs = slack.xml + +docbook_dir = ../../../../doc/docbook +include $(docbook_dir)/Makefile.module diff --git a/src/modules/slack/doc/slack.xml b/src/modules/slack/doc/slack.xml new file mode 100644 index 00000000000..2cf783964bc --- /dev/null +++ b/src/modules/slack/doc/slack.xml @@ -0,0 +1,28 @@ + + + + %docentities; +]> + + + + Slack Module + &kamailioname; + + + Arsen + Semenov + arsperger@gmail.com + + + + 2021 + arsperger.com + + + + + + + \ No newline at end of file diff --git a/src/modules/slack/doc/slack_admin.xml b/src/modules/slack/doc/slack_admin.xml new file mode 100644 index 00000000000..fa8e6cf6b66 --- /dev/null +++ b/src/modules/slack/doc/slack_admin.xml @@ -0,0 +1,175 @@ + + + +%docentities; + +]> + + + + + &adminguide; + +
+ Overview + + This module provides integration with Slack over webhooks. + Slack integration (https://api.slack.com/messaging/webhooks) + + + It relays on libcurl. library (https://curl.se/libcurl). + +
+ +
+ Dependencies +
+ &kamailio; Modules + + The following modules must be loaded before this module: + + + + none. + + + + +
+
+ External Libraries or Applications + + The following libraries or applications must be installed before running + &kamailio; with this module loaded: + + + + libcurl + + + + +
+
+ +
+ Parameters +
+ <varname>slack url</varname> (str) + + Slack webhook url + + + + Default value is not set (empty) + + + + Set <varname>slack webhook URL</varname> parameter + +... +modparam("slack", "slack_url", "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX") +... + + +
+
+ <varname>channel</varname> (str) + + Slack channel name + + + + Default value is #kamailio + + + + Set <varname>channel</varname> parameter + +... +modparam("slack", "channel", "#kamailio") +... + + +
+
+ <varname>username</varname> (str) + + Specify the username for the published message + + + + Default value is webhookbot. + + + + Set <varname>username</varname> parameter + +... +modparam("slack", "username", "webhookbot") +... + + +
+
+ <varname>icon_emoji</varname> (str) + + specify an emoji (using colon shortcodes, eg. :white_check_mark:) + to use as the profile photo alongside the message. + + + + Default value is :ghost: + + + + Set <varname>icon_emoji</varname> parameter + +... +modparam("slack", "icon_emoji", ":ghost:") +... + + +
+ +
+ +
+ Functions +
+ + <function moreinfo="none">slack_send(format)</function> + + + Send a formatted message to slack channel. + + + The parameters are: + + + + + format - The formatted string to be send. + + + + + The parameters can contain pseudo-variables. + + + This function can be used from ANY_ROUTE. + + + <function>slack_send</function> usage + +... + slack_send("Hello from Kamailio! caller=$fU;callee=$tU;callid=$ci"); +... + + +
+
+
diff --git a/src/modules/slack/slack.c b/src/modules/slack/slack.c new file mode 100644 index 00000000000..906e824beed --- /dev/null +++ b/src/modules/slack/slack.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2021 Arsen Semenov arsperger@gmail.com + * + * This file is part of Kamailio, a free SIP server. + * + * Kamailio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * Kamailio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include +#include + +#include "slack.h" + +MODULE_VERSION + +static char *_slmsg_buf = NULL; +static int mod_init(void); +static void mod_destroy(void); + +static int buf_size = 4096; +static char *slack_url = NULL; +static char *slack_channel = SLACK_DEFAULT_CHANNEL; +static char *slack_username = SLACK_DEFAULT_USERNAME; +static char *slack_icon = SLACK_DEFAULT_ICON; + +/** + * Exported functions + */ +static cmd_export_t cmds[] = { + {"slack_send", (cmd_function)slack_send1, 1, slack_fixup, 0, ANY_ROUTE}, + {0, 0, 0, 0, 0, 0} +}; + + +/** + * Exported parameters + */ +static param_export_t mod_params[] = { + { "slack_url", PARAM_STRING|USE_FUNC_PARAM, (void*)_slack_url_param}, + { "channel", PARAM_STRING, &slack_channel }, // channel starts with # + { "username", PARAM_STRING, &slack_username }, + { "icon_emoji", PARAM_STRING, &slack_icon }, + {0, 0, 0} +}; + +/** + * Module description + */ +struct module_exports exports = { + "slack", /* 1 module name */ + DEFAULT_DLFLAGS, /* 2 dlopen flags */ + cmds, /* 3 exported functions */ + mod_params, /* 4 exported parameters */ + 0, /* 5 exported RPC functions */ + 0, /* 6 exported pseudo-variables */ + 0, /* 7 response function */ + mod_init, /* 8 module initialization function */ + 0, /* 9 per-child init function */ + mod_destroy /* 0 destroy function */ +}; + +/** + * Module init + */ +static int mod_init(void) { + LM_INFO("slack module init\n"); + + _slmsg_buf = (char*)pkg_malloc((buf_size+1)*sizeof(char)); + if(_slmsg_buf==NULL) + { + PKG_MEM_ERROR; + return -1; + } + return(0); +} + +/** + * Module destroy + */ +static void mod_destroy() { + LM_INFO("slack module destroy\n"); + if(_slmsg_buf) + pkg_free(_slmsg_buf); + if(slack_url) + pkg_free(slack_url); + return; +} + +/** + * send message with curl + * @return 0 on success, -1 on error + */ +static int _curl_send(const char* uri, str *post_data) +{ + int datasz; + char* send_data; + CURL *curl_handle; + CURLcode res; + // LM_DBG("sending to[%s]\n", uri); + + datasz = snprintf(NULL, 0, BODY_FMT, slack_channel, slack_username, post_data->s, slack_icon); + if (datasz == -1) { + LM_ERR("Error: snprintf error in calculating buffer size\n"); + return -1; + } + send_data = (char*)pkg_mallocxz((datasz+1)*sizeof(char)); + if(send_data==NULL) { + LM_ERR("Error: can not allocate pkg memory [%d] bytes\n", datasz); + return -1; + } + snprintf(send_data, datasz+1, BODY_FMT, slack_channel, slack_username, post_data->s, slack_icon); + + curl_global_init(CURL_GLOBAL_ALL); + + if((curl_handle=curl_easy_init())==NULL) { + LM_ERR("Error: Unable to init cURL library\n"); + curl_global_cleanup(); + return -1; + } + + curl_easy_setopt(curl_handle, CURLOPT_URL, uri); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, send_data); + res = curl_easy_perform(curl_handle); + + if (res != CURLE_OK) { + LM_ERR("slack request send error: %s\n", curl_easy_strerror(res)); + curl_easy_cleanup(curl_handle); + curl_global_cleanup(); + if(send_data) { + pkg_free(send_data); + } + return -1; + } + + LM_INFO("slack request sent [%d]\n", datasz); + curl_easy_cleanup(curl_handle); + curl_global_cleanup(); + if (send_data) { + pkg_free(send_data); + } + return 0; +} + +/** + * parse slack_url param2 + */ +static int _slack_parse_url_param(char *val) +{ + size_t len; + len = strlen(val); + if(len > SLACK_URL_MAX_SIZE) { + LM_ERR("webhook url max size exceeded %d\n", SLACK_URL_MAX_SIZE); + return -1; + } + + if(strncmp(val, "https://hooks.slack.com", 23)) { + LM_ERR("slack invalid webhook url [%s]\n", val); + return -1; + } + + // TODO: parse webhook to multiple channels? eg.: chan1=>https://AAA/BBB/CC, chan2=>... + + slack_url = (char*)pkg_malloc(len + 1); + if (slack_url==NULL) { + PKG_MEM_ERROR; + return -1; + } + strncpy(slack_url, val, len); + slack_url[len] = '\0'; + + return 0; +} + +/** + * parse slack_url param + */ +int _slack_url_param(modparam_t type, void *val) +{ + if(val==NULL) { + LM_ERR("webhook url not specified\n"); + return -1; + } + + return _slack_parse_url_param((char*)val); +} + +static int slack_fixup_helper(void** param, int param_no) +{ + sl_msg_t *sm; + str s; + + sm = (sl_msg_t*)pkg_malloc(sizeof(sl_msg_t)); + if(sm==NULL) + { + PKG_MEM_ERROR; + return -1; + } + memset(sm, 0, sizeof(sl_msg_t)); + s.s = (char*)(*param); + s.len = strlen(s.s); + + if(pv_parse_format(&s, &sm->m)<0) + { + LM_ERR("wrong format[%s]\n", (char*)(*param)); + pkg_free(sm); + return E_UNSPEC; + } + *param = (void*)sm; + return 0; +} + + +static int slack_fixup(void** param, int param_no) +{ + if(param_no!=1 || param==NULL || *param==NULL) + { + LM_ERR("invalid parameter number %d\n", param_no); + return E_UNSPEC; + } + return slack_fixup_helper(param, param_no); +} + +/** + * send text message to slack + */ +static inline int slack_helper(struct sip_msg* msg, sl_msg_t *sm) +{ + str txt; + txt.len = buf_size; + + if(_slack_print_log(msg, sm->m, _slmsg_buf, &txt.len)<0) + return -1; + + txt.s = _slmsg_buf; + + return _curl_send(slack_url, &txt); +} + +static int slack_send1(struct sip_msg* msg, char* frm, char* str2) +{ + return slack_helper(msg, (sl_msg_t*)frm); +} + + +/** + * Kemi + * send slack msg after evaluation of pvars + * @return 0 on success, -1 on error + */ +static int ki_slack_send(sip_msg_t *msg, str *slmsg) +{ + pv_elem_t *xmodel=NULL; + str txt = STR_NULL; + int res; + + if(pv_parse_format(slmsg, &xmodel)<0) { + LM_ERR("wrong format[%s]\n", slmsg->s); + return -1; + } + if(pv_printf_s(msg, xmodel, &txt)!=0) { + LM_ERR("Error: cannot eval reparsed value\n"); + pv_elem_free_all(xmodel); + return -1; + } + + res = _curl_send(slack_url, &txt); + pv_elem_free_all(xmodel); + return res; +} + + +/* clang-format off */ +static sr_kemi_t sr_kemi_slack_exports[] = { + { str_init("slack"), str_init("slack_send"), + SR_KEMIP_INT, ki_slack_send, + { SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE, + SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE } + }, + + { {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } } +}; +/* clang-format on */ + + +int mod_register(char *path, int *dlflags, void *p1, void *p2) +{ + sr_kemi_modules_add(sr_kemi_slack_exports); + return 0; +} diff --git a/src/modules/slack/slack.h b/src/modules/slack/slack.h new file mode 100644 index 00000000000..322315f1c7e --- /dev/null +++ b/src/modules/slack/slack.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 Arsen Semenov arsperger@gmail.com + * + * This file is part of Kamailio, a free SIP server. + * + * Kamailio is free software; you can redistribute it and/or modify + * it under the tertc of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * Kamailio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef slack_h +#define slack_h + +#include "../../core/sr_module.h" +#include "../../core/dprint.h" +#include "../../core/parser/parse_content.h" +#include "../../core/pvar.h" +#include "../../core/kemi.h" + +#include +#include + +#define BODY_FMT "{\"channel\": \"%s\", \"username\": \"%s\", \"text\": \"%s\", \"icon_emoji\": \"%s\" }" +#define SLACK_URL_MAX_SIZE 128 +#define SLACK_DEFAULT_CHANNEL "#webtest" +#define SLACK_DEFAULT_USERNAME "webhookbot" +#define SLACK_DEFAULT_ICON ":ghost:" + +static int _slack_print_log(struct sip_msg* msg, pv_elem_p list, char *buf, int *len) +{ + return pv_printf(msg, list, buf, len); +} + +static int _curl_send(const char* uri, str *post_data ); +static int _slack_parse_url_param(char *val); +static int _slack_url_param(modparam_t type, void *val); + +static int slack_fixup(void** param, int param_no); +static int slack_send1(struct sip_msg* msg, char* frm, char* str2); +static int slack_fixup_helper(void** param, int param_no); + +typedef struct _sl_msg +{ + pv_elem_t *m; +} sl_msg_t; + +#endif /* slack_h */