Give Tethereal a "-D" flag, inspired by WinPcap's "-D" flag, which
[obnox/wireshark/wip.git] / packet-rtsp.c
1 /* packet-rtsp.c
2  * Routines for RTSP packet disassembly (RFC 2326)
3  *
4  * Jason Lango <jal@netapp.com>
5  * Liberally copied from packet-http.c, by Guy Harris <guy@alum.mit.edu>
6  *
7  * $Id: packet-rtsp.c,v 1.36 2001/01/25 06:14:14 guy Exp $
8  *
9  * Ethereal - Network traffic analyzer
10  * By Gerald Combs <gerald@zing.org>
11  * Copyright 1998 Gerald Combs
12  *
13  * 
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License
16  * as published by the Free Software Foundation; either version 2
17  * of the License, or (at your option) any later version.
18  * 
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27  *
28  *
29  */
30
31 #include "config.h"
32
33 #ifdef HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36
37 #include <string.h>
38 #include <ctype.h>
39 #include <stdlib.h>
40
41 #include <glib.h>
42 #include "packet.h"
43 #include "packet-rtp.h"
44 #include "packet-rtcp.h"
45 #include "conversation.h"
46 #include "strutil.h"
47
48 static int proto_rtsp = -1;
49 static gint ett_rtsp = -1;
50
51 static gint ett_rtspframe = -1;
52
53 static int hf_rtsp_method = -1;
54 static int hf_rtsp_url = -1;
55 static int hf_rtsp_status = -1;
56
57 #define TCP_PORT_RTSP                   554
58
59 /*
60  * Takes an array of bytes, assumed to contain a null-terminated
61  * string, as an argument, and returns the length of the string -
62  * i.e., the size of the array, minus 1 for the null terminator.
63  */
64 #define STRLEN_CONST(str)       (sizeof (str) - 1)
65
66 #define RTSP_FRAMEHDR   ('$')
67
68 static int
69 dissect_rtspinterleaved(tvbuff_t *tvb, int offset, packet_info *pinfo,
70         proto_tree *tree)
71 {
72         proto_tree      *rtspframe_tree;
73         proto_item      *ti;
74         int             orig_offset;
75         guint8          rf_start;       /* always RTSP_FRAMEHDR */
76         guint8          rf_chan;        /* interleaved channel id */
77         guint16         rf_len;         /* packet length */
78         gint            framelen;
79         tvbuff_t        *next_tvb;
80
81         orig_offset = offset;
82         rf_start = tvb_get_guint8(tvb, offset);
83         rf_chan = tvb_get_guint8(tvb, offset+1);
84         rf_len = tvb_get_ntohs(tvb, offset+2);
85
86         if (check_col(pinfo->fd, COL_INFO))
87                 col_add_fstr(pinfo->fd, COL_INFO, 
88                         "Interleaved channel 0x%02x, %u bytes",
89                         rf_chan, rf_len);
90
91         if (tree == NULL) {
92                 /*
93                  * We're not building a full protocol tree; all we care
94                  * about is setting the column info.
95                  */
96                 return -1;
97         }
98
99         ti = proto_tree_add_protocol_format(tree, proto_rtsp, tvb, offset, 4,
100                 "RTSP Interleaved Frame, Channel: 0x%02x, %u bytes",
101                 rf_chan, rf_len);
102         rtspframe_tree = proto_item_add_subtree(ti, ett_rtspframe);
103
104         proto_tree_add_text(rtspframe_tree, tvb, offset, 1,
105                 "Magic: 0x%02x",
106                 rf_start);
107         offset += 1;
108
109         proto_tree_add_text(rtspframe_tree, tvb, offset, 1,
110                 "Channel: 0x%02x",
111                 rf_chan);
112         offset += 1;
113
114         proto_tree_add_text(rtspframe_tree, tvb, offset, 2,
115                 "Length: %u bytes",
116                 rf_len);
117         offset += 2;
118
119         /*
120          * We set the actual length of the tvbuff for the interleaved
121          * stuff to the minimum of what's left in the tvbuff and the
122          * length in the header.
123          *
124          * XXX - what if there's nothing left in the tvbuff?
125          * We'd want a BoundsError exception to be thrown, so
126          * that a Short Frame would be reported.
127          */
128         framelen = tvb_length_remaining(tvb, offset);
129         if (framelen > rf_len)
130                 framelen = rf_len;
131         next_tvb = tvb_new_subset(tvb, offset, framelen, rf_len);
132         dissect_data(next_tvb, 0, pinfo, tree);
133         offset += rf_len;
134
135         return offset - orig_offset;
136 }
137
138 static void process_rtsp_request(tvbuff_t *tvb, int offset, const u_char *data,
139         int linelen, proto_tree *tree);
140
141 static void process_rtsp_reply(tvbuff_t *tvb, int offset, const u_char *data,
142         int linelen, proto_tree *tree);
143
144 typedef enum {
145         RTSP_REQUEST,
146         RTSP_REPLY,
147         NOT_RTSP
148 } rtsp_type_t;
149
150 static const char *rtsp_methods[] = {
151         "DESCRIBE", "ANNOUNCE", "GET_PARAMETER", "OPTIONS",
152         "PAUSE", "PLAY", "RECORD", "REDIRECT", "SETUP",
153         "SET_PARAMETER", "TEARDOWN"
154 };
155
156 #define RTSP_NMETHODS   (sizeof rtsp_methods / sizeof rtsp_methods[0])
157
158 static dissector_handle_t sdp_handle;
159
160 static rtsp_type_t
161 is_rtsp_request_or_reply(const u_char *line, int linelen)
162 {
163         int             ii;
164
165         /* Is this an RTSP reply? */
166         if (linelen >= 5 && strncasecmp("RTSP/", line, 5) == 0) {
167                 /*
168                  * Yes.
169                  */
170                 return RTSP_REPLY;
171         }
172
173         /*
174          * Is this an RTSP request?
175          * Check whether the line begins with one of the RTSP request
176          * methods.
177          */
178         for (ii = 0; ii < RTSP_NMETHODS; ii++) {
179                 size_t len = strlen(rtsp_methods[ii]);
180                 if (linelen >= len &&
181                     strncasecmp(rtsp_methods[ii], line, len) == 0)
182                         return RTSP_REQUEST;
183         }
184         return NOT_RTSP;
185 }
186
187 static const char rtsp_content_type[] = "Content-Type:";
188
189 static int
190 is_content_sdp(const u_char *line, int linelen)
191 {
192         static const char type[] = "application/sdp";
193         size_t          typelen = STRLEN_CONST(type);
194
195         line += STRLEN_CONST(rtsp_content_type);
196         linelen -= STRLEN_CONST(rtsp_content_type);
197         while (linelen > 0 && (*line == ' ' || *line == '\t')) {
198                 line++;
199                 linelen--;
200         }
201
202         if (linelen < typelen || strncasecmp(type, line, typelen))
203                 return FALSE;
204
205         line += typelen;
206         linelen -= typelen;
207         if (linelen > 0 && !isspace(*line))
208                 return FALSE;
209
210         return TRUE;
211 }
212
213 static const char rtsp_transport[] = "Transport:";
214 static const char rtsp_sps[] = "server_port=";
215 static const char rtsp_cps[] = "client_port=";
216 static const char rtsp_rtp[] = "rtp/avp";
217
218 static void
219 rtsp_create_conversation(const u_char *line_begin, int line_len)
220 {
221         conversation_t  *conv;
222         u_char          buf[256];
223         u_char          *tmp;
224         int             c_data_port, c_mon_port;
225         int             s_data_port, s_mon_port;
226
227         if (line_len > sizeof(buf) - 1) {
228                 /*
229                  * Don't overflow the buffer.
230                  */
231                 line_len = sizeof(buf) - 1;
232         }
233         memcpy(buf, line_begin, line_len);
234         buf[line_len] = '\0';
235
236         tmp = buf + STRLEN_CONST(rtsp_transport);
237         while (*tmp && isspace(*tmp))
238                 tmp++;
239         if (strncasecmp(tmp, rtsp_rtp, strlen(rtsp_rtp)) != 0)
240                 return; /* we don't know this transport */
241
242         c_data_port = c_mon_port = 0;
243         s_data_port = s_mon_port = 0;
244         if ((tmp = strstr(buf, rtsp_sps))) {
245                 tmp += strlen(rtsp_sps);
246                 if (sscanf(tmp, "%u-%u", &s_data_port, &s_mon_port) < 1)
247                         g_warning("rtsp: failed to parse server_port");
248         }
249         if ((tmp = strstr(buf, rtsp_cps))) {
250                 tmp += strlen(rtsp_cps);
251                 if (sscanf(tmp, "%u-%u", &c_data_port, &c_mon_port) < 1)
252                         g_warning("rtsp: failed to parse client_port");
253         }
254         if (!c_data_port || !s_data_port)
255                 return;
256
257         conv = conversation_new(&pi.src, &pi.dst, PT_UDP, s_data_port,
258                 c_data_port, 0, 0);
259         conversation_set_dissector(conv, dissect_rtp);
260
261         if (!c_mon_port || !s_mon_port)
262                 return;
263
264         conv = conversation_new(&pi.src, &pi.dst, PT_UDP, s_mon_port,
265                 c_mon_port, 0, 0);
266         conversation_set_dissector(conv, dissect_rtcp);
267 }
268
269 static const char rtsp_content_length[] = "Content-Length:";
270
271 static int
272 rtsp_get_content_length(const u_char *line_begin, int line_len)
273 {
274         u_char          buf[256];
275         u_char          *tmp;
276         long            content_length;
277         char            *p;
278         u_char          *up;
279
280         if (line_len > sizeof(buf) - 1) {
281                 /*
282                  * Don't overflow the buffer.
283                  */
284                 line_len = sizeof(buf) - 1;
285         }
286         memcpy(buf, line_begin, line_len);
287         buf[line_len] = '\0';
288
289         tmp = buf + STRLEN_CONST(rtsp_content_length);
290         while (*tmp && isspace(*tmp))
291                 tmp++;
292         content_length = strtol(tmp, &p, 10);
293         up = p;
294         if (up == tmp || (*up != '\0' && !isspace(*up)))
295                 return -1;      /* not a valid number */
296         return content_length;
297 }
298
299 static int
300 dissect_rtspmessage(tvbuff_t *tvb, int offset, packet_info *pinfo,
301         proto_tree *tree)
302 {
303         proto_tree      *rtsp_tree;
304         proto_item      *ti = NULL;
305         const u_char    *line;
306         gint            next_offset;
307         const u_char    *linep, *lineend;
308         int             orig_offset, linelen;
309         u_char          c;
310         gboolean        is_mime_header;
311         int             is_sdp = FALSE;
312         int             datalen;
313         int             content_length;
314         int             reported_datalen;
315
316         orig_offset = offset;
317         rtsp_tree = NULL;
318         if (tree) {
319                 ti = proto_tree_add_item(tree, proto_rtsp, tvb, offset,
320                         tvb_length_remaining(tvb, offset), FALSE);
321                 rtsp_tree = proto_item_add_subtree(ti, ett_rtsp);
322         }
323
324         if (check_col(pinfo->fd, COL_INFO)) {
325                 /*
326                  * Put the first line from the buffer into the summary
327                  * if it's an RTSP request or reply (but leave out the
328                  * line terminator).
329                  * Otherwise, just call it a continuation.
330                  */
331                 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset);
332                 line = tvb_get_ptr(tvb, offset, linelen);
333                 switch (is_rtsp_request_or_reply(line, linelen)) {
334
335                 case RTSP_REQUEST:
336                 case RTSP_REPLY:
337                         col_add_str(pinfo->fd, COL_INFO,
338                             format_text(line, linelen));
339                         break;
340
341                 default:
342                         col_set_str(pinfo->fd, COL_INFO, "Continuation");
343                         break;
344                 }
345         }
346
347         /*
348          * We haven't yet seen a Content-Length header.
349          */
350         content_length = -1;
351
352         /*
353          * Process the packet data, a line at a time.
354          */
355         while (tvb_offset_exists(tvb, offset)) {
356                 /*
357                  * We haven't yet concluded that this is a MIME header.
358                  */
359                 is_mime_header = FALSE;
360
361                 /*
362                  * Find the end of the line.
363                  */
364                 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset);
365
366                 /*
367                  * Get a buffer that refers to the line.
368                  */
369                 line = tvb_get_ptr(tvb, offset, linelen);
370                 lineend = line + linelen;
371
372                 /*
373                  * OK, does it look like an RTSP request or
374                  * response?
375                  */
376                 switch (is_rtsp_request_or_reply(line, linelen)) {
377
378                 case RTSP_REQUEST:
379                         if (rtsp_tree != NULL)
380                                 process_rtsp_request(tvb, offset, line, linelen,
381                                     rtsp_tree);
382                         goto is_rtsp;
383
384                 case RTSP_REPLY:
385                         if (rtsp_tree != NULL)
386                                 process_rtsp_reply(tvb, offset, line, linelen,
387                                     rtsp_tree);
388                         goto is_rtsp;
389
390                 case NOT_RTSP:
391                         break;
392                 }
393
394                 /*
395                  * No.  Does it look like a blank line (as would
396                  * appear at the end of an RTSP request)?
397                  */
398                 if (linelen == 0)
399                         goto is_rtsp;   /* Yes. */
400
401                 /*
402                  * No.  Does it look like a MIME header?
403                  */
404                 linep = line;
405                 while (linep < lineend) {
406                         c = *linep++;
407                         if (!isprint(c))
408                                 break;  /* not printable, not a MIME header */
409                         switch (c) {
410
411                         case '(':
412                         case ')':
413                         case '<':
414                         case '>':
415                         case '@':
416                         case ',':
417                         case ';':
418                         case '\\':
419                         case '"':
420                         case '/':
421                         case '[':
422                         case ']':
423                         case '?':
424                         case '=':
425                         case '{':
426                         case '}':
427                                 /*
428                                  * It's a tspecial, so it's not
429                                  * part of a token, so it's not
430                                  * a field name for the beginning
431                                  * of a MIME header.
432                                  */
433                                 goto not_rtsp;
434
435                         case ':':
436                                 /*
437                                  * This ends the token; we consider
438                                  * this to be a MIME header.
439                                  */
440                                 is_mime_header = TRUE;
441                                 goto is_rtsp;
442                         }
443                 }
444
445         not_rtsp:
446                 /*
447                  * We don't consider this part of an RTSP request or
448                  * reply, so we don't display it.
449                  */
450                 break;
451
452         is_rtsp:
453                 /*
454                  * Put this line.
455                  */
456                 if (rtsp_tree) {
457                         proto_tree_add_text(rtsp_tree, tvb, offset,
458                             next_offset - offset, "%s",
459                             tvb_format_text(tvb, offset, next_offset - offset));
460                 }
461                 if (is_mime_header) {
462                         /*
463                          * Process some MIME headers specially.
464                          */
465 #define MIME_HDR_MATCHES(header) \
466         (linelen > STRLEN_CONST(header) && \
467          strncasecmp(line, (header), STRLEN_CONST(header)) == 0)
468
469                         if (MIME_HDR_MATCHES(rtsp_transport)) {
470                                 /*
471                                  * Based on the port numbers specified
472                                  * in the Transport: header, set up
473                                  * a conversation that will be dissected
474                                  * with the appropriate dissector.
475                                  */
476                                 rtsp_create_conversation(line, linelen);
477                         } else if (MIME_HDR_MATCHES(rtsp_content_type)) {
478                                 /*
479                                  * If the Content-Type: header says this
480                                  * is SDP, dissect the payload as SDP.
481                                  */
482                                 if (is_content_sdp(line, linelen))
483                                         is_sdp = TRUE;
484                         } else if (MIME_HDR_MATCHES(rtsp_content_length)) {
485                                 /*
486                                  * Only the amount specified by the
487                                  * Content-Length: header should be treated
488                                  * as payload.
489                                  */
490                                 content_length = rtsp_get_content_length(line,
491                                     linelen);
492                         }
493                 }
494                 offset = next_offset;
495         }
496
497         /*
498          * If a content length was supplied, the amount of data to be
499          * processed as RTSP payload is the minimum of the content
500          * length and the amount of data remaining in the frame.
501          *
502          * If no content length was supplied, the amount of data to be
503          * processed is the amount of data remaining in the frame.
504          */
505         datalen = tvb_length_remaining(tvb, offset);
506         if (content_length != -1) {
507                 if (datalen > content_length)
508                         datalen = content_length;
509
510                 /*
511                  * XXX - for now, if the content length is greater
512                  * than the amount of data left in this frame (not
513                  * the amount of *captured* data left in the frame
514                  * minus the current offset, but the amount of *actual*
515                  * data that was reported to be in the frame minus
516                  * the current offset), limit it to the amount
517                  * of data left in this frame.
518                  *
519                  * If we ever handle data that crosses frame
520                  * boundaries, we'll need to remember the actual
521                  * content length.
522                  */
523                 reported_datalen = tvb_reported_length_remaining(tvb, offset);
524                 if (content_length > reported_datalen)
525                         content_length = reported_datalen;
526         }
527
528         if (datalen > 0) {
529                 /*
530                  * There's stuff left over; process it.
531                  */
532                 if (is_sdp) {
533                         tvbuff_t *new_tvb;
534
535                         /*
536                          * Fix up the top-level item so that it doesn't
537                          * include the SDP stuff.
538                          */
539                         if (ti != NULL)
540                                 proto_item_set_len(ti, offset);
541
542                         /*
543                          * Now create a tvbuff for the SDP stuff and
544                          * dissect it.
545                          *
546                          * The amount of data to be processed that's
547                          * available in the tvbuff is "datalen", which
548                          * is the minimum of the amount of data left in
549                          * the tvbuff and any specified content length.
550                          *
551                          * The amount of data to be processed that's in
552                          * this frame, regardless of whether it was
553                          * captured or not, is "content_length",
554                          * which, if no content length was specified,
555                          * is -1, i.e. "to the end of the frame.
556                          */
557                         new_tvb = tvb_new_subset(tvb, offset, datalen,
558                             content_length);
559                         call_dissector(sdp_handle, new_tvb, pinfo, tree);
560                 } else {
561                         if (tvb_get_guint8(tvb, offset) == RTSP_FRAMEHDR) {
562                                 /*
563                                  * This is interleaved stuff; don't
564                                  * treat it as raw data - set "datalen"
565                                  * to 0, so we won't skip the offset
566                                  * past it, which will cause our
567                                  * caller to process that stuff itself.
568                                  */
569                                 datalen = 0;
570                         } else {
571                                 proto_tree_add_text(rtsp_tree, tvb, offset,
572                                     datalen, "Data (%d bytes)", datalen);
573                         }
574                 }
575
576                 /*
577                  * We've processed "datalen" bytes worth of data
578                  * (which may be no data at all); advance the
579                  * offset past whatever data we've processed, so they
580                  * don't process it.
581                  */
582                 offset += datalen;
583         }
584         return offset - orig_offset;
585 }
586
587 static void
588 process_rtsp_request(tvbuff_t *tvb, int offset, const u_char *data,
589         int linelen, proto_tree *tree)
590 {
591         const u_char    *lineend = data + linelen;
592         int             ii;
593         const u_char    *url;
594         const u_char    *url_start;
595         u_char          *tmp_url;
596
597         /* Request Methods */
598         for (ii = 0; ii < RTSP_NMETHODS; ii++) {
599                 size_t len = strlen(rtsp_methods[ii]);
600                 if (linelen >= len && !strncasecmp(rtsp_methods[ii], data, len))
601                         break;
602         }
603         if (ii == RTSP_NMETHODS) {
604                 /*
605                  * We got here because "is_rtsp_request_or_reply()" returned
606                  * RTSP_REQUEST, so we know one of the request methods
607                  * matched, so we "can't get here".
608                  */
609                 g_assert_not_reached();
610         }
611
612         /* Method name */
613         proto_tree_add_string_hidden(tree, hf_rtsp_method, tvb, offset,
614                 strlen(rtsp_methods[ii]), rtsp_methods[ii]);
615
616         /* URL */
617         url = data;
618         while (url < lineend && !isspace(*url))
619                 url++;
620         while (url < lineend && isspace(*url))
621                 url++;
622         url_start = url;
623         while (url < lineend && !isspace(*url))
624                 url++;
625         tmp_url = g_malloc(url - url_start + 1);
626         memcpy(tmp_url, url_start, url - url_start);
627         tmp_url[url - url_start] = 0;
628         proto_tree_add_string_hidden(tree, hf_rtsp_url, tvb,
629                 offset + (url_start - data), url - url_start, tmp_url);
630         g_free(tmp_url);
631 }
632
633 static void
634 process_rtsp_reply(tvbuff_t *tvb, int offset, const u_char *data,
635         int linelen, proto_tree *tree)
636 {
637         const u_char    *lineend = data + linelen;
638         const u_char    *status = data;
639         const u_char    *status_start;
640         unsigned int    status_i;
641
642         /* status code */
643         while (status < lineend && !isspace(*status))
644                 status++;
645         while (status < lineend && isspace(*status))
646                 status++;
647         status_start = status;
648         status_i = 0;
649         while (status < lineend && isdigit(*status))
650                 status_i = status_i * 10 + *status++ - '0';
651         proto_tree_add_uint_hidden(tree, hf_rtsp_status, tvb,
652                 offset + (status_start - data),
653                 status - status_start, status_i);
654 }
655
656 static void
657 dissect_rtsp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
658 {
659         int             offset = 0;
660         int             len;
661
662         if (check_col(pinfo->fd, COL_PROTOCOL))
663                 col_set_str(pinfo->fd, COL_PROTOCOL, "RTSP");
664         if (check_col(pinfo->fd, COL_INFO))
665                 col_clear(pinfo->fd, COL_INFO);
666
667         while (tvb_offset_exists(tvb, offset)) {
668                 len = (tvb_get_guint8(tvb, offset) == RTSP_FRAMEHDR)
669                         ? dissect_rtspinterleaved(tvb, offset, pinfo, tree)
670                         : dissect_rtspmessage(tvb, offset, pinfo, tree);
671                 if (len == -1)
672                         break;
673                 offset += len;
674
675                 /*
676                  * OK, we've set the Protocol and Info columns for the
677                  * first RTSP message; make the columns non-writable,
678                  * so that we don't change it for subsequent RTSP messages.
679                  */
680                 col_set_writable(pinfo->fd, FALSE);
681         }
682 }
683
684 void
685 proto_register_rtsp(void)
686 {
687         static gint *ett[] = {
688                 &ett_rtspframe,
689                 &ett_rtsp,
690         };
691         static hf_register_info hf[] = {
692         { &hf_rtsp_method,
693         { "Method", "rtsp.method", FT_STRING, BASE_NONE, NULL, 0 }},
694         { &hf_rtsp_url,
695         { "URL", "rtsp.url", FT_STRING, BASE_NONE, NULL, 0 }},
696         { &hf_rtsp_status,
697         { "Status", "rtsp.status", FT_UINT32, BASE_DEC, NULL, 0 }},
698         };
699
700         proto_rtsp = proto_register_protocol("Real Time Streaming Protocol",
701                 "RTSP", "rtsp");
702         proto_register_field_array(proto_rtsp, hf, array_length(hf));
703         proto_register_subtree_array(ett, array_length(ett));
704 }
705
706 void
707 proto_reg_handoff_rtsp(void)
708 {
709         dissector_add("tcp.port", TCP_PORT_RTSP, dissect_rtsp, proto_rtsp);
710
711         /*
712          * Get a handle for the SDP dissector.
713          */
714         sdp_handle = find_dissector("sdp");
715 }