/src/opensips/cachedb/cachedb_id.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2011 OpenSIPS Solutions |
3 | | * |
4 | | * This file is part of opensips, a free SIP server. |
5 | | * |
6 | | * opensips is free software; you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation; either version 2 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * opensips is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | | * |
20 | | * |
21 | | * history: |
22 | | * --------- |
23 | | * 2011-09-xx created (vlad-paiu) |
24 | | */ |
25 | | |
26 | | |
27 | | #include "cachedb_id.h" |
28 | | #include "../dprint.h" |
29 | | #include "../mem/mem.h" |
30 | | #include "../ut.h" |
31 | | #include <stdlib.h> |
32 | | #include <string.h> |
33 | | |
34 | | |
35 | | /** |
36 | | * Duplicate a string |
37 | | * \param dst destination |
38 | | * \param begin start of the string |
39 | | * \param end end of the string |
40 | | */ |
41 | | static int dupl_string(char** dst, const char* begin, const char* end) |
42 | 0 | { |
43 | 0 | str old, new; |
44 | |
|
45 | 0 | if (*dst) pkg_free(*dst); |
46 | |
|
47 | 0 | *dst = pkg_malloc(end - begin + 1); |
48 | 0 | if ((*dst) == NULL) { |
49 | 0 | LM_ERR("pkg malloc failed on %p/%p\n", begin, end); |
50 | 0 | return -1; |
51 | 0 | } |
52 | | |
53 | 0 | old.s = (char*)begin; |
54 | 0 | old.len = end - begin; |
55 | 0 | new.s = *dst; |
56 | 0 | un_escape(&old, &new ); |
57 | |
|
58 | 0 | new.s[new.len] = '\0'; |
59 | 0 | return 0; |
60 | 0 | } |
61 | | |
62 | | |
63 | | /** |
64 | | * Parse a database URL of form |
65 | | * scheme[:group]://[username[:password]@]hostname[:port]/database[?options] |
66 | | * |
67 | | * \param id filled id struct |
68 | | * \param url parsed URL |
69 | | * \return 0 if parsing was successful and -1 otherwise |
70 | | */ |
71 | | static int parse_cachedb_url(struct cachedb_id* id, const str* url) |
72 | 0 | { |
73 | 0 | #define SHORTEST_DB_URL "s://" |
74 | 0 | #define SHORTEST_DB_URL_LEN (sizeof(SHORTEST_DB_URL) - 1) |
75 | |
|
76 | 0 | enum state { |
77 | 0 | ST_SCHEME, /* Scheme part */ |
78 | 0 | ST_SLASH1, /* First slash */ |
79 | 0 | ST_SLASH1_GRP, /* Group Name or first / */ |
80 | 0 | ST_SLASH2, /* Second slash */ |
81 | 0 | ST_USER_HOST, /* Username or hostname */ |
82 | 0 | ST_PASS_PORT, /* Password or port part */ |
83 | 0 | ST_HOST, /* Hostname part */ |
84 | 0 | ST_HOST6, /* Hostname part IPv6 */ |
85 | 0 | ST_PORT, /* Port part */ |
86 | 0 | ST_DB, /* Database part */ |
87 | 0 | ST_OPTIONS /* Options part */ |
88 | 0 | }; |
89 | |
|
90 | 0 | enum state st; |
91 | 0 | unsigned int len, i, ipv6_flag=0, multi_hosts=0; |
92 | 0 | char* begin, *last_at, *last_slash, *last_qm; |
93 | 0 | char* prev_token,*start_host=NULL,*start_prev=NULL,*ptr; |
94 | |
|
95 | 0 | prev_token = 0; |
96 | |
|
97 | 0 | if (!id || !url || !url->s) { |
98 | 0 | goto err; |
99 | 0 | } |
100 | | |
101 | 0 | len = url->len; |
102 | 0 | if (len < SHORTEST_DB_URL_LEN) { |
103 | 0 | goto err; |
104 | 0 | } |
105 | | |
106 | 0 | LM_DBG("parsing [%.*s]\n",url->len,url->s); |
107 | | /* Initialize all attributes to 0 */ |
108 | 0 | memset(id, 0, sizeof(struct cachedb_id)); |
109 | 0 | st = ST_SCHEME; |
110 | 0 | begin = url->s; |
111 | |
|
112 | 0 | if (dupl_string(&id->initial_url,url->s,url->s+url->len) < 0) |
113 | 0 | goto err; |
114 | | |
115 | 0 | last_slash = q_memrchr(url->s, '/', url->len); |
116 | 0 | last_qm = q_memrchr(url->s, '?', url->len); |
117 | | |
118 | | /* ignore any '@' characters inside the "params" part */ |
119 | 0 | if (last_qm || last_slash) |
120 | 0 | last_at = q_memrchr(url->s, '@', |
121 | 0 | last_slash ? (last_slash-url->s) : (last_qm-url->s)); |
122 | 0 | else |
123 | 0 | last_at = q_memrchr(url->s, '@', url->len); |
124 | |
|
125 | 0 | for(i = 0; i < len; i++) { |
126 | 0 | switch(st) { |
127 | 0 | case ST_SCHEME: |
128 | 0 | switch(url->s[i]) { |
129 | 0 | case ':': |
130 | 0 | st = ST_SLASH1_GRP; |
131 | 0 | if (dupl_string(&id->scheme, begin, url->s + i) < 0) goto err; |
132 | 0 | begin = url->s+i+1; |
133 | 0 | break; |
134 | 0 | } |
135 | 0 | break; |
136 | | |
137 | 0 | case ST_SLASH1_GRP: |
138 | 0 | switch(url->s[i]) { |
139 | 0 | case ':': |
140 | 0 | st = ST_SLASH1; |
141 | 0 | if (dupl_string(&id->group_name,begin,url->s+i) < 0) goto err; |
142 | 0 | break; |
143 | 0 | case '/': |
144 | | /* a '/' not right after ':' ?? */ |
145 | 0 | if (begin!=(url->s+i)) |
146 | 0 | goto err; |
147 | 0 | st = ST_SLASH2; |
148 | 0 | break; |
149 | 0 | } |
150 | 0 | break; |
151 | | |
152 | 0 | case ST_SLASH1: |
153 | 0 | switch(url->s[i]) { |
154 | 0 | case '/': |
155 | 0 | st = ST_SLASH2; |
156 | 0 | break; |
157 | | |
158 | 0 | default: |
159 | 0 | goto err; |
160 | 0 | } |
161 | 0 | break; |
162 | | |
163 | 0 | case ST_SLASH2: |
164 | 0 | switch(url->s[i]) { |
165 | 0 | case '/': |
166 | 0 | st = ST_USER_HOST; |
167 | 0 | begin = url->s + i + 1; |
168 | 0 | break; |
169 | | |
170 | 0 | default: |
171 | 0 | goto err; |
172 | 0 | } |
173 | 0 | break; |
174 | | |
175 | 0 | case ST_USER_HOST: |
176 | 0 | switch(url->s[i]) { |
177 | 0 | case '@': |
178 | 0 | if (&url->s[i] < last_at) |
179 | 0 | break; |
180 | | |
181 | 0 | st = ST_HOST; |
182 | 0 | multi_hosts = 0; |
183 | 0 | if (dupl_string(&id->username, begin, url->s + i) < 0) goto err; |
184 | 0 | begin = url->s + i + 1; |
185 | 0 | break; |
186 | | |
187 | 0 | case ':': |
188 | 0 | if (multi_hosts) |
189 | 0 | continue; |
190 | | |
191 | 0 | st = ST_PASS_PORT; |
192 | 0 | if (dupl_string(&prev_token, begin, url->s + i) < 0) goto err; |
193 | 0 | start_prev = begin; |
194 | 0 | begin = url->s + i + 1; |
195 | 0 | break; |
196 | | |
197 | 0 | case '[': |
198 | 0 | st = ST_HOST6; |
199 | 0 | begin = url->s + i + 1; |
200 | 0 | break; |
201 | | |
202 | 0 | case '/': |
203 | 0 | if (dupl_string(&id->host, begin, url->s + i) < 0) goto err; |
204 | 0 | begin = url->s + i + 1; |
205 | 0 | st = ST_DB; |
206 | 0 | break; |
207 | | |
208 | 0 | case ',': |
209 | 0 | multi_hosts = 1; |
210 | 0 | break; |
211 | 0 | } |
212 | 0 | break; |
213 | | |
214 | 0 | case ST_PASS_PORT: |
215 | 0 | switch(url->s[i]) { |
216 | 0 | case '@': |
217 | 0 | if (&url->s[i] < last_at) |
218 | 0 | break; |
219 | | |
220 | 0 | st = ST_HOST; |
221 | 0 | id->username = prev_token; |
222 | 0 | if (dupl_string(&id->password, begin, url->s + i) < 0) goto err; |
223 | 0 | begin = url->s + i + 1; |
224 | 0 | start_host = begin; |
225 | 0 | break; |
226 | | |
227 | 0 | case '/': |
228 | 0 | id->host = prev_token; |
229 | 0 | id->port = str2s(begin, url->s + i - begin, 0); |
230 | 0 | begin = url->s + i + 1; |
231 | 0 | st = ST_DB; |
232 | 0 | break; |
233 | | |
234 | 0 | case ',': |
235 | | /* password could have a "," -> do a look-ahead to confirm */ |
236 | 0 | if (q_memchr(url->s + i, '@', len - i)) |
237 | 0 | continue; |
238 | | |
239 | 0 | st=ST_HOST; |
240 | 0 | start_host=start_prev; |
241 | 0 | id->flags |= CACHEDB_ID_MULTIPLE_HOSTS; |
242 | 0 | break; |
243 | 0 | } |
244 | 0 | break; |
245 | | |
246 | 0 | case ST_HOST: |
247 | 0 | switch(url->s[i]) { |
248 | 0 | case '[': |
249 | 0 | st = ST_HOST6; |
250 | 0 | begin = url->s + i + 1; |
251 | 0 | break; |
252 | | |
253 | 0 | case ':': |
254 | 0 | LM_DBG("in host - :\n"); |
255 | 0 | if (id->flags & CACHEDB_ID_MULTIPLE_HOSTS) { |
256 | 0 | LM_DBG("multiple hosts, skipping\n"); |
257 | 0 | break; |
258 | 0 | } |
259 | | |
260 | 0 | st = ST_PORT; |
261 | 0 | if (dupl_string(&id->host, begin, url->s + i - ipv6_flag) < 0) goto err; |
262 | 0 | start_host = begin; |
263 | 0 | begin = url->s + i + 1; |
264 | 0 | break; |
265 | | |
266 | 0 | case '/': |
267 | 0 | if (id->flags & CACHEDB_ID_MULTIPLE_HOSTS) |
268 | 0 | ptr = start_host; |
269 | 0 | else |
270 | 0 | ptr = begin; |
271 | |
|
272 | 0 | if (dupl_string(&id->host, ptr, url->s + i - ipv6_flag) < 0) goto err; |
273 | 0 | begin = url->s + i + 1; |
274 | 0 | st = ST_DB; |
275 | 0 | break; |
276 | | |
277 | 0 | case ',': |
278 | 0 | id->flags |= CACHEDB_ID_MULTIPLE_HOSTS; |
279 | 0 | break; |
280 | 0 | } |
281 | 0 | break; |
282 | | |
283 | 0 | case ST_HOST6: |
284 | 0 | switch(url->s[i]) { |
285 | 0 | case ']': |
286 | 0 | ipv6_flag = 1; |
287 | 0 | st = ST_HOST; |
288 | 0 | break; |
289 | 0 | } |
290 | 0 | break; |
291 | | |
292 | 0 | case ST_PORT: |
293 | 0 | switch(url->s[i]) { |
294 | 0 | case '/': |
295 | 0 | id->port = str2s(begin, url->s + i - begin, 0); |
296 | 0 | begin = url->s + i + 1; |
297 | 0 | st = ST_DB; |
298 | 0 | break; |
299 | | |
300 | 0 | case ',': |
301 | 0 | st = ST_HOST; |
302 | 0 | pkg_free(id->host); |
303 | 0 | id->host=NULL; |
304 | 0 | begin = start_host; |
305 | 0 | id->flags |= CACHEDB_ID_MULTIPLE_HOSTS; |
306 | 0 | break; |
307 | 0 | } |
308 | 0 | break; |
309 | | |
310 | 0 | case ST_DB: |
311 | 0 | switch(url->s[i]) { |
312 | 0 | case '?': |
313 | 0 | if (url->s + i > begin && |
314 | 0 | dupl_string(&id->database, begin, url->s + i) < 0) goto err; |
315 | 0 | if (url->s + i + 1 == url->s + len) { |
316 | 0 | st = ST_OPTIONS; |
317 | 0 | break; |
318 | 0 | } |
319 | 0 | if (dupl_string(&id->extra_options, url->s + i + 1, url->s + len) < 0) goto err; |
320 | 0 | return 0; |
321 | 0 | } |
322 | 0 | break; |
323 | | |
324 | 0 | case ST_OPTIONS: |
325 | 0 | break; |
326 | 0 | } |
327 | 0 | } |
328 | | |
329 | 0 | LM_DBG("final st: %d, begin: %s, start_host: %s\n", st, begin, start_host); |
330 | |
|
331 | 0 | if (multi_hosts) |
332 | 0 | id->flags |= CACHEDB_ID_MULTIPLE_HOSTS; |
333 | |
|
334 | 0 | if (st == ST_PORT || st == ST_PASS_PORT) { |
335 | 0 | int rc; |
336 | 0 | if (url->s + i - begin == 0) |
337 | 0 | goto err; |
338 | | |
339 | 0 | id->port = str2s(begin, url->s + i - begin, &rc); |
340 | 0 | if (rc != 0) |
341 | 0 | goto err; |
342 | | |
343 | 0 | if (prev_token && !id->host) |
344 | 0 | id->host = prev_token; |
345 | |
|
346 | 0 | return 0; |
347 | 0 | } |
348 | | |
349 | 0 | if (st == ST_DB) { |
350 | 0 | if (begin < url->s + len && |
351 | 0 | dupl_string(&id->database, begin, url->s + len) < 0) goto err; |
352 | 0 | return 0; |
353 | 0 | } |
354 | | |
355 | 0 | if (st == ST_HOST || st == ST_USER_HOST) { |
356 | 0 | if (begin == url->s+url->len) { |
357 | 0 | if (st == ST_USER_HOST) { |
358 | | /* Not considered an error - to cope with modules that |
359 | | * offer cacheDB functionality backed up by OpenSIPS mem */ |
360 | 0 | id->flags |= CACHEDB_ID_NO_URL; |
361 | 0 | LM_DBG("Just scheme, no actual url\n"); |
362 | 0 | return 0; |
363 | 0 | } else { |
364 | 0 | goto err; |
365 | 0 | } |
366 | 0 | } |
367 | | |
368 | 0 | if (start_host) |
369 | 0 | begin = start_host; |
370 | |
|
371 | 0 | if (begin < url->s + len && |
372 | 0 | dupl_string(&id->host, begin, url->s + len) < 0) goto err; |
373 | 0 | return 0; |
374 | 0 | } |
375 | | |
376 | 0 | if (st != ST_DB && st != ST_OPTIONS) goto err; |
377 | 0 | return 0; |
378 | | |
379 | 0 | err: |
380 | 0 | if (id && id->initial_url) pkg_free(id->initial_url); |
381 | 0 | if (id && id->scheme) pkg_free(id->scheme); |
382 | 0 | if (id && id->username) pkg_free(id->username); |
383 | 0 | if (id && id->password) pkg_free(id->password); |
384 | 0 | if (id && id->host) pkg_free(id->host); |
385 | 0 | if (id && id->database) pkg_free(id->database); |
386 | 0 | if (id && id->extra_options) pkg_free(id->extra_options); |
387 | 0 | if (prev_token && prev_token != id->host && prev_token != id->username) |
388 | 0 | pkg_free(prev_token); |
389 | |
|
390 | 0 | return -1; |
391 | 0 | } |
392 | | |
393 | | |
394 | | /** |
395 | | * Create a new connection identifier |
396 | | * \param url database URL |
397 | | * \return connection identifier, or zero on error |
398 | | */ |
399 | | struct cachedb_id* new_cachedb_id(const str* url) |
400 | 0 | { |
401 | 0 | struct cachedb_id* ptr; |
402 | |
|
403 | 0 | if (!url || !url->s) { |
404 | 0 | LM_ERR("invalid parameter\n"); |
405 | 0 | return 0; |
406 | 0 | } |
407 | | |
408 | 0 | ptr = pkg_malloc(sizeof(struct cachedb_id)); |
409 | 0 | if (!ptr) { |
410 | 0 | LM_ERR("no private memory left\n"); |
411 | 0 | goto err; |
412 | 0 | } |
413 | 0 | memset(ptr, 0, sizeof(struct cachedb_id)); |
414 | |
|
415 | 0 | if (parse_cachedb_url(ptr, url) < 0) { |
416 | 0 | LM_ERR("error while parsing database URL: '%s'\n", |
417 | 0 | db_url_escape(url)); |
418 | 0 | goto err; |
419 | 0 | } |
420 | | |
421 | 0 | return ptr; |
422 | | |
423 | 0 | err: |
424 | 0 | if (ptr) pkg_free(ptr); |
425 | 0 | return 0; |
426 | 0 | } |
427 | | |
428 | | |
429 | | /** |
430 | | * Compare two connection identifiers |
431 | | * \param id1 first identifier |
432 | | * \param id2 second identifier |
433 | | * \return one if both are equal, zero otherwise |
434 | | */ |
435 | | int cmp_cachedb_id(struct cachedb_id* id1, struct cachedb_id* id2) |
436 | 0 | { |
437 | 0 | if (!id1 || !id2) return 0; |
438 | | |
439 | | /* connections with different flags never match */ |
440 | 0 | if (id1->flags != id2->flags) return 0; |
441 | | /* different scehemes - never match */ |
442 | 0 | if (strcmp(id1->scheme,id2->scheme)) return 0; |
443 | | |
444 | 0 | if (id1->flags == CACHEDB_ID_NO_URL) { |
445 | | /* no url - always match, based just on scheme */ |
446 | 0 | return 1; |
447 | 0 | } |
448 | | |
449 | | /* different group names - never match */ |
450 | 0 | if ((id1->group_name == NULL && id2->group_name != NULL) || |
451 | 0 | (id1->group_name != NULL && id2->group_name == NULL)) |
452 | 0 | return 0; |
453 | 0 | if (id1->group_name && strcmp(id1->group_name,id2->group_name)) return 0; |
454 | | |
455 | | /* different usernames - never match */ |
456 | 0 | if ((id1->username == NULL && id2->username != NULL) || |
457 | 0 | (id1->username != NULL && id2->username == NULL)) |
458 | 0 | return 0; |
459 | 0 | if (id1->username && strcmp(id1->username,id2->username)) return 0; |
460 | | |
461 | | /* different passwords - never match */ |
462 | 0 | if ((id1->password == NULL && id2->password != NULL) || |
463 | 0 | (id1->password != NULL && id2->password == NULL)) |
464 | 0 | return 0; |
465 | 0 | if (id1->password && strcmp(id1->password,id2->password)) return 0; |
466 | | |
467 | 0 | if (strcmp(id1->host,id2->host)) return 0; |
468 | | |
469 | 0 | if ((id1->database == NULL && id2->database != NULL) || |
470 | 0 | (id1->database != NULL && id2->database == NULL)) |
471 | 0 | return 0; |
472 | 0 | if (id1->database && strcmp(id1->database,id2->database)) return 0; |
473 | | |
474 | 0 | if ((!id1->extra_options && id2->extra_options) || |
475 | 0 | (id1->extra_options && !id2->extra_options)) |
476 | 0 | return 0; |
477 | 0 | if (id1->extra_options && |
478 | 0 | strcmp(id1->extra_options, id2->extra_options)) return 0; |
479 | | |
480 | 0 | if (id1->flags != CACHEDB_ID_MULTIPLE_HOSTS) { |
481 | | /* also check port as it is not included in host member */ |
482 | 0 | if (id1->port != id2->port) return 0; |
483 | 0 | } |
484 | | |
485 | 0 | return 1; |
486 | 0 | } |
487 | | |
488 | | |
489 | | /** |
490 | | * Free a connection identifier |
491 | | * \param id identifier |
492 | | */ |
493 | | void free_cachedb_id(struct cachedb_id* id) |
494 | 0 | { |
495 | 0 | if (!id) return; |
496 | | |
497 | 0 | if (id->initial_url) pkg_free(id->initial_url); |
498 | 0 | if (id->scheme) pkg_free(id->scheme); |
499 | 0 | if (id->group_name) pkg_free(id->group_name); |
500 | 0 | if (id->username) pkg_free(id->username); |
501 | 0 | if (id->password) pkg_free(id->password); |
502 | 0 | if (id->host) pkg_free(id->host); |
503 | 0 | if (id->database) pkg_free(id->database); |
504 | 0 | if (id->extra_options) pkg_free(id->extra_options); |
505 | 0 | pkg_free(id); |
506 | 0 | } |