b2d6de0266ca9f40d2cccecb31553433eef15194
[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.24 2000/11/12 21:23:53 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
40 #include <glib.h>
41 #include "packet.h"
42 #include "packet-sdp.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 int hf_rtsp_method = -1;
52 static int hf_rtsp_url = -1;
53 static int hf_rtsp_status = -1;
54
55 #define TCP_PORT_RTSP                   554
56
57 static void process_rtsp_request(tvbuff_t *tvb, int offset, const u_char *data,
58         int linelen, proto_tree *tree);
59
60 static void process_rtsp_reply(tvbuff_t *tvb, int offset, const u_char *data,
61         int linelen, proto_tree *tree);
62
63 typedef enum {
64         RTSP_REQUEST,
65         RTSP_REPLY,
66         NOT_RTSP
67 } rtsp_type_t;
68
69 static const char *rtsp_methods[] = {
70         "DESCRIBE", "ANNOUNCE", "GET_PARAMETER", "OPTIONS",
71         "PAUSE", "PLAY", "RECORD", "REDIRECT", "SETUP",
72         "SET_PARAMETER", "TEARDOWN"
73 };
74
75 #define RTSP_NMETHODS   (sizeof rtsp_methods / sizeof rtsp_methods[0])
76
77 static rtsp_type_t
78 is_rtsp_request_or_reply(const u_char *line, int linelen)
79 {
80         int             ii;
81
82         /* Is this an RTSP reply? */
83         if (linelen >= 5 && strncasecmp("RTSP/", line, 5) == 0) {
84                 /*
85                  * Yes.
86                  */
87                 return RTSP_REPLY;
88         }
89
90         /*
91          * Is this an RTSP request?
92          * Check whether the line begins with one of the RTSP request
93          * methods.
94          */
95         for (ii = 0; ii < RTSP_NMETHODS; ii++) {
96                 size_t len = strlen(rtsp_methods[ii]);
97                 if (linelen >= len &&
98                     strncasecmp(rtsp_methods[ii], line, len) == 0)
99                         return RTSP_REQUEST;
100         }
101         return NOT_RTSP;
102 }
103
104 static int
105 is_content_sdp(const u_char *line, int linelen)
106 {
107         const char      *hdr = "Content-Type:";
108         size_t          hdrlen = strlen(hdr);
109         const char      *type = "application/sdp";
110         size_t          typelen = strlen(type);
111
112         if (linelen < hdrlen || strncasecmp(hdr, line, hdrlen))
113                 return 0;
114
115         line += hdrlen;
116         linelen -= hdrlen;
117         while (linelen > 0 && (*line == ' ' || *line == '\t')) {
118                 line++;
119                 linelen--;
120         }
121
122         if (linelen < typelen || strncasecmp(type, line, typelen))
123                 return 0;
124
125         line += typelen;
126         linelen -= typelen;
127         if (linelen > 0 && !isspace(*line))
128                 return 0;
129
130         return 1;
131 }
132
133 static const char rtsp_transport[] = "Transport:";
134 static const char rtsp_sps[] = "server_port=";
135 static const char rtsp_cps[] = "client_port=";
136 static const char rtsp_rtp[] = "rtp/avp";
137
138 static void
139 rtsp_create_conversation(const u_char *trans_begin, const u_char *trans_end)
140 {
141         conversation_t  *conv;
142         u_char          tbuf[256];
143         u_char          *tmp;
144         int             c_data_port, c_mon_port;
145         int             s_data_port, s_mon_port;
146
147         strncpy(tbuf, trans_begin, trans_end - trans_begin);
148         tbuf[sizeof(tbuf)-1] = 0;
149
150         tmp = tbuf + strlen(rtsp_transport);
151         while (*tmp && isspace(*tmp))
152                 tmp++;
153         if (strncasecmp(tmp, rtsp_rtp, strlen(rtsp_rtp)) != 0)
154                 return;
155
156         c_data_port = c_mon_port = 0;
157         s_data_port = s_mon_port = 0;
158         if ((tmp = strstr(tbuf, rtsp_sps))) {
159                 tmp += strlen(rtsp_sps);
160                 if (sscanf(tmp, "%u-%u", &s_data_port, &s_mon_port) < 1)
161                         g_warning("rtsp: failed to parse server_port");
162         }
163         if ((tmp = strstr(tbuf, rtsp_cps))) {
164                 tmp += strlen(rtsp_cps);
165                 if (sscanf(tmp, "%u-%u", &c_data_port, &c_mon_port) < 1)
166                         g_warning("rtsp: failed to parse client_port");
167         }
168         if (!c_data_port || !s_data_port)
169                 return;
170
171         conv = conversation_new(&pi.src, &pi.dst, PT_UDP, s_data_port,
172                 c_data_port, 0, 0);
173         conversation_set_dissector(conv, dissect_rtp);
174
175         if (!c_mon_port || !s_mon_port)
176                 return;
177
178         conv = conversation_new(&pi.src, &pi.dst, PT_UDP, s_mon_port,
179                 c_mon_port, 0, 0);
180         conversation_set_dissector(conv, dissect_rtcp);
181 }
182
183 static void
184 dissect_rtsp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
185 {
186         proto_tree      *rtsp_tree;
187         proto_item      *ti = NULL;
188         gint            offset = 0;
189         const u_char    *line;
190         gint            next_offset;
191         const u_char    *linep, *lineend;
192         int             linelen;
193         u_char          c;
194         int             is_sdp = FALSE;
195         int             datalen;
196
197         CHECK_DISPLAY_AS_DATA(proto_rtsp, tvb, pinfo, tree);
198
199         pinfo->current_proto = "RTSP";
200
201         rtsp_tree = NULL;
202         if (tree) {
203                 ti = proto_tree_add_item(tree, proto_rtsp, tvb, offset,
204                         tvb_length_remaining(tvb, offset), FALSE);
205                 rtsp_tree = proto_item_add_subtree(ti, ett_rtsp);
206         }
207
208         if (check_col(pinfo->fd, COL_PROTOCOL))
209                 col_add_str(pinfo->fd, COL_PROTOCOL, "RTSP");
210         if (check_col(pinfo->fd, COL_INFO)) {
211                 /*
212                  * Put the first line from the buffer into the summary
213                  * if it's an RTSP request or reply (but leave out the
214                  * line terminator).
215                  * Otherwise, just call it a continuation.
216                  */
217                 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset);
218                 line = tvb_get_ptr(tvb, offset, linelen);
219                 switch (is_rtsp_request_or_reply(line, linelen)) {
220
221                 case RTSP_REQUEST:
222                 case RTSP_REPLY:
223                         col_add_str(pinfo->fd, COL_INFO,
224                             format_text(line, linelen));
225                         break;
226
227                 default:
228                         col_add_str(pinfo->fd, COL_INFO, "Continuation");
229                         break;
230                 }
231         }
232
233         /*
234          * Process the packet data, a line at a time.
235          */
236         while (tvb_length_remaining(tvb, offset)) {
237                 /*
238                  * Find the end of the line.
239                  */
240                 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset);
241
242                 /*
243                  * Get a buffer that refers to the line.
244                  */
245                 line = tvb_get_ptr(tvb, offset, linelen);
246                 lineend = line + linelen;
247
248                 /*
249                  * OK, does it look like an RTSP request or
250                  * response?
251                  */
252                 switch (is_rtsp_request_or_reply(line, linelen)) {
253
254                 case RTSP_REQUEST:
255                         if (rtsp_tree != NULL)
256                                 process_rtsp_request(tvb, offset, line, linelen,
257                                     rtsp_tree);
258                         goto is_rtsp;
259
260                 case RTSP_REPLY:
261                         if (rtsp_tree != NULL)
262                                 process_rtsp_reply(tvb, offset, line, linelen,
263                                     rtsp_tree);
264                         goto is_rtsp;
265
266                 case NOT_RTSP:
267                         break;
268                 }
269
270                 /*
271                  * No.  Does it look like a blank line (as would
272                  * appear at the end of an RTSP request)?
273                  */
274                 if (linelen == 0)
275                         goto is_rtsp;   /* Yes. */
276
277                 /*
278                  * No.  Does it look like a MIME header?
279                  */
280                 linep = line;
281                 while (linep < lineend) {
282                         c = *linep++;
283                         if (!isprint(c))
284                                 break;  /* not printable, not a MIME header */
285                         switch (c) {
286
287                         case '(':
288                         case ')':
289                         case '<':
290                         case '>':
291                         case '@':
292                         case ',':
293                         case ';':
294                         case '\\':
295                         case '"':
296                         case '/':
297                         case '[':
298                         case ']':
299                         case '?':
300                         case '=':
301                         case '{':
302                         case '}':
303                                 /*
304                                  * It's a tspecial, so it's not
305                                  * part of a token, so it's not
306                                  * a field name for the beginning
307                                  * of a MIME header.
308                                  */
309                                 goto not_rtsp;
310
311                         case ':':
312                                 /*
313                                  * This ends the token; we consider
314                                  * this to be a MIME header.
315                                  */
316                                 if (is_content_sdp(line, linelen))
317                                         is_sdp = TRUE;
318                                 goto is_rtsp;
319                         }
320                 }
321
322         not_rtsp:
323                 /*
324                  * We don't consider this part of an RTSP request or
325                  * reply, so we don't display it.
326                  */
327                 break;
328
329         is_rtsp:
330                 /*
331                  * Put this line.
332                  */
333                 if (rtsp_tree) {
334                         proto_tree_add_text(rtsp_tree, tvb, offset,
335                             next_offset - offset, "%s",
336                             tvb_format_text(tvb, offset, next_offset - offset));
337                 }
338                 if (linelen > strlen(rtsp_transport) &&
339                         strncasecmp(line, rtsp_transport,
340                                 strlen(rtsp_transport)) == 0)
341                         rtsp_create_conversation(line, line + linelen);
342                 offset = next_offset;
343         }
344
345         datalen = tvb_length_remaining(tvb, offset);
346         if (is_sdp) {
347                 if (datalen > 0) {
348                         tvbuff_t *new_tvb;
349
350                         /*
351                          * Fix up the top-level item so that it doesn't
352                          * include the SDP stuff.
353                          */
354                         if (ti != NULL)
355                                 proto_item_set_len(ti, offset);
356
357                         /*
358                          * Now creat a tvbuff for the SDP stuff and
359                          * dissect it.
360                          */
361                         new_tvb = tvb_new_subset(tvb, offset, -1, -1);
362                         dissect_sdp(new_tvb, pinfo, tree);
363                 }
364         } else {
365                 if (datalen > 0) {
366                         proto_tree_add_text(rtsp_tree, tvb, offset, datalen,
367                             "Data (%d bytes)", datalen);
368                 }
369         }
370 }
371
372 static void
373 process_rtsp_request(tvbuff_t *tvb, int offset, const u_char *data,
374         int linelen, proto_tree *tree)
375 {
376         const u_char    *lineend = data + linelen;
377         int             ii;
378         const u_char    *url;
379         const u_char    *url_start;
380         u_char          *tmp_url;
381
382         /* Request Methods */
383         for (ii = 0; ii < RTSP_NMETHODS; ii++) {
384                 size_t len = strlen(rtsp_methods[ii]);
385                 if (linelen >= len && !strncasecmp(rtsp_methods[ii], data, len))
386                         break;
387         }
388         if (ii == RTSP_NMETHODS) {
389                 /*
390                  * We got here because "is_rtsp_request_or_reply()" returned
391                  * RTSP_REQUEST, so we know one of the request methods
392                  * matched, so we "can't get here".
393                  */
394                 g_assert_not_reached();
395         }
396
397         /* Method name */
398         proto_tree_add_string_hidden(tree, hf_rtsp_method, tvb, offset,
399                 strlen(rtsp_methods[ii]), rtsp_methods[ii]);
400
401         /* URL */
402         url = data;
403         while (url < lineend && !isspace(*url))
404                 url++;
405         while (url < lineend && isspace(*url))
406                 url++;
407         url_start = url;
408         while (url < lineend && !isspace(*url))
409                 url++;
410         tmp_url = g_malloc(url - url_start + 1);
411         memcpy(tmp_url, url_start, url - url_start);
412         tmp_url[url - url_start] = 0;
413         proto_tree_add_string_hidden(tree, hf_rtsp_url, tvb,
414                 offset + (url_start - data), url - url_start, tmp_url);
415         g_free(tmp_url);
416 }
417
418 static void
419 process_rtsp_reply(tvbuff_t *tvb, int offset, const u_char *data,
420         int linelen, proto_tree *tree)
421 {
422         const u_char    *lineend = data + linelen;
423         const u_char    *status = data;
424         const u_char    *status_start;
425         unsigned int    status_i;
426
427         /* status code */
428         while (status < lineend && !isspace(*status))
429                 status++;
430         while (status < lineend && isspace(*status))
431                 status++;
432         status_start = status;
433         status_i = 0;
434         while (status < lineend && isdigit(*status))
435                 status_i = status_i * 10 + *status++ - '0';
436         proto_tree_add_uint_hidden(tree, hf_rtsp_status, tvb,
437                 offset + (status_start - data),
438                 status - status_start, status_i);
439 }
440
441 void
442 proto_register_rtsp(void)
443 {
444         static gint *ett[] = {
445                 &ett_rtsp,
446         };
447         static hf_register_info hf[] = {
448         { &hf_rtsp_method,
449         { "Method", "rtsp.method", FT_STRING, BASE_NONE, NULL, 0 }},
450         { &hf_rtsp_url,
451         { "URL", "rtsp.url", FT_STRING, BASE_NONE, NULL, 0 }},
452         { &hf_rtsp_status,
453         { "Status", "rtsp.status", FT_UINT32, BASE_DEC, NULL, 0 }},
454         };
455
456         proto_rtsp = proto_register_protocol("Real Time Streaming Protocol",
457                 "rtsp");
458         proto_register_field_array(proto_rtsp, hf, array_length(hf));
459         proto_register_subtree_array(ett, array_length(ett));
460 }
461
462 void
463 proto_reg_handoff_rtsp(void)
464 {
465         dissector_add("tcp.port", TCP_PORT_RTSP, dissect_rtsp);
466 }