3 ##############################################################################################
4 # uagen (http://www.fabiankeil.de/sourcecode/uagen/)
6 # $Id: uagen.pl,v 1.3 2010/01/11 11:47:27 fabiankeil Exp $
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)'.
11 # Examples (created with v1.0):
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
21 # Copyright (c) 2006-2009 Fabian Keil <fk@fabiankeil.de>
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.
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 ##############################################################################################
43 UAGEN_VERSION => 'uagen 1.0.10',
45 UAGEN_LOGFILE => '/var/log/uagen.log',
46 ACTION_FILE => '/etc/privoxy/user-agent.action',
47 MOZILLA_PREFS_FILE => '',
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.
59 BROWSER_VERSION => "3.6",
60 BROWSER_REVISION => '1.9.2',
61 BROWSER_RELEASE_DATE => '20100121',
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
68 #######################################################################################
70 sub generate_creation_time($) {
71 my $release_date = $_ = shift;
73 my ($rel_year, $rel_mon, $rel_day);
74 my ($c_day, $c_mon, $c_year);
76 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
82 log_error("Invalid release date format: $release_date. Using "
83 . BROWSER_RELEASE_DATE . " instead.");
84 $release_date = BROWSER_RELEASE_DATE;
86 $rel_year = substr $release_date, 0, 4;
87 $rel_mon = substr $release_date, 4, 2;
88 $rel_day = substr $release_date, 6, 2;
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 ) );
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 );
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;
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 ) );
115 return sprintf "%.2i%.2i%.2i", $c_year, $c_mon, $c_day;
118 sub generate_language_settings() {
122 my $language_i = int rand (@languages);
123 my $accept_language = $languages[$language_i];
124 $accept_language =~ tr/[A-Z]/[a-z]/;
126 return ($languages[$language_i], $accept_language);
129 sub generate_platform_and_os() {
135 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
136 order_is_inversed => 0,
141 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
142 order_is_inversed => 0,
147 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
148 order_is_inversed => 0,
153 architectures => [ 'i586', 'i686', 'x86_64' ],
154 order_is_inversed => 0,
159 architectures => [ 'i86pc', 'sun4u' ],
160 order_is_inversed => 0,
164 platform => 'Macintosh',
165 architectures => [ 'PPC', 'Intel' ],
166 order_is_inversed => 1,
170 platform => 'Windows',
171 architectures => [ 'NT 5.1' ],
172 order_is_inversed => 0,
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'};
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];
188 my $platform = $os_data{$os}{'platform'};
191 $os_or_cpu = sprintf "%s %s",
192 $os_data{$os}{'order_is_inversed'} ? ( $arch, $os ) : ( $os, $arch );
194 return $platform, $os_or_cpu;
197 sub generate_firefox_user_agent() {
200 our $browser_version;
201 our $browser_revision;
202 our $browser_release_date;
204 my $mozillaversion = '5.0';
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;
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;
216 return $accept_language, $firefox_user_agent;
226 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
230 my $logtime = sprintf "%i/%.2i/%.2i %.2i:%.2i", $year, $mon, $mday, $hour,
233 return if $no_logging;
235 open( LOGFILE, ">>" . $logfile ) || die "Writing " . $logfile . " failed";
236 printf LOGFILE UAGEN_VERSION . " ($logtime) $message\n";
245 $message = "Error: $message";
246 log_to_file($message);
252 sub write_action_file() {
256 our $accept_language;
257 our $no_hide_accept_language;
258 our $action_injection;
260 my $action_file_content = '';
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 .= $_;
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",
278 open( ACTIONFILE, ">" . $action_file )
279 or log_error "Writing action file $action_file failed!";
280 print ACTIONFILE $action_file_content;
286 sub write_prefs_file() {
288 our $mozilla_prefs_file;
290 our $accept_language;
292 my $prefs_file_content = '';
294 if (open( PREFSFILE, $mozilla_prefs_file )) {
296 while (<PREFSFILE>) {
297 s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
298 s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
299 $prefs_file_content .= $_;
303 log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
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)
311 open( PREFSFILE, ">" . $mozilla_prefs_file )
312 or log_error "Writing prefs file $mozilla_prefs_file failed!";
313 print PREFSFILE $prefs_file_content;
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";
327 our $browser_version;
328 our $browser_revision;
329 our $browser_release_date;
332 our $mozilla_prefs_file;
334 my $comma_separated_languages;
336 $loop = $loop ? ' ' . $loop : '';
337 $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
339 $comma_separated_languages .= $_ . ",";
341 chop $comma_separated_languages;
347 Options and their default values if there are any:
348 [--action-file $action_file]
350 [--browser-release-date $browser_release_date]
351 [--browser-revision $browser_revision]
352 [--browser-version $browser_version]
355 [--language-overwrite $comma_separated_languages]
359 [--no-hide-accept-language]
361 [--prefs-file$mozilla_prefs_file]
364 [--sleeping-time $sleeping_time]
366 see "perldoc $0" for more information
375 my $no_action_file = NO_ACTION_FILE;
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;
386 our $no_hide_accept_language = 0;
387 our $action_injection = 0;
390 our ( $accept_language, $user_agent );
391 our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
392 our $clean_prefs = 0;
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,
406 'sleeping-time' => \$sleeping_time,
407 'prefs-file=s' => \$mozilla_prefs_file,
408 'clean-prefs-file' => \$clean_prefs,
410 'version' => sub {VersionMessage() && exit(0)}
414 @languages = split(/,/,join(',',@languages));
416 @languages = LANGUAGES;
419 srand( time ^ ( $$ + ( $$ << 15 ) ) );
423 ( $accept_language, $user_agent ) = generate_firefox_user_agent();
425 print "$user_agent\n" unless $silent;
427 write_action_file() unless $no_action_file;
428 write_prefs_file() if $mozilla_prefs_file;
430 log_to_file "Generated User-Agent: $user_agent";
432 } while ($loop && sleep($sleeping_time * 60));
440 B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
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>]
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.
460 The generated language is also used as parameter for the
461 B<hide-accept-language> action which is understood by Privoxy since
464 Additionally the User-Agent can be written into prefs.js files which are
465 used by many Mozilla browsers.
469 B<--action-file> I<action_file> Privoxy action file to write the
470 generated actions into. Default is /etc/privoxy/user-agent.action.
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.
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.
483 B<--browser-revision> I<browser_revision> Use a custom revision.
484 B<uagen> will use it without any sanity checks.
486 B<--browser-version> I<browser_version> Use a custom browser version.
487 B<uagen> will use it without any sanity checks.
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.
494 B<--help> List command line options and exit.
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.
501 B<--logfile> I<logfile> Logfile to save error messages and the generated
502 User-Agents. Default is /var/log/uagen.log.
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).
507 B<--no-logfile> Don't log anything.
509 B<--no-action-file> Don't write the action file.
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.
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.
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.
523 B<--quiet> Don't print the generated User-Agent to the console.
525 B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
527 B<--silent> Don't print the generated User-Agent to the console.
529 B<--version> Print version and exit.
531 The second dash is optional, options can be shortened, as long as there are
534 =head1 PRIVOXY CONFIGURATION
536 In Privoxy's configuration file the line:
538 actionsfile user-agent.action
540 should be added after:
542 actionfile default.action
546 actionfile user.action
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.
552 If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
556 Without any options, B<uagen> creates an action file like:
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} \
563 with the --no-accept-language option the generated file
564 could look like this one:
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} \
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.
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.
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.
587 Some parameters can't be specified at the command line.
595 Fabian Keil <fk@fabiankeil.de>
597 http://www.fabiankeil.de/sourcecode/uagen/
599 http://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)