Coverage Report

Created: 2026-09-14 06:45

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