1 /*!
2 * \file sccp_linedevice.c
3 * \brief SCCP LineDevice
4 * \author Sergio Chersovani <mlists [at] c-net.it>
5 * \note Reworked, but based on chan_sccp code.
6 * The original chan_sccp driver that was made by Zozo which itself was derived from the chan_skinny driver.
7 * Modified by Jan Czmok and Julien Goodwin
8 * \note This program is free software and may be modified and distributed under the terms of the GNU Public License.
9 * See the LICENSE file at the top of the source tree.
10 *
11 */
12 /*
13 * File: sccp_linedevice.c
14 * Author: dkgroot
15 *
16 * Created on September 22, 2019, 4:38 PM
17 */
18
19 #include "config.h"
20 #include "common.h"
21 #include "sccp_device.h"
22 #include "sccp_line.h"
23 #include "sccp_linedevice.h"
24 #include "sccp_utils.h"
25
26 SCCP_FILE_VERSION(__FILE__, "");
27
28 /*!
29 * \brief Free a Line as scheduled command
30 * \param ptr SCCP Line Pointer
31 * \return success as int
32 *
33 * \callgraph
34 * \callergraph
35 *
36 */
__sccp_lineDevice_destroy(const void * ptr)37 static int __sccp_lineDevice_destroy(const void * ptr)
38 {
39 sccp_linedevice_t * ld = (sccp_linedevice_t *)ptr;
40
41 sccp_log((DEBUGCAT_DEVICE + DEBUGCAT_LINE + DEBUGCAT_CONFIG))(VERBOSE_PREFIX_1 "%s: LineDevice FREE %p\n", DEV_ID_LOG(ld->device), ld);
42 if(ld->line) {
43 sccp_line_release(&ld->line); /* explicit release of line retained in ld */
44 }
45 if(ld->device) {
46 sccp_device_release(&ld->device); /* explicit release of device retained in ld */
47 }
48 return 0;
49 }
50
51 /*!
52 * \brief Register Extension to Asterisk regextension
53 * \param l SCCP Line
54 * \param subscriptionId subscriptionId
55 * \param onoff On/Off as int
56 * \note used for DUNDi Discovery
57 */
regcontext_exten(constLineDevicePtr ld,int onoff)58 static void regcontext_exten(constLineDevicePtr ld, int onoff)
59 {
60 char multi[256] = "";
61 char * stringp = NULL;
62
63 char * ext = "";
64
65 char cntxt[SCCP_MAX_CONTEXT];
66 char * context = cntxt;
67
68 struct pbx_context * con = NULL;
69 struct pbx_find_info q = { .stacklen = 0 };
70
71 if(sccp_strlen_zero(GLOB(regcontext))) {
72 return;
73 }
74
75 if(!ld || !ld->line) {
76 return;
77 }
78 sccp_line_t * l = ld->line;
79 // struct subscriptionId *subscriptionId = &(ld->subscriptionId);
80
81 sccp_copy_string(multi, S_OR(l->regexten, l->name), sizeof(multi));
82 stringp = multi;
83 while((ext = strsep(&stringp, "&"))) {
84 if((context = strchr(ext, '@'))) {
85 *context++ = '\0'; /* split ext@context */
86 if(!pbx_context_find(context)) {
87 pbx_log(LOG_WARNING, "Context specified in regcontext=%s (sccp.conf) must exist\n", context);
88 continue;
89 }
90 } else {
91 sccp_copy_string(cntxt, GLOB(regcontext), sizeof(cntxt));
92 context = cntxt;
93 }
94 con = pbx_context_find_or_create(NULL, NULL, context, "SCCP"); /* make sure the context exists */
95 if(con) {
96 if(onoff) {
97 /* register */
98
99 if(!pbx_exists_extension(NULL, context, ext, 1, NULL) && pbx_add_extension(context, 0, ext, 1, NULL, NULL, "Noop", pbx_strdup(l->name), sccp_free_ptr, "SCCP")) {
100 sccp_log((DEBUGCAT_LINE + DEBUGCAT_CONFIG))(VERBOSE_PREFIX_1 "Registered RegContext: %s, Extension: %s, Line: %s\n", context, ext, l->name);
101 }
102
103 /* register extension + subscriptionId */
104 /* if (subscriptionId && subscriptionId->number && !sccp_strlen_zero(subscriptionId->number) && !sccp_strlen_zero(subscriptionId->name)) {
105 snprintf(extension, sizeof(extension), "%s@%s", ext, subscriptionId->number);
106 snprintf(name, sizeof(name), "%s%s", l->name, subscriptionId->name);
107 if (!pbx_exists_extension(NULL, context, extension, 2, NULL) && pbx_add_extension(context, 0, extension, 2, NULL, NULL, "Noop", pbx_strdup(name), sccp_free_ptr, "SCCP")) {
108 sccp_log((DEBUGCAT_LINE + DEBUGCAT_CONFIG)) (VERBOSE_PREFIX_1 "Registered RegContext: %s, Extension: %s, Line: %s\n", context, extension, name);
109 }
110 } */
111 } else {
112 /* un-register */
113
114 if(SCCP_LIST_GETSIZE(&l->devices) == 1) { // only remove entry if it is the last one (shared line)
115 if(pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) {
116 ast_context_remove_extension(context, ext, 1, NULL);
117 sccp_log((DEBUGCAT_LINE + DEBUGCAT_CONFIG))(VERBOSE_PREFIX_1 "Unregistered RegContext: %s, Extension: %s\n", context, ext);
118 }
119 }
120
121 /* unregister extension + subscriptionId */
122 /* if (subscriptionId && subscriptionId->number && !sccp_strlen_zero(subscriptionId->number) && !sccp_strlen_zero(subscriptionId->name)) {
123 snprintf(extension, sizeof(extension), "%s@%s", ext, subscriptionId->number);
124 // if (pbx_exists_extension(NULL, context, extension, 2, NULL)) {
125 if (pbx_find_extension(NULL, NULL, &q, context, extension, 2, NULL, "", E_MATCH)) {
126 ast_context_remove_extension(context, extension, 2, NULL);
127 sccp_log((DEBUGCAT_LINE + DEBUGCAT_CONFIG)) (VERBOSE_PREFIX_1 "Unregistered RegContext: %s, Extension: %s\n", context, extension);
128 }
129 } */
130 }
131 } else {
132 pbx_log(LOG_ERROR, "SCCP: context '%s' does not exist and could not be created\n", context);
133 }
134 }
135 }
136
137 /*!
138 * \brief Set a Call Forward on a specific Line
139 * \param line SCCP Line
140 * \param device device that requested the forward
141 * \param type Call Forward Type as uint8_t
142 * \param number Number to which should be forwarded
143 * \todo we should check, that extension is reachable on line
144 *
145 * \callgraph
146 * \callergraph
147 *
148 * \todo implement cfwd_noanswer
149 */
sccp_linedevice_cfwd(lineDevicePtr ld,sccp_cfwd_t type,char * number)150 void sccp_linedevice_cfwd(lineDevicePtr ld, sccp_cfwd_t type, char * number)
151 {
152 if(!ld || !ld->line) {
153 return;
154 }
155
156 if(type == SCCP_CFWD_NONE) {
157 for(uint x = SCCP_CFWD_ALL; x < SCCP_CFWD_SENTINEL; x++) {
158 ld->cfwd[x].enabled = FALSE;
159 ld->cfwd[x].number[0] = '\0';
160 }
161 sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: all Call Forwards have been disabled on line %s\n", DEV_ID_LOG(ld->device), ld->line->name);
162 } else {
163 if(!number || sccp_strlen_zero(number)) {
164 ld->cfwd[type].enabled = FALSE;
165 ld->cfwd[type].number[0] = '\0';
166 sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: Call Forward to an empty number. Invalid. Cfwd Disabled\n", DEV_ID_LOG(ld->device));
167 } else {
168 ld->cfwd[type].enabled = TRUE;
169 sccp_copy_string(ld->cfwd[type].number, number, sizeof(ld->cfwd[type].number));
170 sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: Call Forward %s enabled on line %s to number %s\n", DEV_ID_LOG(ld->device), sccp_cfwd2str(type), ld->line->name, number);
171 }
172 }
173 sccp_feat_changed(ld->device, ld, sccp_cfwd2feature(type));
174 sccp_dev_forward_status(ld->line, ld->lineInstance, ld->device);
175 }
176
sccp_linedevice_get_cfwd_string(constLineDevicePtr ld,char * const buffer,size_t size)177 const char * const sccp_linedevice_get_cfwd_string(constLineDevicePtr ld, char * const buffer, size_t size)
178 {
179 if(!ld) {
180 buffer[0] = '\0';
181 return NULL;
182 }
183 snprintf(buffer, size, "All:%s, Busy:%s, NoAnswer:%s", ld->cfwd[SCCP_CFWD_ALL].enabled ? ld->cfwd[SCCP_CFWD_ALL].number : "off", ld->cfwd[SCCP_CFWD_BUSY].enabled ? ld->cfwd[SCCP_CFWD_BUSY].number : "off",
184 ld->cfwd[SCCP_CFWD_NOANSWER].enabled ? ld->cfwd[SCCP_CFWD_NOANSWER].number : "off");
185 return buffer;
186 }
187
188 /*!
189 * \brief Attach a Device to a line
190 * \param line SCCP Line
191 * \param d SCCP Device
192 * \param lineInstance lineInstance as uint8_t
193 * \param subscriptionId Subscription ID for addressing individual devices on the line
194 *
195 */
sccp_linedevice_create(constDevicePtr d,constLinePtr l,uint8_t lineInstance,sccp_subscription_id_t * subscriptionId)196 void sccp_linedevice_create(constDevicePtr d, constLinePtr l, uint8_t lineInstance, sccp_subscription_id_t * subscriptionId)
197 {
198 AUTO_RELEASE(sccp_line_t, line, sccp_line_retain(l));
199 AUTO_RELEASE(sccp_device_t, device, sccp_device_retain(d));
200
201 if(!device || !line) {
202 pbx_log(LOG_ERROR, "SCCP: sccp_linedevice_create: No line or device provided\n");
203 return;
204 }
205 sccp_linedevice_t * ld = NULL;
206
207 if((ld = sccp_linedevice_find(device, l))) {
208 sccp_log((DEBUGCAT_LINE))(VERBOSE_PREFIX_3 "%s: device already registered for line '%s'\n", DEV_ID_LOG(device), l->name);
209 sccp_linedevice_release(&ld); /* explicit release of found ld */
210 return;
211 }
212
213 sccp_log((DEBUGCAT_LINE))(VERBOSE_PREFIX_3 "%s: add device to line %s\n", DEV_ID_LOG(device), line->name);
214 #if CS_REFCOUNT_DEBUG
215 sccp_refcount_addRelationship(device, line);
216 #endif
217 char ld_id[REFCOUNT_INDENTIFIER_SIZE];
218
219 snprintf(ld_id, REFCOUNT_INDENTIFIER_SIZE, "%s/%s", device->id, line->name);
220 ld = (sccp_linedevice_t *)sccp_refcount_object_alloc(sizeof(sccp_linedevice_t), SCCP_REF_LINEDEVICE, ld_id, __sccp_lineDevice_destroy);
221 if(!ld) {
222 pbx_log(LOG_ERROR, SS_Memory_Allocation_Error, ld_id);
223 return;
224 }
225 memset(ld, 0, sizeof *ld);
226 #if CS_REFCOUNT_DEBUG
227 sccp_refcount_addRelationship(l, ld);
228 sccp_refcount_addRelationship(device, ld);
229 #endif
230 *(sccp_device_t **)&(ld->device) = sccp_device_retain(device); // const cast to emplace device
231 *(sccp_line_t **)&(ld->line) = sccp_line_retain(line); // const cast to emplace line
232 ld->lineInstance = lineInstance;
233 if(NULL != subscriptionId) {
234 memcpy(&ld->subscriptionId, subscriptionId, sizeof(ld->subscriptionId));
235 }
236
237 SCCP_LIST_LOCK(&line->devices);
238 SCCP_LIST_INSERT_HEAD(&line->devices, ld, list);
239 SCCP_LIST_UNLOCK(&line->devices);
240
241 ld->line->statistic.numberOfActiveDevices++;
242 ld->device->configurationStatistic.numberOfLines++;
243
244 sccp_line_updatePreferencesFromDevicesToLine(line);
245
246 // fire event for new device
247 sccp_event_t * event = sccp_event_allocate(SCCP_EVENT_DEVICE_ATTACHED);
248 if(event) {
249 event->deviceAttached.ld = sccp_linedevice_retain(ld);
250 sccp_event_fire(event);
251 }
252 regcontext_exten(ld, 1);
253 sccp_log((DEBUGCAT_LINE))(VERBOSE_PREFIX_3 "%s: added ld: %p with device: %s\n", line->name, ld, DEV_ID_LOG(device));
254 }
255
256 /*!
257 * \brief Remove a Device from a Line
258 *
259 * Fire SCCP_EVENT_DEVICE_DETACHED event after removing device.
260 *
261 * \param l SCCP Line
262 * \param device SCCP Device
263 *
264 * \note device can be NULL, mening remove all device from this line
265 *
266 */
sccp_linedevice_remove(constDevicePtr d,linePtr l)267 void sccp_linedevice_remove(constDevicePtr d, linePtr l)
268 {
269 sccp_linedevice_t * ld = NULL;
270
271 if(!l) {
272 return;
273 }
274 sccp_log_and((DEBUGCAT_HIGH + DEBUGCAT_LINE))(VERBOSE_PREFIX_3 "%s: remove device from line %s\n", DEV_ID_LOG(d), l->name);
275
276 SCCP_LIST_LOCK(&l->devices);
277 SCCP_LIST_TRAVERSE_SAFE_BEGIN(&l->devices, ld, list) {
278 if(d == NULL || ld->device == d) {
279 #if CS_REFCOUNT_DEBUG
280 sccp_refcount_removeRelationship(d ? d : ld->device, l);
281 #endif
282 regcontext_exten(ld, 0);
283 SCCP_LIST_REMOVE_CURRENT(list);
284 l->statistic.numberOfActiveDevices--;
285 sccp_event_t * event = sccp_event_allocate(SCCP_EVENT_DEVICE_DETACHED);
286 if(event) {
287 event->deviceAttached.ld = sccp_linedevice_retain(ld);
288 sccp_event_fire(event);
289 }
290 sccp_linedevice_release(&ld); /* explicit release of list retained ld */
291 #ifdef CS_SCCP_REALTIME
292 if(l->realtime && SCCP_LIST_GETSIZE(&l->devices) == 0 && SCCP_LIST_GETSIZE(&l->channels) == 0) {
293 sccp_line_clean(l, TRUE);
294 }
295 #endif
296 if(d)
297 break /*early*/;
298 }
299 }
300 SCCP_LIST_TRAVERSE_SAFE_END;
301 SCCP_LIST_UNLOCK(&l->devices);
302
303 if(GLOB(module_running) == TRUE && d) {
304 sccp_line_updatePreferencesFromDevicesToLine(l);
305 sccp_line_updateCapabilitiesFromDevicesToLine(l);
306 }
307 }
308
sccp_linedevice_indicateMWI(constLineDevicePtr ld)309 void sccp_linedevice_indicateMWI(constLineDevicePtr ld)
310 {
311 AUTO_RELEASE(sccp_device_t, d, sccp_device_retain(ld->device));
312 AUTO_RELEASE(sccp_line_t, l, sccp_line_retain(ld->line));
313 if(l && d) {
314 sccp_log((DEBUGCAT_MWI))(VERBOSE_PREFIX_3 "%s: (sccp_line_indicateMWI) Set voicemail lamp:%s on device:%s\n", l->name, l->voicemailStatistic.newmsgs ? "on" : "off", d->id);
315 sccp_device_setLamp(d, SKINNY_STIMULUS_VOICEMAIL, ld->lineInstance, l->voicemailStatistic.newmsgs ? d->mwilamp : SKINNY_LAMP_OFF);
316 }
317 }
318
319 /*!
320 * \brief Get Device Configuration
321 * \param device SCCP Device
322 * \param line SCCP Line
323 * \param filename Debug FileName
324 * \param lineno Debug LineNumber
325 * \param func Debug Function Name
326 * \return SCCP Line Devices
327 *
328 * \callgraph
329 * \callergraph
330 *
331 * \warning
332 * - line->devices is not always locked
333 */
__sccp_linedevice_find(constDevicePtr device,constLinePtr line,const char * filename,int lineno,const char * func)334 lineDevicePtr __sccp_linedevice_find(constDevicePtr device, constLinePtr line, const char * filename, int lineno, const char * func)
335 {
336 sccp_linedevice_t * ld = NULL;
337 sccp_line_t * l = NULL; // loose const qualifier, to be able to lock the list;
338 if(!line) {
339 pbx_log(LOG_NOTICE, "SCCP: [%s:%d]->linedevice_find: No line provided to search in\n", filename, lineno);
340 return NULL;
341 }
342 l = (sccp_line_t *)line; // loose const qualifier, to be able to lock the list;
343
344 if(!device) {
345 pbx_log(LOG_NOTICE, "SCCP: [%s:%d]->linedevice_find: No device provided to search for (line: %s)\n", filename, lineno, line->name);
346 return NULL;
347 }
348
349 SCCP_LIST_LOCK(&l->devices);
350 ld = SCCP_LIST_FIND(&l->devices, sccp_linedevice_t, tmplinedevice, list, (device == tmplinedevice->device), TRUE, filename, lineno, func);
351 SCCP_LIST_UNLOCK(&l->devices);
352
353 if(!ld) {
354 sccp_log_and((DEBUGCAT_LINE + DEBUGCAT_HIGH))(VERBOSE_PREFIX_3 "%s: [%s:%d]->linedevice_find: ld for line %s could not be found. Returning NULL\n", DEV_ID_LOG(device), filename, lineno, line->name);
355 }
356 return ld;
357 }
358
__sccp_linedevice_findByLineinstance(constDevicePtr device,uint16_t instance,const char * filename,int lineno,const char * func)359 lineDevicePtr __sccp_linedevice_findByLineinstance(constDevicePtr device, uint16_t instance, const char * filename, int lineno, const char * func)
360 {
361 sccp_linedevice_t * ld = NULL;
362
363 if(instance < 1) {
364 pbx_log(LOG_NOTICE, "%s: [%s:%d]->linedevice_find: No line provided to search in\n", DEV_ID_LOG(device), filename, lineno);
365 return NULL;
366 }
367 if(!device) {
368 pbx_log(LOG_NOTICE, "SCCP: [%s:%d]->linedevice_find: No device provided to search for (lineinstance: %d)\n", filename, lineno, instance);
369 return NULL;
370 }
371
372 if (instance < device->lineButtons.size && device->lineButtons.instance[instance]) { /* 0 < instance < lineButton.size */
373 ld = sccp_linedevice_retain(device->lineButtons.instance[instance]);
374 }
375
376 if(!ld) {
377 sccp_log_and((DEBUGCAT_LINE + DEBUGCAT_HIGH))(VERBOSE_PREFIX_3 "%s: [%s:%d]->linedevice_find: ld for lineinstance %d could not be found. Returning NULL\n", DEV_ID_LOG(device), filename, lineno, instance);
378 }
379 return ld;
380 }
381
382 /* create linebutton array */
sccp_linedevice_createButtonsArray(devicePtr device)383 void sccp_linedevice_createButtonsArray(devicePtr device)
384 {
385 sccp_linedevice_t * ld = NULL;
386 uint8_t lineInstances = 0;
387 btnlist * btn = NULL;
388 uint8_t i = 0;
389
390 if(device->lineButtons.size) {
391 sccp_linedevice_deleteButtonsArray(device);
392 }
393
394 btn = device->buttonTemplate;
395
396 for(i = 0; i < StationMaxButtonTemplateSize; i++) {
397 if(btn[i].type == SKINNY_BUTTONTYPE_LINE && btn[i].instance > lineInstances && btn[i].ptr) {
398 lineInstances = btn[i].instance;
399 }
400 }
401
402 device->lineButtons.instance = (sccp_linedevice_t **)sccp_calloc(lineInstances + SCCP_FIRST_LINEINSTANCE, sizeof(sccp_linedevice_t *));
403 if(!device->lineButtons.instance) {
404 pbx_log(LOG_ERROR, SS_Memory_Allocation_Error, device->id);
405 return;
406 }
407 device->lineButtons.size = lineInstances + SCCP_FIRST_LINEINSTANCE; /* add the offset of SCCP_FIRST_LINEINSTANCE for explicit access */
408
409 for(i = 0; i < StationMaxButtonTemplateSize; i++) {
410 if(btn[i].type == SKINNY_BUTTONTYPE_LINE && btn[i].ptr) {
411 ld = sccp_linedevice_find(device, (sccp_line_t *)btn[i].ptr);
412 if(!(device->lineButtons.instance[btn[i].instance] = ld)) {
413 pbx_log(LOG_ERROR, "%s: ld could not be found or retained\n", device->id);
414 device->lineButtons.size--;
415 sccp_free(device->lineButtons.instance);
416 }
417 }
418 }
419 }
420
sccp_linedevice_deleteButtonsArray(devicePtr device)421 void sccp_linedevice_deleteButtonsArray(devicePtr device)
422 {
423 uint8_t i = 0;
424
425 if(device->lineButtons.instance) {
426 for(i = SCCP_FIRST_LINEINSTANCE; i < device->lineButtons.size; i++) {
427 if(device->lineButtons.instance[i]) {
428 sccp_linedevice_t * tmpld = device->lineButtons.instance[i]; /* castless conversion */
429 sccp_linedevice_release(&tmpld); /* explicit release of retained ld */
430 device->lineButtons.instance[i] = NULL;
431 }
432 }
433 device->lineButtons.size = 0;
434 sccp_free(device->lineButtons.instance);
435 }
436 }
437
438 // kate: indent-width 8; replace-tabs off; indent-mode cstyle; auto-insert-doxygen on; line-numbers on; tab-indents on; keep-extra-spaces off; auto-brackets off;
439