Coverage Report

Created: 2026-04-12 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensips/mi/mi.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2006 Voice Sistem SRL
3
 * Copyright (C) 2018 OpenSIPS Solutions
4
 *
5
 * This file is part of opensips, a free SIP server.
6
 *
7
 * opensips is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * opensips is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
 *
21
 *
22
 */
23
24
/*
25
 * OpenSIPS Management Interface
26
 *
27
 * The OpenSIPS management interface (MI) is a plugin architecture with a few different
28
 * handlers that gives access to the management interface over various transports.
29
 *
30
 * The OpenSIPS core and modules register commands to the interface at runtime.
31
 * Look into the various module documentation files for information of these
32
 * commands.
33
 *
34
 */
35
36
#include <string.h>
37
38
#include "../dprint.h"
39
#include "../mem/mem.h"
40
#include "../mem/shm_mem.h"
41
#include "../lib/cJSON.h"
42
#include "../lib/osips_malloc.h"
43
#include "mi.h"
44
#include "mi_trace.h"
45
46
static struct mi_cmd*  mi_cmds = 0;
47
static int mi_cmds_no = 0;
48
49
static mi_response_t *build_err_resp(int code, const char *msg, int msg_len,
50
    const char *details, int details_len);
51
52
0
#define MI_MODULE_SEP ':'
53
0
#define MI_DEFAULT_MODULE_NAME "core"
54
55
struct mi_mod_group {
56
  int id;
57
  str_const module;
58
  int start;
59
  int end;
60
};
61
62
struct mi_cmd_alias {
63
  int id;
64
  str name;
65
  int cmd_idx;
66
};
67
68
static struct mi_mod_group *mi_mod_groups = 0;
69
static int mi_mod_groups_no = 0;
70
static struct mi_cmd_alias *mi_cmd_aliases = 0;
71
static int mi_cmd_aliases_no = 0;
72
73
void _init_mi_sys_mem_hooks(void)
74
0
{
75
0
  cJSON_InitHooks(&sys_mem_hooks);
76
0
}
77
78
void _init_mi_shm_mem_hooks(void)
79
0
{
80
0
  cJSON_InitHooks(&shm_mem_hooks);
81
0
}
82
83
void _init_mi_pkg_mem_hooks(void)
84
0
{
85
0
  cJSON_InitHooks(NULL);
86
0
}
87
88
static inline int get_mi_id(const char *name, int len)
89
0
{
90
0
  int n;
91
0
  int i;
92
93
0
  for( n=0,i=0 ; i<len ; n+=name[i] ,i++ );
94
0
  return n;
95
0
}
96
97
98
static inline struct mi_cmd* lookup_mi_cmd_id(int id, const char *name, int len)
99
0
{
100
0
  int i;
101
102
0
  for( i=0 ; i<mi_cmds_no ; i++ ) {
103
0
    if ( id==mi_cmds[i].id && len==mi_cmds[i].name.len &&
104
0
    memcmp(mi_cmds[i].name.s,name,len)==0 )
105
0
      return &mi_cmds[i];
106
0
  }
107
108
0
  return 0;
109
0
}
110
111
static void mark_ambiguous_mi_cmd_name(struct mi_cmd *new_cmd)
112
0
{
113
0
  const char *new_local_name;
114
0
  int new_local_len;
115
0
  int i;
116
117
0
  new_local_name = new_cmd->name.s + new_cmd->module.len + 1;
118
0
  new_local_len = new_cmd->name.len - new_cmd->module.len - 1;
119
120
0
  for (i = 0; i < mi_cmds_no - 1; i++) {
121
0
    if (mi_cmds[i].local_id != new_cmd->local_id ||
122
0
        mi_cmds[i].name.len - mi_cmds[i].module.len - 1 != new_local_len ||
123
0
        memcmp(mi_cmds[i].name.s + mi_cmds[i].module.len + 1,
124
0
          new_local_name, new_local_len) != 0)
125
0
      continue;
126
127
0
    mi_cmds[i].flags |= MI_LOCAL_NAME_AMBIGUOUS;
128
0
    new_cmd->flags |= MI_LOCAL_NAME_AMBIGUOUS;
129
0
  }
130
131
0
}
132
133
static int add_mi_cmd_alias(const char *name, int len, int cmd_idx)
134
0
{
135
0
  struct mi_cmd_alias *aliases;
136
0
  int id;
137
0
  int i;
138
139
0
  if (!name || len <= 0)
140
0
    return 0;
141
142
0
  id = get_mi_id(name, len);
143
0
  for (i = 0; i < mi_cmd_aliases_no; i++) {
144
0
    if (mi_cmd_aliases[i].id != id ||
145
0
        mi_cmd_aliases[i].name.len != len ||
146
0
        memcmp(mi_cmd_aliases[i].name.s, name, len) != 0)
147
0
      continue;
148
0
    if (mi_cmd_aliases[i].cmd_idx == cmd_idx)
149
0
      return 0;
150
0
  }
151
152
0
  aliases = (struct mi_cmd_alias *)pkg_realloc(mi_cmd_aliases,
153
0
      (mi_cmd_aliases_no + 1) * sizeof(struct mi_cmd_alias));
154
0
  if (!aliases) {
155
0
    LM_ERR("no more pkg memory\n");
156
0
    return -1;
157
0
  }
158
159
0
  mi_cmd_aliases = aliases;
160
0
  mi_cmd_aliases[mi_cmd_aliases_no].id = id;
161
0
  mi_cmd_aliases[mi_cmd_aliases_no].name.s = (char *)name;
162
0
  mi_cmd_aliases[mi_cmd_aliases_no].name.len = len;
163
0
  mi_cmd_aliases[mi_cmd_aliases_no].cmd_idx = cmd_idx;
164
0
  mi_cmd_aliases_no++;
165
166
0
  return 0;
167
0
}
168
169
static inline struct mi_cmd* lookup_mi_cmd_alias(const char *name, int len)
170
0
{
171
0
  struct mi_cmd *match = NULL;
172
0
  struct mi_cmd *cmd;
173
0
  int id;
174
0
  int i;
175
176
0
  id = get_mi_id(name, len);
177
0
  for (i = 0; i < mi_cmd_aliases_no; i++) {
178
0
    if (mi_cmd_aliases[i].id != id ||
179
0
        mi_cmd_aliases[i].name.len != len ||
180
0
        memcmp(mi_cmd_aliases[i].name.s, name, len) != 0)
181
0
      continue;
182
183
0
    cmd = &mi_cmds[mi_cmd_aliases[i].cmd_idx];
184
0
    if (!match) {
185
0
      match = cmd;
186
0
      continue;
187
0
    }
188
189
0
    if (match != cmd)
190
0
      return NULL;
191
0
  }
192
193
0
  return match;
194
0
}
195
196
static inline struct mi_cmd* lookup_mi_cmd_local(const char *name, int len,
197
    int *ambiguous)
