Coverage Report

Created: 2025-07-11 06:28

/src/opensips/mi/mi.c
Line
Count
Source (jump to first uncovered line)
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
void _init_mi_sys_mem_hooks(void)
50
0
{
51
0
  cJSON_InitHooks(&sys_mem_hooks);
52
0
}
53
54
void _init_mi_shm_mem_hooks(void)
55
0
{
56
0
  cJSON_InitHooks(&shm_mem_hooks);
57
0
}
58
59
void _init_mi_pkg_mem_hooks(void)
60
0
{
61
0
  cJSON_InitHooks(NULL);
62
0
}
63
64
static inline int get_mi_id( char *name, int len)
65
0
{
66
0
  int n;
67
0
  int i;
68
69
0
  for( n=0,i=0 ; i<len ; n+=name[i] ,i++ );
70
0
  return n;
71
0
}
72
73
74
static inline struct mi_cmd* lookup_mi_cmd_id(int id,char *name, int len)
75
0
{
76
0
  int i;
77
78
0
  for( i=0 ; i<mi_cmds_no ; i++ ) {
79
0
    if ( id==mi_cmds[i].id && len==mi_cmds[i].name.len &&
80
0
    memcmp(mi_cmds[i].name.s,name,len)==0 )
81
0
      return &mi_cmds[i];
82
0
  }
83
84
0
  return 0;
85
0
}
86
87
88
int register_mi_mod(const char *mod_name, const mi_export_t *mis)
89
0
{
90
0
  int ret;
91
0
  int i;
92
93
0
  if (mis==0)
94
0
    return 0;
95
96
0
  for ( i=0 ; mis[i].name ; i++ ) {
97
0
    ret = register_mi_cmd(mis[i].name, mis[i].help, mis[i].flags,
98
0
        mis[i].init_f, mis[i].recipes, mod_name);
99
0
    if (ret!=0) {
100
0
      LM_ERR("failed to register cmd <%s> for module %s\n",
101
0
          mis[i].name,mod_name);
102
0
    }
103
0
  }
104
105
0
  return 0;
106
0
}
107
108
109
int init_mi_child(void)
110
0
{
111
0
  int i;
112
113
0
  for ( i=0 ; i<mi_cmds_no ; i++ ) {
114
0
    if ( mi_cmds[i].init_f && mi_cmds[i].init_f()!=0 ) {
115
0
      LM_ERR("failed to init <%.*s>\n",
116
0
          mi_cmds[i].name.len,mi_cmds[i].name.s);
117
0
      return -1;
118
0
    }
119
0
  }
120
0
  return 0;
121
0
}
122
123
124
125
int register_mi_cmd(char *name, char *help, unsigned int flags,
126
    mi_child_init_f in, const mi_recipe_t *recipes, const char* mod_name)
127
0
{
128
0
  struct mi_cmd *cmds;
129
0
  int id;
130
0
  int len;
131
132
0
  if (recipes==0 || name==0) {
133
0
    LM_ERR("invalid params recipes=%p, name=%s\n", recipes, name);
134
0
    return -1;
135
0
  }
136
137
0
  len = strlen(name);
138
0
  id = get_mi_id(name,len);
139
140
0
  if (lookup_mi_cmd_id( id, name, len)) {
141
0
    LM_ERR("command <%.*s> already registered\n", len, name);
142
0
    return -1;
143
0
  }
144
145
0
  cmds = (struct mi_cmd*)pkg_realloc( mi_cmds,
146
0
      (mi_cmds_no+1)*sizeof(struct mi_cmd) );
147
0
  if (cmds==0) {
148
0
    LM_ERR("no more pkg memory\n");
149
0
    return -1;
150
0
  }
151
152
0
  mi_cmds = cmds;
153
0
  mi_cmds_no++;
154
155
0
  cmds = &cmds[mi_cmds_no-1];
156
157
0
  cmds->init_f = in;
158
0
  cmds->flags = flags;
159
0
  cmds->name.s = name;
160
0
  cmds->name.len = len;
161
0
  cmds->module.s = mod_name;
162
0
  cmds->module.len = strlen(mod_name);
163
0
  cmds->help.s = help;
164
0
  cmds->help.len = help ? strlen(help) : 0;
165
0
  cmds->id = id;
166
0
  cmds->recipes = recipes;
167
168
  /**
169
   * FIXME we should check if trace_api is loaded not to lose unnecessary space
170
   * but some mi commands might have been already registered before loading the api
171
   * an example is the statistics mi commands
172
   */
173
0
  cmds->trace_mask = shm_malloc(sizeof(volatile unsigned char));
174
0
  if ( !cmds->trace_mask ) {
175
0
    LM_ERR("no more shm mem!\n");
176
0
    return -1;
177
0
  }
178
179
  /* by default all commands are traced */
180
0
  *cmds->trace_mask = (~(volatile unsigned char)0);
181
182
0
  return 0;
183
0
}
184
185
186
187
struct mi_cmd* lookup_mi_cmd( char *name, int len)
188
0
{
189
0
  int id;
190
191
0
  id = get_mi_id(name,len);
192
0
  return lookup_mi_cmd_id( id, name, len);
193
0
}
194
195
196
void get_mi_cmds( struct mi_cmd** cmds, int *size)
197
0
{
198
0
  *cmds = mi_cmds;
199
0
  *size = mi_cmds_no;
200
0
}
201
202
int parse_mi_request(const char *req, const char **end_ptr, mi_request_t *parsed)
203
0
{
204
0
  mi_item_t *req_jsonrpc;
205
206
0
  _init_mi_sys_mem_hooks();
207
208
0
  parsed->req_obj = cJSON_ParseWithOpts(req, end_ptr, 0);
209
0
  if (!parsed->req_obj) {
210
0
    _init_mi_pkg_mem_hooks();
211
0
    return -1;
212
0
  }
213
214
  /* check if the request is a valid JSON-RPC Request object */
215
  /* get request id (if absent -> notification) */
216
0
  parsed->id = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_ID_S);
217
0
  if (parsed->id && !(parsed->id->type & (cJSON_NULL|cJSON_Number|cJSON_String)))
218
0
    parsed->invalid = 1;
219
220
  /* check 'jsonrpc' member */
221
0
  req_jsonrpc = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_S);
222
0
  if (!req_jsonrpc || !(req_jsonrpc->type & cJSON_String) ||
223
0
    strcmp(req_jsonrpc->valuestring, JSONRPC_VERS_S))
224
0
    parsed->invalid = 1;
225
226
  /* check 'method' member */
227
0
  parsed->method = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_METHOD_S);
228
0
  if (!parsed->method || !(parsed->method->type & cJSON_String)) {
229
0
    parsed->method = NULL;
230
0
    parsed->invalid = 1;
231
0
  }
232
233
  /* check 'params' member */
234
0
  parsed->params = cJSON_GetObjectItem(parsed->req_obj, JSONRPC_PARAMS_S);
235
0
  if (parsed->params) {
236
0
    if (!(parsed->params->type & (cJSON_Array|cJSON_Object)))
237
0
      parsed->invalid = 1;
238
0
    else if (!parsed->params->child) {
239
0
      parsed->params = NULL;
240
0
    }
241
0
  }
242
243
0
  _init_mi_pkg_mem_hooks();
244
245
0
  return 0;
246
0
}
247
248
char *mi_get_req_method(mi_request_t *req)
249
0
{
250
0
  if (!req || !req->method)
251
0
    return NULL;
252
253
0
  return req->method->valuestring;
254
0
}
255
256
static int match_named_params(const mi_recipe_t *recipe, mi_item_t *req_params)
257
0
{
258
0
  mi_item_t *param;
259
0
  int i;
260
261
0
  for (i = 0; recipe->params[i]; i++) {
262
0
    for (param = req_params->child; param; param = param->next)
263
0
      if (param->string && !strcmp(recipe->params[i], param->string))
264
0
        break;
265
266
0
    if (!param)
267
0
      return 0;
268
0
  }
269
270
0
  return 1;
271
0
}
272
273
static int match_no_params(const mi_recipe_t *recipe, mi_item_t *req_params)
274
0
{
275
0
  mi_item_t *param;
276
0
  int i, j;
277
278
0
  for (i = 0; recipe->params[i]; i++) ;
279
280
0
  for (param = req_params->child, j = 0; param; param = param->next, j++) ;
281
282
0
  return i == j;
283
0
}
284
285
static const mi_recipe_t *get_cmd_recipe(const mi_recipe_t *recipes, mi_item_t *req_params,
286
                int pos_params, int *params_err)
