3 ##############################################################################################
4 # uagen (http://www.fabiankeil.de/sourcecode/uagen/)
6 # $Id: uagen.pl,v 1.24 2012/10/21 13:42:46 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.2.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 => "17.0",
64 BROWSER_REVISION => '17.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';
208 my $creation_time = $randomize_release_date ?
209 generate_creation_time($browser_release_date) : $browser_release_date;
210 my ( $locale, $accept_language ) = generate_language_settings();
211 my ( $platform, $os_or_cpu ) = generate_platform_and_os;
213 my $firefox_user_agent =
214 sprintf "Mozilla/%s (%s; %s; rv:%s) Gecko/%s Firefox/%s",
215 $mozillaversion, $platform, $os_or_cpu, $browser_revision,
216 $creation_time, $browser_version;
218 return $accept_language, $firefox_user_agent;
228 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
232 my $logtime = sprintf "%i/%.2i/%.2i %.2i:%.2i", $year, $mon, $mday, $hour,
235 return if $no_logging;
237 open(my $log_fd, ">>", $logfile) || die "Writing " . $logfile . " failed";
238 printf $log_fd UAGEN_VERSION . " ($logtime) $message\n";
246 $message = "Error: $message";
247 log_to_file($message);
253 sub write_action_file() {
257 our $accept_language;
258 our $no_hide_accept_language;
259 our $action_injection;
261 my $action_file_content = '';
263 if ($action_injection){
264 open(my $actionfile_fd, "<", $action_file)
265 or log_error "Reading action file $action_file failed!";
266 while (<$actionfile_fd>) {
267 s@(hide-accept-language\{).*?(\})@$1$accept_language$2@;
268 s@(hide-user-agent\{).*?(\})@$1$user_agent$2@;
269 $action_file_content .= $_;
271 close($actionfile_fd);
273 $action_file_content = "{";
274 $action_file_content .= sprintf "+hide-accept-language{%s} \\\n",
275 $accept_language unless $no_hide_accept_language;
276 $action_file_content .= sprintf " +hide-user-agent{%s} \\\n}\n/\n",
279 open(my $actionfile_fd, ">", $action_file)
280 or log_error "Writing action file $action_file failed!";
281 print $actionfile_fd $action_file_content;
282 close($actionfile_fd);
287 sub write_prefs_file() {
289 our $mozilla_prefs_file;
291 our $accept_language;
294 my $prefs_file_content = '';
297 if (open($prefsfile_fd, "<", $mozilla_prefs_file)) {
299 while (<$prefsfile_fd>) {
300 s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
301 s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
302 $prefs_file_content .= $_;
304 close($prefsfile_fd);
306 log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
309 $prefs_file_content .=
310 sprintf("user_pref(\"general.useragent.override\", \"%s\");\n", $user_agent) .
311 sprintf("user_pref(\"intl.accept_languages\", \"%s\");\n", $accept_language)
314 open($prefsfile_fd, ">", $mozilla_prefs_file)
315 or log_error "Writing prefs file $mozilla_prefs_file failed!";
316 print $prefsfile_fd $prefs_file_content;
317 close($prefsfile_fd);
321 sub VersionMessage() {
322 printf UAGEN_VERSION . "\n" . 'Copyright (C) 2006-2011 Fabian Keil <fk@fabiankeil.de> ' .
323 "\nhttp://www.fabiankeil.de/sourcecode/uagen/\n";
330 our $browser_version;
331 our $browser_revision;
332 our $browser_release_date;
335 our $mozilla_prefs_file;
337 my $comma_separated_languages;
339 $loop = $loop ? ' ' . $loop : '';
340 $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
342 $comma_separated_languages .= $_ . ",";
344 chop $comma_separated_languages;
350 Options and their default values if there are any:
351 [--action-file $action_file]
353 [--browser-release-date $browser_release_date]
354 [--browser-revision $browser_revision]
355 [--browser-version $browser_version]
358 [--language-overwrite $comma_separated_languages]
362 [--no-hide-accept-language]
364 [--prefs-file$mozilla_prefs_file]
365 [--randomize-release-date]
368 [--sleeping-time $sleeping_time]
370 see "perldoc $0" for more information
379 my $no_action_file = NO_ACTION_FILE;
381 our $silent = SILENT;
382 our $no_logging = NO_LOGGING;
383 our $logfile = UAGEN_LOGFILE;
384 our $action_file = ACTION_FILE;
385 our $randomize_release_date = RANDOMIZE_RELEASE_DATE;
386 our $browser_version = BROWSER_VERSION;
387 our $browser_revision = BROWSER_REVISION;
388 our $browser_release_date = BROWSER_RELEASE_DATE;
389 our $sleeping_time = SLEEPING_TIME;
391 our $no_hide_accept_language = 0;
392 our $action_injection = 0;
395 our ( $accept_language, $user_agent );
396 our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
397 our $clean_prefs = 0;
399 GetOptions('logfile=s' => \$logfile,
400 'action-file=s' => \$action_file,
401 'language-overwrite=s@' => \@languages,
402 'silent|quiet' => \$silent,
403 'no-hide-accept-language' => \$no_hide_accept_language,
404 'no-logfile' => \$no_logging,
405 'no-action-file' => \$no_action_file,
406 'randomize-release-date' => \$randomize_release_date,
407 'browser-version=s' => \$browser_version,
408 'browser-revision=s' => \$browser_revision,
409 'browser-release-date=s' => \$browser_release_date,
410 'action-injection' => \$action_injection,
412 'sleeping-time' => \$sleeping_time,
413 'prefs-file=s' => \$mozilla_prefs_file,
414 'clean-prefs-file' => \$clean_prefs,
416 'version' => sub {VersionMessage() && exit(0)}
420 @languages = split(/,/,join(',',@languages));
422 @languages = LANGUAGES;
425 srand( time ^ ( $$ + ( $$ << 15 ) ) );
429 ( $accept_language, $user_agent ) = generate_firefox_user_agent();
431 print "$user_agent\n" unless $silent;
433 write_action_file() unless $no_action_file;
434 write_prefs_file() if $mozilla_prefs_file;
436 log_to_file "Generated User-Agent: $user_agent";
438 } while ($loop && sleep($sleeping_time * 60));
445 B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
449 B<uagen> [B<--action-file> I<action_file>] [B<--action-injection>]
450 [B<--browser-release-date> I<browser_release_date>]
451 [B<--browser-revision> I<browser_revision>]
452 [B<--browser-version> I<browser_version>]
453 [B<--clean-prefs-file>]
454 [B<--help>] [B<--language-overwrite> I<language(s)>]
455 [B<--logfile> I<logfile>] [B<--loop>] [B<--no-action-file>] [B<--no-logfile>]
456 [B<--prefs-file> I<prefs_file>] [B<--randomize-release-date>]
457 [B<--quiet>] [B<--sleeping-time> I<minutes>] [B<--silent>] [B<--version>]
461 B<uagen> generates a fake Firefox User-Agent and writes it into a Privoxy action file
462 as parameter for Privoxy's B<hide-user-agent> action. Operating system, architecture,
463 platform, language and, optionally, the build date are randomized.
465 The generated language is also used as parameter for the
466 B<hide-accept-language> action which is understood by Privoxy since
469 Additionally the User-Agent can be written into prefs.js files which are
470 used by many Mozilla browsers.
474 B<--action-file> I<action_file> Privoxy action file to write the
475 generated actions into. Default is /etc/privoxy/user-agent.action.
477 B<--action-injection> Don't generate a new action file from scratch,
478 but read an old one and just replace the action values. Useful
479 to keep custom URL patterns. For this to work, the action file
480 has to be already present. B<uagen> neither checks the syntax
481 nor cares if all actions are present. Garbage in, garbage out.
483 B<--browser-release-date> I<browser_release_date> Date to use.
484 The format is YYYYMMDD. Some sanity checks are done, but you
485 shouldn't rely on them.
487 B<--browser-revision> I<browser_revision> Use a custom revision.
488 B<uagen> will use it without any sanity checks.
490 B<--browser-version> I<browser_version> Use a custom browser version.
491 B<uagen> will use it without any sanity checks.
493 B<--clean-prefs-file> The I<prefs_file> is read and the variables
494 B<general.useragent.override> and B<intl.accept_languages> are removed.
495 Only effective if I<prefs_file> is set, and only useful if you want
496 to use the browser's defaults again.
498 B<--help> List command line options and exit.
500 B<--language-overwrite> I<language(s)> Comma separated list of language codes
501 to overwrite the default values. B<uagen> chooses one of them for the generated
502 User-Agent, by default the chosen language in lower cases is also used as
503 B<hide-accept-language> parameter.
505 B<--logfile> I<logfile> Logfile to save error messages and the generated
506 User-Agents. Default is /var/log/uagen.log.
508 B<--loop> Don't exit after the generation of the action file. Sleep for
509 a while and generate a new one instead. Useful if you don't have cron(8).
511 B<--no-logfile> Don't log anything.
513 B<--no-action-file> Don't write the action file.
515 B<--no-hide-accept-language> Stay compatible with Privoxy 3.0.3
516 and don't generate the B<hide-accept-language> action line. You should
517 really update your Privoxy version instead.
519 B<--prefs-file> I<prefs_file> Use the generated User-Agent to set the
520 B<general.useragent.override> variable in the Mozilla preference file
521 I<prefs_file>, The B<intl.accept_languages> variable will be set as well.
523 Firefox's preference file is usually located in
524 ~/.mozilla/firefox/*.default/prefs.js. Note that Firefox doesn't reread
525 the file once it is running.
527 B<--randomize-release-date> Randomly pick a date between the configured
528 release date and the actual date. Note that Firefox versions after 4.0
529 no longer provide the build date in the User-Agent header, so if you
530 randomize the date anyway, it will be obvious that the generated User-Agent
533 B<--quiet> Don't print the generated User-Agent to the console.
535 B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
537 B<--silent> Don't print the generated User-Agent to the console.
539 B<--version> Print version and exit.
541 The second dash is optional, options can be shortened, as long as there are
544 =head1 PRIVOXY CONFIGURATION
546 In Privoxy's configuration file the line:
548 actionsfile user-agent.action
550 should be added after:
552 actionfile default.action
556 actionfile user.action
558 This way the user can still use custom User-Agents
559 in I<user.action>. I<user-agent> has to be the name
560 of the generated action file.
562 If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
566 Without any options, B<uagen> creates an action file like:
568 {+hide-accept-language{en-ca} \
569 +hide-user-agent{Mozilla/5.0 (X11; U; OpenBSD i386; en-CA; rv:1.8.0.4) Gecko/20060628 Firefox/1.5.0.4} \
573 with the --no-accept-language option the generated file
574 could look like this one:
576 {+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD i386; de-DE; rv:1.8.0.4) Gecko/20060720 Firefox/1.5.0.4} \
582 If the browser opens an encrypted connection, Privoxy can't inspect
583 the content and the browser's headers reach the server unmodified.
584 It is the user's job to use Privoxy's limit-connect action to make sure
585 there are no encrypted connections to untrusted sites.
587 Mozilla users can alter the browser's User-Agent with the
588 B<--prefs-file> option. But note that the preference file is only read
589 on startup. If the browser is already running, B<uagen's> changes will be ignored.
591 Hiding the User-Agent is pointless if the browser accepts all
592 cookies or even is configured for remote maintenance through Flash,
593 JavaScript, Java or similar security problems.
597 Some parameters can't be specified at the command line.
605 Fabian Keil <fk@fabiankeil.de>
607 http://www.fabiankeil.de/sourcecode/uagen/
609 http://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)