198
0
{
199
0
  struct mi_cmd *cmd;
200
0
  struct mi_cmd *match = NULL;
201
0
  int local_id;
202
0
  int i;
203
204
0
  if (ambiguous)
205
0
    *ambiguous = 0;
206
207
0
  local_id = get_mi_id(name, len);
208
209
0
  for (i = 0; i < mi_cmds_no; i++) {
210
0
    cmd = &mi_cmds[i];
211
0
    if (cmd->local_id != local_id ||
212
0
        cmd->name.len - cmd->module.len - 1 != len ||
213
0
        memcmp(cmd->name.s + cmd->module.len + 1, name, len) != 0)
214
0
      continue;
215
216
0
    if ((cmd->flags & MI_LOCAL_NAME_AMBIGUOUS) || match) {
217
0
      if (ambiguous)
218
0
        *ambiguous = 1;
219
0
      return NULL;
220
0
    }
221
222
0
    match = cmd;
223
0
  }
224
225
0
  return match;
226
0
}
227
228
static const char *get_mi_mod_name(const char *mod_name)
229
0
{
230
0
  if (!mod_name || !*mod_name)
231
0
    return MI_DEFAULT_MODULE_NAME;
232
233
0
  return mod_name;
234
0
}
235
236
static mi_response_t *build_ambiguous_mi_cmd_resp(const char *name, int len)
237
0
{
238
0
  struct mi_cmd *cmd;
239
0
  mi_response_t *resp;
240
0
  int *matches;
241
0
  char *details;
242
0
  int details_len;
243
0
  int local_len;
244
0
  int needed;
245
0
  int is_local;
246
0
  int i;
247
0
  int n;
248
0
  int p;
249
0
  int j;
250
251
0
  if (!name || len <= 0)
252
0
    return NULL;
253
254
0
  if (mi_cmds_no <= 1)
255
0
    return NULL;
256
257
0
  matches = pkg_malloc(mi_cmds_no * sizeof(int));
258
0
  if (!matches) {
259
0
    LM_ERR("no more pkg memory\n");
260
0
    return build_err_resp(JSONRPC_AMBIG_METHOD_CODE,
261
0
        MI_SSTR(JSONRPC_AMBIG_METHOD_MSG),
262
0
        MI_SSTR("ambiguous MI command"));
263
0
  }
264
265
0
  is_local = (memchr(name, MI_MODULE_SEP, len) == NULL);
266
0
  n = 0;
267
0
  if (is_local) {
268
0
    for (i = 0; i < mi_cmds_no; i++) {
269
0
      cmd = &mi_cmds[i];
270
0
      local_len = cmd->name.len - cmd->module.len - 1;
271
0
      if (local_len != len ||
272
0
          memcmp(cmd->name.s + cmd->module.len + 1, name, len) != 0)
273
0
        continue;
274
275
0
      matches[n++] = i;
276
0
    }
277
0
  }
278
279
0
  for (i = 0; i < mi_cmd_aliases_no; i++) {
280
0
    if (mi_cmd_aliases[i].name.len != len ||
281
0
        memcmp(mi_cmd_aliases[i].name.s, name, len) != 0)
282
0
      continue;
283
284
0
    for (j = 0; j < n; j++)
285
0
      if (matches[j] == mi_cmd_aliases[i].cmd_idx)
286
0
        break;
287
0
    if (j == n)
288
0
      matches[n++] = mi_cmd_aliases[i].cmd_idx;
289
0
  }
290
291
0
  if (n < 2) {
292
0
    pkg_free(matches);
293
0
    return NULL;
294
0
  }
295
296
0
  details_len = 0;
297
0
  for (i = 0; i < n; i++) {
298
0
    details_len += mi_cmds[matches[i]].name.len;
299
0
    if (i > 0)
300
0
      details_len += 2;
301
0
  }
302
303
0
  needed = sizeof("supported names: ") - 1 + details_len;
304
0
  details = pkg_malloc(needed + 1);
305
0
  if (!details) {
306
0
    LM_ERR("no more pkg memory\n");
307
0
    pkg_free(matches);
308
0
    return build_err_resp(JSONRPC_AMBIG_METHOD_CODE,
309
0
        MI_SSTR(JSONRPC_AMBIG_METHOD_MSG),
310
0
        MI_SSTR("ambiguous MI command"));
311
0
  }
312
313
0
  memcpy(details, "supported names: ", sizeof("supported names: ") - 1);
314
0
  p = sizeof("supported names: ") - 1;
315
0
  for (i = 0; i < n; i++) {
316
0
    if (i > 0) {
317
0
      details[p++] = ',';
318
0
      details[p++] = ' ';
319
0
    }
320
321
0
    cmd = &mi_cmds[matches[i]];
322
0
    memcpy(details + p, cmd->name.s, cmd->name.len);
323
0
    p += cmd->name.len;
324
0
  }
325
326
0
  details[p] = '\0';
327
0
  resp = build_err_resp(JSONRPC_AMBIG_METHOD_CODE,
328
0
      MI_SSTR(JSONRPC_AMBIG_METHOD_MSG), details, p);
329
0
  pkg_free(matches);
330
0
  pkg_free(details);
331
0
  return resp;
332
0
}
333
334
static int add_mi_mod_group(const char *mod_name, int mod_len, int start, int end)
335
0
{
336
0
  struct mi_mod_group *groups;
337
0
  int mod_id;
338
339
0
  if (start >= end)
340
0
    return 0;
341
342
0
  mod_id = get_mi_id(mod_name, mod_len);
343
344
0
  if (mi_mod_groups_no > 0 &&
345
0
      mi_mod_groups[mi_mod_groups_no - 1].id == mod_id &&
346
0
      mi_mod_groups[mi_mod_groups_no - 1].end == start &&
347
0
      mi_mod_groups[mi_mod_groups_no - 1].module.len == mod_len &&
348
0
      memcmp(mi_mod_groups[mi_mod_groups_no - 1].module.s,
349
0
        mod_name, mod_len) == 0) {
350
0
    mi_mod_groups[mi_mod_groups_no - 1].end = end;
351
0
    return 0;
352
0
  }
353
354
0
  groups = (struct mi_mod_group *)pkg_realloc(mi_mod_groups,
355
0
      (mi_mod_groups_no + 1) * sizeof(struct mi_mod_group));
356
0
  if (!groups) {
357
0
    LM_ERR("no more pkg memory\n");
358
0
    return -1;
359
0
  }
360
361
0
  mi_mod_groups = groups;
362
0
  mi_mod_groups[mi_mod_groups_no].id = mod_id;
363
0
  mi_mod_groups[mi_mod_groups_no].module.s = mod_name;
364
0
  mi_mod_groups[mi_mod_groups_no].module.len = mod_len;
365
0
  mi_mod_groups[mi_mod_groups_no].start = start;
366
0
  mi_mod_groups[mi_mod_groups_no].end = end;
367
0
  mi_mod_groups_no++;
368
369
0
  return 0;
370
0
}
371
372
static char *build_mi_cmd_name(const char *mod_name, const char *cmd_name)
373
0
{
374
0
  int mod_len;
375
0
  int cmd_len;
376
0
  char *full_name;
377
378
0
  mod_len = strlen(mod_name);
379
0
  cmd_len = strlen(cmd_name);
380
381
0
  full_name = pkg_malloc(mod_len + 1 + cmd_len + 1);
382
0
  if (!full_name)
383
0
    return NULL;
384
385
0
  memcpy(full_name, mod_name, mod_len);
386
0
  full_name[mod_len] = MI_MODULE_SEP;
387
0
  memcpy(full_name + mod_len + 1, cmd_name, cmd_len);
388
0
  full_name[mod_len + 1 + cmd_len] = '\0';
389
390
0
  return full_name;
391
0
}
392
393
394
int register_mi_mod(const char *mod_name, const mi_export_t *mis)
395
0
{
396
0
  const char *module_name;
397
0
  const char *const *aliases;
398
0
  char *cmd_name;
399
0
  int cmd_idx;
400
0
  int mod_len;
401
0
  int start;
402
0
  int ret;
403
0
  int i;
404
0
  int j;
405
406
0
  if (mis==0)
407
0
    return 0;
408
409
0
  module_name = get_mi_mod_name(mod_name);
410
0
  mod_len = strlen(module_name);
411
0
  start = mi_cmds_no;
412
413
0
  for ( i=0 ; mis[i].name ; i++ ) {
414
0
    if (strchr(mis[i].name, MI_MODULE_SEP)) {
415
0
      LM_ERR("invalid MI cmd <%s> for module %s (must not include '%c')\n",
416
0
          mis[i].name, module_name, MI_MODULE_SEP);
417
0
      return -1;
418
0
    }
419
420
0
    cmd_name = build_mi_cmd_name(module_name, mis[i].name);
421
0
    if (!cmd_name) {
422
0
      LM_ERR("oom while building MI cmd <%s> for module %s\n",
423
0
          mis[i].name, module_name);
424
0
      return -1;
425
0
    }
426
427
0
    ret = register_mi_cmd(cmd_name, mis[i].help, mis[i].flags,
428
0
        mis[i].init_f, MI_EXPORT_RECIPES(mis[i]), module_name);
429
0
    if (ret!=0) {
430
0
      LM_ERR("failed to register cmd <%s> for module %s\n",
431
0
          mis[i].name, module_name);
432
0
      pkg_free(cmd_name);
433
0
      continue;
434
0
    }
435
436
0
    cmd_idx = mi_cmds_no - 1;
437
0
    if (add_mi_cmd_alias(mis[i].name, strlen(mis[i].name), cmd_idx) < 0)
438
0
      return -1;
439
440
0
    aliases = MI_EXPORT_ALIASES(mis[i]);
441
0
    for (j = 0; aliases && aliases[j]; j++)
442
0
      if (add_mi_cmd_alias(aliases[j], strlen(aliases[j]), cmd_idx) < 0)
443
0
        return -1;
444
0
  }
445
446
0
  if (add_mi_mod_group(module_name, mod_len, start, mi_cmds_no) < 0)
447
0
    return -1;
448
449
0
  return 0;
450
0
}
451
452
int register_mi_cmd_alias(const char *mod_name, const char *cmd_name,
453
    const char *alias)
