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