3 ##############################################################################################
4 # uagen (http://www.fabiankeil.de/sourcecode/uagen/)
6 # $Id: uagen.pl,v 1.16 2011/06/29 20:29:58 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-2011 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.1',
45 UAGEN_LOGFILE => '/var/log/uagen.log',
46 ACTION_FILE => '/etc/privoxy/user-agent.action',
47 MOZILLA_PREFS_FILE => '',
54 # As of Firefox 4, the "Gecko token" has been frozen
55 # http://hacks.mozilla.org/2010/09/final-user-agent-string-for-firefox-4/
56 RANDOMIZE_RELEASE_DATE => 0,
58 # These variables belong together. If you only change one of them, the generated
59 # User-Agent might be invalid. If you're not sure which values make sense,
60 # are too lazy to check, but want to change them anyway, take the values you
61 # see in the "Help/About Mozilla Firefox" menu.
63 BROWSER_VERSION => "5.0",
64 BROWSER_REVISION => '5.0',
65 BROWSER_RELEASE_DATE => '20100101',
68 use constant LANGUAGES => qw(
69 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
72 #######################################################################################
74 sub generate_creation_time($) {
75 my $release_date = shift;
77 my ($rel_year, $rel_mon, $rel_day);
78 my ($c_day, $c_mon, $c_year);
80 my (undef, undef, undef, $mday, $mon, $year, undef, undef, undef) = localtime($now);
84 unless ($release_date =~ m/\d{6}/) {
85 log_error("Invalid release date format: $release_date. Using "
86 . BROWSER_RELEASE_DATE . " instead.");
87 $release_date = BROWSER_RELEASE_DATE;
89 $rel_year = substr($release_date, 0, 4);
90 $rel_mon = substr($release_date, 4, 2);
91 $rel_day = substr($release_date, 6, 2);
94 die "release year in the future" if ($year < $rel_year);
95 die "release month in the future"
96 if (($year == $rel_year) and ($mon < $rel_mon));
97 die "release day in the future"
98 if (($year == $rel_year) and ($mon == $rel_mon) and ($mday < $rel_day));
100 my @c_time = (0, 0, 0, $rel_day, $rel_mon - 1, $rel_year - 1900, 0, 0, 0);
101 my $c_seconds = timelocal(@c_time);
103 $c_seconds = $now - (int rand ($now - $c_seconds));
104 @c_time = localtime($c_seconds);
105 (undef, undef, undef, $c_day, $c_mon, $c_year, undef, undef, undef) = @c_time;
110 die "Compilation year in the future" if ($year < $c_year);
111 die "Compilation month in the future"
112 if (($year == $c_year) and ($mon < $c_mon));
113 die "Compilation day in the future"
114 if (($year == $c_year) and ($mon == $c_mon) and ($mday < $c_day));
116 return sprintf("%.2i%.2i%.2i", $c_year, $c_mon, $c_day);
119 sub generate_language_settings() {
123 my $language_i = int rand (@languages);
124 my $accept_language = $languages[$language_i];
125 $accept_language =~ tr/[A-Z]/[a-z]/;
127 return ($languages[$language_i], $accept_language);
130 sub generate_platform_and_os() {
136 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
137 order_is_inversed => 0,
142 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
143 order_is_inversed => 0,
148 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
149 order_is_inversed => 0,
154 architectures => [ 'i586', 'i686', 'x86_64' ],
155 order_is_inversed => 0,
160 architectures => [ 'i86pc', 'sun4u' ],
161 order_is_inversed => 0,
165 platform => 'Macintosh',
166 architectures => [ 'PPC', 'Intel' ],
167 order_is_inversed => 1,
171 platform => 'Windows',
172 architectures => [ 'NT 5.1' ],
173 order_is_inversed => 0,
179 foreach my $os_name ( keys %os_data ) {
180 push @os_names, ($os_name) x $os_data{$os_name}{'karma'}
181 if $os_data{$os_name}{'karma'};
184 my $os_i = int rand(@os_names);
185 my $os = $os_names[$os_i];
186 my $arch_i = int rand( @{ $os_data{$os}{'architectures'} } );
187 my $arch = $os_data{$os}{'architectures'}[$arch_i];
189 my $platform = $os_data{$os}{'platform'};
192 $os_or_cpu = sprintf "%s %s",
193 $os_data{$os}{'order_is_inversed'} ? ( $arch, $os ) : ( $os, $arch );
195 return $platform, $os_or_cpu;
198 sub generate_firefox_user_agent() {
201 our $browser_version;
202 our $browser_revision;
203 our $browser_release_date;
204 our $randomize_release_date;
206 my $mozillaversion = '5.0';
209 my $creation_time = $randomize_release_date ?
210 generate_creation_time($browser_release_date) : $browser_release_date;
211 my ( $locale, $accept_language ) = generate_language_settings();
212 my ( $platform, $os_or_cpu ) = generate_platform_and_os;
214 my $firefox_user_agent =
215 sprintf "Mozilla/%s (%s; %s; %s; %s; rv:%s) Gecko/%s Firefox/%s",
216 $mozillaversion, $platform, $security, $os_or_cpu, $locale, $browser_revision,
217 $creation_time, $browser_version;
219 return $accept_language, $firefox_user_agent;
229 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
233 my $logtime = sprintf "%i/%.2i/%.2i %.2i:%.2i", $year, $mon, $mday, $hour,
236 return if $no_logging;
238 open(my $log_fd, ">>" . $logfile) || die "Writing " . $logfile . " failed";
239 printf $log_fd UAGEN_VERSION . " ($logtime) $message\n";
247 $message = "Error: $message";
248 log_to_file($message);
254 sub write_action_file() {
258 our $accept_language;
259 our $no_hide_accept_language;
260 our $action_injection;
262 my $action_file_content = '';
264 if ($action_injection){
265 open(my $actionfile_fd, "<", $action_file)
266 or log_error "Reading action file $action_file failed!";
267 while (<$actionfile_fd>) {
268 s@(hide-accept-language\{).*?(\})@$1$accept_language$2@;
269 s@(hide-user-agent\{).*?(\})@$1$user_agent$2@;
270 $action_file_content .= $_;
272 close($actionfile_fd);
274 $action_file_content = "{";
275 $action_file_content .= sprintf "+hide-accept-language{%s} \\\n",
276 $accept_language unless $no_hide_accept_language;
277 $action_file_content .= sprintf " +hide-user-agent{%s} \\\n}\n/\n",
280 open(my $actionfile_fd, ">" . $action_file)
281 or log_error "Writing action file $action_file failed!";
282 print $actionfile_fd $action_file_content;
283 close($actionfile_fd);
288 sub write_prefs_file() {
290 our $mozilla_prefs_file;
292 our $accept_language;
295 my $prefs_file_content = '';
298 if (open($prefsfile_fd, $mozilla_prefs_file)) {
300 while (<$prefsfile_fd>) {
301 s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
302 s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
303 $prefs_file_content .= $_;
305 close($prefsfile_fd);
307 log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
310 $prefs_file_content .=
311 sprintf("user_pref(\"general.useragent.override\", \"%s\");\n", $user_agent) .
312 sprintf("user_pref(\"intl.accept_languages\", \"%s\");\n", $accept_language)
315 open($prefsfile_fd, ">" . $mozilla_prefs_file)
316 or log_error "Writing prefs file $mozilla_prefs_file failed!";
317 print $prefsfile_fd $prefs_file_content;
318 close($prefsfile_fd);
322 sub VersionMessage() {
323 printf UAGEN_VERSION . "\n" . 'Copyright (C) 2006-2011 Fabian Keil <fk@fabiankeil.de> ' .
324 "\nhttp://www.fabiankeil.de/sourcecode/uagen/\n";
331 our $browser_version;
332 our $browser_revision;
333 our $browser_release_date;
336 our $mozilla_prefs_file;
338 my $comma_separated_languages;
340 $loop = $loop ? ' ' . $loop : '';
341 $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
343 $comma_separated_languages .= $_ . ",";
345 chop $comma_separated_languages;
351 Options and their default values if there are any:
352 [--action-file $action_file]
354 [--browser-release-date $browser_release_date]
355 [--browser-revision $browser_revision]
356 [--browser-version $browser_version]
359 [--language-overwrite $comma_separated_languages]
363 [--no-hide-accept-language]
365 [--prefs-file$mozilla_prefs_file]
366 [--randomize-release-date]
369 [--sleeping-time $sleeping_time]
371 see "perldoc $0" for more information
380 my $no_action_file = NO_ACTION_FILE;
382 our $silent = SILENT;
383 our $no_logging = NO_LOGGING;
384 our $logfile = UAGEN_LOGFILE;
385 our $action_file = ACTION_FILE;
386 our $randomize_release_date = RANDOMIZE_RELEASE_DATE;
387 our $browser_version = BROWSER_VERSION;
388 our $browser_revision = BROWSER_REVISION;
389 our $browser_release_date = BROWSER_RELEASE_DATE;
390 our $sleeping_time = SLEEPING_TIME;
392 our $no_hide_accept_language = 0;
393 our $action_injection = 0;
396 our ( $accept_language, $user_agent );
397 our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
398 our $clean_prefs = 0;
400 GetOptions('logfile=s' => \$logfile,
401 'action-file=s' => \$action_file,
402 'language-overwrite=s@' => \@languages,
403 'silent|quiet' => \$silent,
404 'no-hide-accept-language' => \$no_hide_accept_language,
405 'no-logfile' => \$no_logging,
406 'no-action-file' => \$no_action_file,
407 'randomize-release-date' => \$randomize_release_date,
408 'browser-version=s' => \$browser_version,
409 'browser-revision=s' => \$browser_revision,
410 'browser-release-date=s' => \$browser_release_date,
411 'action-injection' => \$action_injection,
413 'sleeping-time' => \$sleeping_time,
414 'prefs-file=s' => \$mozilla_prefs_file,
415 'clean-prefs-file' => \$clean_prefs,
417 'version' => sub {VersionMessage() && exit(0)}
421 @languages = split(/,/,join(',',@languages));
423 @languages = LANGUAGES;
426 srand( time ^ ( $$ + ( $$ << 15 ) ) );
430 ( $accept_language, $user_agent ) = generate_firefox_user_agent();
432 print "$user_agent\n" unless $silent;
434 write_action_file() unless $no_action_file;
435 write_prefs_file() if $mozilla_prefs_file;
437 log_to_file "Generated User-Agent: $user_agent";
439 } while ($loop && sleep($sleeping_time * 60));
446 B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
450 B<uagen> [B<--action-file> I<action_file>] [B<--action-injection>]
451 [B<--browser-release-date> I<browser_release_date>]
452 [B<--browser-revision> I<browser_revision>]
453 [B<--browser-version> I<browser_version>]
454 [B<--clean-prefs-file>]
455 [B<--help>] [B<--language-overwrite> I<language(s)>]
456 [B<--logfile> I<logfile>] [B<--loop>] [B<--no-action-file>] [B<--no-logfile>]
457 [B<--prefs-file> I<prefs_file>] [B<--randomize-release-date>]
458 [B<--quiet>] [B<--sleeping-time> I<minutes>] [B<--silent>] [B<--version>]
462 B<uagen> generates a fake Firefox User-Agent and writes it into a Privoxy action file
463 as parameter for Privoxy's B<hide-user-agent> action. Operating system, architecture,
464 platform, language and, optionally, the build date are randomized.
466 The generated language is also used as parameter for the
467 B<hide-accept-language> action which is understood by Privoxy since
470 Additionally the User-Agent can be written into prefs.js files which are
471 used by many Mozilla browsers.
475 B<--action-file> I<action_file> Privoxy action file to write the
476 generated actions into. Default is /etc/privoxy/user-agent.action.
478 B<--action-injection> Don't generate a new action file from scratch,
479 but read an old one and just replace the action values. Useful
480 to keep custom URL patterns. For this to work, the action file
481 has to be already present. B<uagen> neither checks the syntax
482 nor cares if all actions are present. Garbage in, garbage out.
484 B<--browser-release-date> I<browser_release_date> Date to use.
485 The format is YYYYMMDD. Some sanity checks are done, but you
486 shouldn't rely on them.
488 B<--browser-revision> I<browser_revision> Use a custom revision.
489 B<uagen> will use it without any sanity checks.
491 B<--browser-version> I<browser_version> Use a custom browser version.
492 B<uagen> will use it without any sanity checks.
494 B<--clean-prefs-file> The I<prefs_file> is read and the variables
495 B<general.useragent.override> and B<intl.accept_languages> are removed.
496 Only effective if I<prefs_file> is set, and only useful if you want
497 to use the browser's defaults again.
499 B<--help> List command line options and exit.
501 B<--language-overwrite> I<language(s)> Comma separated list of language codes
502 to overwrite the default values. B<uagen> chooses one of them for the generated
503 User-Agent, by default the chosen language in lower cases is also used as
504 B<hide-accept-language> parameter.
506 B<--logfile> I<logfile> Logfile to save error messages and the generated
507 User-Agents. Default is /var/log/uagen.log.
509 B<--loop> Don't exit after the generation of the action file. Sleep for
510 a while and generate a new one instead. Useful if you don't have cron(8).
512 B<--no-logfile> Don't log anything.
514 B<--no-action-file> Don't write the action file.
516 B<--no-hide-accept-language> Stay compatible with Privoxy 3.0.3
517 and don't generate the B<hide-accept-language> action line. You should
518 really update your Privoxy version instead.
520 B<--prefs-file> I<prefs_file> Use the generated User-Agent to set the
521 B<general.useragent.override> variable in the Mozilla preference file
522 I<prefs_file>, The B<intl.accept_languages> variable will be set as well.
524 Firefox's preference file is usually located in
525 ~/.mozilla/firefox/*.default/prefs.js. Note that Firefox doesn't reread
526 the file once it is running.
528 B<--randomize-release-date> Randomly pick a date between the configured
529 release date and the actual date. Note that Firefox versions after 4.0
530 no longer provide the build date in the User-Agent header, so if you
531 randomize the date anyway, it will be obvious that the generated User-Agent
534 B<--quiet> Don't print the generated User-Agent to the console.
536 B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
538 B<--silent> Don't print the generated User-Agent to the console.
540 B<--version> Print version and exit.
542 The second dash is optional, options can be shortened, as long as there are
545 =head1 PRIVOXY CONFIGURATION
547 In Privoxy's configuration file the line:
549 actionsfile user-agent.action
551 should be added after:
553 actionfile default.action
557 actionfile user.action
559 This way the user can still use custom User-Agents
560 in I<user.action>. I<user-agent> has to be the name
561 of the generated action file.
563 If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
567 Without any options, B<uagen> creates an action file like:
569 {+hide-accept-language{en-ca} \
570 +hide-user-agent{Mozilla/5.0 (X11; U; OpenBSD i386; en-CA; rv:1.8.0.4) Gecko/20060628 Firefox/1.5.0.4} \
574 with the --no-accept-language option the generated file
575 could look like this one:
577 {+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD i386; de-DE; rv:1.8.0.4) Gecko/20060720 Firefox/1.5.0.4} \
583 If the browser opens an encrypted connection, Privoxy can't inspect
584 the content and the browser's headers reach the server unmodified.
585 It is the user's job to use Privoxy's limit-connect action to make sure
586 there are no encrypted connections to untrusted sites.
588 Mozilla users can alter the browser's User-Agent with the
589 B<--prefs-file> option. But note that the preference file is only read
590 on startup. If the browser is already running, B<uagen's> changes will be ignored.
592 Hiding the User-Agent is pointless if the browser accepts all
593 cookies or even is configured for remote maintenance through Flash,
594 JavaScript, Java or similar security problems.
598 Some parameters can't be specified at the command line.
606 Fabian Keil <fk@fabiankeil.de>
608 http://www.fabiankeil.de/sourcecode/uagen/
610 http://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)