454
0
{
455
0
  const char *module_name;
456
0
  struct mi_cmd *cmd;
457
0
  char *full_name;
458
0
  int cmd_idx;
459
0
  int id;
460
0
  int len;
461
0
  int ret;
462
463
0
  if (!cmd_name || !alias) {
464
0
    LM_ERR("invalid params cmd_name=%s alias=%s\n", cmd_name, alias);
465
0
    return -1;
466
0
  }
467
468
0
  module_name = get_mi_mod_name(mod_name);
469
0
  full_name = build_mi_cmd_name(module_name, cmd_name);
470
0
  if (!full_name) {
471
0
    LM_ERR("oom while building canonical MI cmd for alias registration\n");
472
0
    return -1;
473
0
  }
474
475
0
  len = strlen(full_name);
476
0
  id = get_mi_id(full_name, len);
477
0
  cmd = lookup_mi_cmd_id(id, full_name, len);
478
0
  if (!cmd) {
479
0
    LM_ERR("cannot register alias <%s>: unknown canonical cmd <%s>\n",
480
0
        alias, full_name);
481
0
    pkg_free(full_name);
482
0
    return -1;
483
0
  }
484
485
0
  cmd_idx = cmd - mi_cmds;
486
0
  ret = add_mi_cmd_alias(alias, strlen(alias), cmd_idx);
487
0
  pkg_free(full_name);
488
0
  return ret;
489
0
}
490
491
int register_mi_cmd_aliases(const char *mod_name, const char *cmd_name,
492
    const char *const *aliases)
