If we see a file with an unknown network type, report the type in a
[obnox/wireshark/wip.git] / prefs.c
1 /* prefs.c
2  * Routines for handling preferences
3  *
4  * $Id: prefs.c,v 1.30 2000/01/29 16:41:14 gram Exp $
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@zing.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * 
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  * 
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  * 
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #ifdef HAVE_SYS_TYPES_H
31 #include <sys/types.h>
32 #endif
33
34 #ifdef HAVE_DIRECT_H
35 #include <direct.h>
36 #endif
37
38 #include <stdlib.h>
39 #include <ctype.h>
40 #include <errno.h>
41
42 #ifdef HAVE_UNISTD_H
43 #include <unistd.h>
44 #endif
45
46 #include <sys/stat.h>
47
48 #include "globals.h"
49 #include "packet.h"
50 #include "file.h"
51 #include "prefs.h"
52 #include "column.h"
53 #include "print.h"
54 #include "util.h"
55
56 /* Internal functions */
57 static int    set_pref(gchar*, gchar*);
58 static GList *get_string_list(gchar *);
59 static void   clear_string_list(GList *);
60
61 #define PF_NAME "preferences"
62
63 static int init_prefs = 1;
64 static gchar *pf_path = NULL;
65
66 e_prefs prefs;
67
68 gchar   *gui_ptree_line_style_text[] =
69         { "NONE", "SOLID", "DOTTED", "TABBED", NULL };
70
71 gchar   *gui_ptree_expander_style_text[] =
72         { "NONE", "SQUARE", "TRIANGLE", "CIRCULAR", NULL };
73
74
75 /* Parse through a list of comma-separated, quoted strings.  Return a
76    list of the string data */
77 static GList *
78 get_string_list(gchar *str) {
79   enum { PRE_QUOT, IN_QUOT, POST_QUOT };
80
81   gint      state = PRE_QUOT, i = 0, j = 0;
82   gboolean  backslash = FALSE;
83   gchar     cur_c, *slstr = NULL;
84   GList    *sl = NULL;
85   
86   while ((cur_c = str[i]) != '\0') {
87     if (cur_c == '"' && ! backslash) {
88       switch (state) {
89         case PRE_QUOT:
90           state = IN_QUOT;
91           slstr = (gchar *) g_malloc(sizeof(gchar) * COL_MAX_LEN);
92           j = 0;
93           break;
94         case IN_QUOT:
95           state  = POST_QUOT;
96           slstr[j] = '\0';
97           sl = g_list_append(sl, slstr);
98           break;
99         case POST_QUOT:
100           clear_string_list(sl);
101           return NULL;
102           break;
103         default:
104           break;
105       }
106     } else if (cur_c == '\\' && ! backslash) {
107       backslash = TRUE;
108     } else if (cur_c == ',' && state == POST_QUOT) {
109       state = PRE_QUOT;
110     } else if (state == IN_QUOT && j < COL_MAX_LEN) {
111       slstr[j] = str[i];
112       j++;
113     }
114     i++;
115   }
116   if (state != POST_QUOT) {
117     clear_string_list(sl);
118   }
119   return(sl);
120 }
121
122 void
123 clear_string_list(GList *sl) {
124   GList *l = sl;
125   
126   while (l) {
127     g_free(l->data);
128     l = g_list_remove_link(l, l);
129   }
130 }
131
132 /* Takes an string and a pointer to an array of strings, and a default int value.
133  * The array must be terminated by a NULL string. If the string is found in the array
134  * of strings, the index of that string in the array is returned. Otherwise, the
135  * default value that was passed as the third argument is returned.
136  */
137 static int
138 find_index_from_string_array(char *needle, char **haystack, int default_value)
139 {
140         int i = 0;
141
142         while (haystack[i] != NULL) {
143                 if (strcmp(needle, haystack[i]) == 0) {
144                         return i;
145                 }
146                 i++;    
147         }
148         return default_value;
149 }
150
151 /* Preferences file format:
152  * - Configuration directives start at the beginning of the line, and 
153  *   are terminated with a colon.
154  * - Directives can be continued on the next line by preceding them with
155  *   whitespace.
156  *
157  * Example:
158
159 # This is a comment line
160 print.command: lpr
161 print.file: /a/very/long/path/
162         to/ethereal-out.ps
163  *
164  */
165
166 #define MAX_VAR_LEN    48
167 #define MAX_VAL_LEN  1024
168
169 #define DEF_NUM_COLS    6
170 e_prefs *
171 read_prefs(char **pf_path_return) {
172   enum { START, IN_VAR, PRE_VAL, IN_VAL, IN_SKIP };
173   FILE     *pf;
174   gchar     cur_var[MAX_VAR_LEN], cur_val[MAX_VAL_LEN];
175   int       got_c, state = START, i;
176   gint      var_len = 0, val_len = 0, fline = 1, pline = 1;
177   gboolean  got_val = FALSE;
178   fmt_data *cfmt;
179   gchar    *col_fmt[] = {"No.",      "%m", "Time",        "%t",
180                          "Source",   "%s", "Destination", "%d",
181                          "Protocol", "%p", "Info",        "%i"};
182
183   
184   /* Initialize preferences.  With any luck, these values will be
185      overwritten below. */
186   if (init_prefs) {
187     init_prefs       = 0;
188     prefs.pr_format  = PR_FMT_TEXT;
189     prefs.pr_dest    = PR_DEST_CMD;
190     prefs.pr_file    = g_strdup("ethereal.out");
191     prefs.pr_cmd     = g_strdup("lpr");
192     prefs.col_list = NULL;
193     for (i = 0; i < DEF_NUM_COLS; i++) {
194       cfmt = (fmt_data *) g_malloc(sizeof(fmt_data));
195       cfmt->title = g_strdup(col_fmt[i * 2]);
196       cfmt->fmt   = g_strdup(col_fmt[(i * 2) + 1]);
197       prefs.col_list = g_list_append(prefs.col_list, cfmt);
198     }
199     prefs.num_cols  = DEF_NUM_COLS;
200     prefs.st_client_fg.pixel =     0;
201     prefs.st_client_fg.red   = 32767;
202     prefs.st_client_fg.green =     0;
203     prefs.st_client_fg.blue  =     0;
204     prefs.st_client_bg.pixel = 65535;
205     prefs.st_client_bg.red   = 65535;
206     prefs.st_client_bg.green = 65535;
207     prefs.st_client_bg.blue  = 65535;
208     prefs.st_server_fg.pixel =     0;
209     prefs.st_server_fg.red   =     0;
210     prefs.st_server_fg.green =     0;
211     prefs.st_server_fg.blue  = 32767;
212     prefs.st_server_bg.pixel = 65535;
213     prefs.st_server_bg.red   = 65535;
214     prefs.st_server_bg.green = 65535;
215     prefs.st_server_bg.blue  = 65535;
216     prefs.gui_scrollbar_on_right = TRUE;
217     prefs.gui_plist_sel_browse = FALSE;
218     prefs.gui_ptree_sel_browse = FALSE;
219     prefs.gui_ptree_line_style = 0;
220     prefs.gui_ptree_expander_style = 1;
221   }
222
223   if (! pf_path) {
224     pf_path = (gchar *) g_malloc(strlen(get_home_dir()) + strlen(PF_DIR) +
225       strlen(PF_NAME) + 4);
226     sprintf(pf_path, "%s/%s/%s", get_home_dir(), PF_DIR, PF_NAME);
227   }
228     
229   *pf_path_return = NULL;
230   if ((pf = fopen(pf_path, "r")) == NULL) {
231     if (errno != ENOENT)
232       *pf_path_return = pf_path;
233     return &prefs;
234   }
235     
236   while ((got_c = getc(pf)) != EOF) {
237     if (got_c == '\n') {
238       state = START;
239       fline++;
240       continue;
241     }
242     if (var_len >= MAX_VAR_LEN) {
243       g_warning ("%s line %d: Variable too long", pf_path, fline);
244       state = IN_SKIP;
245       var_len = 0;
246       continue;
247     }
248     if (val_len >= MAX_VAL_LEN) {
249       g_warning ("%s line %d: Value too long", pf_path, fline);
250       state = IN_SKIP;
251       var_len = 0;
252       continue;
253     }
254     
255     switch (state) {
256       case START:
257         if (isalnum(got_c)) {
258           if (var_len > 0) {
259             if (got_val) {
260               cur_var[var_len] = '\0';
261               cur_val[val_len] = '\0';
262               if (! set_pref(cur_var, cur_val))
263                 g_warning ("%s line %d: Bogus preference", pf_path, pline);
264             } else {
265               g_warning ("%s line %d: Incomplete preference", pf_path, pline);
266             }
267           }
268           state      = IN_VAR;
269           got_val    = FALSE;
270           cur_var[0] = got_c;
271           var_len    = 1;
272           pline = fline;
273         } else if (isspace(got_c) && var_len > 0 && got_val) {
274           state = PRE_VAL;
275         } else if (got_c == '#') {
276           state = IN_SKIP;
277         } else {
278           g_warning ("%s line %d: Malformed line", pf_path, fline);
279         }
280         break;
281       case IN_VAR:
282         if (got_c != ':') {
283           cur_var[var_len] = got_c;
284           var_len++;
285         } else {
286           state   = PRE_VAL;
287           val_len = 0;
288           got_val = TRUE;
289         }
290         break;
291       case PRE_VAL:
292         if (!isspace(got_c)) {
293           state = IN_VAL;
294           cur_val[val_len] = got_c;
295           val_len++;
296         }
297         break;
298       case IN_VAL:
299         if (got_c != '#')  {
300           cur_val[val_len] = got_c;
301           val_len++;
302         } else {
303           while (isspace(cur_val[val_len]) && val_len > 0)
304             val_len--;
305           state = IN_SKIP;
306         }
307         break;
308     }
309   }
310   if (var_len > 0) {
311     if (got_val) {
312       cur_var[var_len] = '\0';
313       cur_val[val_len] = '\0';
314       if (! set_pref(cur_var, cur_val))
315         g_warning ("%s line %d: Bogus preference", pf_path, pline);
316     } else {
317       g_warning ("%s line %d: Incomplete preference", pf_path, pline);
318     }
319   }
320   fclose(pf);
321   
322   return &prefs;
323 }
324
325 #define PRS_PRINT_FMT    "print.format"
326 #define PRS_PRINT_DEST   "print.destination"
327 #define PRS_PRINT_FILE   "print.file"
328 #define PRS_PRINT_CMD    "print.command"
329 #define PRS_COL_FMT      "column.format"
330 #define PRS_STREAM_CL_FG "stream.client.fg"
331 #define PRS_STREAM_CL_BG "stream.client.bg"
332 #define PRS_STREAM_SR_FG "stream.server.fg"
333 #define PRS_STREAM_SR_BG "stream.server.bg"
334 #define PRS_GUI_SCROLLBAR_ON_RIGHT "gui.scrollbar_on_right"
335 #define PRS_GUI_PLIST_SEL_BROWSE "gui.packet_list_sel_browse"
336 #define PRS_GUI_PTREE_SEL_BROWSE "gui.protocol_tree_sel_browse"
337 #define PRS_GUI_PTREE_LINE_STYLE "gui.protocol_tree_line_style"
338 #define PRS_GUI_PTREE_EXPANDER_STYLE "gui.protocol_tree_expander_style"
339
340 #define RED_COMPONENT(x)   ((((x) >> 16) & 0xff) * 65535 / 255)
341 #define GREEN_COMPONENT(x) ((((x) >>  8) & 0xff) * 65535 / 255)
342 #define BLUE_COMPONENT(x)   (((x)        & 0xff) * 65535 / 255)
343
344 static gchar *pr_formats[] = { "text", "postscript" };
345 static gchar *pr_dests[]   = { "command", "file" };
346
347 int
348 set_pref(gchar *pref, gchar *value) {
349   GList    *col_l;
350   gint      llen;
351   fmt_data *cfmt;
352   unsigned long int cval;
353
354   if (strcmp(pref, PRS_PRINT_FMT) == 0) {
355     if (strcmp(value, pr_formats[PR_FMT_TEXT]) == 0) {
356       prefs.pr_format = PR_FMT_TEXT;
357     } else if (strcmp(value, pr_formats[PR_FMT_PS]) == 0) {
358       prefs.pr_format = PR_FMT_PS;
359     } else {
360       return 0;
361     }
362   } else if (strcmp(pref, PRS_PRINT_DEST) == 0) {
363     if (strcmp(value, pr_dests[PR_DEST_CMD]) == 0) {
364       prefs.pr_dest = PR_DEST_CMD;
365     } else if (strcmp(value, pr_dests[PR_DEST_FILE]) == 0) {
366       prefs.pr_dest = PR_DEST_FILE;
367     } else {
368       return 0;
369     }
370   } else if (strcmp(pref, PRS_PRINT_FILE) == 0) {
371     if (prefs.pr_file) g_free(prefs.pr_file);
372     prefs.pr_file = g_strdup(value);
373   } else if (strcmp(pref, PRS_PRINT_CMD) == 0) {
374     if (prefs.pr_cmd) g_free(prefs.pr_cmd);
375     prefs.pr_cmd = g_strdup(value);
376   } else if (strcmp(pref, PRS_COL_FMT) == 0) {
377     if ((col_l = get_string_list(value)) && (g_list_length(col_l) % 2) == 0) {
378       while (prefs.col_list) {
379         cfmt = prefs.col_list->data;
380         g_free(cfmt->title);
381         g_free(cfmt->fmt);
382         g_free(cfmt);
383         prefs.col_list = g_list_remove_link(prefs.col_list, prefs.col_list);
384       }
385       llen             = g_list_length(col_l);
386       prefs.num_cols   = llen / 2;
387       col_l = g_list_first(col_l);
388       while(col_l) {
389         cfmt = (fmt_data *) g_malloc(sizeof(fmt_data));
390         cfmt->title    = g_strdup(col_l->data);
391         col_l          = col_l->next;
392         cfmt->fmt      = g_strdup(col_l->data);
393         col_l          = col_l->next;
394         prefs.col_list = g_list_append(prefs.col_list, cfmt);
395       }
396       /* To do: else print some sort of error? */
397     }
398     clear_string_list(col_l);
399   } else if (strcmp(pref, PRS_STREAM_CL_FG) == 0) {
400     cval = strtoul(value, NULL, 16);
401     prefs.st_client_fg.pixel = 0;
402     prefs.st_client_fg.red   = RED_COMPONENT(cval);
403     prefs.st_client_fg.green = GREEN_COMPONENT(cval);
404     prefs.st_client_fg.blue  = BLUE_COMPONENT(cval);
405   } else if (strcmp(pref, PRS_STREAM_CL_BG) == 0) {
406     cval = strtoul(value, NULL, 16);
407     prefs.st_client_bg.pixel = 0;
408     prefs.st_client_bg.red   = RED_COMPONENT(cval);
409     prefs.st_client_bg.green = GREEN_COMPONENT(cval);
410     prefs.st_client_bg.blue  = BLUE_COMPONENT(cval);
411   } else if (strcmp(pref, PRS_STREAM_SR_FG) == 0) {
412     cval = strtoul(value, NULL, 16);
413     prefs.st_server_fg.pixel = 0;
414     prefs.st_server_fg.red   = RED_COMPONENT(cval);
415     prefs.st_server_fg.green = GREEN_COMPONENT(cval);
416     prefs.st_server_fg.blue  = BLUE_COMPONENT(cval);
417   } else if (strcmp(pref, PRS_STREAM_SR_BG) == 0) {
418     cval = strtoul(value, NULL, 16);
419     prefs.st_server_bg.pixel = 0;
420     prefs.st_server_bg.red   = RED_COMPONENT(cval);
421     prefs.st_server_bg.green = GREEN_COMPONENT(cval);
422     prefs.st_server_bg.blue  = BLUE_COMPONENT(cval);
423   } else if (strcmp(pref, PRS_GUI_SCROLLBAR_ON_RIGHT) == 0) {
424     if (strcmp(value, "TRUE") == 0) {
425             prefs.gui_scrollbar_on_right = TRUE;
426     }
427     else {
428             prefs.gui_scrollbar_on_right = FALSE;
429     }
430   } else if (strcmp(pref, PRS_GUI_PLIST_SEL_BROWSE) == 0) {
431     if (strcmp(value, "TRUE") == 0) {
432             prefs.gui_plist_sel_browse = TRUE;
433     }
434     else {
435             prefs.gui_plist_sel_browse = FALSE;
436     }
437   } else if (strcmp(pref, PRS_GUI_PTREE_SEL_BROWSE) == 0) {
438     if (strcmp(value, "TRUE") == 0) {
439             prefs.gui_ptree_sel_browse = TRUE;
440     }
441     else {
442             prefs.gui_ptree_sel_browse = FALSE;
443     }
444   } else if (strcmp(pref, PRS_GUI_PTREE_LINE_STYLE) == 0) {
445           prefs.gui_ptree_line_style =
446                   find_index_from_string_array(value, gui_ptree_line_style_text, 0);
447   } else if (strcmp(pref, PRS_GUI_PTREE_EXPANDER_STYLE) == 0) {
448           prefs.gui_ptree_expander_style =
449                   find_index_from_string_array(value, gui_ptree_expander_style_text, 1);
450   } else {
451     return 0;
452   }
453   
454   return 1;
455 }
456
457 int
458 write_prefs(char **pf_path_return) {
459   FILE        *pf;
460   struct stat  s_buf;
461   
462   /* To do:
463    * - Split output lines longer than MAX_VAL_LEN
464    * - Create a function for the preference directory check/creation
465    *   so that duplication can be avoided with filter.c
466    */
467
468   if (! pf_path) {
469     pf_path = (gchar *) g_malloc(strlen(get_home_dir()) + strlen(PF_DIR) +
470       strlen(PF_NAME) + 4);
471   }
472
473   sprintf(pf_path, "%s/%s", get_home_dir(), PF_DIR);
474   if (stat(pf_path, &s_buf) != 0)
475 #ifdef WIN32
476     mkdir(pf_path);
477 #else
478     mkdir(pf_path, 0755);
479 #endif
480
481   sprintf(pf_path, "%s/%s/%s", get_home_dir(), PF_DIR, PF_NAME);
482   if ((pf = fopen(pf_path, "w")) == NULL) {
483     *pf_path_return = pf_path;
484     return errno;
485   }
486     
487   fputs("# Configuration file for Ethereal " VERSION ".\n"
488     "#\n"
489     "# This file is regenerated each time preferences are saved within\n"
490     "# Ethereal.  Making manual changes should be safe, however.\n"
491     "\n"
492     "######## Printing ########\n"
493     "\n", pf);
494
495   fprintf (pf, "# Can be one of \"text\" or \"postscript\".\n"
496     "print.format: %s\n\n", pr_formats[prefs.pr_format]);
497
498   fprintf (pf, "# Can be one of \"command\" or \"file\".\n"
499     "print.destination: %s\n\n", pr_dests[prefs.pr_dest]);
500
501   fprintf (pf, "# This is the file that gets written to when the "
502     "destination is set to \"file\"\n"
503     "%s: %s\n\n", PRS_PRINT_FILE, prefs.pr_file);
504
505   fprintf (pf, "# Output gets piped to this command when the destination "
506     "is set to \"command\"\n"
507     "%s: %s\n\n", PRS_PRINT_CMD, prefs.pr_cmd);
508
509   fprintf (pf, "# Packet list column format.  Each pair of strings consists "
510     "of a column title \n# and its format.\n"
511     "%s: %s\n\n", PRS_COL_FMT, col_format_to_pref_str());
512
513   fprintf (pf, "# TCP stream window color preferences.  Each value is a six "
514     "digit hexadecimal value in the form rrggbb.\n");
515   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_CL_FG,
516     (prefs.st_client_fg.red * 255 / 65535),
517     (prefs.st_client_fg.green * 255 / 65535),
518     (prefs.st_client_fg.blue * 255 / 65535));
519   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_CL_BG,
520     (prefs.st_client_bg.red * 255 / 65535),
521     (prefs.st_client_bg.green * 255 / 65535),
522     (prefs.st_client_bg.blue * 255 / 65535));
523   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_SR_FG,
524     (prefs.st_server_fg.red * 255 / 65535),
525     (prefs.st_server_fg.green * 255 / 65535),
526     (prefs.st_server_fg.blue * 255 / 65535));
527   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_SR_BG,
528     (prefs.st_server_bg.red * 255 / 65535),
529     (prefs.st_server_bg.green * 255 / 65535),
530     (prefs.st_server_bg.blue * 255 / 65535));
531
532   fprintf(pf, "\n# Vertical scrollbars should be on right side? TRUE/FALSE\n");
533   fprintf(pf, PRS_GUI_SCROLLBAR_ON_RIGHT ": %s\n",
534                   prefs.gui_scrollbar_on_right == TRUE ? "TRUE" : "FALSE");
535
536   fprintf(pf, "\n# Packet-list selection bar can be used to browse w/o selecting? TRUE/FALSE\n");
537   fprintf(pf, PRS_GUI_PLIST_SEL_BROWSE ": %s\n",
538                   prefs.gui_plist_sel_browse == TRUE ? "TRUE" : "FALSE");
539
540   fprintf(pf, "\n# Protocol-tree selection bar can be used to browse w/o selecting? TRUE/FALSE\n");
541   fprintf(pf, PRS_GUI_PTREE_SEL_BROWSE ": %s\n",
542                   prefs.gui_ptree_sel_browse == TRUE ? "TRUE" : "FALSE");
543
544   fprintf(pf, "\n# Protocol-tree line style. One of: NONE, SOLID, DOTTED, TABBED\n");
545   fprintf(pf, PRS_GUI_PTREE_LINE_STYLE ": %s\n",
546                   gui_ptree_line_style_text[prefs.gui_ptree_line_style]);
547
548   fprintf(pf, "\n# Protocol-tree expander style. One of: NONE, SQUARE, TRIANGLE, CIRCULAR\n");
549   fprintf(pf, PRS_GUI_PTREE_EXPANDER_STYLE ": %s\n",
550                   gui_ptree_expander_style_text[prefs.gui_ptree_expander_style]);
551
552   fclose(pf);
553
554   /* XXX - catch I/O errors (e.g. "ran out of disk space") and return
555      an error indication, or maybe write to a new preferences file and
556      rename that file on top of the old one only if there are not I/O
557      errors. */
558   return 0;
559 }