287
0
{
288
0
  const mi_recipe_t *match = NULL;
289
0
  int i;
290
291
0
  for (i = 0; recipes[i].cmd; i++) {
292
0
    if (!req_params) {
293
0
      if (recipes[i].params[0] == NULL)
294
0
        return &recipes[i];
295
0
      else
296
0
        continue;
297
0
    } else {
298
0
      if (recipes[i].params[0] == NULL)
299
0
        continue;
300
0
    }
301
302
0
    if (pos_params) {
303
0
      if (match_no_params(&recipes[i], req_params)) {
304
0
        if (match) {
305
0
          *params_err = -2;
306
0
          return NULL;
307
0
        } else {
308
0
          match = &recipes[i];
309
0
        }
310
0
      }
311
0
    } else {
312
0
      if (match_no_params(&recipes[i], req_params))
313
0
        *params_err = -3;
314
0
      else
315
0
        continue;
316
317
0
      if (match_named_params(&recipes[i], req_params))
318
0
        return &recipes[i];
319
0
    }
320
0
  }
321
322
0
  return match;
323
0
}
324
325
static mi_response_t *build_err_resp(int code, const char *msg, int msg_len,
326
                const char *details, int details_len)
327
0
{
328
0
  mi_response_t *err_resp;
329
330
0
  err_resp = init_mi_error_extra(code, msg, msg_len, details, details_len);
331
0
  if (!err_resp)
332
0
    LM_ERR("Failed to build MI error response object\n");
333
334
0
  return err_resp;
335
0
}
336
337
mi_response_t *handle_mi_request(mi_request_t *req, struct mi_cmd *cmd,
338
              struct mi_handler *async_hdl)
339
0
{
340
0
  mi_response_t *resp;
341
0
  const mi_recipe_t *cmd_recipe;
342
0
  mi_params_t cmd_params;
343
0
  int params_err = -1;
344
0
  int pos_params;
345
346
0
  if (!req->req_obj) {  /* error parsing the request JSON text */
347
0
    LM_ERR("Failed to parse the request JSON text\n");
348
0
    return build_err_resp(JSONRPC_PARSE_ERR_CODE,
349
0
          MI_SSTR(JSONRPC_PARSE_ERR_MSG), NULL, 0);
350
0
  }
351
352
0
  if (req->invalid) {  /* invalid jsonrpc request */
353
0
    LM_ERR("Invalid JSON-RPC request\n");
354
0
    return build_err_resp(JSONRPC_INVAL_REQ_CODE,
355
0
          MI_SSTR(JSONRPC_INVAL_REQ_MSG), NULL, 0);
356
0
  }
357
358
0
  if (!cmd) {
359
0
    LM_ERR("Command not found\n");
360
0
    return build_err_resp(JSONRPC_NOT_FOUND_CODE,
361
0
        MI_SSTR(JSONRPC_NOT_FOUND_MSG), NULL, 0);
362
0
  }
363
364
0
  pos_params = req->params ? req->params->type & cJSON_Array : 0;
365
0
  if (pos_params && (cmd->flags & MI_NAMED_PARAMS_ONLY)) {
366
0
    LM_ERR("Command only supports named parameters\n");
367
0
    return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
368
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG),
369
0
        MI_SSTR(ERR_DET_POS_PARAMS_S));
370
0
  }
371
372
  /* use the correct 'recipe' of the command based
373
   * on the received parameters */