493
0
{
494
0
  int i;
495
496
0
  if (!aliases)
497
0
    return 0;
498
499
0
  for (i = 0; aliases[i]; i++)
500
0
    if (register_mi_cmd_alias(mod_name, cmd_name, aliases[i]) < 0)
501
0
      return -1;
502
503
0
  return 0;
504
0
}
505
506
507
int init_mi_child(void)
508
0
{
509
0
  int i;
510
511
0
  for ( i=0 ; i<mi_cmds_no ; i++ ) {
512
0
    if ( mi_cmds[i].init_f && mi_cmds[i].init_f()!=0 ) {
513
0
      LM_ERR("failed to init <%.*s>\n",
514
0
          mi_cmds[i].name.len,mi_cmds[i].name.s);
515
0
      return -1;
516
0
    }
517
0
  }
518
0
  return 0;
519
0
}
520
521
522
523
int register_mi_cmd(char *name, char *help, unsigned int flags,
524
    mi_child_init_f in, const mi_recipe_t *recipes, const char* mod_name)
525
0
{
526
0
  struct mi_cmd *cmds;
527
0
  int mod_len;
528
0
  int id;
529
0
  int len;
530
531
0
  if (recipes==0 || name==0 || mod_name==0) {
532
0
    LM_ERR("invalid params recipes=%p, name=%s\n", recipes, name);
533
0
    return -1;
534
0
  }
535
536
0
  mod_len = strlen(mod_name);
537
0
  len = strlen(name);
538
0
  if (len <= mod_len + 1 || memcmp(name, mod_name, mod_len) != 0 ||
539
0
      name[mod_len] != MI_MODULE_SEP) {
540
0
    LM_ERR("invalid command <%s> for module %s\n", name, mod_name);
541
0
    return -1;
542
0
  }
543
544
0
  id = get_mi_id(name,len);
545
546
0
  if (lookup_mi_cmd_id( id, name, len)) {
547
0
    LM_ERR("command <%.*s> already registered\n", len, name);
548
0
    return -1;
549
0
  }
550
551
0
  cmds = (struct mi_cmd*)pkg_realloc( mi_cmds,
552
0
      (mi_cmds_no+1)*sizeof(struct mi_cmd) );
553
0
  if (cmds==0) {
554
0
    LM_ERR("no more pkg memory\n");
555
0
    return -1;
556
0
  }
557
558
0
  mi_cmds = cmds;
559
0
  mi_cmds_no++;
560
561
0
  cmds = &cmds[mi_cmds_no-1];
562
563
0
  cmds->init_f = in;
564
0
  cmds->flags = flags & (~MI_LOCAL_NAME_AMBIGUOUS);
565
0
  cmds->name.s = name;
566
0
  cmds->name.len = len;
567
0
  cmds->module.s = mod_name;
568
0
  cmds->module.len = strlen(mod_name);
569
0
  cmds->local_id = get_mi_id(name + mod_len + 1, len - mod_len - 1);
570
0
  cmds->help.s = help;
571
0
  cmds->help.len = help ? strlen(help) : 0;
572
0
  cmds->id = id;
573
0
  cmds->recipes = recipes;
574
575
0
  mark_ambiguous_mi_cmd_name(cmds);
576
577
  /**
578
   * FIXME we should check if trace_api is loaded not to lose unnecessary space
579
   * but some mi commands might have been already registered before loading the api
580
   * an example is the statistics mi commands
581
   */
582
0
  cmds->trace_mask = shm_malloc(sizeof(volatile unsigned char));
583
0
  if ( !cmds->trace_mask ) {
584
0
    LM_ERR("no more shm mem!\n");
585
0
    return -1;
586
0
  }
587
588
  /* by default all commands are traced */
589
0
  *cmds->trace_mask = (~(volatile unsigned char)0);
590
591
0
  return 0;
592
0
}
593
594
595
596
struct mi_cmd* lookup_mi_cmd( char *name, int len)
597
0
{
598
0
  char *cmd_name;
599
0
  struct mi_cmd *cmd;
600
0
  int amb;
601
0
  int mod_len;
602
0
  int mod_id;
603
0
  int id;
604
0
  int i;
605
0
  int j;
606
607
0
  cmd_name = memchr(name, MI_MODULE_SEP, len);
608
0
  if (!cmd_name) {
609
0
    cmd = lookup_mi_cmd_local(name, len, &amb);
610
0
    if (cmd || amb)
611
0
      return cmd;
612
0
    return lookup_mi_cmd_alias(name, len);
613
0
  }
614
0
  if (cmd_name == name || cmd_name == name + len - 1)
615
0
    return NULL;
616
617
0
  mod_len = cmd_name - name;
618
0
  mod_id = get_mi_id(name, mod_len);
619
0
  id = get_mi_id(name, len);
620
621
0
  for (i = 0; i < mi_mod_groups_no; i++) {
622
0
    if (mi_mod_groups[i].id != mod_id ||
623
0
        mi_mod_groups[i].module.len != mod_len ||
624
0
        memcmp(mi_mod_groups[i].module.s, name, mod_len) != 0)
625
0
      continue;
626
627
0
    for (j = mi_mod_groups[i].start; j < mi_mod_groups[i].end; j++) {
628
0
      cmd = &mi_cmds[j];
629
0
      if (id == cmd->id && len == cmd->name.len &&
630
0
          memcmp(cmd->name.s, name, len) == 0)
631
0
        return cmd;
632
0
    }
633
0
  }
634
635
0
  return lookup_mi_cmd_alias(name, len);
636
0
}
637
638
639
void get_mi_cmds( struct mi_cmd** cmds, int *size)
640
0
{
641
0
  *cmds = mi_cmds;
642
0
  *size = mi_cmds_no;
643
0
}
644
645
int parse_mi_request(const char *req, const char **end_ptr, mi_request_t *parsed)
646
0
{
647
0
  mi_item_t *req_jsonrpc;
648
649
0
  _init_mi_sys_mem_hooks();
650
651
0
  parsed->req_obj = cJSON_ParseWithOpts(req, end_ptr, 0);
652
0
  if (!parsed->req_obj) {
653
0
    _init_mi_pkg_mem_hooks();
654
0
    return -1;
655
0
  }
656
657
  /* check if the request is a valid JSON-RPC Request object */
658
  /* get request id (if absent -> notification) */
659
0
  parsed->id = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_ID_S);
