1 /*! \file utils.h
2 * \author Lorenzo Miniero <lorenzo@meetecho.com>
3 * \copyright GNU General Public License v3
4 * \brief TURN REST API client
5 * \details Implementation of the \c draft-uberti-rtcweb-turn-rest-00
6 * draft, that is a REST API that can be used to access TURN services,
7 * more specifically credentials to use. Currently implemented in both
8 * rfc5766-turn-server and coturn, and so should be generic enough to
9 * be usable here.
10 * \note This implementation depends on \c libcurl and is optional.
11 *
12 * \ingroup core
13 * \ref core
14 */
15
16 #ifdef HAVE_TURNRESTAPI
17
18 #include <netdb.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <curl/curl.h>
22 #include <jansson.h>
23 #include <agent.h>
24
25 #include "turnrest.h"
26 #include "debug.h"
27 #include "mutex.h"
28 #include "ip-utils.h"
29 #include "utils.h"
30
31 static const char *api_server = NULL;
32 static const char *api_key = NULL;
33 static gboolean api_http_get = FALSE;
34 static uint api_timeout;
35 static janus_mutex api_mutex = JANUS_MUTEX_INITIALIZER;
36
37
38 /* Buffer we use to receive the response via libcurl */
39 typedef struct janus_turnrest_buffer {
40 char *buffer;
41 size_t size;
42 } janus_turnrest_buffer;
43
44 /* Callback we use to progressively receive the whole response via libcurl in the buffer */
janus_turnrest_callback(void * payload,size_t size,size_t nmemb,void * data)45 static size_t janus_turnrest_callback(void *payload, size_t size, size_t nmemb, void *data) {
46 size_t realsize = size * nmemb;
47 janus_turnrest_buffer *buf = (struct janus_turnrest_buffer *)data;
48 /* (Re)allocate if needed */
49 buf->buffer = g_realloc(buf->buffer, buf->size+realsize+1);
50 /* Update the buffer */
51 memcpy(&(buf->buffer[buf->size]), payload, realsize);
52 buf->size += realsize;
53 buf->buffer[buf->size] = 0;
54 /* Done! */
55 return realsize;
56 }
57
58
janus_turnrest_init(void)59 void janus_turnrest_init(void) {
60 /* Initialize libcurl, needed for contacting the TURN REST API backend */
61 curl_global_init(CURL_GLOBAL_ALL);
62 }
63
janus_turnrest_deinit(void)64 void janus_turnrest_deinit(void) {
65 /* Cleanup the libcurl initialization */
66 curl_global_cleanup();
67 janus_mutex_lock(&api_mutex);
68 g_free((char *)api_server);
69 g_free((char *)api_key);
70 janus_mutex_unlock(&api_mutex);
71 }
72
janus_turnrest_set_backend(const char * server,const char * key,const char * method,const uint timeout)73 void janus_turnrest_set_backend(const char *server, const char *key, const char *method, const uint timeout) {
74 janus_mutex_lock(&api_mutex);
75
76 /* Get rid of the old values first */
77 g_free((char *)api_server);
78 api_server = NULL;
79 g_free((char *)api_key);
80 api_key = NULL;
81
82 if(server != NULL) {
83 /* Set a new server now */
84 api_server = g_strdup(server);
85 if(key != NULL)
86 api_key = g_strdup(key);
87 if(method != NULL) {
88 if(!strcasecmp(method, "get")) {
89 api_http_get = TRUE;
90 } else if(!strcasecmp(method, "post")) {
91 api_http_get = FALSE;
92 } else {
93 JANUS_LOG(LOG_WARN, "Unknown method '%s' for TURN REST API, assuming POST\n", method);
94 api_http_get = FALSE;
95 }
96 }
97 api_timeout = timeout;
98 }
99 janus_mutex_unlock(&api_mutex);
100 }
101
janus_turnrest_get_backend(void)102 const char *janus_turnrest_get_backend(void) {
103 return api_server;
104 }
105
janus_turnrest_instance_destroy(gpointer data)106 static void janus_turnrest_instance_destroy(gpointer data) {
107 janus_turnrest_instance *instance = (janus_turnrest_instance *)data;
108 if(instance == NULL)
109 return;
110 g_free(instance->server);
111 g_free(instance);
112 }
113
janus_turnrest_response_destroy(janus_turnrest_response * response)114 void janus_turnrest_response_destroy(janus_turnrest_response *response) {
115 if(response == NULL)
116 return;
117 g_free(response->username);
118 g_free(response->password);
119 g_list_free_full(response->servers, janus_turnrest_instance_destroy);
120 g_free(response);
121 }
122
janus_turnrest_request(const char * user)123 janus_turnrest_response *janus_turnrest_request(const char *user) {
124 janus_mutex_lock(&api_mutex);
125 if(api_server == NULL) {
126 janus_mutex_unlock(&api_mutex);
127 return NULL;
128 }
129 /* Prepare the request URI */
130 char query_string[512];
131 g_snprintf(query_string, 512, "service=turn");
132 if(api_key != NULL) {
133 /* Note: we've been using 'api' as a query string parameter for
134 * a while, but the expired draft this implementation follows
135 * suggested 'key' instead: as such, we send them both
136 * See https://github.com/meetecho/janus-gateway/issues/1416 */
137 char buffer[256];
138 g_snprintf(buffer, 256, "&api=%s", api_key);
139 janus_strlcat(query_string, buffer, 512);
140 g_snprintf(buffer, 256, "&key=%s", api_key);
141 janus_strlcat(query_string, buffer, 512);
142 }
143 if(user != NULL) {
144 /* Note: 'username' is supposedly optional, but a commonly used
145 * TURN REST API server implementation requires it. As such, we
146 * now send that too, letting the Janus core tell us what to use
147 * See https://github.com/meetecho/janus-gateway/issues/2199 */
148 char buffer[256];
149 g_snprintf(buffer, 256, "&username=%s", user);
150 janus_strlcat(query_string, buffer, 512);
151 }
152 char request_uri[1024];
153 g_snprintf(request_uri, 1024, "%s?%s", api_server, query_string);
154 JANUS_LOG(LOG_VERB, "Sending request: %s\n", request_uri);
155 janus_mutex_unlock(&api_mutex);
156 /* Prepare the libcurl context */
157 CURLcode res;
158 CURL *curl = curl_easy_init();
159 if(curl == NULL) {
160 JANUS_LOG(LOG_ERR, "libcurl error\n");
161 return NULL;
162 }
163 curl_easy_setopt(curl, CURLOPT_URL, request_uri);
164 curl_easy_setopt(curl, api_http_get ? CURLOPT_HTTPGET : CURLOPT_POST, 1);
165 if(!api_http_get) {
166 /* FIXME Some servers don't like a POST with no data */
167 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, query_string);
168 }
169 curl_easy_setopt(curl, CURLOPT_TIMEOUT, api_timeout);
170 /* For getting data, we use an helper struct and the libcurl callback */
171 janus_turnrest_buffer data;
172 data.buffer = g_malloc0(1);
173 data.size = 0;
174 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, janus_turnrest_callback);
175 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&data);
176 curl_easy_setopt(curl, CURLOPT_USERAGENT, "Janus/1.0");
177 /* Send the request */
178 res = curl_easy_perform(curl);
179 if(res != CURLE_OK) {
180 JANUS_LOG(LOG_ERR, "Couldn't send the request: %s\n", curl_easy_strerror(res));
181 g_free(data.buffer);
182 curl_easy_cleanup(curl);
183 return NULL;
184 }
185 /* Cleanup the libcurl context */
186 curl_easy_cleanup(curl);
187 /* Process the response */
188 JANUS_LOG(LOG_VERB, "Got %zu bytes from the TURN REST API server\n", data.size);
189 JANUS_LOG(LOG_VERB, "%s\n", data.buffer);
190 json_error_t error;
191 json_t *root = json_loads(data.buffer, 0, &error);
192 if(!root) {
193 JANUS_LOG(LOG_ERR, "Couldn't parse response: error on line %d: %s", error.line, error.text);
194 g_free(data.buffer);
195 return NULL;
196 }
197 g_free(data.buffer);
198 json_t *username = json_object_get(root, "username");
199 if(!username) {
200 JANUS_LOG(LOG_ERR, "Invalid response: missing username\n");
201 return NULL;
202 }
203 if(!json_is_string(username)) {
204 JANUS_LOG(LOG_ERR, "Invalid response: username should be a string\n");
205 return NULL;
206 }
207 json_t *password = json_object_get(root, "password");
208 if(!password) {
209 JANUS_LOG(LOG_ERR, "Invalid response: missing password\n");
210 return NULL;
211 }
212 if(!json_is_string(password)) {
213 JANUS_LOG(LOG_ERR, "Invalid response: password should be a string\n");
214 return NULL;
215 }
216 json_t *ttl = json_object_get(root, "ttl");
217 if(ttl && (!json_is_integer(ttl) || json_integer_value(ttl) < 0)) {
218 JANUS_LOG(LOG_ERR, "Invalid response: ttl should be a positive integer\n");
219 return NULL;
220 }
221 json_t *uris = json_object_get(root, "uris");
222 if(!uris) {
223 JANUS_LOG(LOG_ERR, "Invalid response: missing uris\n");
224 return NULL;
225 }
226 if(!json_is_array(uris) || json_array_size(uris) == 0) {
227 JANUS_LOG(LOG_ERR, "Invalid response: uris should be a non-empty array\n");
228 return NULL;
229 }
230 /* Turn the response into a janus_turnrest_response object we can use */
231 janus_turnrest_response *response = g_malloc(sizeof(janus_turnrest_response));
232 response->username = g_strdup(json_string_value(username));
233 response->password = g_strdup(json_string_value(password));
234 response->ttl = ttl ? json_integer_value(ttl) : 0;
235 response->servers = NULL;
236 size_t i = 0;
237 for(i=0; i<json_array_size(uris); i++) {
238 json_t *uri = json_array_get(uris, i);
239 if(uri == NULL || !json_is_string(uri)) {
240 JANUS_LOG(LOG_WARN, "Skipping invalid TURN URI (not a string)...\n");
241 continue;
242 }
243 const char *turn_uri = json_string_value(uri);
244 if(strstr(turn_uri, "turn:") != turn_uri && strstr(turn_uri, "turns:") != turn_uri) {
245 JANUS_LOG(LOG_WARN, "Skipping invalid TURN URI '%s' (not a TURN URI)...\n", turn_uri);
246 continue;
247 }
248 janus_turnrest_instance *instance = g_malloc(sizeof(janus_turnrest_instance));
249 instance->transport = NICE_RELAY_TYPE_TURN_UDP;
250 if(strstr(turn_uri, "turns:") == turn_uri || strstr(turn_uri, "transport=tls") != NULL)
251 instance->transport = NICE_RELAY_TYPE_TURN_TLS;
252 else if(strstr(turn_uri, "transport=tcp") != NULL)
253 instance->transport = NICE_RELAY_TYPE_TURN_TCP;
254 gchar **parts = NULL;
255 if(strstr(turn_uri, "?") != NULL) {
256 parts = g_strsplit(turn_uri, "?", -1);
257 turn_uri = parts[0];
258 }
259 gchar **uri_parts = g_strsplit(turn_uri, ":", -1);
260 /* Resolve the TURN URI address */
261 struct addrinfo *res = NULL;
262 janus_network_address addr;
263 janus_network_address_string_buffer addr_buf;
264 if(getaddrinfo(uri_parts[1], NULL, NULL, &res) != 0 ||
265 janus_network_address_from_sockaddr(res->ai_addr, &addr) != 0 ||
266 janus_network_address_to_string_buffer(&addr, &addr_buf) != 0) {
267 JANUS_LOG(LOG_WARN, "Skipping invalid TURN URI '%s' (could not resolve the address)...\n", uri_parts[1]);
268 if(res != NULL)
269 freeaddrinfo(res);
270 g_strfreev(uri_parts);
271 g_strfreev(parts);
272 janus_turnrest_instance_destroy(instance);
273 continue;
274 }
275 freeaddrinfo(res);
276 instance->server = g_strdup(janus_network_address_string_from_buffer(&addr_buf));
277 if(uri_parts[2] == NULL) {
278 /* No port? Use 3478 by default */
279 instance->port = 3478;
280 } else if(janus_string_to_uint16(uri_parts[2], &instance->port) < 0) {
281 JANUS_LOG(LOG_ERR, "Invalid TURN instance port: %s (falling back to 3478)\n", uri_parts[2]);
282 instance->port = 3478;
283 }
284 g_strfreev(uri_parts);
285 g_strfreev(parts);
286 /* Add the server to the list */
287 response->servers = g_list_append(response->servers, instance);
288 }
289 if(response->servers == NULL) {
290 JANUS_LOG(LOG_ERR, "Couldn't find any valid TURN URI in the response...\n");
291 janus_turnrest_response_destroy(response);
292 return NULL;
293 }
294 /* Done */
295 return response;
296 }
297
298 #endif
299