Skip to content

Commit c38df36

Browse files
committed
app_polycompush: Add PolycomPush application.
Add an application to streamline the process of delivering push notifications to Polycom IP phones, particularly multiple, at once. This can be done in the dialplan, but it's quite unwieldy to tell what's going on. PHREAKSCRIPT-71 #close
1 parent 34f9367 commit c38df36

File tree

3 files changed

+315
-0
lines changed

3 files changed

+315
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ PhreakScript installs:
3636
- Cisco Call Manager support for `chan_sip` (with `--cisco` flag)
3737
- `chan_sccp` (improved community Skinny/SCCP channel driver), with compilation fixes (with `--sccp` flag)
3838
- Message Send Protocol send support
39+
- Streamlined delivery of push notifications to Polycom IP phones
3940
- AGI `RECORD FILE` option to require noise before silence detection
4041
- Build system support for ALSA-dependent modules
4142
- Adds the following applications:

apps/app_polycompush.c

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/*
2+
* Asterisk -- An open source telephony toolkit.
3+
*
4+
* Copyright (C) 2025, Naveen Albert <[email protected]>
5+
*
6+
* See http://www.asterisk.org for more information about
7+
* the Asterisk project. Please do not directly contact
8+
* any of the maintainers of this project for assistance;
9+
* the project provides a web site, mailing lists and IRC
10+
* channels for your use.
11+
*
12+
*/
13+
14+
/*! \file
15+
*
16+
* \brief Polycom push notifications
17+
*
18+
* \author Naveen Albert <[email protected]>
19+
*/
20+
21+
/*** MODULEINFO
22+
<depend>res_curl</depend>
23+
<depend>curl</depend>
24+
<support_level>extended</support_level>
25+
***/
26+
27+
#include "asterisk.h"
28+
29+
#include <curl/curl.h>
30+
31+
#include "asterisk/module.h"
32+
#include "asterisk/channel.h"
33+
#include "asterisk/pbx.h"
34+
#include "asterisk/utils.h"
35+
#include "asterisk/app.h"
36+
37+
/*** DOCUMENTATION
38+
<application name="PolycomPush" language="en_US">
39+
<synopsis>
40+
Send push notification to Polycom IP phone(s)
41+
</synopsis>
42+
<syntax>
43+
<parameter name="device" required="yes" argsep="&amp;">
44+
<para>A list of devices to which the push notification should be sent.</para>
45+
</parameter>
46+
<parameter name="username" required="yes">
47+
<para>Push notification username.</para>
48+
</parameter>
49+
<parameter name="password" required="yes">
50+
<para>Push notification password.</para>
51+
</parameter>
52+
<parameter name="options" required="no">
53+
<optionlist>
54+
<option name="s">
55+
<para>Use HTTPS instead of HTTP for notify request(s).</para>
56+
</option>
57+
</optionlist>
58+
</parameter>
59+
<parameter name="message" required="yes">
60+
<para>Notification to push to device(s).</para>
61+
</parameter>
62+
</syntax>
63+
<description>
64+
<para>This application simplifies the process of delivering push notifications to Polycom IP phones.</para>
65+
<para>Provide the devices corresponding to the Polycom IP phones to which notifications should be sent,
66+
and this application will look up their current IP addresses and deliver the notification using
67+
the appropriate HTTP/HTTPS request.</para>
68+
</description>
69+
</application>
70+
***/
71+
72+
static char *app = "PolycomPush";
73+
74+
static int get_ip(char *buf, size_t len, const char *device)
75+
{
76+
char tmp[282];
77+
char workspace[256];
78+
char *endpoint;
79+
80+
endpoint = strchr(device, '/');
81+
if (!endpoint) {
82+
ast_log(LOG_WARNING, "Invalid tech/device: %s\n", device);
83+
return -1;
84+
}
85+
86+
endpoint++; /* Eat the slash so we just have the name without the tech */
87+
88+
if (ast_begins_with(device, "PJSIP/")) {
89+
snprintf(tmp, sizeof(tmp), "PJSIP_AOR(%s,contact)", endpoint);
90+
if (ast_func_read(NULL, tmp, workspace, sizeof(workspace))) {
91+
ast_log(LOG_ERROR, "Failed to get contact for %s\n", endpoint);
92+
return -1;
93+
}
94+
if (ast_strlen_zero(workspace)) {
95+
ast_log(LOG_WARNING, "No AOR found for %s\n", endpoint);
96+
return -1;
97+
}
98+
ast_debug(3, "Contact for endpoint %s is %s\n", endpoint, workspace);
99+
/* If multiple contacts are present, then there's no real way to know which one to use.
100+
* Just yell at the user that there should only be 1 contact! */
101+
if (strchr(workspace, ',')) {
102+
ast_log(LOG_WARNING, "Multiple contacts detected for endpoint '%s': %s\n", endpoint, workspace);
103+
/* This will probably fail now, but go ahead and fail anyways */
104+
}
105+
snprintf(tmp, sizeof(tmp), "PJSIP_CONTACT(%s,via_addr)", workspace);
106+
if (ast_func_read(NULL, tmp, buf, len)) {
107+
ast_log(LOG_ERROR, "Failed to get IP address using contact %s\n", workspace);
108+
return -1;
109+
}
110+
ast_debug(3, "IP address of PJSIP/%s is '%s'\n", endpoint, buf);
111+
} else if (ast_begins_with(device, "SIP/")) {
112+
snprintf(tmp, sizeof(tmp), "SIPPEER(%s,via_addr)", endpoint);
113+
if (ast_func_read(NULL, tmp, buf, len)) {
114+
ast_log(LOG_ERROR, "Failed to get IP address for %s\n", endpoint);
115+
return -1;
116+
}
117+
ast_debug(3, "IP address of SIP/%s is '%s'\n", endpoint, buf);
118+
} else {
119+
ast_log(LOG_WARNING, "Unsupported channel technology: %s\n", device);
120+
return -1;
121+
}
122+
return 0;
123+
}
124+
125+
enum {
126+
OPT_HTTPS = (1 << 0),
127+
};
128+
129+
AST_APP_OPTIONS(app_opts, {
130+
AST_APP_OPTION('s', OPT_HTTPS),
131+
});
132+
133+
static size_t curl_write_string_callback(char *rawdata, size_t size, size_t nmemb, void *userdata)
134+
{
135+
struct ast_str **buffer = userdata;
136+
return ast_str_append(buffer, 0, "%.*s", (int) (size * nmemb), rawdata);
137+
}
138+
139+
struct push_notify_target {
140+
const char *device;
141+
pthread_t thread;
142+
/* References to common data */
143+
const char *userpwd;
144+
const char *message;
145+
unsigned int https:1;
146+
AST_RWLIST_ENTRY(push_notify_target) entry;
147+
char data[];
148+
};
149+
150+
AST_LIST_HEAD_NOLOCK(push_notify, push_notify_target);
151+
152+
static int notify(const char *device, int https, const char *userpwd, const char *message)
153+
{
154+
char curl_errbuf[CURL_ERROR_SIZE + 1];
155+
struct curl_slist *headers;
156+
long http_code;
157+
CURL *curl;
158+
struct ast_str *str;
159+
char ip[128];
160+
char url[sizeof(ip) + 13];
161+
162+
if (get_ip(ip, sizeof(ip), device)) {
163+
return -1;
164+
}
165+
166+
curl = curl_easy_init();
167+
if (!curl) {
168+
return -1;
169+
}
170+
171+
str = ast_str_create(512);
172+
if (!str) {
173+
curl_easy_cleanup(curl);
174+
return -1;
175+
}
176+
177+
curl_errbuf[CURL_ERROR_SIZE] = '\0';
178+
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
179+
180+
snprintf(url, sizeof(url), "http%s://%s/push", https ? "s" : "", ip);
181+
curl_easy_setopt(curl, CURLOPT_URL, url);
182+
ast_debug(2, "Making request to %s, with payload: %s\n", url, message);
183+
184+
headers = NULL;
185+
headers = curl_slist_append(headers, "Content-Type: application/xhtml+xml");
186+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
187+
188+
curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
189+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_string_callback);
190+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
191+
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 8L);
192+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 8L);
193+
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);
194+
curl_easy_setopt(curl, CURLOPT_USERPWD, userpwd);
195+
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, message);
196+
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); /* Only digest auth is accepted */
197+
if (https) {
198+
/* The phones use self-signed certs */
199+
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
200+
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
201+
}
202+
203+
if (curl_easy_perform(curl)) {
204+
ast_log(LOG_WARNING, "%s\n", curl_errbuf);
205+
curl_easy_cleanup(curl);
206+
ast_free(str);
207+
return -1;
208+
}
209+
210+
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
211+
ast_verb(4, "%-36s [%ld] %s\n", device, http_code, ast_str_buffer(str));
212+
curl_easy_cleanup(curl);
213+
ast_free(str);
214+
return http_code != 200;
215+
}
216+
217+
static void *notifier(void *varg)
218+
{
219+
struct push_notify_target *p = varg;
220+
if (notify(p->device, p->https, p->userpwd, p->message)) {
221+
ast_log(LOG_WARNING, "Failed to push notification to %s\n", p->device);
222+
}
223+
return NULL;
224+
}
225+
226+
static int predial_exec(struct ast_channel *chan, const char *data)
227+
{
228+
struct ast_flags flags;
229+
char *parse;
230+
char *device, *devices;
231+
struct push_notify n;
232+
struct push_notify_target *p;
233+
char userpwd[128];
234+
AST_DECLARE_APP_ARGS(args,
235+
AST_APP_ARG(devices);
236+
AST_APP_ARG(username);
237+
AST_APP_ARG(password);
238+
AST_APP_ARG(options);
239+
AST_APP_ARG(message);
240+
);
241+
242+
if (ast_strlen_zero(data)) {
243+
ast_log(LOG_WARNING, "Missing arguments\n");
244+
return -1;
245+
}
246+
247+
parse = ast_strdupa(data);
248+
AST_STANDARD_APP_ARGS(args, parse);
249+
250+
if (ast_strlen_zero(args.devices)) {
251+
ast_log(LOG_WARNING, "Missing devices\n");
252+
return -1;
253+
} else if (ast_strlen_zero(args.username) || ast_strlen_zero(args.password)) {
254+
ast_log(LOG_WARNING, "Must specify username and password\n");
255+
return -1;
256+
} else if (ast_strlen_zero(args.message)) {
257+
ast_log(LOG_WARNING, "Message is empty\n");
258+
return -1;
259+
}
260+
261+
if (!ast_strlen_zero(args.options)) {
262+
ast_app_parse_options(app_opts, &flags, NULL, args.options);
263+
}
264+
265+
snprintf(userpwd, sizeof(userpwd), "%s:%s", args.username, args.password);
266+
267+
memset(&n, 0, sizeof(n));
268+
269+
devices = args.devices;
270+
while ((device = strsep(&devices, "&"))) {
271+
/* Make the cURL requests in parallel, since they can be slow (1-2 seconds per endpoint) */
272+
p = ast_calloc(1, sizeof(*p) + strlen(device) + 1);
273+
if (!p) {
274+
continue;
275+
}
276+
strcpy(p->data, device);
277+
p->device = p->data;
278+
p->userpwd = userpwd;
279+
p->https = ast_test_flag(&flags, OPT_HTTPS);
280+
p->message = args.message;
281+
ast_debug(2, "Launching thread to handle %s\n", device);
282+
if (ast_pthread_create(&p->thread, NULL, notifier, p)) {
283+
ast_free(p);
284+
continue;
285+
}
286+
AST_LIST_INSERT_TAIL(&n, p, entry);
287+
}
288+
/* Wait for all the notifications to finish and clean up */
289+
ast_debug(2, "Waiting for threads to clean up\n");
290+
while ((p = AST_LIST_REMOVE_HEAD(&n, entry))) {
291+
pthread_join(p->thread, NULL);
292+
ast_free(p);
293+
}
294+
295+
return 0;
296+
}
297+
298+
static int unload_module(void)
299+
{
300+
return ast_unregister_application(app);
301+
}
302+
303+
static int load_module(void)
304+
{
305+
return ast_register_application_xml(app, predial_exec);
306+
}
307+
308+
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Polycom Push Notifications",
309+
.support_level = AST_MODULE_SUPPORT_EXTENDED,
310+
.load = load_module,
311+
.unload = unload_module,
312+
.requires = "res_curl",
313+
);

phreaknet.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,6 +2480,7 @@ phreak_patches() {
24802480
phreak_tree_module "apps/app_mwi.c"
24812481
phreak_tree_module "apps/app_partialplayback.c"
24822482
phreak_tree_module "apps/app_playdigits.c"
2483+
phreak_tree_module "apps/app_polycompush.c"
24832484
phreak_tree_module "apps/app_predial.c"
24842485
phreak_tree_module "apps/app_pulsar.c"
24852486
phreak_tree_module "apps/app_randomplayback.c"

0 commit comments

Comments
 (0)