660
0
  if (parsed->id && !(parsed->id->type & (cJSON_NULL|cJSON_Number|cJSON_String)))
661
0
    parsed->invalid = 1;
662
663
  /* check 'jsonrpc' member */
664
0
  req_jsonrpc = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_S);
665
0
  if (!req_jsonrpc || !(req_jsonrpc->type & cJSON_String) ||
666
0
    strcmp(req_jsonrpc->valuestring, JSONRPC_VERS_S))
667
0
    parsed->invalid = 1;
668
669
  /* check 'method' member */
670
0
  parsed->method = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_METHOD_S);
671
0
  if (!parsed->method || !(parsed->method->type & cJSON_String)) {
672
0
    parsed->method = NULL;
673
0
    parsed->invalid = 1;
674
0
  }
675
676
  /* check 'params' member */
677
0
  parsed->params = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_PARAMS_S);
678
0
  if (parsed->params) {
679
0
    if (!(parsed->params->type & (cJSON_Array|cJSON_Object)))
680
0
      parsed->invalid = 1;
681
0
    else if (!parsed->params->child) {
682
0
      parsed->params = NULL;
683
0
    }
684
0
  }
685
686
0
  _init_mi_pkg_mem_hooks();
687
688
0
  return 0;
689
0
}
690
691
char *mi_get_req_method(mi_request_t *req)
692
0
{
693
0
  if (!req || !req->method)
694
0
    return NULL;
695
696
0
  return req->method->valuestring;
697
0
}
698
699
static int match_named_params(const mi_recipe_t *recipe, mi_item_t *req_params)
700
0
{
701
0
  mi_item_t *param;
702
0
  int i;
703
704
0
  for (i = 0; recipe->params[i]; i++) {
705
0
    for (param = req_params->child; param; param = param->next)
706
0
      if (param->string && !strcmp(recipe->params[i], param->string))
707
0
        break;
708
709
0
    if (!param)
710
0
      return 0;
711
0
  }
712
713
0
  return 1;
714
0
}
715
716
static int match_no_params(const mi_recipe_t *recipe, mi_item_t *req_params)
717
0
{
718
0
  mi_item_t *param;
719
0
  int i, j;
720
721
0
  for (i = 0; recipe->params[i]; i++) ;
722
723
0
  for (param = req_params->child, j = 0; param; param = param->next, j++) ;
724
725
0
  return i == j;
726
0
}
727
728
static const mi_recipe_t *get_cmd_recipe(const mi_recipe_t *recipes, mi_item_t *req_params,
729
                int pos_params, int *params_err)
