Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-irc.c
blob6b6b954b7995f57f9eb791db71b40b919dbce2ef
1 /* packet-irc.c
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * Copied from packet-tftp.c
9 * SPDX-License-Identifier: GPL-2.0-or-later
13 * Routines for IRC packet dissection
15 * See
17 * http://www.irchelp.org/irchelp/rfc/
19 * and the RFCs and other documents it mentions, such as RFC 1459, RFCs
20 * 2810, 2811, 2812, and 2813,
22 * For CTCP, see :
23 * http://www.irchelp.org/irchelp/rfc/ctcpspec.html
24 * http://web.archive.org/web/20031203073050/http://www.invlogic.com/irc/ctcp.html
25 * https://www.ietf.org/archive/id/draft-oakley-irc-ctcp-02.txt
28 #include "config.h"
30 #include <epan/packet.h>
31 #include <epan/expert.h>
33 void proto_register_irc(void);
34 void proto_reg_handoff_irc(void);
36 static int proto_irc;
37 static int proto_irc_ctcp;
38 static int hf_irc_request;
39 static int hf_irc_request_prefix;
40 static int hf_irc_request_command;
41 static int hf_irc_request_command_param;
42 static int hf_irc_request_trailer;
43 static int hf_irc_response;
44 static int hf_irc_response_prefix;
45 static int hf_irc_response_command;
46 static int hf_irc_response_num_command;
47 static int hf_irc_response_command_param;
48 static int hf_irc_response_trailer;
49 static int hf_irc_ctcp;
50 static int hf_irc_ctcp_command;
51 static int hf_irc_ctcp_params;
53 static int ett_irc;
54 static int ett_irc_request;
55 static int ett_irc_request_command;
56 static int ett_irc_response;
57 static int ett_irc_response_command;
59 static expert_field ei_irc_missing_end_delimiter;
60 static expert_field ei_irc_numeric_request_command;
61 static expert_field ei_irc_response_command;
62 static expert_field ei_irc_prefix_missing_ending_space;
63 static expert_field ei_irc_request_command;
64 static expert_field ei_irc_tag_data_invalid;
66 /* This must be a null-terminated string */
67 static const uint8_t TAG_DELIMITER[] = {0x01, 0x00};
68 /* patterns used for tvb_ws_mempbrk_pattern_uint8 */
69 static ws_mempbrk_pattern pbrk_tag_delimiter;
71 static dissector_handle_t ctcp_handle;
73 #define TCP_PORT_RANGE "6667,57000" /* Not IANA registered */
75 static int
76 dissect_irc_ctcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
78 proto_tree *ctcp_tree;
79 proto_item *ti;
80 const uint8_t *str_command, *str_params;
81 int space_offset = -1;
83 ti = proto_tree_add_item(tree, hf_irc_ctcp, tvb, 0, -1, ENC_ASCII|ENC_NA);
84 ctcp_tree = proto_item_add_subtree(ti, ett_irc);
86 space_offset = tvb_find_uint8(tvb, 1, -1, ' ');
87 if (space_offset == -1) {
88 proto_tree_add_item_ret_string(ctcp_tree, hf_irc_ctcp_command, tvb, 0, tvb_reported_length(tvb), ENC_ASCII|ENC_NA, pinfo->pool, &str_command);
90 else {
91 proto_tree_add_item_ret_string(ctcp_tree, hf_irc_ctcp_command, tvb, 0, space_offset, ENC_ASCII|ENC_NA, pinfo->pool, &str_command);
92 proto_tree_add_item_ret_string(ctcp_tree, hf_irc_ctcp_params, tvb, space_offset+1, tvb_reported_length(tvb)-space_offset-1, ENC_ASCII|ENC_NA, pinfo->pool, &str_params);
95 return tvb_captured_length(tvb);
98 static void
99 dissect_irc_tag_data(proto_tree *tree, proto_item *item, tvbuff_t *tvb, int offset, int datalen, packet_info *pinfo, const uint8_t* command)
101 unsigned char found_start_needle = 0,
102 found_end_needle = 0;
103 int tag_start_offset, tag_end_offset;
104 tvbuff_t *next_tvb;
106 tag_start_offset = tvb_ws_mempbrk_pattern_uint8(tvb, offset, datalen, &pbrk_tag_delimiter, &found_start_needle);
107 if (tag_start_offset == -1)
109 /* no tag data */
110 return;
113 tag_end_offset = tvb_ws_mempbrk_pattern_uint8(tvb, tag_start_offset+1, datalen, &pbrk_tag_delimiter, &found_end_needle);
114 if (tag_end_offset == -1)
116 expert_add_info(pinfo, item, &ei_irc_missing_end_delimiter);
117 return;
120 if ((strcmp(command, "NOTICE") != 0) &&
121 (strcmp(command, "PRIVMSG") != 0))
123 expert_add_info(pinfo, item, &ei_irc_tag_data_invalid);
126 /* Placeholder to call CTCP dissector, strip out delimiter */
127 if(tree) {
128 next_tvb = tvb_new_subset_length(tvb, tag_start_offset+1, datalen-2 );
129 dissect_irc_ctcp(next_tvb, pinfo, tree, NULL);
133 static void
134 dissect_irc_request(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset, int linelen)
136 proto_tree *request_tree, *command_tree = NULL;
137 proto_item *request_item;
138 int start_offset = offset;
139 int end_offset = start_offset+linelen;
140 int eop_offset = -1,
141 eoc_offset = -1,
142 eocp_offset,
143 tag_start_offset, tag_end_offset;
144 const uint8_t *str_command;
145 unsigned char found_tag_needle = 0;
146 bool first_command_param = true;
148 request_item = proto_tree_add_item(tree, hf_irc_request, tvb, offset, linelen, ENC_ASCII);
149 if (linelen <= 0)
150 return;
152 request_tree = proto_item_add_subtree(request_item, ett_irc_request );
154 /* Check if message has a prefix */
155 if (tvb_get_uint8(tvb, offset) == ':')
157 /* find the end of the prefix */
158 eop_offset = tvb_find_uint8(tvb, offset+1, linelen-1, ' ');
159 if (eop_offset == -1)
161 expert_add_info(pinfo, request_item, &ei_irc_prefix_missing_ending_space);
162 return;
165 proto_tree_add_item(request_tree, hf_irc_request_prefix, tvb, offset+1, eop_offset-offset-1, ENC_ASCII);
166 offset = eop_offset+1;
169 /* clear out any whitespace before command */
170 while(offset < end_offset && tvb_get_uint8(tvb, offset) == ' ')
172 offset++;
174 if (offset == end_offset)
176 expert_add_info(pinfo, request_item, &ei_irc_request_command);
177 return;
180 eoc_offset = tvb_find_uint8(tvb, offset, end_offset-offset, ' ');
181 if (eoc_offset == -1)
183 const uint8_t* col_str;
184 proto_tree_add_item_ret_string(request_tree, hf_irc_request_command, tvb, offset, end_offset-offset, ENC_ASCII|ENC_NA, pinfo->pool, &col_str);
185 col_append_fstr( pinfo->cinfo, COL_INFO, " (%s)", col_str);
187 /* Warn if there is a "numeric" command */
188 if ((end_offset-offset == 3) &&
189 (g_ascii_isdigit(tvb_get_uint8(tvb, offset))) &&
190 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+1))) &&
191 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+2))))
193 expert_add_info(pinfo, request_item, &ei_irc_numeric_request_command);
195 return;
198 proto_tree_add_item_ret_string(request_tree, hf_irc_request_command, tvb, offset, eoc_offset-offset, ENC_ASCII|ENC_NA, pinfo->pool, &str_command);
199 col_append_fstr( pinfo->cinfo, COL_INFO, " (%s)", str_command);
201 /* Warn if there is a "numeric" command */
202 if ((eoc_offset-offset == 3) &&
203 (g_ascii_isdigit(tvb_get_uint8(tvb, offset))) &&
204 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+1))) &&
205 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+2))))
207 expert_add_info(pinfo, request_item, &ei_irc_numeric_request_command);
210 offset = eoc_offset+1;
212 /* clear out any whitespace before command parameter */
213 while(offset < end_offset && tvb_get_uint8(tvb, offset) == ' ')
215 offset++;
217 if (offset == end_offset)
219 /* No command parameters */
220 return;
223 /* Check if message has a trailer */
224 if (tvb_get_uint8(tvb, offset) == ':')
226 proto_tree_add_item(request_tree, hf_irc_request_trailer, tvb, offset+1, end_offset-offset-1, ENC_ASCII);
227 dissect_irc_tag_data(request_tree, request_item, tvb, offset+1, end_offset-offset-1, pinfo, str_command);
228 return;
231 while(offset < end_offset)
233 eocp_offset = tvb_find_uint8(tvb, offset, end_offset-offset, ' ');
234 tag_start_offset = tvb_ws_mempbrk_pattern_uint8(tvb, offset, end_offset-offset, &pbrk_tag_delimiter, &found_tag_needle);
236 /* Create subtree when the first parameter is found */
237 if (first_command_param)
239 command_tree = proto_tree_add_subtree(request_tree, tvb, offset, end_offset-offset,
240 ett_irc_request_command, NULL, "Command parameters");
241 first_command_param = false;
244 if (((eocp_offset == -1) && (tag_start_offset == -1)) ||
245 ((eocp_offset != -1) && (tag_start_offset == -1)) ||
246 (eocp_offset < tag_start_offset))
248 /* regular message should be dissected */
250 if (eocp_offset == -1)
252 proto_tree_add_item(command_tree, hf_irc_request_command_param, tvb, offset, end_offset-offset, ENC_ASCII);
253 return;
256 proto_tree_add_item(command_tree, hf_irc_request_command_param, tvb, offset, eocp_offset-offset, ENC_ASCII);
257 offset = eocp_offset+1;
259 /* clear out any whitespace before next command parameter */
260 while(offset < end_offset && tvb_get_uint8(tvb, offset) == ' ')
262 offset++;
264 if (offset == end_offset)
266 break;
269 /* Check if message has a trailer */
270 if (tvb_get_uint8(tvb, offset) == ':')
272 proto_tree_add_item(request_tree, hf_irc_request_trailer, tvb, offset+1, end_offset-offset-1, ENC_ASCII);
273 dissect_irc_tag_data(request_tree, request_item, tvb, offset+1, end_offset-offset-1, pinfo, str_command);
274 return;
277 else if (((eocp_offset == -1) && (tag_start_offset != -1)) ||
278 (eocp_offset > tag_start_offset))
280 /* tag data dissected */
282 found_tag_needle = 0;
283 tag_end_offset = tvb_ws_mempbrk_pattern_uint8(tvb, tag_start_offset+1, end_offset-tag_start_offset-1, &pbrk_tag_delimiter, &found_tag_needle);
284 if (tag_end_offset == -1)
286 expert_add_info(pinfo, request_item, &ei_irc_missing_end_delimiter);
287 return;
290 dissect_irc_tag_data(request_tree, request_item, tvb, tag_start_offset, tag_end_offset-tag_start_offset, pinfo, str_command);
291 offset = tag_end_offset+1;
296 static void
297 dissect_irc_response(proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo, int offset, int linelen)
299 proto_tree *response_tree, *command_tree = NULL;
300 proto_item *response_item, *hidden_item;
301 int start_offset = offset;
302 int end_offset = start_offset+linelen;
303 int eop_offset = -1,
304 eoc_offset = -1,
305 eocp_offset,
306 tag_start_offset, tag_end_offset;
307 const uint8_t* str_command;
308 uint16_t num_command;
309 unsigned char found_tag_needle = 0;
310 bool first_command_param = true;
312 response_item = proto_tree_add_item(tree, hf_irc_response, tvb, offset, linelen, ENC_ASCII);
313 if (linelen <= 0)
314 return;
316 response_tree = proto_item_add_subtree(response_item, ett_irc_response );
318 /* Check if message has a prefix */
319 if (tvb_get_uint8(tvb, offset) == ':')
321 /* find the end of the prefix */
322 eop_offset = tvb_find_uint8(tvb, offset+1, linelen-1, ' ');
323 if (eop_offset == -1)
325 expert_add_info(pinfo, response_item, &ei_irc_prefix_missing_ending_space);
326 return;
329 proto_tree_add_item(response_tree, hf_irc_response_prefix, tvb, offset+1, eop_offset-offset-1, ENC_ASCII);
330 offset = eop_offset+1;
333 /* clear out any whitespace before command */
334 while(offset < end_offset && tvb_get_uint8(tvb, offset) == ' ')
336 offset++;
338 if (offset == end_offset)
340 expert_add_info(pinfo, response_item, &ei_irc_response_command);
341 return;
344 eoc_offset = tvb_find_uint8(tvb, offset, end_offset-offset, ' ');
345 if (eoc_offset == -1)
347 const uint8_t* col_str;
348 proto_tree_add_item_ret_string(response_tree, hf_irc_response_command, tvb, offset, end_offset-offset, ENC_ASCII|ENC_NA, pinfo->pool, &col_str);
349 col_append_fstr( pinfo->cinfo, COL_INFO, " (%s)", col_str);
351 /* if response command is numeric, allow it to be filtered as an integer */
352 if ((end_offset-offset == 3) &&
353 (g_ascii_isdigit(tvb_get_uint8(tvb, offset))) &&
354 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+1))) &&
355 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+2))))
357 num_command = ((tvb_get_uint8(tvb, offset)-0x30)*100) + ((tvb_get_uint8(tvb, offset+1)-0x30)*10) + (tvb_get_uint8(tvb, offset+2)-0x30);
358 hidden_item = proto_tree_add_uint(response_tree, hf_irc_response_num_command, tvb, offset, end_offset-offset, num_command);
359 proto_item_set_hidden(hidden_item);
361 return;
364 proto_tree_add_item_ret_string(response_tree, hf_irc_response_command, tvb, offset, eoc_offset-offset, ENC_ASCII|ENC_NA, pinfo->pool, &str_command);
365 col_append_fstr( pinfo->cinfo, COL_INFO, " (%s)", str_command);
367 /* if response command is numeric, allow it to be filtered as an integer */
368 if ((eoc_offset-offset == 3) &&
369 (g_ascii_isdigit(tvb_get_uint8(tvb, offset))) &&
370 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+1))) &&
371 (g_ascii_isdigit(tvb_get_uint8(tvb, offset+2))))
373 num_command = ((tvb_get_uint8(tvb, offset)-0x30)*100) + ((tvb_get_uint8(tvb, offset+1)-0x30)*10) + (tvb_get_uint8(tvb, offset+2)-0x30);
374 hidden_item = proto_tree_add_uint(response_tree, hf_irc_response_num_command, tvb, offset, eoc_offset-offset, num_command);
375 proto_item_set_hidden(hidden_item);
378 offset = eoc_offset+1;
380 /* clear out any whitespace before command parameter */
381 while(offset < end_offset && tvb_get_uint8(tvb, offset) == ' ')
383 offset++;
385 if (offset == end_offset)
387 /* No command parameters */
388 return;
391 /* Check if message has a trailer */
392 if (tvb_get_uint8(tvb, offset) == ':')
394 proto_tree_add_item(response_tree, hf_irc_response_trailer, tvb, offset+1, end_offset-offset-1, ENC_ASCII);
395 dissect_irc_tag_data(response_tree, response_item, tvb, offset+1, end_offset-offset-1, pinfo, str_command);
396 return;
399 while(offset < end_offset)
401 eocp_offset = tvb_find_uint8(tvb, offset, end_offset-offset, ' ');
402 tag_start_offset = tvb_ws_mempbrk_pattern_uint8(tvb, offset, end_offset-offset, &pbrk_tag_delimiter, &found_tag_needle);
404 /* Create subtree when the first parameter is found */
405 if (first_command_param)
407 command_tree = proto_tree_add_subtree(response_tree, tvb, offset, end_offset-offset,
408 ett_irc_response_command , NULL, "Command parameters");
409 first_command_param = false;
412 if ((tag_start_offset == -1) || (eocp_offset < tag_start_offset))
414 /* regular message should be dissected */
416 if (eocp_offset == -1)
418 proto_tree_add_item(command_tree, hf_irc_response_command_param, tvb, offset, end_offset-offset, ENC_ASCII);
419 return;
422 proto_tree_add_item(command_tree, hf_irc_response_command_param, tvb, offset, eocp_offset-offset, ENC_ASCII);
423 offset = eocp_offset+1;
425 /* clear out any whitespace before next command parameter */
426 while(offset < end_offset && tvb_get_uint8(tvb, offset) == ' ')
428 offset++;
430 if (offset == end_offset)
432 break;
435 /* Check if message has a trailer */
436 if (tvb_get_uint8(tvb, offset) == ':')
438 proto_tree_add_item(response_tree, hf_irc_response_trailer, tvb, offset+1, end_offset-offset-1, ENC_ASCII);
439 dissect_irc_tag_data(response_tree, response_item, tvb, offset+1, end_offset-offset-1, pinfo, str_command);
440 return;
443 else if ((eocp_offset == -1) || (eocp_offset > tag_start_offset))
445 /* tag data dissected */
447 found_tag_needle = 0;
448 tag_end_offset = tvb_ws_mempbrk_pattern_uint8(tvb, tag_start_offset+1, end_offset-tag_start_offset-1, &pbrk_tag_delimiter, &found_tag_needle);
449 if (tag_end_offset == -1)
451 expert_add_info(pinfo, response_item, &ei_irc_missing_end_delimiter);
452 return;
455 dissect_irc_tag_data(response_tree, response_item, tvb, tag_start_offset, tag_end_offset-tag_start_offset, pinfo, str_command);
456 offset = tag_end_offset+1;
461 static int
462 dissect_irc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
464 proto_tree *irc_tree, *ti;
465 int offset = 0;
466 int next_offset;
467 int linelen;
469 col_set_str(pinfo->cinfo, COL_PROTOCOL, "IRC");
471 col_set_str(pinfo->cinfo, COL_INFO,
472 (pinfo->match_uint == pinfo->destport) ? "Request" : "Response");
474 ti = proto_tree_add_item(tree, proto_irc, tvb, 0, -1, ENC_NA);
475 irc_tree = proto_item_add_subtree(ti, ett_irc);
478 * Process the packet data, a line at a time.
480 while (tvb_offset_exists(tvb, offset))
483 * Find the end of the line.
485 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, false);
486 if (next_offset == offset) {
488 * XXX - we really want the "show data a
489 * line at a time" loops in various
490 * dissectors to do reassembly and to
491 * throw an exception if there's no
492 * line ending in the current packet
493 * and we're not doing reassembly.
495 break;
498 if (linelen != 0)
500 if (pinfo->match_uint == pinfo->destport)
502 dissect_irc_request(irc_tree, tvb, pinfo, offset, linelen);
504 else
506 dissect_irc_response(irc_tree, tvb, pinfo, offset, linelen);
509 offset = next_offset;
511 return tvb_captured_length(tvb);
514 void
515 proto_register_irc(void)
517 static hf_register_info hf[] = {
518 { &hf_irc_response, { "Response", "irc.response", FT_STRING, BASE_NONE,
519 NULL, 0x0, "Line of response message", HFILL }},
521 { &hf_irc_request, { "Request", "irc.request", FT_STRING, BASE_NONE,
522 NULL, 0x0, "Line of request message", HFILL }},
524 { &hf_irc_request_prefix, { "Prefix", "irc.request.prefix", FT_STRING, BASE_NONE,
525 NULL, 0x0, "Request prefix", HFILL }},
527 { &hf_irc_request_command, { "Command", "irc.request.command", FT_STRING, BASE_NONE,
528 NULL, 0x0, "Request command", HFILL }},
530 { &hf_irc_request_command_param, { "Parameter", "irc.request.command_parameter", FT_STRING, BASE_NONE,
531 NULL, 0x0, "Request command parameter", HFILL }},
533 { &hf_irc_request_trailer, { "Trailer", "irc.request.trailer", FT_STRING, BASE_NONE,
534 NULL, 0x0, "Request trailer", HFILL }},
536 { &hf_irc_response_prefix, { "Prefix", "irc.response.prefix", FT_STRING, BASE_NONE,
537 NULL, 0x0, "Response prefix", HFILL }},
539 { &hf_irc_response_command, { "Command", "irc.response.command", FT_STRING, BASE_NONE,
540 NULL, 0x0, "Response command", HFILL }},
542 { &hf_irc_response_num_command, { "Command", "irc.response.num_command", FT_UINT16, BASE_DEC,
543 NULL, 0x0, "Response (numeric) command", HFILL }},
545 { &hf_irc_response_command_param, { "Parameter", "irc.response.command_parameter", FT_STRING, BASE_NONE,
546 NULL, 0x0, "Response command parameter", HFILL }},
548 { &hf_irc_response_trailer, { "Trailer", "irc.response.trailer", FT_STRING, BASE_NONE,
549 NULL, 0x0, "Response trailer", HFILL }},
551 { &hf_irc_ctcp, { "CTCP", "irc.ctcp", FT_STRING, BASE_NONE,
552 NULL, 0x0, NULL, HFILL }},
554 { &hf_irc_ctcp_command, { "Command", "irc.ctcp.command", FT_STRING, BASE_NONE,
555 NULL, 0x0, "CTCP command", HFILL }},
557 { &hf_irc_ctcp_params, { "Parameters", "irc.ctcp.parameters", FT_STRING, BASE_NONE,
558 NULL, 0x0, "CTCP parameters", HFILL }},
561 static int *ett[] = {
562 &ett_irc,
563 &ett_irc_request,
564 &ett_irc_request_command,
565 &ett_irc_response,
566 &ett_irc_response_command
569 static ei_register_info ei[] = {
570 { &ei_irc_missing_end_delimiter, { "irc.missing_end_delimiter", PI_MALFORMED, PI_ERROR, "Missing ending tag delimiter (0x01)", EXPFILL }},
571 { &ei_irc_tag_data_invalid, { "irc.tag_data_invalid", PI_PROTOCOL, PI_WARN, "Tag data outside of NOTICE or PRIVMSG command", EXPFILL }},
572 { &ei_irc_prefix_missing_ending_space, { "irc.prefix_missing_ending_space", PI_MALFORMED, PI_ERROR, "Prefix missing ending <space>", EXPFILL }},
573 { &ei_irc_request_command, { "irc.request.command.missing", PI_MALFORMED, PI_ERROR, "Request has no command", EXPFILL }},
574 { &ei_irc_numeric_request_command, { "irc.request.command.numeric", PI_PROTOCOL, PI_WARN, "Numeric command not allowed in request", EXPFILL }},
575 { &ei_irc_response_command, { "irc.response.command.missing", PI_MALFORMED, PI_ERROR, "Response has no command", EXPFILL }},
578 expert_module_t* expert_irc;
580 proto_irc = proto_register_protocol("Internet Relay Chat", "IRC", "irc");
581 register_dissector("irc", dissect_irc, proto_irc);
582 proto_register_field_array(proto_irc, hf, array_length(hf));
583 proto_register_subtree_array(ett, array_length(ett));
584 expert_irc = expert_register_protocol(proto_irc);
585 expert_register_field_array(expert_irc, ei, array_length(ei));
587 /* subdissector code */
588 proto_irc_ctcp = proto_register_protocol_in_name_only("Client To Client Protocol", "CTCP", "irc.ctcp", proto_irc, FT_PROTOCOL);
590 /* compile patterns */
591 ws_mempbrk_compile(&pbrk_tag_delimiter, TAG_DELIMITER);
594 void
595 proto_reg_handoff_irc(void)
597 dissector_add_uint_range_with_preference("tcp.port", TCP_PORT_RANGE, find_dissector("irc"));
599 ctcp_handle = create_dissector_handle(dissect_irc_ctcp, proto_irc_ctcp);
603 * Editor modelines - https://www.wireshark.org/tools/modelines.html
605 * Local variables:
606 * c-basic-offset: 4
607 * tab-width: 8
608 * indent-tabs-mode: nil
609 * End:
611 * vi: set shiftwidth=4 tabstop=8 expandtab:
612 * :indentSize=4:tabSize=8:noTabs=true: