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