730
0
{
731
0
  const mi_recipe_t *match = NULL;
732
0
  int i;
733
734
0
  for (i = 0; recipes[i].cmd; i++) {
735
0
    if (!req_params) {
736
0
      if (recipes[i].params[0] == NULL)
737
0
        return &recipes[i];
738
0
      else
739
0
        continue;
740
0
    } else {
741
0
      if (recipes[i].params[0] == NULL)
742
0
        continue;
743
0
    }
744
745
0
    if (pos_params) {
746
0
      if (match_no_params(&recipes[i], req_params)) {
747
0
        if (match) {
748
0
          *params_err = -2;
749
0
          return NULL;
750
0
        } else {
751
0
          match = &recipes[i];
752
0
        }
753
0
      }
754
0
    } else {
755
0
      if (match_no_params(&recipes[i], req_params))
756
0
        *params_err = -3;
757
0
      else
758
0
        continue;
759
760
0
      if (match_named_params(&recipes[i], req_params))
761
0
        return &recipes[i];
762
0
    }
763
0
  }
764
765
0
  return match;
766
0
}
767
768
static mi_response_t *build_err_resp(int code, const char *msg, int msg_len,
769
                const char *details, int details_len)
770
0
{
771
0
  mi_response_t *err_resp;
772
773
0
  err_resp = init_mi_error_extra(code, msg, msg_len, details, details_len);
774
0
  if (!err_resp)
775
0
    LM_ERR("Failed to build MI error response object\n");
776
777
0
  return err_resp;
778
0
}
779
780
mi_response_t *handle_mi_request(mi_request_t *req, struct mi_cmd *cmd,
781
              struct mi_handler *async_hdl)
