/src/rtpproxy/src/rtpp_command.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2004-2006 Maxim Sobolev <sobomax@FreeBSD.org> |
3 | | * Copyright (c) 2006-2014 Sippy Software, Inc., http://www.sippysoft.com |
4 | | * All rights reserved. |
5 | | * |
6 | | * Redistribution and use in source and binary forms, with or without |
7 | | * modification, are permitted provided that the following conditions |
8 | | * are met: |
9 | | * 1. Redistributions of source code must retain the above copyright |
10 | | * notice, this list of conditions and the following disclaimer. |
11 | | * 2. Redistributions in binary form must reproduce the above copyright |
12 | | * notice, this list of conditions and the following disclaimer in the |
13 | | * documentation and/or other materials provided with the distribution. |
14 | | * |
15 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
16 | | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
17 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
18 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
19 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
20 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
21 | | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
22 | | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
23 | | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
24 | | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
25 | | * SUCH DAMAGE. |
26 | | * |
27 | | */ |
28 | | |
29 | | #if defined(HAVE_CONFIG_H) |
30 | | #include "config.h" |
31 | | #endif |
32 | | |
33 | | #include <sys/types.h> |
34 | | #include <sys/socket.h> |
35 | | #include <netinet/in.h> |
36 | | #include <errno.h> |
37 | | #include <stddef.h> |
38 | | #include <stdio.h> |
39 | | #include <stdlib.h> |
40 | | #include <string.h> |
41 | | #include <unistd.h> |
42 | | |
43 | | #include "rtpp_debug.h" |
44 | | #include "rtpp_log.h" |
45 | | #include "rtpp_cfg.h" |
46 | | #include "rtpp_defines.h" |
47 | | #include "rtpp_types.h" |
48 | | #include "rtpp_log_obj.h" |
49 | | #include "rtpp_refcnt.h" |
50 | | #include "rtpp_time.h" |
51 | | #include "rtpp_command.h" |
52 | | #include "rtpp_command_async.h" |
53 | | #include "commands/rpcpv1_copy.h" |
54 | | #include "commands/rpcpv1_delete.h" |
55 | | #include "rtpp_command_parse.h" |
56 | | #include "commands/rpcpv1_play.h" |
57 | | #include "rtpp_command_ecodes.h" |
58 | | #include "rtpp_command_args.h" |
59 | | #include "rtpp_command_sub.h" |
60 | | #include "rtpp_command_private.h" |
61 | | #include "commands/rpcpv1_record.h" |
62 | | #include "commands/rpcpv1_norecord.h" |
63 | | #include "rtpp_command_rcache.h" |
64 | | #include "commands/rpcpv1_query.h" |
65 | | #include "commands/rpcpv1_stats.h" |
66 | | #include "commands/rpcpv1_ul.h" |
67 | | #include "commands/rpcpv1_ul_subc.h" |
68 | | #include "commands/rpcpv1_ver.h" |
69 | | #include "rtpp_controlfd.h" |
70 | | #include "rtpp_hash_table.h" |
71 | | #include "rtpp_mallocs.h" |
72 | | #include "rtpp_netio_async.h" |
73 | | #include "rtpp_network.h" |
74 | | #include "rtpp_pipe.h" |
75 | | #include "rtpp_port_table.h" |
76 | | #include "rtpp_stream.h" |
77 | | #include "rtpp_session.h" |
78 | | #include "rtpp_socket.h" |
79 | | #include "rtpp_util.h" |
80 | | #include "rtpp_stats.h" |
81 | | #include "rtpp_weakref.h" |
82 | | #include "rtpp_proc_async.h" |
83 | | |
84 | | struct rtpp_command_priv { |
85 | | struct rtpp_command pub; |
86 | | const struct rtpp_cfg *cfs; |
87 | | int controlfd; |
88 | | rtpp_str_const_t cookie; |
89 | | int umode; |
90 | | char buf_r[256]; |
91 | | struct rtpp_cmd_rcache *rcache_obj; |
92 | | struct rtpp_timestamp dtime; |
93 | | }; |
94 | | |
95 | | struct d_opts; |
96 | | |
97 | | static int create_twinlistener(unsigned int, void *); |
98 | | static void handle_info(const struct rtpp_cfg *, struct rtpp_command *); |
99 | | |
100 | | struct create_twinlistener_args { |
101 | | const struct rtpp_cfg *cfs; |
102 | | const struct sockaddr *ia; |
103 | | struct rtpp_socket **fds; |
104 | | int *port; |
105 | | }; |
106 | | |
107 | | static int |
108 | | create_twinlistener(unsigned int port, void *ap) |
109 | 0 | { |
110 | 0 | struct sockaddr_storage iac; |
111 | 0 | int rval, i, so_rcvbuf; |
112 | 0 | struct create_twinlistener_args *ctap; |
113 | |
|
114 | 0 | RTPP_DBG_ASSERT(port >= 1 && IS_VALID_PORT(port - 1)); |
115 | |
|
116 | 0 | ctap = (struct create_twinlistener_args *)ap; |
117 | |
|
118 | 0 | ctap->fds[0] = ctap->fds[1] = NULL; |
119 | |
|
120 | 0 | rval = RTPP_PTU_BRKERR; |
121 | 0 | for (i = 0; i < 2; i++) { |
122 | 0 | ctap->fds[i] = rtpp_socket_ctor(ctap->ia->sa_family, SOCK_DGRAM); |
123 | 0 | if (ctap->fds[i] == NULL) { |
124 | 0 | RTPP_ELOG(ctap->cfs->glog, RTPP_LOG_ERR, "can't create %s socket", |
125 | 0 | SA_AF2STR(ctap->ia)); |
126 | 0 | goto failure; |
127 | 0 | } |
128 | 0 | memcpy(&iac, ctap->ia, SA_LEN(ctap->ia)); |
129 | 0 | satosin(&iac)->sin_port = htons(port); |
130 | 0 | if (CALL_METHOD(ctap->fds[i], bind2, sstosa(&iac), SA_LEN(ctap->ia)) != 0) { |
131 | 0 | if (errno != EADDRINUSE && errno != EACCES) { |
132 | 0 | RTPP_ELOG(ctap->cfs->glog, RTPP_LOG_ERR, "can't bind to the %s port %d", |
133 | 0 | SA_AF2STR(ctap->ia), port); |
134 | 0 | } else { |
135 | 0 | rval = RTPP_PTU_ONEMORE; |
136 | 0 | } |
137 | 0 | goto failure; |
138 | 0 | } |
139 | 0 | port++; |
140 | 0 | if ((ctap->ia->sa_family == AF_INET) && (ctap->cfs->tos >= 0) && |
141 | 0 | (CALL_METHOD(ctap->fds[i], settos, ctap->cfs->tos) == -1)) |
142 | 0 | RTPP_ELOG(ctap->cfs->glog, RTPP_LOG_ERR, "unable to set TOS to %d", ctap->cfs->tos); |
143 | 0 | so_rcvbuf = 256 * 1024; |
144 | 0 | if (CALL_METHOD(ctap->fds[i], setrbuf, so_rcvbuf) == -1) |
145 | 0 | RTPP_ELOG(ctap->cfs->glog, RTPP_LOG_ERR, "unable to set 256K receive buffer size"); |
146 | 0 | if (CALL_METHOD(ctap->fds[i], setnonblock) < 0) |
147 | 0 | goto failure; |
148 | 0 | CALL_METHOD(ctap->fds[i], settimestamp); |
149 | 0 | } |
150 | 0 | RTPP_DBG_ASSERT(port > 2 && IS_VALID_PORT(port - 2)); |
151 | 0 | *ctap->port = port - 2; |
152 | 0 | return RTPP_PTU_OK; |
153 | | |
154 | 0 | failure: |
155 | 0 | for (i = 0; i < 2; i++) |
156 | 0 | if (ctap->fds[i] != NULL) { |
157 | 0 | RTPP_OBJ_DECREF(ctap->fds[i]); |
158 | 0 | ctap->fds[i] = NULL; |
159 | 0 | } |
160 | 0 | return rval; |
161 | 0 | } |
162 | | |
163 | | int |
164 | | rtpp_create_listener(const struct rtpp_cfg *cfsp, const struct sockaddr *ia, int *port, |
165 | | struct rtpp_socket **fds) |
166 | 0 | { |
167 | 0 | struct create_twinlistener_args cta; |
168 | 0 | int i; |
169 | 0 | struct rtpp_port_table *rpp; |
170 | |
|
171 | 0 | memset(&cta, '\0', sizeof(cta)); |
172 | 0 | cta.cfs = cfsp; |
173 | 0 | cta.fds = fds; |
174 | 0 | cta.ia = ia; |
175 | 0 | cta.port = port; |
176 | |
|
177 | 0 | for (i = 0; i < 2; i++) |
178 | 0 | fds[i] = NULL; |
179 | |
|
180 | 0 | rpp = RTPP_PT_SELECT(cfsp, ia->sa_family); |
181 | 0 | return (CALL_METHOD(rpp, get_port, create_twinlistener, |
182 | 0 | &cta)); |
183 | 0 | } |
184 | | |
185 | | void |
186 | | rtpc_doreply(struct rtpp_command *cmd, char *buf, int len, int errd) |
187 | 1.97k | { |
188 | 1.97k | struct rtpp_command_priv *pvt; |
189 | | |
190 | 1.97k | PUB2PVT(cmd, pvt); |
191 | | |
192 | 1.97k | if (len > 0 && buf[len - 1] == '\n') { |
193 | 1.97k | RTPP_LOG(pvt->cfs->glog, RTPP_LOG_DBUG, "sending reply \"%.*s\\n\"", |
194 | 1.97k | len - 1, buf); |
195 | 1.97k | } else { |
196 | 0 | RTPP_LOG(pvt->cfs->glog, RTPP_LOG_DBUG, "sending reply \"%.*s\"", |
197 | 0 | len, buf); |
198 | 0 | } |
199 | 1.97k | if (pvt->umode == 0) { |
200 | 1.97k | if (write(pvt->controlfd, buf, len) < 0) { |
201 | 0 | RTPP_DBG_ASSERT(!IS_WEIRD_ERRNO(errno)); |
202 | 0 | } |
203 | 1.97k | } else { |
204 | 0 | if (pvt->cookie.s != NULL) { |
205 | 0 | len = snprintf(pvt->buf_r, sizeof(pvt->buf_r), "%.*s %.*s", |
206 | 0 | (int)pvt->cookie.len, pvt->cookie.s, len, buf); |
207 | 0 | buf = pvt->buf_r; |
208 | 0 | CALL_METHOD(pvt->rcache_obj, insert, rtpp_str_fix(&pvt->cookie), pvt->buf_r, |
209 | 0 | cmd->dtime->mono); |
210 | 0 | } |
211 | 0 | rtpp_anetio_sendto(pvt->cfs->rtpp_proc_cf->netio, pvt->controlfd, buf, |
212 | 0 | len, 0, sstosa(&cmd->raddr), cmd->rlen); |
213 | 0 | } |
214 | 1.97k | cmd->csp->ncmds_repld.cnt++; |
215 | 1.97k | if (errd == 0) { |
216 | 173 | cmd->csp->ncmds_succd.cnt++; |
217 | 1.80k | } else { |
218 | 1.80k | cmd->csp->ncmds_errs.cnt++; |
219 | 1.80k | } |
220 | 1.97k | } |
221 | | |
222 | | void |
223 | | reply_number(struct rtpp_command *cmd, int number) |
224 | 93 | { |
225 | 93 | int len; |
226 | | |
227 | 93 | len = snprintf(cmd->buf_t, sizeof(cmd->buf_t), "%d\n", number); |
228 | 93 | rtpc_doreply(cmd, cmd->buf_t, len, 0); |
229 | 93 | } |
230 | | |
231 | | void |
232 | | reply_ok(struct rtpp_command *cmd) |
233 | 2 | { |
234 | | |
235 | 2 | reply_number(cmd, 0); |
236 | 2 | } |
237 | | |
238 | | void |
239 | | reply_error(struct rtpp_command *cmd, |
240 | | int ecode) |
241 | 1.75k | { |
242 | 1.75k | int len; |
243 | | |
244 | 1.75k | len = snprintf(cmd->buf_t, sizeof(cmd->buf_t), "E%d\n", ecode); |
245 | 1.75k | rtpc_doreply(cmd, cmd->buf_t, len, 1); |
246 | 1.75k | } |
247 | | |
248 | | void |
249 | | free_command(struct rtpp_command *cmd) |
250 | 1.97k | { |
251 | 1.97k | struct rtpp_command_priv *pvt; |
252 | | |
253 | 1.97k | PUB2PVT(cmd, pvt); |
254 | 1.97k | if (pvt->rcache_obj != NULL) { |
255 | 0 | RTPP_OBJ_DECREF(pvt->rcache_obj); |
256 | 0 | } |
257 | 1.97k | if (cmd->sp != NULL) { |
258 | 0 | RTPP_OBJ_DECREF(cmd->sp); |
259 | 0 | } |
260 | 9.87k | for (int i = 0; i < MAX_SUBC_NUM; i++) { |
261 | 7.89k | if (cmd->after_success[i].args.dyn != NULL) |
262 | 24 | free(cmd->after_success[i].args.dyn); |
263 | 7.89k | } |
264 | 1.97k | free(pvt); |
265 | 1.97k | } |
266 | | |
267 | | struct rtpp_command * |
268 | | rtpp_command_ctor(const struct rtpp_cfg *cfsp, int controlfd, |
269 | | const struct rtpp_timestamp *dtime, struct rtpp_command_stats *csp, int umode) |
270 | 1.97k | { |
271 | 1.97k | struct rtpp_command_priv *pvt; |
272 | 1.97k | struct rtpp_command *cmd; |
273 | | |
274 | 1.97k | pvt = rtpp_zmalloc(sizeof(struct rtpp_command_priv)); |
275 | 1.97k | if (pvt == NULL) { |
276 | 0 | return (NULL); |
277 | 0 | } |
278 | 1.97k | cmd = &(pvt->pub); |
279 | 1.97k | pvt->controlfd = controlfd; |
280 | 1.97k | pvt->cfs = cfsp; |
281 | 1.97k | pvt->dtime.wall = dtime->wall; |
282 | 1.97k | pvt->dtime.mono = dtime->mono; |
283 | 1.97k | cmd->dtime = &pvt->dtime; |
284 | 1.97k | cmd->csp = csp; |
285 | 1.97k | cmd->glog = cfsp->glog; |
286 | 1.97k | pvt->umode = umode; |
287 | 1.97k | return (cmd); |
288 | 1.97k | } |
289 | | |
290 | | struct rtpp_command * |
291 | | get_command(const struct rtpp_cfg *cfsp, struct rtpp_ctrl_sock *rcsp, int controlfd, int *rval, |
292 | | const struct rtpp_timestamp *dtime, struct rtpp_command_stats *csp, |
293 | | struct rtpp_cmd_rcache *rcache_obj) |
294 | 0 | { |
295 | 0 | char *bp; |
296 | 0 | int len; |
297 | 0 | struct rtpp_command *cmd; |
298 | 0 | struct rtpp_command_priv *pvt; |
299 | 0 | int umode = RTPP_CTRL_ISDG(rcsp); |
300 | 0 | size_t bsize; |
301 | 0 | socklen_t asize, *lp; |
302 | 0 | struct sockaddr *raddr; |
303 | |
|
304 | 0 | cmd = rtpp_command_ctor(cfsp, controlfd, dtime, csp, umode); |
305 | 0 | if (cmd == NULL) { |
306 | 0 | bp = rcsp->emrg.buf; |
307 | 0 | bsize = sizeof(rcsp->emrg.buf); |
308 | 0 | } else { |
309 | 0 | bp = cmd->buf; |
310 | 0 | bsize = sizeof(cmd->buf); |
311 | 0 | } |
312 | 0 | if (umode == 0) { |
313 | 0 | for (;;) { |
314 | 0 | len = read(controlfd, bp, bsize - 1); |
315 | 0 | if (len == 0) { |
316 | 0 | RTPP_LOG(cfsp->glog, RTPP_LOG_DBUG, |
317 | 0 | "EOF before receiving any command data"); |
318 | 0 | if (cmd != NULL) |
319 | 0 | free_command(cmd); |
320 | 0 | *rval = GET_CMD_EOF; |
321 | 0 | return (NULL); |
322 | 0 | } |
323 | 0 | if (len != -1 || (errno != EAGAIN && errno != EINTR)) |
324 | 0 | break; |
325 | 0 | } |
326 | 0 | } else { |
327 | 0 | PUB2PVT(cmd, pvt); |
328 | 0 | if (cmd == NULL) { |
329 | 0 | asize = sizeof(rcsp->emrg.addr); |
330 | 0 | lp = &asize; |
331 | 0 | raddr = sstosa(&rcsp->emrg.addr); |
332 | 0 | } else { |
333 | 0 | cmd->rlen = sizeof(cmd->raddr); |
334 | 0 | lp = &cmd->rlen; |
335 | 0 | raddr = sstosa(&cmd->raddr); |
336 | 0 | } |
337 | 0 | len = recvfrom(controlfd, bp, bsize - 1, 0, raddr, lp); |
338 | 0 | } |
339 | 0 | if (len == -1) { |
340 | 0 | if (errno != EAGAIN && errno != EINTR) |
341 | 0 | RTPP_ELOG(cfsp->glog, RTPP_LOG_ERR, "can't read from control socket"); |
342 | 0 | if (cmd != NULL) |
343 | 0 | free_command(cmd); |
344 | 0 | *rval = GET_CMD_IOERR; |
345 | 0 | return (NULL); |
346 | 0 | } |
347 | 0 | if (cmd == NULL) { |
348 | 0 | *rval = GET_CMD_ENOMEM; |
349 | 0 | csp->ncmds_rcvd.cnt++; |
350 | 0 | csp->ncmds_errs.cnt++; |
351 | 0 | return (NULL); |
352 | 0 | } |
353 | 0 | cmd->buf[len] = '\0'; |
354 | |
|
355 | 0 | if (rtpp_command_split(cmd, len, rval, rcache_obj) != 0) { |
356 | | /* Error reply is handled by the rtpp_command_split() */ |
357 | 0 | free_command(cmd); |
358 | 0 | return (NULL); |
359 | 0 | } |
360 | 0 | return (cmd); |
361 | 0 | } |
362 | | |
363 | 9.11k | #define ISAMPAMP(vp) ((vp)->len == 2 && (vp)->s[0] == '&' && (vp)->s[1] == '&') |
364 | | |
365 | | static int |
366 | | rtpp_command_guard_retrans(struct rtpp_command *cmd, |
367 | | struct rtpp_cmd_rcache *rcache_obj) |
368 | 0 | { |
369 | 0 | size_t len; |
370 | 0 | struct rtpp_command_priv *pvt; |
371 | |
|
372 | 0 | PUB2PVT(cmd, pvt); |
373 | 0 | if (CALL_METHOD(rcache_obj, lookup, rtpp_str_fix(&pvt->cookie), pvt->buf_r, |
374 | 0 | sizeof(pvt->buf_r)) == 1) { |
375 | 0 | len = strlen(pvt->buf_r); |
376 | 0 | rtpp_anetio_sendto(pvt->cfs->rtpp_proc_cf->netio, pvt->controlfd, |
377 | 0 | pvt->buf_r, len, 0, sstosa(&cmd->raddr), cmd->rlen); |
378 | 0 | cmd->csp->ncmds_rcvd.cnt--; |
379 | 0 | cmd->csp->ncmds_rcvd_ndups.cnt++; |
380 | 0 | return (1); |
381 | 0 | } |
382 | 0 | RTPP_OBJ_INCREF(rcache_obj); |
383 | 0 | pvt->rcache_obj = rcache_obj; |
384 | 0 | return (0); |
385 | 0 | } |
386 | | |
387 | | int |
388 | | rtpp_command_split(struct rtpp_command *cmd, int len, int *rval, |
389 | | struct rtpp_cmd_rcache *rcache_obj) |
390 | 1.97k | { |
391 | 1.97k | rtpp_str_const_t *ap; |
392 | 1.97k | struct rtpp_command_priv *pvt; |
393 | 1.97k | struct rtpp_command_args *cap; |
394 | 1.97k | char mbuf[RTPP_CMD_BUFLEN]; |
395 | | |
396 | 1.97k | PUB2PVT(cmd, pvt); |
397 | 1.97k | if (len > 0 && cmd->buf[len - 1] == '\n') { |
398 | 4 | RTPP_LOG(pvt->cfs->glog, RTPP_LOG_DBUG, "received command \"%.*s\\n\"", |
399 | 4 | len - 1, cmd->buf); |
400 | 1.97k | } else { |
401 | 1.97k | RTPP_LOG(pvt->cfs->glog, RTPP_LOG_DBUG, "received command \"%s\"", |
402 | 1.97k | cmd->buf); |
403 | 1.97k | } |
404 | 1.97k | cmd->csp->ncmds_rcvd.cnt++; |
405 | | |
406 | 1.97k | cap = &cmd->args; |
407 | 1.97k | rtpp_strsplit(cmd->buf, mbuf, len, sizeof(mbuf)); |
408 | 1.97k | char *mp, *mp_next; |
409 | 1.97k | ap = cap->v; |
410 | 11.5k | for (mp = mbuf; mp != NULL && (mp - mbuf) < len; mp = mp_next) { |
411 | 9.58k | rtpp_str_const_t tap; |
412 | 9.58k | mp_next = memchr(mp, '\0', len - (mp - mbuf)); |
413 | 9.58k | if (mp_next == NULL) { |
414 | 1.95k | tap.len = mbuf + len - mp; |
415 | 7.62k | } else { |
416 | 7.62k | tap.len = mp_next - mp; |
417 | 7.62k | mp_next += 1; |
418 | 7.62k | } |
419 | 9.58k | if (tap.len == 0) { |
420 | 452 | continue; |
421 | 452 | } |
422 | 9.13k | tap.s = cmd->buf + (mp - mbuf); |
423 | 9.13k | size_t slen = strlen(tap.s); |
424 | 9.13k | RTPP_DBG_ASSERT(slen <= tap.len); |
425 | 9.13k | if (slen < tap.len) { |
426 | | /* \0 inside a parameter is not allowed */ |
427 | 18 | goto synerr; |
428 | 18 | } |
429 | 9.11k | *ap = tap; |
430 | | |
431 | 9.11k | if (cap == &cmd->args) { |
432 | | /* Stream communication mode doesn't use cookie */ |
433 | 8.71k | if (pvt->umode != 0 && cap->c == 0 && pvt->cookie.s == NULL) { |
434 | 0 | pvt->cookie = *ap; |
435 | 0 | if (rtpp_command_guard_retrans(cmd, rcache_obj)) { |
436 | 0 | *rval = GET_CMD_OK; |
437 | 0 | return (1); |
438 | 0 | } |
439 | 0 | continue; |
440 | 0 | } |
441 | 8.71k | } |
442 | | |
443 | 9.11k | if (ISAMPAMP(ap)) { |
444 | 248 | if (cmd->subc.n == (MAX_SUBC_NUM - 1)) |
445 | 1 | goto synerr; |
446 | 247 | ap->s = NULL; |
447 | 247 | ap->len = 0; |
448 | 247 | cap = &cmd->subc.args[cmd->subc.n]; |
449 | 247 | cmd->subc.n += 1; |
450 | 247 | ap = cap->v; |
451 | 247 | continue; |
452 | 248 | } |
453 | 8.86k | cap->c++; |
454 | 8.86k | if (++ap >= &cap->v[RTPC_MAX_ARGC]) |
455 | 1 | goto synerr; |
456 | 8.86k | } |
457 | 1.95k | if (cmd->args.c < 1 || (pvt->umode != 0 && pvt->cookie.s == NULL)) { |
458 | 23 | goto synerr; |
459 | 23 | } |
460 | 2.15k | for (int i = 0; i < cmd->subc.n; i++) { |
461 | 228 | cap = &cmd->subc.args[i]; |
462 | 228 | if (cap->c < 1) { |
463 | 4 | goto synerr; |
464 | 4 | } |
465 | 228 | } |
466 | | |
467 | | /* Step I: parse parameters that are common to all ops */ |
468 | 1.92k | if (rtpp_command_pre_parse(pvt->cfs, cmd) != 0) { |
469 | | /* Error reply is handled by the rtpp_command_pre_parse() */ |
470 | 107 | *rval = GET_CMD_INVAL; |
471 | 107 | return (1); |
472 | 107 | } |
473 | | |
474 | 1.82k | return (0); |
475 | 47 | synerr: |
476 | 47 | RTPP_LOG(pvt->cfs->glog, RTPP_LOG_ERR, "command syntax error"); |
477 | 47 | reply_error(cmd, ECODE_PARSE_1); |
478 | 47 | *rval = GET_CMD_INVAL; |
479 | 47 | return (1); |
480 | | |
481 | 1.92k | } |
482 | | |
483 | | int |
484 | | handle_command(const struct rtpp_cfg *cfsp, struct rtpp_command *cmd) |
485 | 1.82k | { |
486 | 1.82k | int i, verbose, rval; |
487 | 1.82k | const char *cp; |
488 | 1.82k | const char *recording_name; |
489 | 1.82k | struct rtpp_session *spa; |
490 | 1.82k | int record_single_file; |
491 | 1.82k | int norecord_all; |
492 | | |
493 | 1.82k | spa = NULL; |
494 | 1.82k | recording_name = NULL; |
495 | 1.82k | norecord_all = 0; |
496 | 1.82k | record_single_file = 0; |
497 | | |
498 | | /* Step II: parse parameters that are specific to a particular op and run simple ops */ |
499 | 1.82k | switch (cmd->cca.op) { |
500 | 89 | case VER_FEATURE: |
501 | 89 | handle_ver_feature(cfsp, cmd); |
502 | 89 | return 0; |
503 | | |
504 | 2 | case GET_VER: |
505 | | /* This returns base version. */ |
506 | 2 | reply_number(cmd, CPROTOVER); |
507 | 2 | return 0; |
508 | | |
509 | 2 | case DELETE_ALL: |
510 | | /* Delete all active sessions */ |
511 | 2 | RTPP_LOG(cfsp->glog, RTPP_LOG_INFO, "deleting all active sessions"); |
512 | 2 | CALL_SMETHOD(cfsp->sessions_wrt, purge); |
513 | 2 | CALL_SMETHOD(cfsp->sessions_ht, purge); |
514 | 2 | reply_ok(cmd); |
515 | 2 | return 0; |
516 | | |
517 | 53 | case INFO: |
518 | 53 | handle_info(cfsp, cmd); |
519 | 53 | return 0; |
520 | | |
521 | 18 | case PLAY: |
522 | | /* |
523 | | * P callid pname codecs from_tag to_tag |
524 | | * |
525 | | * <codecs> could be either comma-separated list of supported |
526 | | * payload types or word "session" (without quotes), in which |
527 | | * case list saved on last session update will be used instead. |
528 | | */ |
529 | 18 | cmd->cca.opts.play = rtpp_command_play_opts_parse(cmd); |
530 | 18 | if (cmd->cca.opts.play == NULL) { |
531 | 16 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "can't parse options"); |
532 | 16 | return 0; |
533 | 16 | } |
534 | 2 | break; |
535 | | |
536 | 2 | case COPY: |
537 | 1 | recording_name = cmd->args.v[2].s; |
538 | | /* Fallthrough */ |
539 | 39 | case RECORD: |
540 | 39 | if (cmd->args.v[0].s[1] == 'S' || cmd->args.v[0].s[1] == 's') { |
541 | 10 | if (cmd->args.v[0].s[2] != '\0') { |
542 | 8 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "command syntax error"); |
543 | 8 | reply_error(cmd, ECODE_PARSE_2); |
544 | 8 | return 0; |
545 | 8 | } |
546 | 2 | record_single_file = RSF_MODE_DFLT(cfsp); |
547 | 29 | } else { |
548 | 29 | if (cmd->args.v[0].s[1] != '\0') { |
549 | 18 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "command syntax error"); |
550 | 18 | reply_error(cmd, ECODE_PARSE_3); |
551 | 18 | return 0; |
552 | 18 | } |
553 | 11 | record_single_file = 0; |
554 | 11 | } |
555 | 13 | break; |
556 | | |
557 | 31 | case NORECORD: |
558 | 31 | if (cmd->args.v[0].s[1] == 'A' || cmd->args.v[0].s[1] == 'a') { |
559 | 10 | if (cmd->args.v[0].s[2] != '\0') { |
560 | 8 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "command syntax error"); |
561 | 8 | reply_error(cmd, ECODE_PARSE_2); |
562 | 8 | return 0; |
563 | 8 | } |
564 | 2 | norecord_all = 1; |
565 | 21 | } else { |
566 | 21 | if (cmd->args.v[0].s[1] != '\0') { |
567 | 20 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "command syntax error"); |
568 | 20 | reply_error(cmd, ECODE_PARSE_3); |
569 | 20 | return 0; |
570 | 20 | } |
571 | 1 | norecord_all = 0; |
572 | 1 | } |
573 | 3 | break; |
574 | | |
575 | 35 | case DELETE: |
576 | | /* D[w] call_id from_tag [to_tag] */ |
577 | 35 | cmd->cca.opts.delete = rtpp_command_del_opts_parse(cmd, &cmd->args); |
578 | 35 | if (cmd->cca.opts.delete == NULL) { |
579 | 10 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "can't parse options"); |
580 | 10 | return 0; |
581 | 10 | } |
582 | 25 | break; |
583 | | |
584 | 1.10k | case UPDATE: |
585 | 1.18k | case LOOKUP: |
586 | 1.18k | cmd->cca.opts.ul = rtpp_command_ul_opts_parse(cfsp, cmd); |
587 | 1.18k | if (cmd->cca.opts.ul == NULL) { |
588 | 603 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "can't parse options"); |
589 | 603 | return 0; |
590 | 603 | } |
591 | 585 | break; |
592 | | |
593 | 585 | case GET_STATS: |
594 | 150 | verbose = 0; |
595 | 1.35k | for (cp = cmd->args.v[0].s + 1; *cp != '\0'; cp++) { |
596 | 1.22k | switch (*cp) { |
597 | 295 | case 'v': |
598 | 1.20k | case 'V': |
599 | 1.20k | verbose = 1; |
600 | 1.20k | break; |
601 | | |
602 | 15 | default: |
603 | 15 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, |
604 | 15 | "STATS: unknown command modifier `%c'", *cp); |
605 | 15 | reply_error(cmd, ECODE_PARSE_5); |
606 | 15 | return 0; |
607 | 1.22k | } |
608 | 1.22k | } |
609 | 135 | i = handle_get_stats(cfsp->rtpp_stats, cmd, verbose); |
610 | 135 | if (i != 0) { |
611 | 92 | reply_error(cmd, i); |
612 | 92 | } |
613 | 135 | return 0; |
614 | | |
615 | 213 | default: |
616 | 213 | break; |
617 | 1.82k | } |
618 | | |
619 | 884 | for (int i = 0; i < cmd->subc.n; i++) { |
620 | 209 | if (rtpp_subcommand_ul_opts_parse(cfsp, &cmd->subc.args[i], |
621 | 209 | &cmd->after_success[i]) != 0) { |
622 | 166 | if (cmd->cca.op == UPDATE || cmd->cca.op == LOOKUP) |
623 | 2 | rtpp_command_ul_opts_free(cmd->cca.opts.ul); |
624 | 166 | reply_error(cmd, ECODE_PARSE_SUBC); |
625 | 166 | return 0; |
626 | 166 | } |
627 | 43 | RTPP_DBG_ASSERT(cmd->after_success[i].handler != NULL); |
628 | 43 | } |
629 | | |
630 | | /* |
631 | | * Record and delete need special handling since they apply to all |
632 | | * streams in the session. |
633 | | */ |
634 | 675 | switch (cmd->cca.op) { |
635 | 25 | case DELETE: |
636 | 25 | i = handle_delete(cfsp, &cmd->cca); |
637 | 25 | break; |
638 | | |
639 | 12 | case RECORD: |
640 | 12 | i = handle_record(cfsp, &cmd->cca, record_single_file); |
641 | 12 | break; |
642 | | |
643 | 3 | case NORECORD: |
644 | 3 | i = handle_norecord(cfsp, &cmd->cca, norecord_all); |
645 | 3 | break; |
646 | | |
647 | 635 | default: |
648 | 635 | i = find_stream(cfsp, cmd->cca.call_id, cmd->cca.from_tag, |
649 | 635 | cmd->cca.to_tag, &spa); |
650 | 635 | if (i != -1) { |
651 | 0 | if (cmd->cca.op != UPDATE) |
652 | 0 | i = NOT(i); |
653 | 0 | RTPP_DBG_ASSERT(cmd->sp == NULL); |
654 | 0 | cmd->sp = spa; |
655 | 0 | } |
656 | 635 | break; |
657 | 675 | } |
658 | | |
659 | 675 | if (i == -1 && cmd->cca.op != UPDATE) { |
660 | 136 | rtpp_str_t to_tag = cmd->cca.to_tag ? *cmd->cca.to_tag : |
661 | 136 | (rtpp_str_t){.len = 4, .s = "NONE"}; |
662 | 136 | RTPP_LOG(cfsp->glog, RTPP_LOG_INFO, |
663 | 136 | "%s request failed: session %.*s, tags %.*s/%.*s not found", cmd->cca.rname, |
664 | 136 | (int)cmd->cca.call_id->len, cmd->cca.call_id->s, (int)cmd->cca.from_tag->len, |
665 | 136 | cmd->cca.from_tag->s, (int)to_tag.len, to_tag.s); |
666 | 136 | switch (cmd->cca.op) { |
667 | 44 | case LOOKUP: |
668 | 44 | rtpp_command_ul_opts_free(cmd->cca.opts.ul); |
669 | 44 | ul_reply_port(cmd, NULL); |
670 | 44 | return 0; |
671 | | |
672 | 2 | case PLAY: |
673 | 2 | rtpp_command_play_opts_free(cmd->cca.opts.play); |
674 | 2 | break; |
675 | | |
676 | 90 | default: |
677 | 90 | RTPP_DBG_ASSERT(cmd->cca.opts.ptr == NULL); |
678 | 90 | break; |
679 | 136 | } |
680 | 92 | reply_error(cmd, ECODE_SESUNKN); |
681 | 92 | return 0; |
682 | 136 | } |
683 | | |
684 | 539 | switch (cmd->cca.op) { |
685 | 0 | case DELETE: |
686 | 0 | case RECORD: |
687 | 0 | case NORECORD: |
688 | 0 | reply_ok(cmd); |
689 | 0 | break; |
690 | | |
691 | 0 | case NOPLAY: |
692 | 0 | CALL_SMETHOD(spa->rtp->stream[i], handle_noplay); |
693 | 0 | reply_ok(cmd); |
694 | 0 | break; |
695 | | |
696 | 0 | case PLAY: |
697 | 0 | rtpp_command_play_handle(spa->rtp->stream[i], cmd); |
698 | 0 | break; |
699 | | |
700 | 0 | case COPY: |
701 | 0 | if (handle_copy(cfsp, spa, i, recording_name, record_single_file) != 0) { |
702 | 0 | reply_error(cmd, ECODE_CPYFAIL); |
703 | 0 | return 0; |
704 | 0 | } |
705 | 0 | reply_ok(cmd); |
706 | 0 | break; |
707 | | |
708 | 0 | case QUERY: |
709 | 0 | rval = handle_query(cfsp, cmd, spa->rtp, i); |
710 | 0 | if (rval != 0) { |
711 | 0 | reply_error(cmd, rval); |
712 | 0 | } |
713 | 0 | break; |
714 | | |
715 | 0 | case LOOKUP: |
716 | 539 | case UPDATE: |
717 | 539 | rtpp_command_ul_handle(cfsp, cmd, i); |
718 | 539 | rtpp_command_ul_opts_free(cmd->cca.opts.ul); |
719 | 539 | break; |
720 | | |
721 | 0 | default: |
722 | | /* Programmatic error, should not happen */ |
723 | 0 | abort(); |
724 | 539 | } |
725 | | |
726 | 539 | return 0; |
727 | 539 | } |
728 | | |
729 | | static void |
730 | | handle_info(const struct rtpp_cfg *cfsp, struct rtpp_command *cmd) |
731 | 53 | { |
732 | | #if 0 |
733 | | struct rtpp_session *spa, *spb; |
734 | | char addrs[4][256]; |
735 | | int brief; |
736 | | #endif |
737 | 53 | int len, i, load; |
738 | 53 | char buf[1024 * 8]; |
739 | 53 | unsigned long long packets_in, packets_out; |
740 | 53 | unsigned long long sessions_created; |
741 | 53 | int sessions_active, rtp_streams_active; |
742 | 53 | const char *opts; |
743 | | |
744 | 53 | opts = &cmd->args.v[0].s[1]; |
745 | | #if 0 |
746 | | brief = 0; |
747 | | #endif |
748 | 53 | load = 0; |
749 | 1.06k | for (i = 0; opts[i] != '\0'; i++) { |
750 | 1.02k | switch (opts[i]) { |
751 | 252 | case 'b': |
752 | 615 | case 'B': |
753 | | #if 0 |
754 | | brief = 1; |
755 | | #endif |
756 | 615 | break; |
757 | | |
758 | 195 | case 'l': |
759 | 398 | case 'L': |
760 | 398 | load = 1; |
761 | 398 | break; |
762 | | |
763 | 16 | default: |
764 | 16 | RTPP_LOG(cfsp->glog, RTPP_LOG_ERR, "command syntax error"); |
765 | 16 | reply_error(cmd, ECODE_PARSE_7); |
766 | 16 | return; |
767 | 1.02k | } |
768 | 1.02k | } |
769 | | |
770 | 37 | packets_in = CALL_SMETHOD(cfsp->rtpp_stats, getlvalbyname, "npkts_rcvd"); |
771 | 37 | packets_out = CALL_SMETHOD(cfsp->rtpp_stats, getlvalbyname, "npkts_relayed") + |
772 | 37 | CALL_SMETHOD(cfsp->rtpp_stats, getlvalbyname, "npkts_played"); |
773 | 37 | sessions_created = CALL_SMETHOD(cfsp->rtpp_stats, getlvalbyname, |
774 | 37 | "nsess_created"); |
775 | 37 | sessions_active = sessions_created - CALL_SMETHOD(cfsp->rtpp_stats, |
776 | 37 | getlvalbyname, "nsess_destroyed"); |
777 | 37 | rtp_streams_active = CALL_SMETHOD(cfsp->rtp_streams_wrt, get_length); |
778 | 37 | len = snprintf(buf, sizeof(buf), "sessions created: %llu\nactive sessions: %d\n" |
779 | 37 | "active streams: %d\npackets received: %llu\npackets transmitted: %llu\n", |
780 | 37 | sessions_created, sessions_active, rtp_streams_active, packets_in, packets_out); |
781 | 37 | if (load != 0) { |
782 | 17 | len += snprintf(buf + len, sizeof(buf) - len, "average load: %f\n", |
783 | 17 | CALL_METHOD(cfsp->rtpp_cmd_cf, get_aload)); |
784 | 17 | } |
785 | | #if 0 |
786 | | XXX this needs work to fix it after rtp/rtcp split |
787 | | for (i = 0; i < cfsp->nsessions && brief == 0; i++) { |
788 | | spa = cfsp->sessions[i]; |
789 | | if (spa == NULL || spa->stream[0]->sidx != i) |
790 | | continue; |
791 | | /* RTCP twin session */ |
792 | | if (spa->rtcp == NULL) { |
793 | | spb = spa->rtp; |
794 | | buf[len++] = '\t'; |
795 | | } else { |
796 | | spb = spa->rtcp; |
797 | | buf[len++] = '\t'; |
798 | | buf[len++] = 'C'; |
799 | | buf[len++] = ' '; |
800 | | } |
801 | | |
802 | | addr2char_r(spb->laddr[1], addrs[0], sizeof(addrs[0])); |
803 | | if (spb->addr[1] == NULL) { |
804 | | strcpy(addrs[1], "NONE"); |
805 | | } else { |
806 | | sprintf(addrs[1], "%s:%d", addr2char(spb->addr[1]), |
807 | | addr2port(spb->addr[1])); |
808 | | } |
809 | | addr2char_r(spb->laddr[0], addrs[2], sizeof(addrs[2])); |
810 | | if (spb->addr[0] == NULL) { |
811 | | strcpy(addrs[3], "NONE"); |
812 | | } else { |
813 | | sprintf(addrs[3], "%s:%d", addr2char(spb->addr[0]), |
814 | | addr2port(spb->addr[0])); |
815 | | } |
816 | | |
817 | | len += snprintf(buf + len, sizeof(buf) - len, |
818 | | "%s/%s: caller = %s:%d/%s, callee = %s:%d/%s, " |
819 | | "stats = %lu/%lu/%lu/%lu, ttl = %d/%d\n", |
820 | | spb->call_id, spb->tag, addrs[0], spb->stream[1]->port, addrs[1], |
821 | | addrs[2], spb->stream[0]->port, addrs[3], spa->pcount[0], spa->pcount[1], |
822 | | spa->pcount[2], spa->pcount[3], spb->ttl[0], spb->ttl[1]); |
823 | | if (len + 512 > sizeof(buf)) { |
824 | | rtpc_doreply(cmd, buf, len); |
825 | | len = 0; |
826 | | } |
827 | | } |
828 | | #endif |
829 | 37 | if (len > 0) { |
830 | 37 | rtpc_doreply(cmd, buf, len, 0); |
831 | 37 | } |
832 | 37 | } |