Coverage Report

Created: 2025-07-11 06:28

/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
}