782
0
{
783
0
  mi_response_t *resp;
784
0
  const mi_recipe_t *cmd_recipe;
785
0
  char *method;
786
0
  mi_params_t cmd_params;
787
0
  int params_err = -1;
788
0
  int pos_params;
789
790
0
  if (!req->req_obj) {  /* error parsing the request JSON text */
791
0
    LM_ERR("Failed to parse the request JSON text\n");
792
0
    return build_err_resp(JSONRPC_PARSE_ERR_CODE,
793
0
          MI_SSTR(JSONRPC_PARSE_ERR_MSG), NULL, 0);
794
0
  }
795
796
0
  if (req->invalid) {  /* invalid jsonrpc request */
797
0
    LM_ERR("Invalid JSON-RPC request\n");
798
0
    return build_err_resp(JSONRPC_INVAL_REQ_CODE,
799
0
          MI_SSTR(JSONRPC_INVAL_REQ_MSG), NULL, 0);
800
0
  }
801
802
0
  if (!cmd) {
803
0
    method = mi_get_req_method(req);
804
0
    resp = build_ambiguous_mi_cmd_resp(method, strlen(method));
805
0
    if (resp)
806
0
      return resp;
807
808
0
    LM_ERR("Command not found\n");
809
0
    return build_err_resp(JSONRPC_NOT_FOUND_CODE,
810
0
        MI_SSTR(JSONRPC_NOT_FOUND_MSG), NULL, 0);
811
0
  }
812
813
0
  pos_params = req->params ? req->params->type & cJSON_Array : 0;
814
0
  if (pos_params && (cmd->flags & MI_NAMED_PARAMS_ONLY)) {
815
0
    LM_ERR("Command only supports named parameters\n");
816
0
    return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
817
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG),
818
0
        MI_SSTR(ERR_DET_POS_PARAMS_S));
819
0
  }
820
821
  /* use the correct 'recipe' of the command based
822
   * on the received parameters */
823
0
  cmd_recipe = get_cmd_recipe(cmd->recipes, req->params, pos_params,
824
0
          &params_err);
825
0
  if (!cmd_recipe) {
826
0
    LM_ERR("Invalid parameters\n");
827
0
    if (params_err == -1)
828
0
      return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
829
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG), MI_SSTR(ERR_DET_NO_PARAMS_S));
830
0
    else if (params_err == -2)
831
0
      return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
832
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG),
833
0
        MI_SSTR(ERR_DET_AMBIG_CALL_S));
834
0
    else
835
0
      return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
836
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG),
837
0
        MI_SSTR(ERR_DET_MATCH_PARAMS_S));
838
0
  }
839
840
0
  cmd_params.item = req->params;
841
0
  cmd_params.list = cmd_recipe->params;
842
843
0
  resp = cmd_recipe->cmd(&cmd_params, async_hdl);
844
845
0
  if (resp == NULL) {
846
0
    LM_ERR("Command failed\n");
847
0
    return build_err_resp(JSONRPC_SERVER_ERR_CODE,
848
0
        MI_SSTR(JSONRPC_SERVER_ERR_MSG), MI_SSTR(ERR_DET_CMD_NULL_S));
849
0
  } else
850
0
    return resp;
