3 ##############################################################################################
4 # uagen (http://www.fabiankeil.de/sourcecode/uagen/)
6 # Generates a pseudo-random Firefox user agent and writes it into a Privoxy action file
7 # and optionally into a Mozilla prefs file. For documentation see 'perldoc uagen(.pl)'.
9 # Examples (created with v1.0):
11 # Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.8.0.2) Gecko/20060421 Firefox/1.5.0.2
12 # Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-CA; rv:1.8.0.2) Gecko/20060425 Firefox/1.5.0.2
13 # Mozilla/5.0 (X11; U; SunOS i86pc; no-NO; rv:1.8.0.2) Gecko/20060420 Firefox/1.5.0.2
14 # Mozilla/5.0 (X11; U; Linux x86_64; de-AT; rv:1.8.0.2) Gecko/20060422 Firefox/1.5.0.2
15 # Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.8.0.2) Gecko/20060415 Firefox/1.5.0.2
16 # Mozilla/5.0 (X11; U; OpenBSD sparc64; pl-PL; rv:1.8.0.2) Gecko/20060429 Firefox/1.5.0.2
17 # Mozilla/5.0 (X11; U; Linux i686; en-CA; rv:1.8.0.2) Gecko/20060413 Firefox/1.5.0.2
19 # Copyright (c) 2006-2011 Fabian Keil <fk@fabiankeil.de>
21 # Permission to use, copy, modify, and distribute this software for any
22 # purpose with or without fee is hereby granted, provided that the above
23 # copyright notice and this permission notice appear in all copies.
25 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
26 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
27 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
28 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
29 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
30 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
31 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32 ##############################################################################################
41 UAGEN_VERSION => 'uagen 1.2.1',
43 UAGEN_LOGFILE => '/var/log/uagen.log',
44 ACTION_FILE => '/etc/privoxy/user-agent.action',
45 MOZILLA_PREFS_FILE => '',
52 # As of Firefox 4, the "Gecko token" has been frozen
53 # http://hacks.mozilla.org/2010/09/final-user-agent-string-for-firefox-4/
54 RANDOMIZE_RELEASE_DATE => 0,
56 # These variables belong together. If you only change one of them, the generated
57 # User-Agent might be invalid. If you're not sure which values make sense,
58 # are too lazy to check, but want to change them anyway, take the values you
59 # see in the "Help/About Mozilla Firefox" menu.
61 BROWSER_VERSION => "17.0",
62 BROWSER_REVISION => '17.0',
63 BROWSER_RELEASE_DATE => '20100101',
66 use constant LANGUAGES => qw(
67 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
70 #######################################################################################
72 sub generate_creation_time($) {
73 my $release_date = shift;
75 my ($rel_year, $rel_mon, $rel_day);
76 my ($c_day, $c_mon, $c_year);
78 my (undef, undef, undef, $mday, $mon, $year, undef, undef, undef) = localtime($now);
82 unless ($release_date =~ m/\d{6}/) {
83 log_error("Invalid release date format: $release_date. Using "
84 . BROWSER_RELEASE_DATE . " instead.");
85 $release_date = BROWSER_RELEASE_DATE;
87 $rel_year = substr($release_date, 0, 4);
88 $rel_mon = substr($release_date, 4, 2);
89 $rel_day = substr($release_date, 6, 2);
92 die "release year in the future" if ($year < $rel_year);
93 die "release month in the future"
94 if (($year == $rel_year) and ($mon < $rel_mon));
95 die "release day in the future"
96 if (($year == $rel_year) and ($mon == $rel_mon) and ($mday < $rel_day));
98 my @c_time = (0, 0, 0, $rel_day, $rel_mon - 1, $rel_year - 1900, 0, 0, 0);
99 my $c_seconds = timelocal(@c_time);
101 $c_seconds = $now - (int rand ($now - $c_seconds));
102 @c_time = localtime($c_seconds);
103 (undef, undef, undef, $c_day, $c_mon, $c_year, undef, undef, undef) = @c_time;
108 die "Compilation year in the future" if ($year < $c_year);
109 die "Compilation month in the future"
110 if (($year == $c_year) and ($mon < $c_mon));
111 die "Compilation day in the future"
112 if (($year == $c_year) and ($mon == $c_mon) and ($mday < $c_day));
114 return sprintf("%.2i%.2i%.2i", $c_year, $c_mon, $c_day);
117 sub generate_language_settings() {
121 my $language_i = int rand (@languages);
122 my $accept_language = $languages[$language_i];
123 $accept_language =~ tr/[A-Z]/[a-z]/;
125 return ($languages[$language_i], $accept_language);
128 sub generate_platform_and_os() {
134 architectures => [ 'i386', 'amd64', 'sparc64' ],
135 order_is_inversed => 0,
140 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
141 order_is_inversed => 0,
146 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
147 order_is_inversed => 0,
152 architectures => [ 'i586', 'i686', 'x86_64' ],
153 order_is_inversed => 0,
158 architectures => [ 'i86pc', 'sun4u' ],
159 order_is_inversed => 0,
163 platform => 'Macintosh',
164 architectures => [ 'PPC', 'Intel' ],
165 order_is_inversed => 1,
169 platform => 'Windows',
170 architectures => [ 'NT 5.1' ],
171 order_is_inversed => 0,
177 foreach my $os_name ( keys %os_data ) {
178 push @os_names, ($os_name) x $os_data{$os_name}{'karma'}
179 if $os_data{$os_name}{'karma'};
182 my $os_i = int rand(@os_names);
183 my $os = $os_names[$os_i];
184 my $arch_i = int rand( @{ $os_data{$os}{'architectures'} } );
185 my $arch = $os_data{$os}{'architectures'}[$arch_i];
187 my $platform = $os_data{$os}{'platform'};
190 $os_or_cpu = sprintf "%s %s",
191 $os_data{$os}{'order_is_inversed'} ? ( $arch, $os ) : ( $os, $arch );
193 return $platform, $os_or_cpu;
196 sub generate_firefox_user_agent() {
199 our $browser_version;
200 our $browser_revision;
201 our $browser_release_date;
202 our $randomize_release_date;
204 my $mozillaversion = '5.0';
206 my $creation_time = $randomize_release_date ?
207 generate_creation_time($browser_release_date) : $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; rv:%s) Gecko/%s Firefox/%s",
213 $mozillaversion, $platform, $os_or_cpu, $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(my $log_fd, ">>", $logfile) || die "Writing " . $logfile . " failed";
236 printf $log_fd UAGEN_VERSION . " ($logtime) $message\n";
244 $message = "Error: $message";
245 log_to_file($message);
251 sub write_action_file() {
255 our $accept_language;
256 our $no_hide_accept_language;
257 our $action_injection;
259 my $action_file_content = '';
261 if ($action_injection){
262 open(my $actionfile_fd, "<", $action_file)
263 or log_error "Reading action file $action_file failed!";
264 while (<$actionfile_fd>) {
265 s@(hide-accept-language\{).*?(\})@$1$accept_language$2@;
266 s@(hide-user-agent\{).*?(\})@$1$user_agent$2@;
267 $action_file_content .= $_;
269 close($actionfile_fd);
271 $action_file_content = "{";
272 $action_file_content .= sprintf "+hide-accept-language{%s} \\\n",
273 $accept_language unless $no_hide_accept_language;
274 $action_file_content .= sprintf " +hide-user-agent{%s} \\\n}\n/\n",
277 open(my $actionfile_fd, ">", $action_file)
278 or log_error "Writing action file $action_file failed!";
279 print $actionfile_fd $action_file_content;
280 close($actionfile_fd);
285 sub write_prefs_file() {
287 our $mozilla_prefs_file;
289 our $accept_language;
292 my $prefs_file_content = '';
295 if (open($prefsfile_fd, "<", $mozilla_prefs_file)) {
297 while (<$prefsfile_fd>) {
298 s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
299 s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
300 $prefs_file_content .= $_;
302 close($prefsfile_fd);
304 log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
307 $prefs_file_content .=
308 sprintf("user_pref(\"general.useragent.override\", \"%s\");\n", $user_agent) .
309 sprintf("user_pref(\"intl.accept_languages\", \"%s\");\n", $accept_language)
312 open($prefsfile_fd, ">", $mozilla_prefs_file)
313 or log_error "Writing prefs file $mozilla_prefs_file failed!";
314 print $prefsfile_fd $prefs_file_content;
315 close($prefsfile_fd);
319 sub VersionMessage() {
320 printf UAGEN_VERSION . "\n" . 'Copyright (C) 2006-2011 Fabian Keil <fk@fabiankeil.de> ' .
321 "\nhttp://www.fabiankeil.de/sourcecode/uagen/\n";
328 our $browser_version;
329 our $browser_revision;
330 our $browser_release_date;
333 our $mozilla_prefs_file;
335 my $comma_separated_languages;
337 $loop = $loop ? ' ' . $loop : '';
338 $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
340 $comma_separated_languages .= $_ . ",";
342 chop $comma_separated_languages;
348 Options and their default values if there are any:
349 [--action-file $action_file]
351 [--browser-release-date $browser_release_date]
352 [--browser-revision $browser_revision]
353 [--browser-version $browser_version]
356 [--language-overwrite $comma_separated_languages]
360 [--no-hide-accept-language]
362 [--prefs-file$mozilla_prefs_file]
363 [--randomize-release-date]
366 [--sleeping-time $sleeping_time]
368 see "perldoc $0" for more information
377 my $no_action_file = NO_ACTION_FILE;
379 our $silent = SILENT;
380 our $no_logging = NO_LOGGING;
381 our $logfile = UAGEN_LOGFILE;
382 our $action_file = ACTION_FILE;
383 our $randomize_release_date = RANDOMIZE_RELEASE_DATE;
384 our $browser_version = BROWSER_VERSION;
385 our $browser_revision = BROWSER_REVISION;
386 our $browser_release_date = BROWSER_RELEASE_DATE;
387 our $sleeping_time = SLEEPING_TIME;
389 our $no_hide_accept_language = 0;
390 our $action_injection = 0;
393 our ( $accept_language, $user_agent );
394 our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
395 our $clean_prefs = 0;
397 GetOptions('logfile=s' => \$logfile,
398 'action-file=s' => \$action_file,
399 'language-overwrite=s@' => \@languages,
400 'silent|quiet' => \$silent,
401 'no-hide-accept-language' => \$no_hide_accept_language,
402 'no-logfile' => \$no_logging,
403 'no-action-file' => \$no_action_file,
404 'randomize-release-date' => \$randomize_release_date,
405 'browser-version=s' => \$browser_version,
406 'browser-revision=s' => \$browser_revision,
407 'browser-release-date=s' => \$browser_release_date,
408 'action-injection' => \$action_injection,
410 'sleeping-time' => \$sleeping_time,
411 'prefs-file=s' => \$mozilla_prefs_file,
412 'clean-prefs-file' => \$clean_prefs,
414 'version' => sub {VersionMessage() && exit(0)}
418 @languages = split(/,/,join(',',@languages));
420 @languages = LANGUAGES;
423 srand( time ^ ( $$ + ( $$ << 15 ) ) );
427 ( $accept_language, $user_agent ) = generate_firefox_user_agent();
429 print "$user_agent\n" unless $silent;
431 write_action_file() unless $no_action_file;
432 write_prefs_file() if $mozilla_prefs_file;
434 log_to_file "Generated User-Agent: $user_agent";
436 } while ($loop && sleep($sleeping_time * 60));
443 B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
447 B<uagen> [B<--action-file> I<action_file>] [B<--action-injection>]
448 [B<--browser-release-date> I<browser_release_date>]
449 [B<--browser-revision> I<browser_revision>]
450 [B<--browser-version> I<browser_version>]
451 [B<--clean-prefs-file>]
452 [B<--help>] [B<--language-overwrite> I<language(s)>]
453 [B<--logfile> I<logfile>] [B<--loop>] [B<--no-action-file>] [B<--no-logfile>]
454 [B<--prefs-file> I<prefs_file>] [B<--randomize-release-date>]
455 [B<--quiet>] [B<--sleeping-time> I<minutes>] [B<--silent>] [B<--version>]
459 B<uagen> generates a fake Firefox User-Agent and writes it into a Privoxy action file
460 as parameter for Privoxy's B<hide-user-agent> action. Operating system, architecture,
461 platform, language and, optionally, the build date are randomized.
463 The generated language is also used as parameter for the
464 B<hide-accept-language> action which is understood by Privoxy since
467 Additionally the User-Agent can be written into prefs.js files which are
468 used by many Mozilla browsers.
472 B<--action-file> I<action_file> Privoxy action file to write the
473 generated actions into. Default is /etc/privoxy/user-agent.action.
475 B<--action-injection> Don't generate a new action file from scratch,
476 but read an old one and just replace the action values. Useful
477 to keep custom URL patterns. For this to work, the action file
478 has to be already present. B<uagen> neither checks the syntax
479 nor cares if all actions are present. Garbage in, garbage out.
481 B<--browser-release-date> I<browser_release_date> Date to use.
482 The format is YYYYMMDD. Some sanity checks are done, but you
483 shouldn't rely on them.
485 B<--browser-revision> I<browser_revision> Use a custom revision.
486 B<uagen> will use it without any sanity checks.
488 B<--browser-version> I<browser_version> Use a custom browser version.
489 B<uagen> will use it without any sanity checks.
491 B<--clean-prefs-file> The I<prefs_file> is read and the variables
492 B<general.useragent.override> and B<intl.accept_languages> are removed.
493 Only effective if I<prefs_file> is set, and only useful if you want
494 to use the browser's defaults again.
496 B<--help> List command line options and exit.
498 B<--language-overwrite> I<language(s)> Comma separated list of language codes
499 to overwrite the default values. B<uagen> chooses one of them for the generated
500 User-Agent, by default the chosen language in lower cases is also used as
501 B<hide-accept-language> parameter.
503 B<--logfile> I<logfile> Logfile to save error messages and the generated
504 User-Agents. Default is /var/log/uagen.log.
506 B<--loop> Don't exit after the generation of the action file. Sleep for
507 a while and generate a new one instead. Useful if you don't have cron(8).
509 B<--no-logfile> Don't log anything.
511 B<--no-action-file> Don't write the action file.
513 B<--no-hide-accept-language> Stay compatible with Privoxy 3.0.3
514 and don't generate the B<hide-accept-language> action line. You should
515 really update your Privoxy version instead.
517 B<--prefs-file> I<prefs_file> Use the generated User-Agent to set the
518 B<general.useragent.override> variable in the Mozilla preference file
519 I<prefs_file>, The B<intl.accept_languages> variable will be set as well.
521 Firefox's preference file is usually located in
522 ~/.mozilla/firefox/*.default/prefs.js. Note that Firefox doesn't reread
523 the file once it is running.
525 B<--randomize-release-date> Randomly pick a date between the configured
526 release date and the actual date. Note that Firefox versions after 4.0
527 no longer provide the build date in the User-Agent header, so if you
528 randomize the date anyway, it will be obvious that the generated User-Agent
531 B<--quiet> Don't print the generated User-Agent to the console.
533 B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
535 B<--silent> Don't print the generated User-Agent to the console.
537 B<--version> Print version and exit.
539 The second dash is optional, options can be shortened, as long as there are
542 =head1 PRIVOXY CONFIGURATION
544 In Privoxy's configuration file the line:
546 actionsfile user-agent.action
548 should be added after:
550 actionfile default.action
554 actionfile user.action
556 This way the user can still use custom User-Agents
557 in I<user.action>. I<user-agent> has to be the name
558 of the generated action file.
560 If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
564 Without any options, B<uagen> creates an action file like:
566 {+hide-accept-language{en-ca} \
567 +hide-user-agent{Mozilla/5.0 (X11; U; OpenBSD i386; en-CA; rv:1.8.0.4) Gecko/20060628 Firefox/1.5.0.4} \
571 with the --no-accept-language option the generated file
572 could look like this one:
574 {+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD i386; de-DE; rv:1.8.0.4) Gecko/20060720 Firefox/1.5.0.4} \
580 If the browser opens an encrypted connection, Privoxy can't inspect
581 the content and the browser's headers reach the server unmodified.
582 It is the user's job to use Privoxy's limit-connect action to make sure
583 there are no encrypted connections to untrusted sites.
585 Mozilla users can alter the browser's User-Agent with the
586 B<--prefs-file> option. But note that the preference file is only read
587 on startup. If the browser is already running, B<uagen's> changes will be ignored.
589 Hiding the User-Agent is pointless if the browser accepts all
590 cookies or even is configured for remote maintenance through Flash,
591 JavaScript, Java or similar security problems.
595 Some parameters can't be specified at the command line.
603 Fabian Keil <fk@fabiankeil.de>
605 http://www.fabiankeil.de/sourcecode/uagen/
607 http://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)