374
0
  cmd_recipe = get_cmd_recipe(cmd->recipes, req->params, pos_params,
375
0
          &params_err);
376
0
  if (!cmd_recipe) {
377
0
    LM_ERR("Invalid parameters\n");
378
0
    if (params_err == -1)
379
0
      return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
380
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG), MI_SSTR(ERR_DET_NO_PARAMS_S));
381
0
    else if (params_err == -2)
382
0
      return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
383
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG),
384
0
        MI_SSTR(ERR_DET_AMBIG_CALL_S));
385
0
    else
386
0
      return build_err_resp(JSONRPC_INVAL_PARAMS_CODE,
387
0
        MI_SSTR(JSONRPC_INVAL_PARAMS_MSG),
388
0
        MI_SSTR(ERR_DET_MATCH_PARAMS_S));
389
0
  }
390
391
0
  cmd_params.item = req->params;
392
0
  cmd_params.list = cmd_recipe->params;
393
394
0
  resp = cmd_recipe->cmd(&cmd_params, async_hdl);
395
396
0
  if (resp == NULL) {
397
0
    LM_ERR("Command failed\n");
398
0
    return build_err_resp(JSONRPC_SERVER_ERR_CODE,
399
0
        MI_SSTR(JSONRPC_SERVER_ERR_MSG), MI_SSTR(ERR_DET_CMD_NULL_S));
400
0
  } else
401
0
    return resp;
402
0
}
403
404
int add_id_to_response(mi_item_t *id, mi_response_t *resp)
405
0
{
406
0
  if (!id) {
407
0
    if (add_mi_null(resp, MI_SSTR(JSONRPC_ID_S)) < 0) {
408
0
      LM_ERR("Failed to add null value to MI item\n");
409
0
      return -1;
410
0
    }
411
412
0
    return 0;
413
0
  }
414
415
0
  switch ((id->type) & 0xFF) {
416
0
    case cJSON_Number:
417
0
      if (add_mi_number(resp, MI_SSTR(JSONRPC_ID_S), id->valueint) < 0) {
418
0
        LM_ERR("Failed to add int value to MI item\n");
419
0
        return -1;
420
0
      }
421
0
      break;
422
0
    case cJSON_String:
423
0
      if (add_mi_string(resp, MI_SSTR(JSONRPC_ID_S), id->valuestring,
424
0
        strlen(id->valuestring)) < 0) {
425
0
        LM_ERR("Failed to add string value to MI item\n");
426
0
        return -1;
427
0
      }
428
0
      break;
429
0
    case cJSON_NULL:
430
0
      if (add_mi_null(resp, MI_SSTR(JSONRPC_ID_S)) < 0) {
431
0
        LM_ERR("Failed to add null value to MI item\n");
432
0
        return -1;
433
0
      }
434
0
      break;
435
0
    default:
436
0
      LM_ERR("'id' must be a String, Number or Null value\n");
437
0
      return -1;
438
0
  }
439
440
0
  return 0;
441
0
}
442
443
static int prepare_mi_response(mi_response_t *resp, mi_item_t *id)
444
0
{
445
0
  mi_item_t *res_err, *res_err_code = NULL;
446
447
0
  res_err = cJSON_GetObjectItem(resp, JSONRPC_ERROR_S);
448
0
  if (res_err) {
449
0
    res_err_code = cJSON_GetObjectItem(res_err, JSONRPC_ERR_CODE_S);
450
0
    if (!res_err_code) {
451
0
      LM_ERR("no error code for MI error response\n");
452
0
      return -1;
453
0
    }
454
0
  }
455
456
0
  if (!id) {
457
    /* this is a jsonrpc notification (no id but valid request otherwise)
458
     * -> no response */
459
0
    if (!res_err)
460
0
      return MI_NO_RPL;
461
462
0
    if (res_err_code->valueint != JSONRPC_PARSE_ERR_CODE &&
463
0
      res_err_code->valueint != JSONRPC_INVAL_REQ_CODE)
464
0
      return MI_NO_RPL;
465
0
  }
466
467
0
  if (add_id_to_response(id, resp) < 0)
468
0
    return -1;
469
470
0
  return 0;
471
0
}
472
473
int print_mi_response(mi_response_t *resp, mi_item_t *id, str *buf, int pretty)
474
0
{
475
0
  int ret = prepare_mi_response(resp, id);
476
477
0
  if (ret != 0)
478
0
    return ret;
479
480
0
  if (cJSON_PrintPreallocated(resp, buf->s, buf->len, pretty) == 0) {
481
0
    LM_ERR("Failed to print JSON\n");
482
0
    return -1;
483
0
  }
484
485
0
  return 0;
486
0
}
487
488
int print_mi_response_flush(mi_response_t *resp, mi_item_t *id,
489
    mi_flush_f *func, void *func_p, str *buf, int pretty)