851
0
}
852
853
int add_id_to_response(mi_item_t *id, mi_response_t *resp)
854
0
{
855
0
  if (!id) {
856
0
    if (add_mi_null(resp, MI_SSTR(JSONRPC_ID_S)) < 0) {
857
0
      LM_ERR("Failed to add null value to MI item\n");
858
0
      return -1;
859
0
    }
860
861
0
    return 0;
862
0
  }
863
864
0
  switch ((id->type) & 0xFF) {
865
0
    case cJSON_Number:
866
0
      if (add_mi_number(resp, MI_SSTR(JSONRPC_ID_S), id->valueint) < 0) {
867
0
        LM_ERR("Failed to add int value to MI item\n");
868
0
        return -1;
869
0
      }
870
0
      break;
871
0
    case cJSON_String:
872
0
      if (add_mi_string(resp, MI_SSTR(JSONRPC_ID_S), id->valuestring,
873
0
        strlen(id->valuestring)) < 0) {
874
0
        LM_ERR("Failed to add string value to MI item\n");
875
0
        return -1;
876
0
      }
877
0
      break;
878
0
    case cJSON_NULL:
879
0
      if (add_mi_null(resp, MI_SSTR(JSONRPC_ID_S)) < 0) {
880
0
        LM_ERR("Failed to add null value to MI item\n");
881
0
        return -1;
882
0
      }
883
0
      break;
884
0
    default:
885
0
      LM_ERR("'id' must be a String, Number or Null value\n");
886
0
      return -1;
887
0
  }
888
889
0
  return 0;
890
0
}
891
892
static int prepare_mi_response(mi_response_t *resp, mi_item_t *id)
893
0
{
894
0
  mi_item_t *res_err, *res_err_code = NULL;
895
896
0
  res_err = cJSON_GetObjectItem(resp, JSONRPC_ERROR_S);
897
0
  if (res_err) {
898
0
    res_err_code = cJSON_GetObjectItem(res_err, JSONRPC_ERR_CODE_S);
899
0
    if (!res_err_code) {
900
0
      LM_ERR("no error code for MI error response\n");
901
0
      return -1;
902
0
    }
903
0
  }
904
905
0
  if (!id) {
906
    /* this is a jsonrpc notification (no id but valid request otherwise)
907
     * -> no response */
908
0
    if (!res_err)
909
0
      return MI_NO_RPL;
910
911
0
    if (res_err_code->valueint != JSONRPC_PARSE_ERR_CODE &&
912
0
      res_err_code->valueint != JSONRPC_INVAL_REQ_CODE)
913
0
      return MI_NO_RPL;
914
0
  }
915
916
0
  if (add_id_to_response(id, resp) < 0)
917
0
    return -1;
918
919
0
  return 0;
920
0
}
921
922
int print_mi_response(mi_response_t *resp, mi_item_t *id, str *buf, int pretty)
923
0
{
924
0
  int ret = prepare_mi_response(resp, id);
925
926
0
  if (ret != 0)
927
0
    return ret;
928
929
0
  if (cJSON_PrintPreallocated(resp, buf->s, buf->len, pretty) == 0) {
930
0
    LM_ERR("Failed to print JSON\n");
931
0
    return -1;
932
0
  }
933
934
0
  return 0;
935
0
}
936
937
int print_mi_response_flush(mi_response_t *resp, mi_item_t *id,
938
    mi_flush_f *func, void *func_p, str *buf, int pretty)
939
0
{
940
0
  int ret = prepare_mi_response(resp, id);
941
942
0
  if (ret != 0)
943
0
    return ret;
944
945
0
  if (cJSON_PrintFlushed(resp, buf->s, buf->len, pretty, func, func_p) == 0) {
946
0
    LM_ERR("Failed to print JSON\n");
947
0
    return -1;
948
0
  }
949
950
0
  return 0;
951
0
}
952
953
void free_mi_request_parsed(mi_request_t *request)
954
0
{
955
0
  _init_mi_sys_mem_hooks();
956
957
0
  if (request->req_obj)
958
0
    cJSON_Delete(request->req_obj);
959
960
0
  _init_mi_pkg_mem_hooks();
961
0
}
962
963
964
#define MI_HELP_STR "Usage: help mi_cmd - " \
965
  "returns information about 'mi_cmd'"
966
#define MI_UNKNOWN_CMD "unknown MI command"
967
#define MI_NO_HELP "not available for this command"
968
#define MI_MODULE_STR "by \"%.*s\" module"
969
970
mi_response_t *w_mi_help(const mi_params_t *params,
971
              struct mi_handler *async_hdl)
972
0
{
973
0
  return init_mi_result_string(MI_SSTR(MI_HELP_STR));
974
0
}
975
976
mi_response_t *w_mi_help_1(const mi_params_t *params,
977
                struct mi_handler *async_hdl)
978
0
{
979
0
  mi_response_t *resp;
980
0
  mi_item_t *resp_obj;
981
0
  struct mi_cmd *cmd;
982
0
  str cmd_s;
983
984
0
  if (get_mi_string_param(params, "mi_cmd", &cmd_s.s, &cmd_s.len) < 0)
985
0
    return init_mi_param_error();
986
987
  /* search the command */
988
0
  cmd = lookup_mi_cmd(cmd_s.s, cmd_s.len);
989
0
  if (!cmd)
990
0
    return init_mi_error(404, MI_SSTR(MI_UNKNOWN_CMD));
991
992
0
  resp = init_mi_result_object(&resp_obj);
993
0
  if (!resp)
994
0
    return 0;
995
996
0
  if (cmd->help.s) {
997
0
    if (add_mi_string(resp_obj, MI_SSTR("Help"), cmd->help.s, cmd->help.len)
998
0
      < 0) {
999
0
      LM_ERR("cannot add mi item\n");
1000
0
      goto error;
1001
0
    }
1002
0
  } else {
1003
0
    if (add_mi_string(resp_obj, MI_SSTR("Help"), MI_SSTR(MI_NO_HELP)) < 0) {
1004
0
      LM_ERR("cannot add mi item\n");
1005
0
      goto error;
1006
0
    }
1007
0
  }
1008
1009
0
  if (cmd->module.len && cmd->module.s && add_mi_string(resp_obj,
1010
0
    MI_SSTR("Exported by"), cmd->module.s, cmd->module.len) < 0) {
1011
0
    LM_ERR("cannot add mi item\n");
1012
0
    goto error;
1013
0
  }
1014
1015
0
  return resp;
1016
1017
0
error:
1018
0
  free_mi_response(resp);
1019
0
  return 0;
1020
0
}