Bump BROWSER_FOO variables for Firefox 3.5.7.
[privoxy.git] / tools / uagen.pl
1 #!/usr/bin/perl
2
3 ##############################################################################################
4 # uagen (http://www.fabiankeil.de/sourcecode/uagen/)
5 #
6 # $Id: uagen.pl,v 1.2 2009/12/26 11:32:12 fabiankeil Exp $
7 #
8 # Generates a pseudo-random Firefox user agent and writes it into a Privoxy action file
9 # and optionally into a Mozilla prefs file. For documentation see 'perldoc uagen(.pl)'.
10 #
11 # Examples (created with v1.0):
12 #
13 # Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.8.0.2) Gecko/20060421 Firefox/1.5.0.2
14 # Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-CA; rv:1.8.0.2) Gecko/20060425 Firefox/1.5.0.2
15 # Mozilla/5.0 (X11; U; SunOS i86pc; no-NO; rv:1.8.0.2) Gecko/20060420 Firefox/1.5.0.2
16 # Mozilla/5.0 (X11; U; Linux x86_64; de-AT; rv:1.8.0.2) Gecko/20060422 Firefox/1.5.0.2
17 # Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.8.0.2) Gecko/20060415 Firefox/1.5.0.2
18 # Mozilla/5.0 (X11; U; OpenBSD sparc64; pl-PL; rv:1.8.0.2) Gecko/20060429 Firefox/1.5.0.2
19 # Mozilla/5.0 (X11; U; Linux i686; en-CA; rv:1.8.0.2) Gecko/20060413 Firefox/1.5.0.2
20 #
21 # Copyright (c) 2006-2009 Fabian Keil <fk@fabiankeil.de>
22 #
23 # Permission to use, copy, modify, and distribute this software for any
24 # purpose with or without fee is hereby granted, provided that the above
25 # copyright notice and this permission notice appear in all copies.
26 #
27 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
28 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
29 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
30 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
31 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
32 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
33 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
34 ##############################################################################################
35
36 use strict;
37 use warnings;
38 use Time::Local;
39 use Getopt::Long;
40
41 use constant {
42
43    UAGEN_VERSION       => 'uagen 1.0.10',
44
45    UAGEN_LOGFILE       => '/var/log/uagen.log',
46    ACTION_FILE         => '/etc/privoxy/user-agent.action',
47    MOZILLA_PREFS_FILE  => '',
48    SILENT              =>  0,
49    NO_LOGGING          =>  0,
50    NO_ACTION_FILE      =>  0,
51    LOOP                =>  0,
52    SLEEPING_TIME       =>  5,
53
54    # These variables belong together. If you only change one of them, the generated
55    # User-Agent might be invalid. If you're not sure which values make sense,
56    # are too lazy to check, but want to change them anyway, take the values you
57    # see in the "Help/About Mozilla Firefox" menu.
58
59    BROWSER_VERSION                   => "3.5.7",
60    BROWSER_REVISION                  => '1.9.1.7',
61    BROWSER_RELEASE_DATE              => '20100106',
62 };
63
64 use constant LANGUAGES => qw(
65    en-AU en-GB en-CA en-NZ en-US en-ZW es-ES de-DE de-AT de-CH fr-FR sk-SK nl-NL no-NO pl-PL
66 );
67
68 #######################################################################################
69
70 sub generate_creation_time($) {
71     my $release_date = $_ = shift;
72
73     my ($rel_year, $rel_mon, $rel_day);
74     my ($c_day, $c_mon, $c_year);
75     my $now = time;
76     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
77        localtime $now;
78     $mon  += 1;
79     $year += 1900;
80
81     unless ( m/\d{6}/ ) {
82         log_error("Invalid release date format: $release_date. Using "
83                   . BROWSER_RELEASE_DATE . " instead.");
84         $release_date = BROWSER_RELEASE_DATE;
85     }
86     $rel_year = substr $release_date, 0, 4;
87     $rel_mon  = substr $release_date, 4, 2;
88     $rel_day  = substr $release_date, 6, 2;
89
90     #1, 2, 3, Check.
91     die "release year in the future" if ( $year < $rel_year );
92     die "release month in the future"
93       if ( ( $year == $rel_year ) and ( $mon < $rel_mon ) );
94     die "release day in the future"
95       if (  ( $year == $rel_year )
96         and ( $mon  == $rel_mon )
97         and ( $mday  < $rel_day ) );
98
99     my @c_time = (0, 0, 0, $rel_day, $rel_mon - 1, $rel_year - 1900, 0, 0, 0);
100     my $c_seconds = &timelocal( @c_time );
101
102     $c_seconds = $now - (int rand ($now - $c_seconds));
103     @c_time = localtime $c_seconds;
104     ($sec, $min, $hour, $c_day, $c_mon, $c_year, $wday, $yday, $isdst) = @c_time;
105     $c_mon  += 1;
106     $c_year += 1900;
107
108     #3, 2, 1, Test.
109     die "Compilation year in the future" if ( $year < $c_year );
110     die "Compilation month in the future"
111       if ( ( $year == $c_year ) and ( $mon < $c_mon ) );
112     die "Compilation day in the future"
113       if ( ( $year == $c_year ) and ( $mon == $c_mon ) and ( $mday < $c_day ) );
114
115     return sprintf "%.2i%.2i%.2i", $c_year, $c_mon, $c_day;
116 }
117
118 sub generate_language_settings() {
119
120     our @languages;
121
122     my $language_i      = int rand (@languages);
123     my $accept_language = $languages[$language_i];
124     $accept_language =~ tr/[A-Z]/[a-z]/;
125
126     return ($languages[$language_i], $accept_language);
127 }
128
129 sub generate_platform_and_os() {
130
131     my %os_data = (
132         FreeBSD => {
133             karma             => 1,
134             platform          => 'X11',
135             architectures     => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
136             order_is_inversed => 0,
137         },
138         OpenBSD => {
139             karma             => 1,
140             platform          => 'X11',
141             architectures     => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
142             order_is_inversed => 0,
143         },
144         NetBSD => {
145             karma             => 1,
146             platform          => 'X11',
147             architectures     => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
148             order_is_inversed => 0,
149         },
150         Linux => {
151             karma             => 1,
152             platform          => 'X11',
153             architectures     => [ 'i586', 'i686', 'x86_64' ],
154             order_is_inversed => 0,
155         },
156         SunOS => {
157             karma             => 1,
158             platform          => 'X11',
159             architectures     => [ 'i86pc', 'sun4u' ],
160             order_is_inversed => 0,
161         },
162         'Mac OS X' => {
163             karma             => 1,
164             platform          => 'Macintosh',
165             architectures     => [ 'PPC', 'Intel' ],
166             order_is_inversed => 1,
167         },
168         Windows => {
169             karma             => 0,
170             platform          => 'Windows',
171             architectures     => [ 'NT 5.1' ],
172             order_is_inversed => 0,
173         }
174     );
175
176     my @os_names;
177
178     foreach my $os_name ( keys %os_data ) {
179         push @os_names, ($os_name) x $os_data{$os_name}{'karma'}
180           if $os_data{$os_name}{'karma'};
181     }
182
183     my $os_i   = int rand(@os_names);
184     my $os     = $os_names[$os_i];
185     my $arch_i = int rand( @{ $os_data{$os}{'architectures'} } );
186     my $arch   = $os_data{$os}{'architectures'}[$arch_i];
187
188     my $platform = $os_data{$os}{'platform'};
189
190     my $os_or_cpu;
191     $os_or_cpu = sprintf "%s %s",
192       $os_data{$os}{'order_is_inversed'} ? ( $arch, $os ) : ( $os, $arch );
193
194     return $platform, $os_or_cpu;
195 }
196
197 sub generate_firefox_user_agent() {
198
199     our $languages;
200     our $browser_version;
201     our $browser_revision;
202     our $browser_release_date;
203
204     my $mozillaversion  = '5.0';
205     my $security        = "U";
206
207     my $creation_time = generate_creation_time($browser_release_date);
208     my ( $locale,   $accept_language ) = generate_language_settings();
209     my ( $platform, $os_or_cpu )       = generate_platform_and_os;
210
211     my $firefox_user_agent =
212       sprintf "Mozilla/%s (%s; %s; %s; %s; rv:%s) Gecko/%s Firefox/%s",
213       $mozillaversion, $platform, $security, $os_or_cpu, $locale, $browser_revision,
214       $creation_time, $browser_version;
215
216     return $accept_language, $firefox_user_agent;
217 }
218
219 sub log_to_file($) {
220
221     my $message = shift;
222
223     our $logfile;
224     our $no_logging;
225
226     my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
227       localtime time;
228     $year += 1900;
229     $mon  += 1;
230     my $logtime = sprintf "%i/%.2i/%.2i %.2i:%.2i", $year, $mon, $mday, $hour,
231       $min;
232
233     return if $no_logging;
234
235     open( LOGFILE, ">>" . $logfile ) || die "Writing " . $logfile . " failed";
236     printf LOGFILE UAGEN_VERSION . " ($logtime) $message\n";
237     close(LOGFILE);
238
239 }
240
241 sub log_error($) {
242
243     my $message = shift;
244
245     $message = "Error: $message";
246     log_to_file($message);
247     print "$message\n";
248
249     exit(1);
250 }
251
252 sub write_action_file() {
253
254     our $action_file;
255     our $user_agent;
256     our $accept_language;
257     our $no_hide_accept_language;
258     our $action_injection;
259
260     my $action_file_content = '';
261
262     if ($action_injection){
263         open( ACTIONFILE, $action_file )
264             or log_error "Reading action file $action_file failed!";
265         while (<ACTIONFILE>) {
266             s@(hide-accept-language\{).*?(\})@$1$accept_language$2@;
267             s@(hide-user-agent\{).*?(\})@$1$user_agent$2@;
268             $action_file_content .= $_;
269         }
270         close (ACTIONFILE);
271     } else {
272         $action_file_content = "{";
273         $action_file_content .= sprintf "+hide-accept-language{%s} \\\n",
274             $accept_language unless $no_hide_accept_language;
275         $action_file_content .= sprintf " +hide-user-agent{%s} \\\n}\n/\n",
276             $user_agent;
277     }
278     open( ACTIONFILE, ">" . $action_file )
279       or log_error "Writing action file $action_file failed!";
280     print ACTIONFILE $action_file_content;
281     close(ACTIONFILE);
282
283     return 0;
284 }
285
286 sub write_prefs_file() {
287
288     our $mozilla_prefs_file;
289     our $user_agent;
290     our $accept_language;
291     our $clean_prefs;
292     my $prefs_file_content = '';
293
294     if (open( PREFSFILE, $mozilla_prefs_file )) {
295
296         while (<PREFSFILE>) {
297             s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
298             s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
299             $prefs_file_content .= $_;
300         }
301         close (PREFSFILE);
302     } else {
303         log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
304     }
305
306     $prefs_file_content .=
307         sprintf("user_pref(\"general.useragent.override\", \"%s\");\n", $user_agent) .
308         sprintf("user_pref(\"intl.accept_languages\", \"%s\");\n", $accept_language)
309         unless $clean_prefs;
310
311     open( PREFSFILE, ">" . $mozilla_prefs_file )
312       or log_error "Writing prefs file $mozilla_prefs_file failed!";
313     print PREFSFILE $prefs_file_content;
314     close(PREFSFILE);
315
316 }
317
318 sub VersionMessage() {
319     printf UAGEN_VERSION . "\n" . 'Copyright (C) 2006-2009 Fabian Keil <fk@fabiankeil.de> ' .
320         "\nhttp://www.fabiankeil.de/sourcecode/uagen/\n";
321 }
322
323 sub help() {
324
325     our $logfile;
326     our $action_file;
327     our $browser_version;
328     our $browser_revision;
329     our $browser_release_date;
330     our $sleeping_time;
331     our $loop;
332     our $mozilla_prefs_file;
333
334     my $comma_separated_languages;
335
336     $loop = $loop ? ' ' . $loop : '';
337     $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
338     foreach (LANGUAGES){
339         $comma_separated_languages .= $_ . ",";
340     }
341     chop $comma_separated_languages;
342
343     VersionMessage;
344
345     print << "    EOF"
346
347 Options and their default values if there are any:
348     [--action-file $action_file]
349     [--action-injection]
350     [--browser-release-date $browser_release_date]
351     [--browser-revision $browser_revision]
352     [--browser-version $browser_version]
353     [--clean-prefs-file]
354     [--help]
355     [--language-overwrite $comma_separated_languages]
356     [--logfile $logfile]
357     [--loop$loop]
358     [--no-action-file]
359     [--no-hide-accept-language]
360     [--no-logfile]
361     [--prefs-file$mozilla_prefs_file]
362     [--quiet]
363     [--silent]
364     [--sleeping-time $sleeping_time]
365     [--version]
366 see "perldoc $0" for more information
367     EOF
368     ;
369     exit(0);
370 }
371
372 sub main() {
373
374     my $error_message;
375     my  $no_action_file          = NO_ACTION_FILE;
376
377     our $silent                  = SILENT;
378     our $no_logging              = NO_LOGGING;
379     our $logfile                 = UAGEN_LOGFILE;
380     our $action_file             = ACTION_FILE;
381     our $browser_version         = BROWSER_VERSION;
382     our $browser_revision        = BROWSER_REVISION;
383     our $browser_release_date    = BROWSER_RELEASE_DATE;
384     our $sleeping_time           = SLEEPING_TIME;
385     our $loop                    = LOOP;
386     our $no_hide_accept_language = 0;
387     our $action_injection        = 0;
388
389     our @languages;
390     our ( $accept_language, $user_agent );
391     our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
392     our $clean_prefs = 0;
393
394     GetOptions('logfile=s' => \$logfile,
395                'action-file=s' => \$action_file,
396                'language-overwrite=s@' => \@languages,
397                'silent|quiet' => \$silent,
398                'no-hide-accept-language' => \$no_hide_accept_language,
399                'no-logfile' => \$no_logging,
400                'no-action-file' => \$no_action_file,
401                'browser-version=s' => \$browser_version,
402                'browser-revision=s' => \$browser_revision,
403                'browser-release-date=s' => \$browser_release_date,
404                'action-injection' => \$action_injection,
405                'loop' => \$loop,
406                'sleeping-time' => \$sleeping_time,
407                'prefs-file=s' => \$mozilla_prefs_file,
408                'clean-prefs-file' => \$clean_prefs,
409                'help' => \&help,
410                'version' => sub {VersionMessage() && exit(0)}
411     ) or exit(0);
412
413     if (@languages) {
414         @languages = split(/,/,join(',',@languages));
415     } else {
416         @languages = LANGUAGES;
417     }
418
419     srand( time ^ ( $$ + ( $$ << 15 ) ) );
420
421     do {
422         $error_message='';
423         ( $accept_language, $user_agent ) = generate_firefox_user_agent();
424
425         print "$user_agent\n" unless $silent;
426
427         write_action_file() unless $no_action_file;
428         write_prefs_file() if $mozilla_prefs_file;
429
430         log_to_file "Generated User-Agent: $user_agent";
431
432     } while ($loop && sleep($sleeping_time * 60));
433 }
434
435 main();
436 exit(0);
437
438 =head1 NAME
439
440 B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
441
442 =head1 SYNOPSIS
443
444 B<uagen> [B<--action-file> I<action_file>] [B<--action-injection>]
445 [B<--browser-release-date> I<browser_release_date>]
446 [B<--browser-revision> I<browser_revision>]
447 [B<--browser-version> I<browser_version>]
448 [B<--clean-prefs-file>]
449 [B<--help>] [B<--language-overwrite> I<language(s)>]
450 [B<--logfile> I<logfile>] [B<--loop>] [B<--no-action-file>] [B<--no-logfile>]
451 [B<--prefs-file> I<prefs_file>]
452 [B<--quiet>] [B<--sleeping-time> I<minutes>] [B<--silent>] [B<--version>]
453
454 =head1 DESCRIPTION
455
456 B<uagen> generates a fake Firefox User-Agent and writes it into a Privoxy action file
457 as parameter for Privoxy's B<hide-user-agent> action. Operating system, architecture,
458 platform, language and build date are randomized.
459
460 The generated language is also used as parameter for the
461 B<hide-accept-language> action which is understood by Privoxy since
462 version 3.0.5 beta.
463
464 Additionally the User-Agent can be written into prefs.js files which are
465 used by many Mozilla browsers.
466
467 =head1 OPTIONS
468
469 B<--action-file> I<action_file> Privoxy action file to write the
470 generated actions into. Default is /etc/privoxy/user-agent.action.
471
472 B<--action-injection> Don't generate a new action file from scratch,
473 but read an old one and just replace the action values. Useful
474 to keep custom URL patterns. For this to work, the action file
475 has to be already present. B<uagen> neither checks the syntax
476 nor cares if all actions are present. Garbage in, garbage out.
477
478 B<--browser-release-date> I<browser_release_date> Date when the faked
479 browser version was first released, format is YYYYMMDD. B<uagen> will
480 pick a date between the release date and the actual date to use it as
481 build time. Some sanity checks are done, but you shouldn't rely on them.
482
483 B<--browser-revision> I<browser_revision> Use a custom revision.
484 B<uagen> will use it without any sanity checks.
485
486 B<--browser-version> I<browser_version> Use a custom browser version.
487 B<uagen> will use it without any sanity checks.
488
489 B<--clean-prefs-file> The I<prefs_file> is read and the variables
490 B<general.useragent.override> and B<intl.accept_languages> are removed.
491 Only effective if I<prefs_file> is set, and only useful if you want
492 to use the browser's defaults again.
493
494 B<--help> List command line options and exit.
495
496 B<--language-overwrite> I<language(s)> Comma separated list of language codes
497 to overwrite the default values. B<uagen> chooses one of them for the generated
498 User-Agent, by default the chosen language in lower cases is also used as
499 B<hide-accept-language> parameter.
500
501 B<--logfile> I<logfile> Logfile to save error messages and the generated
502 User-Agents. Default is /var/log/uagen.log.
503
504 B<--loop> Don't exit after the generation of the action file. Sleep for
505 a while and generate a new one instead. Useful if you don't have cron(8).
506
507 B<--no-logfile> Don't log anything.
508
509 B<--no-action-file> Don't write the action file.
510
511 B<--no-hide-accept-language> Stay compatible with Privoxy 3.0.3
512 and don't generate the B<hide-accept-language> action line. You should
513 really update your Privoxy version instead.
514
515 B<--prefs-file> I<prefs_file> Use the generated User-Agent to set the
516 B<general.useragent.override> variable in the Mozilla preference file
517 I<prefs_file>, The B<intl.accept_languages> variable will be set as well.
518
519 Firefox's preference file is usually located in
520 ~/.mozilla/firefox/*.default/prefs.js. Note that Firefox doesn't reread
521 the file once it is running.
522
523 B<--quiet> Don't print the generated User-Agent to the console.
524
525 B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
526
527 B<--silent> Don't print the generated User-Agent to the console.
528
529 B<--version> Print version and exit.
530
531 The second dash is optional, options can be shortened, as long as there are
532 no ambiguities.
533
534 =head1 PRIVOXY CONFIGURATION
535
536 In Privoxy's configuration file the line:
537
538     actionsfile user-agent.action
539
540 should be added after:
541
542     actionfile default.action
543
544 and before:
545
546     actionfile user.action
547
548 This way the user can still use custom User-Agents
549 in I<user.action>. I<user-agent> has to be the name
550 of the generated action file.
551
552 If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
553
554 =head1 EXAMPLES
555
556 Without any options, B<uagen> creates an action file like:
557
558  {+hide-accept-language{en-ca} \
559   +hide-user-agent{Mozilla/5.0 (X11; U; OpenBSD i386; en-CA; rv:1.8.0.4) Gecko/20060628 Firefox/1.5.0.4} \
560  }
561  /
562
563 with the --no-accept-language option the generated file
564 could look like this one:
565
566  {+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD i386; de-DE; rv:1.8.0.4) Gecko/20060720 Firefox/1.5.0.4} \
567  }
568  /
569
570 =head1 CAVEATS
571
572 If the browser opens an encrypted connection, Privoxy can't inspect
573 the content and the browser's headers reach the server unmodified.
574 It is the user's job to use Privoxy's limit-connect action to make sure
575 there are no encrypted connections to untrusted sites.
576
577 Mozilla users can alter the browser's User-Agent with the
578 B<--prefs-file> option. But note that the preference file is only read
579 on startup. If the browser is already running, B<uagen's> changes will be ignored.
580
581 Hiding the User-Agent is pointless if the browser accepts all
582 cookies or even is configured for remote maintenance through Flash,
583 JavaScript, Java or similar security problems.
584
585 =head1 BUGS
586
587 Some parameters can't be specified at the command line.
588
589 =head1 SEE ALSO
590
591 privoxy(1)
592
593 =head1 AUTHOR
594
595 Fabian Keil <fk@fabiankeil.de>
596
597 http://www.fabiankeil.de/sourcecode/uagen/
598
599 http://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)
600
601 =cut
602