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