slack: reuse http_client api
[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         if(httpc_load_api(&httpapi) != 0) {
82                 LM_ERR("can not bind to http_client API \n");
83                 return -1;
84         }
85
86         _slmsg_buf = (char*)pkg_malloc((buf_size+1)*sizeof(char));
87         if(_slmsg_buf==NULL)
88         {
89                 PKG_MEM_ERROR;
90                 return -1;
91         }
92         return(0);
93 }
94
95 /**
96  * Module destroy
97  */
98 static void mod_destroy() {
99         LM_INFO("slack module destroy\n");
100         if(_slmsg_buf)
101                 pkg_free(_slmsg_buf);
102         if(slack_url)
103                 pkg_free(slack_url);
104         return;
105 }
106
107 /* free and reset str */
108 static void slack_free_str(str *string)
109 {
110         str ptr = STR_NULL;
111         if (string->s == NULL)
112                 return;
113
114         ptr = *string;
115
116         if(ptr.s != NULL && ptr.len > 0)
117                 pkg_free(ptr.s);
118
119         string->s = NULL;
120         string->len = 0;
121
122         return;
123 }
124
125 /**
126  * send slack message using http_client api
127  * @return 0 on success, -1 on error
128  */
129 static int slack_curl_send(struct sip_msg* msg, char* uri, str *post_data)
130 {
131         int datasz;
132         char* send_data;
133         str ret = STR_NULL;
134         int curl = 0;
135
136         datasz = snprintf(NULL, 0, BODY_FMT, slack_channel, slack_username, post_data->s, slack_icon);
137         if (datasz < 0) {
138                 LM_ERR("snprintf error in calculating buffer size\n");
139         return -1;
140         }
141         send_data = (char*)pkg_mallocxz((datasz+1)*sizeof(char));
142         if(send_data==NULL) {
143         LM_ERR("can not allocate pkg memory [%d] bytes\n", datasz);
144         return -1;
145     }
146     snprintf(send_data, datasz+1, BODY_FMT, slack_channel, slack_username, post_data->s, slack_icon);
147
148         /* send request */
149         curl = httpapi.http_client_query(msg, uri, &ret, send_data, NULL);
150         pkg_free(send_data);
151
152         if(curl >= 300 || curl < 100) {
153                 LM_ERR("request failed with error: %d\n", curl);
154                 slack_free_str(&ret);
155                 return -1;
156         }
157
158         LM_DBG("slack send response: [%.*s]\n", ret.len, ret.s);
159         slack_free_str(&ret);
160
161         return 0;
162 }
163
164 /**
165  * parse slack_url param2
166  */
167 static int _slack_parse_url_param(char *val)
168 {
169         size_t len;
170         len = strlen(val);
171         if(len > SLACK_URL_MAX_SIZE) {
172                 LM_ERR("webhook url max size exceeded %d\n", SLACK_URL_MAX_SIZE);
173                 return -1;
174         }
175
176         if(strncmp(val, "https://hooks.slack.com", 23)) {
177                 LM_ERR("slack invalid webhook url [%s]\n", val);
178                 return -1;
179         }
180
181         // TODO: parse webhook to multiple channels? eg.: chan1=>https://AAA/BBB/CC, chan2=>...
182
183         slack_url = (char*)pkg_malloc(len + 1);
184         if (slack_url==NULL) {
185                 PKG_MEM_ERROR;
186                 return -1;
187         }
188         strncpy(slack_url, val, len);
189         slack_url[len] = '\0';
190
191         return 0;
192 }
193
194 /**
195  * parse slack_url param
196  */
197 int _slack_url_param(modparam_t type, void *val)
198 {
199         if(val==NULL) {
200                 LM_ERR("webhook url not specified\n");
201                 return -1;
202         }
203
204         return _slack_parse_url_param((char*)val);
205 }
206
207 static int slack_fixup_helper(void** param, int param_no)
208 {
209         sl_msg_t *sm;
210         str s;
211
212         sm = (sl_msg_t*)pkg_malloc(sizeof(sl_msg_t));
213         if(sm==NULL)
214         {
215                 PKG_MEM_ERROR;
216                 return -1;
217         }
218         memset(sm, 0, sizeof(sl_msg_t));
219         s.s = (char*)(*param);
220         s.len = strlen(s.s);
221
222         if(pv_parse_format(&s, &sm->m)<0)
223         {
224                 LM_ERR("wrong format[%s]\n", (char*)(*param));
225                 pkg_free(sm);
226                 return E_UNSPEC;
227         }
228         *param = (void*)sm;
229         return 0;
230 }
231
232
233 static int slack_fixup(void** param, int param_no)
234 {
235         if(param_no!=1 || param==NULL || *param==NULL)
236         {
237                 LM_ERR("invalid parameter number %d\n", param_no);
238                 return E_UNSPEC;
239         }
240         return slack_fixup_helper(param, param_no);
241 }
242
243 /**
244  * send text message to slack
245  */
246 static inline int slack_helper(struct sip_msg* msg, sl_msg_t *sm)
247 {
248         str txt;
249         txt.len = buf_size;
250
251         if(_slack_print_log(msg, sm->m, _slmsg_buf, &txt.len)<0)
252                 return -1;
253
254         txt.s = _slmsg_buf;
255
256         return slack_curl_send(msg, slack_url, &txt);
257 }
258
259 static int slack_send1(struct sip_msg* msg, char* frm, char* str2)
260 {
261         return slack_helper(msg, (sl_msg_t*)frm);
262 }
263
264
265 /**
266  * Kemi
267  * send slack msg after evaluation of pvars
268  * @return 0 on success, -1 on error
269  */
270 static int ki_slack_send(sip_msg_t *msg, str *slmsg)
271 {
272         pv_elem_t *xmodel=NULL;
273         str txt = STR_NULL;
274         int res;
275
276         if(pv_parse_format(slmsg, &xmodel)<0) {
277                 LM_ERR("wrong format[%s]\n", slmsg->s);
278                 return -1;
279         }
280         if(pv_printf_s(msg, xmodel, &txt)!=0) {
281                 LM_ERR("cannot eval reparsed value\n");
282                 pv_elem_free_all(xmodel);
283                 return -1;
284         }
285
286         res = slack_curl_send(msg, slack_url, &txt);
287         pv_elem_free_all(xmodel);
288         return res;
289 }
290
291
292 /* clang-format off */
293 static sr_kemi_t sr_kemi_slack_exports[] = {
294         { str_init("slack"), str_init("slack_send"),
295                 SR_KEMIP_INT, ki_slack_send,
296                 { SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
297                         SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
298         },
299
300         { {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
301 };
302 /* clang-format on */
303
304
305 int mod_register(char *path, int *dlflags, void *p1, void *p2)
306 {
307         sr_kemi_modules_add(sr_kemi_slack_exports);
308         return 0;
309 }