490
0
{
491
0
  int ret = prepare_mi_response(resp, id);
492
493
0
  if (ret != 0)
494
0
    return ret;
495
496
0
  if (cJSON_PrintFlushed(resp, buf->s, buf->len, pretty, func, func_p) == 0) {
497
0
    LM_ERR("Failed to print JSON\n");
498
0
    return -1;
499
0
  }
500
501
0
  return 0;
502
0
}
503
504
void free_mi_request_parsed(mi_request_t *request)
505
0
{
506
0
  _init_mi_sys_mem_hooks();
507
508
0
  if (request->req_obj)
509
0
    cJSON_Delete(request->req_obj);
510
511
0
  _init_mi_pkg_mem_hooks();
512
0
}
513
514
515
#define MI_HELP_STR "Usage: help mi_cmd - " \
516
  "returns information about 'mi_cmd'"
517
#define MI_UNKNOWN_CMD "unknown MI command"
518
#define MI_NO_HELP "not available for this command"
519
#define MI_MODULE_STR "by \"%.*s\" module"
520
521
mi_response_t *w_mi_help(const mi_params_t *params,
522
              struct mi_handler *async_hdl)
523
0
{
524
0
  return init_mi_result_string(MI_SSTR(MI_HELP_STR));
525
0
}
526
527
mi_response_t *w_mi_help_1(const mi_params_t *params,
528
                struct mi_handler *async_hdl)
529
0
{
530
0
  mi_response_t *resp;
531
0
  mi_item_t *resp_obj;
532
0
  struct mi_cmd *cmd;
533
0
  str cmd_s;
534
535
0
  if (get_mi_string_param(params, "mi_cmd", &cmd_s.s, &cmd_s.len) < 0)
536
0
    return init_mi_param_error();
537
538
  /* search the command */
539
0
  cmd = lookup_mi_cmd(cmd_s.s, cmd_s.len);
540
0
  if (!cmd)
541
0
    return init_mi_error(404, MI_SSTR(MI_UNKNOWN_CMD));
542
543
0
  resp = init_mi_result_object(&resp_obj);
544
0
  if (!resp)
545
0
    return 0;
546
547
0
  if (cmd->help.s) {
548
0
    if (add_mi_string(resp_obj, MI_SSTR("Help"), cmd->help.s, cmd->help.len)
549
0
      < 0) {
550
0
      LM_ERR("cannot add mi item\n");
551
0
      goto error;
552
0
    }
553
0
  } else {
554
0
    if (add_mi_string(resp_obj, MI_SSTR("Help"), MI_SSTR(MI_NO_HELP)) < 0) {
555
0
      LM_ERR("cannot add mi item\n");
556
0
      goto error;
557
0
    }
558
0
  }
559
560
0
  if (cmd->module.len && cmd->module.s && add_mi_string(resp_obj,
561
0
    MI_SSTR("Exported by"), cmd->module.s, cmd->module.len) < 0) {
562
0
    LM_ERR("cannot add mi item\n");
563
0
    goto error;
564
0
  }
565
566
0
  return resp;
567
568
0
error:
569
0
  free_mi_response(resp);
570
0
  return 0;
571
0
}