30ab96bed4f99c125eab66fa1730591f6e09a271
[kamailio] / src / modules / slack / slack.c
1 /*
2  * Copyright (C) 2021 Arsen Semenov arsperger@gmail.com
3  *
4  * This file is part of Kamailio, a free SIP server.
5  *
6  * Kamailio is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version
10  *
11  * Kamailio is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include "slack.h"
26
27 MODULE_VERSION
28
29 static char *_slmsg_buf = NULL;
30 static int mod_init(void);
31 static void mod_destroy(void);
32
33 static int buf_size = 4096;
34 static char *slack_url = NULL;
35 static char *slack_channel = SLACK_DEFAULT_CHANNEL;
36 static char *slack_username = SLACK_DEFAULT_USERNAME;
37 static char *slack_icon = SLACK_DEFAULT_ICON;
38
39 /**
40  * Exported functions
41  */
42 static cmd_export_t cmds[] = {
43         {"slack_send",                  (cmd_function)slack_send1,                      1, slack_fixup,  0, ANY_ROUTE},
44         {0, 0, 0, 0, 0, 0}
45 };
46
47
48 /**
49  * Exported parameters
50  */
51 static param_export_t mod_params[] = {
52         { "slack_url",                  PARAM_STRING|USE_FUNC_PARAM, (void*)_slack_url_param},
53         { "channel",                    PARAM_STRING, &slack_channel }, // channel starts with #
54         { "username",                   PARAM_STRING, &slack_username },
55         { "icon_emoji",                 PARAM_STRING, &slack_icon },
56         {0, 0, 0}
57 };
58
59 /**
60  * Module description
61  */
62 struct module_exports exports = {
63         "slack",        /* 1 module name */
64         DEFAULT_DLFLAGS, /* 2 dlopen flags */
65         cmds,            /* 3 exported functions */
66         mod_params,      /* 4 exported parameters */
67         0,               /* 5 exported RPC functions */
68         0,                       /* 6 exported pseudo-variables */
69         0,               /* 7 response function */
70         mod_init,        /* 8 module initialization function */
71         0,                       /* 9 per-child init function */
72         mod_destroy      /* 0 destroy function */
73 };
74
75 /**
76  * Module init
77  */
78 static int mod_init(void) {
79         LM_INFO("slack module init\n");
80
81         _slmsg_buf = (char*)pkg_malloc((buf_size+1)*sizeof(char));
82         if(_slmsg_buf==NULL)
83         {
84                 PKG_MEM_ERROR;
85                 return -1;
86         }
87         return(0);
88 }
89
90 /**
91  * Module destroy
92  */
93 static void mod_destroy() {
94         LM_INFO("slack module destroy\n");
95         if(_slmsg_buf)
96                 pkg_free(_slmsg_buf);
97         if(slack_url)
98                 pkg_free(slack_url);
99         return;
100 }
101
102 /**
103  * send message with curl
104  * @return 0 on success, -1 on error
105  */
106 static int _curl_send(const char* uri, str *post_data)
107 {
108         int datasz;
109         char* send_data;
110         CURL *curl_handle;
111         CURLcode res;
112         // LM_DBG("sending to[%s]\n", uri);
113
114         datasz = snprintf(NULL, 0, BODY_FMT, slack_channel, slack_username, post_data->s, slack_icon);
115         if (datasz < 0) {
116                 LM_ERR("snprintf error in calculating buffer size\n");
117         return -1;
118         }
119         send_data = (char*)pkg_mallocxz((datasz+1)*sizeof(char));
120         if(send_data==NULL) {
121         LM_ERR("can not allocate pkg memory [%d] bytes\n", datasz);
122         return -1;
123     }
124     snprintf(send_data, datasz+1, BODY_FMT, slack_channel, slack_username, post_data->s, slack_icon);
125
126         curl_global_init(CURL_GLOBAL_ALL);
127
128         if((curl_handle=curl_easy_init())==NULL) {
129         LM_ERR("Unable to init cURL library\n");
130                 curl_global_cleanup();
131                 pkg_free(send_data);
132         return -1;
133     }
134
135         curl_easy_setopt(curl_handle, CURLOPT_URL, uri);
136         curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, send_data);
137         res = curl_easy_perform(curl_handle);
138
139         if (res != CURLE_OK) {
140                 LM_ERR("slack request send error: %s\n", curl_easy_strerror(res));
141                 curl_easy_cleanup(curl_handle);
142                 curl_global_cleanup();
143                 pkg_free(send_data);
144                 return -1;
145         }
146
147         LM_INFO("slack request sent [%d]\n", datasz);
148         curl_easy_cleanup(curl_handle);
149         curl_global_cleanup();
150         pkg_free(send_data);
151         return 0;
152 }
153
154 /**
155  * parse slack_url param2
156  */
157 static int _slack_parse_url_param(char *val)
158 {
159         size_t len;
160         len = strlen(val);
161         if(len > SLACK_URL_MAX_SIZE) {
162                 LM_ERR("webhook url max size exceeded %d\n", SLACK_URL_MAX_SIZE);
163                 return -1;
164         }
165
166         if(strncmp(val, "https://hooks.slack.com", 23)) {
167                 LM_ERR("slack invalid webhook url [%s]\n", val);
168                 return -1;
169         }
170
171         // TODO: parse webhook to multiple channels? eg.: chan1=>https://AAA/BBB/CC, chan2=>...
172
173         slack_url = (char*)pkg_malloc(len + 1);
174         if (slack_url==NULL) {
175                 PKG_MEM_ERROR;
176                 return -1;
177         }
178         strncpy(slack_url, val, len);
179         slack_url[len] = '\0';
180
181         return 0;
182 }
183
184 /**
185  * parse slack_url param
186  */
187 int _slack_url_param(modparam_t type, void *val)
188 {
189         if(val==NULL) {
190                 LM_ERR("webhook url not specified\n");
191                 return -1;
192         }
193
194         return _slack_parse_url_param((char*)val);
195 }
196
197 static int slack_fixup_helper(void** param, int param_no)
198 {
199         sl_msg_t *sm;
200         str s;
201
202         sm = (sl_msg_t*)pkg_malloc(sizeof(sl_msg_t));
203         if(sm==NULL)
204         {
205                 PKG_MEM_ERROR;
206                 return -1;
207         }
208         memset(sm, 0, sizeof(sl_msg_t));
209         s.s = (char*)(*param);
210         s.len = strlen(s.s);
211
212         if(pv_parse_format(&s, &sm->m)<0)
213         {
214                 LM_ERR("wrong format[%s]\n", (char*)(*param));
215                 pkg_free(sm);
216                 return E_UNSPEC;
217         }
218         *param = (void*)sm;
219         return 0;
220 }
221
222
223 static int slack_fixup(void** param, int param_no)
224 {
225         if(param_no!=1 || param==NULL || *param==NULL)
226         {
227                 LM_ERR("invalid parameter number %d\n", param_no);
228                 return E_UNSPEC;
229         }
230         return slack_fixup_helper(param, param_no);
231 }
232
233 /**
234  * send text message to slack
235  */
236 static inline int slack_helper(struct sip_msg* msg, sl_msg_t *sm)
237 {
238         str txt;
239         txt.len = buf_size;
240
241         if(_slack_print_log(msg, sm->m, _slmsg_buf, &txt.len)<0)
242                 return -1;
243
244         txt.s = _slmsg_buf;
245
246         return _curl_send(slack_url, &txt);
247 }
248
249 static int slack_send1(struct sip_msg* msg, char* frm, char* str2)
250 {
251         return slack_helper(msg, (sl_msg_t*)frm);
252 }
253
254
255 /**
256  * Kemi
257  * send slack msg after evaluation of pvars
258  * @return 0 on success, -1 on error
259  */
260 static int ki_slack_send(sip_msg_t *msg, str *slmsg)
261 {
262         pv_elem_t *xmodel=NULL;
263         str txt = STR_NULL;
264         int res;
265
266         if(pv_parse_format(slmsg, &xmodel)<0) {
267                 LM_ERR("wrong format[%s]\n", slmsg->s);
268                 return -1;
269         }
270         if(pv_printf_s(msg, xmodel, &txt)!=0) {
271                 LM_ERR("cannot eval reparsed value\n");
272                 pv_elem_free_all(xmodel);
273                 return -1;
274         }
275
276         res = _curl_send(slack_url, &txt);
277         pv_elem_free_all(xmodel);
278         return res;
279 }
280
281
282 /* clang-format off */
283 static sr_kemi_t sr_kemi_slack_exports[] = {
284         { str_init("slack"), str_init("slack_send"),
285                 SR_KEMIP_INT, ki_slack_send,
286                 { SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
287                         SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
288         },
289
290         { {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
291 };
292 /* clang-format on */
293
294
295 int mod_register(char *path, int *dlflags, void *p1, void *p2)
296 {
297         sr_kemi_modules_add(sr_kemi_slack_exports);
298         return 0;
299 }