--- /dev/null
+#!/usr/bin/perl
+
+##############################################################################################
+# uagen (http://www.fabiankeil.de/sourcecode/uagen/)
+#
+# $Id: uagen.pl,v 1.62 2009/12/23 17:20:19 fk Exp fk $
+#
+# Generates a pseudo-random Firefox user agent and writes it into a Privoxy action file
+# and optionally into a Mozilla prefs file. For documentation see 'perldoc uagen(.pl)'.
+#
+# Examples (created with v1.0):
+#
+# Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.8.0.2) Gecko/20060421 Firefox/1.5.0.2
+# Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-CA; rv:1.8.0.2) Gecko/20060425 Firefox/1.5.0.2
+# Mozilla/5.0 (X11; U; SunOS i86pc; no-NO; rv:1.8.0.2) Gecko/20060420 Firefox/1.5.0.2
+# Mozilla/5.0 (X11; U; Linux x86_64; de-AT; rv:1.8.0.2) Gecko/20060422 Firefox/1.5.0.2
+# Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.8.0.2) Gecko/20060415 Firefox/1.5.0.2
+# Mozilla/5.0 (X11; U; OpenBSD sparc64; pl-PL; rv:1.8.0.2) Gecko/20060429 Firefox/1.5.0.2
+# Mozilla/5.0 (X11; U; Linux i686; en-CA; rv:1.8.0.2) Gecko/20060413 Firefox/1.5.0.2
+#
+# Copyright (c) 2006-2009 Fabian Keil <fk@fabiankeil.de>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+##############################################################################################
+
+use strict;
+use warnings;
+use Time::Local;
+use Getopt::Long;
+
+use constant {
+
+ UAGEN_VERSION => 'uagen 1.0.10',
+
+ UAGEN_LOGFILE => '/var/log/uagen.log',
+ ACTION_FILE => '/etc/privoxy/user-agent.action',
+ MOZILLA_PREFS_FILE => '',
+ SILENT => 0,
+ NO_LOGGING => 0,
+ NO_ACTION_FILE => 0,
+ LOOP => 0,
+ SLEEPING_TIME => 5,
+
+ # These variables belong together. If you only change one of them, the generated
+ # User-Agent might be invalid. If you're not sure which values make sense,
+ # are too lazy to check, but want to change them anyway, take the values you
+ # see in the "Help/About Mozilla Firefox" menu.
+
+ BROWSER_VERSION => "3.5.6",
+ BROWSER_REVISION => '1.9.1.6',
+ BROWSER_RELEASE_DATE => '20091217',
+};
+
+use constant LANGUAGES => qw(
+ 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
+);
+
+#######################################################################################
+
+sub generate_creation_time($) {
+ my $release_date = $_ = shift;
+
+ my ($rel_year, $rel_mon, $rel_day);
+ my ($c_day, $c_mon, $c_year);
+ my $now = time;
+ my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
+ localtime $now;
+ $mon += 1;
+ $year += 1900;
+
+ unless ( m/\d{6}/ ) {
+ log_error("Invalid release date format: $release_date. Using "
+ . BROWSER_RELEASE_DATE . " instead.");
+ $release_date = BROWSER_RELEASE_DATE;
+ }
+ $rel_year = substr $release_date, 0, 4;
+ $rel_mon = substr $release_date, 4, 2;
+ $rel_day = substr $release_date, 6, 2;
+
+ #1, 2, 3, Check.
+ die "release year in the future" if ( $year < $rel_year );
+ die "release month in the future"
+ if ( ( $year == $rel_year ) and ( $mon < $rel_mon ) );
+ die "release day in the future"
+ if ( ( $year == $rel_year )
+ and ( $mon == $rel_mon )
+ and ( $mday < $rel_day ) );
+
+ my @c_time = (0, 0, 0, $rel_day, $rel_mon - 1, $rel_year - 1900, 0, 0, 0);
+ my $c_seconds = &timelocal( @c_time );
+
+ $c_seconds = $now - (int rand ($now - $c_seconds));
+ @c_time = localtime $c_seconds;
+ ($sec, $min, $hour, $c_day, $c_mon, $c_year, $wday, $yday, $isdst) = @c_time;
+ $c_mon += 1;
+ $c_year += 1900;
+
+ #3, 2, 1, Test.
+ die "Compilation year in the future" if ( $year < $c_year );
+ die "Compilation month in the future"
+ if ( ( $year == $c_year ) and ( $mon < $c_mon ) );
+ die "Compilation day in the future"
+ if ( ( $year == $c_year ) and ( $mon == $c_mon ) and ( $mday < $c_day ) );
+
+ return sprintf "%.2i%.2i%.2i", $c_year, $c_mon, $c_day;
+}
+
+sub generate_language_settings() {
+
+ our @languages;
+
+ my $language_i = int rand (@languages);
+ my $accept_language = $languages[$language_i];
+ $accept_language =~ tr/[A-Z]/[a-z]/;
+
+ return ($languages[$language_i], $accept_language);
+}
+
+sub generate_platform_and_os() {
+
+ my %os_data = (
+ FreeBSD => {
+ karma => 1,
+ platform => 'X11',
+ architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
+ order_is_inversed => 0,
+ },
+ OpenBSD => {
+ karma => 1,
+ platform => 'X11',
+ architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
+ order_is_inversed => 0,
+ },
+ NetBSD => {
+ karma => 1,
+ platform => 'X11',
+ architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
+ order_is_inversed => 0,
+ },
+ Linux => {
+ karma => 1,
+ platform => 'X11',
+ architectures => [ 'i586', 'i686', 'x86_64' ],
+ order_is_inversed => 0,
+ },
+ SunOS => {
+ karma => 1,
+ platform => 'X11',
+ architectures => [ 'i86pc', 'sun4u' ],
+ order_is_inversed => 0,
+ },
+ 'Mac OS X' => {
+ karma => 1,
+ platform => 'Macintosh',
+ architectures => [ 'PPC', 'Intel' ],
+ order_is_inversed => 1,
+ },
+ Windows => {
+ karma => 0,
+ platform => 'Windows',
+ architectures => [ 'NT 5.1' ],
+ order_is_inversed => 0,
+ }
+ );
+
+ my @os_names;
+
+ foreach my $os_name ( keys %os_data ) {
+ push @os_names, ($os_name) x $os_data{$os_name}{'karma'}
+ if $os_data{$os_name}{'karma'};
+ }
+
+ my $os_i = int rand(@os_names);
+ my $os = $os_names[$os_i];
+ my $arch_i = int rand( @{ $os_data{$os}{'architectures'} } );
+ my $arch = $os_data{$os}{'architectures'}[$arch_i];
+
+ my $platform = $os_data{$os}{'platform'};
+
+ my $os_or_cpu;
+ $os_or_cpu = sprintf "%s %s",
+ $os_data{$os}{'order_is_inversed'} ? ( $arch, $os ) : ( $os, $arch );
+
+ return $platform, $os_or_cpu;
+}
+
+sub generate_firefox_user_agent() {
+
+ our $languages;
+ our $browser_version;
+ our $browser_revision;
+ our $browser_release_date;
+
+ my $mozillaversion = '5.0';
+ my $security = "U";
+
+ my $creation_time = generate_creation_time($browser_release_date);
+ my ( $locale, $accept_language ) = generate_language_settings();
+ my ( $platform, $os_or_cpu ) = generate_platform_and_os;
+
+ my $firefox_user_agent =
+ sprintf "Mozilla/%s (%s; %s; %s; %s; rv:%s) Gecko/%s Firefox/%s",
+ $mozillaversion, $platform, $security, $os_or_cpu, $locale, $browser_revision,
+ $creation_time, $browser_version;
+
+ return $accept_language, $firefox_user_agent;
+}
+
+sub log_to_file($) {
+
+ my $message = shift;
+
+ our $logfile;
+ our $no_logging;
+
+ my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
+ localtime time;
+ $year += 1900;
+ $mon += 1;
+ my $logtime = sprintf "%i/%.2i/%.2i %.2i:%.2i", $year, $mon, $mday, $hour,
+ $min;
+
+ return if $no_logging;
+
+ open( LOGFILE, ">>" . $logfile ) || die "Writing " . $logfile . " failed";
+ printf LOGFILE UAGEN_VERSION . " ($logtime) $message\n";
+ close(LOGFILE);
+
+}
+
+sub log_error($) {
+
+ my $message = shift;
+
+ $message = "Error: $message";
+ log_to_file($message);
+ print "$message\n";
+
+ exit(1);
+}
+
+sub write_action_file() {
+
+ our $action_file;
+ our $user_agent;
+ our $accept_language;
+ our $no_hide_accept_language;
+ our $action_injection;
+
+ my $action_file_content = '';
+
+ if ($action_injection){
+ open( ACTIONFILE, $action_file )
+ or log_error "Reading action file $action_file failed!";
+ while (<ACTIONFILE>) {
+ s@(hide-accept-language\{).*?(\})@$1$accept_language$2@;
+ s@(hide-user-agent\{).*?(\})@$1$user_agent$2@;
+ $action_file_content .= $_;
+ }
+ close (ACTIONFILE);
+ } else {
+ $action_file_content = "{";
+ $action_file_content .= sprintf "+hide-accept-language{%s} \\\n",
+ $accept_language unless $no_hide_accept_language;
+ $action_file_content .= sprintf " +hide-user-agent{%s} \\\n}\n/\n",
+ $user_agent;
+ }
+ open( ACTIONFILE, ">" . $action_file )
+ or log_error "Writing action file $action_file failed!";
+ print ACTIONFILE $action_file_content;
+ close(ACTIONFILE);
+
+ return 0;
+}
+
+sub write_prefs_file() {
+
+ our $mozilla_prefs_file;
+ our $user_agent;
+ our $accept_language;
+ our $clean_prefs;
+ my $prefs_file_content = '';
+
+ if (open( PREFSFILE, $mozilla_prefs_file )) {
+
+ while (<PREFSFILE>) {
+ s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
+ s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
+ $prefs_file_content .= $_;
+ }
+ close (PREFSFILE);
+ } else {
+ log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
+ }
+
+ $prefs_file_content .=
+ sprintf("user_pref(\"general.useragent.override\", \"%s\");\n", $user_agent) .
+ sprintf("user_pref(\"intl.accept_languages\", \"%s\");\n", $accept_language)
+ unless $clean_prefs;
+
+ open( PREFSFILE, ">" . $mozilla_prefs_file )
+ or log_error "Writing prefs file $mozilla_prefs_file failed!";
+ print PREFSFILE $prefs_file_content;
+ close(PREFSFILE);
+
+}
+
+sub VersionMessage() {
+ printf UAGEN_VERSION . "\n" . 'Copyright (C) 2006-2009 Fabian Keil <fk@fabiankeil.de> ' .
+ "\nhttp://www.fabiankeil.de/sourcecode/uagen/\n";
+}
+
+sub help() {
+
+ our $logfile;
+ our $action_file;
+ our $browser_version;
+ our $browser_revision;
+ our $browser_release_date;
+ our $sleeping_time;
+ our $loop;
+ our $mozilla_prefs_file;
+
+ my $comma_separated_languages;
+
+ $loop = $loop ? ' ' . $loop : '';
+ $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
+ foreach (LANGUAGES){
+ $comma_separated_languages .= $_ . ",";
+ }
+ chop $comma_separated_languages;
+
+ VersionMessage;
+
+ print << " EOF"
+
+Options and their default values if there are any:
+ [--action-file $action_file]
+ [--action-injection]
+ [--browser-release-date $browser_release_date]
+ [--browser-revision $browser_revision]
+ [--browser-version $browser_version]
+ [--clean-prefs-file]
+ [--help]
+ [--language-overwrite $comma_separated_languages]
+ [--logfile $logfile]
+ [--loop$loop]
+ [--no-action-file]
+ [--no-hide-accept-language]
+ [--no-logfile]
+ [--prefs-file$mozilla_prefs_file]
+ [--quiet]
+ [--silent]
+ [--sleeping-time $sleeping_time]
+ [--version]
+see "perldoc $0" for more information
+ EOF
+ ;
+ exit(0);
+}
+
+sub main() {
+
+ my $error_message;
+ my $no_action_file = NO_ACTION_FILE;
+
+ our $silent = SILENT;
+ our $no_logging = NO_LOGGING;
+ our $logfile = UAGEN_LOGFILE;
+ our $action_file = ACTION_FILE;
+ our $browser_version = BROWSER_VERSION;
+ our $browser_revision = BROWSER_REVISION;
+ our $browser_release_date = BROWSER_RELEASE_DATE;
+ our $sleeping_time = SLEEPING_TIME;
+ our $loop = LOOP;
+ our $no_hide_accept_language = 0;
+ our $action_injection = 0;
+
+ our @languages;
+ our ( $accept_language, $user_agent );
+ our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
+ our $clean_prefs = 0;
+
+ GetOptions('logfile=s' => \$logfile,
+ 'action-file=s' => \$action_file,
+ 'language-overwrite=s@' => \@languages,
+ 'silent|quiet' => \$silent,
+ 'no-hide-accept-language' => \$no_hide_accept_language,
+ 'no-logfile' => \$no_logging,
+ 'no-action-file' => \$no_action_file,
+ 'browser-version=s' => \$browser_version,
+ 'browser-revision=s' => \$browser_revision,
+ 'browser-release-date=s' => \$browser_release_date,
+ 'action-injection' => \$action_injection,
+ 'loop' => \$loop,
+ 'sleeping-time' => \$sleeping_time,
+ 'prefs-file=s' => \$mozilla_prefs_file,
+ 'clean-prefs-file' => \$clean_prefs,
+ 'help' => \&help,
+ 'version' => sub {VersionMessage() && exit(0)}
+ ) or exit(0);
+
+ if (@languages) {
+ @languages = split(/,/,join(',',@languages));
+ } else {
+ @languages = LANGUAGES;
+ }
+
+ srand( time ^ ( $$ + ( $$ << 15 ) ) );
+
+ do {
+ $error_message='';
+ ( $accept_language, $user_agent ) = generate_firefox_user_agent();
+
+ print "$user_agent\n" unless $silent;
+
+ write_action_file() unless $no_action_file;
+ write_prefs_file() if $mozilla_prefs_file;
+
+ log_to_file "Generated User-Agent: $user_agent";
+
+ } while ($loop && sleep($sleeping_time * 60));
+}
+
+main();
+exit(0);
+
+=head1 NAME
+
+B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
+
+=head1 SYNOPSIS
+
+B<uagen> [B<--action-file> I<action_file>] [B<--action-injection>]
+[B<--browser-release-date> I<browser_release_date>]
+[B<--browser-revision> I<browser_revision>]
+[B<--browser-version> I<browser_version>]
+[B<--clean-prefs-file>]
+[B<--help>] [B<--language-overwrite> I<language(s)>]
+[B<--logfile> I<logfile>] [B<--loop>] [B<--no-action-file>] [B<--no-logfile>]
+[B<--prefs-file> I<prefs_file>]
+[B<--quiet>] [B<--sleeping-time> I<minutes>] [B<--silent>] [B<--version>]
+
+=head1 DESCRIPTION
+
+B<uagen> generates a fake Firefox User-Agent and writes it into a Privoxy action file
+as parameter for Privoxy's B<hide-user-agent> action. Operating system, architecture,
+platform, language and build date are randomized.
+
+The generated language is also used as parameter for the
+B<hide-accept-language> action which is understood by Privoxy since
+version 3.0.5 beta.
+
+Additionally the User-Agent can be written into prefs.js files which are
+used by many Mozilla browsers.
+
+=head1 OPTIONS
+
+B<--action-file> I<action_file> Privoxy action file to write the
+generated actions into. Default is /etc/privoxy/user-agent.action.
+
+B<--action-injection> Don't generate a new action file from scratch,
+but read an old one and just replace the action values. Useful
+to keep custom URL patterns. For this to work, the action file
+has to be already present. B<uagen> neither checks the syntax
+nor cares if all actions are present. Garbage in, garbage out.
+
+B<--browser-release-date> I<browser_release_date> Date when the faked
+browser version was first released, format is YYYYMMDD. B<uagen> will
+pick a date between the release date and the actual date to use it as
+build time. Some sanity checks are done, but you shouldn't rely on them.
+
+B<--browser-revision> I<browser_revision> Use a custom revision.
+B<uagen> will use it without any sanity checks.
+
+B<--browser-version> I<browser_version> Use a custom browser version.
+B<uagen> will use it without any sanity checks.
+
+B<--clean-prefs-file> The I<prefs_file> is read and the variables
+B<general.useragent.override> and B<intl.accept_languages> are removed.
+Only effective if I<prefs_file> is set, and only useful if you want
+to use the browser's defaults again.
+
+B<--help> List command line options and exit.
+
+B<--language-overwrite> I<language(s)> Comma separated list of language codes
+to overwrite the default values. B<uagen> chooses one of them for the generated
+User-Agent, by default the chosen language in lower cases is also used as
+B<hide-accept-language> parameter.
+
+B<--logfile> I<logfile> Logfile to save error messages and the generated
+User-Agents. Default is /var/log/uagen.log.
+
+B<--loop> Don't exit after the generation of the action file. Sleep for
+a while and generate a new one instead. Useful if you don't have cron(8).
+
+B<--no-logfile> Don't log anything.
+
+B<--no-action-file> Don't write the action file.
+
+B<--no-hide-accept-language> Stay compatible with Privoxy 3.0.3
+and don't generate the B<hide-accept-language> action line. You should
+really update your Privoxy version instead.
+
+B<--prefs-file> I<prefs_file> Use the generated User-Agent to set the
+B<general.useragent.override> variable in the Mozilla preference file
+I<prefs_file>, The B<intl.accept_languages> variable will be set as well.
+
+Firefox's preference file is usually located in
+~/.mozilla/firefox/*.default/prefs.js. Note that Firefox doesn't reread
+the file once it is running.
+
+B<--quiet> Don't print the generated User-Agent to the console.
+
+B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
+
+B<--silent> Don't print the generated User-Agent to the console.
+
+B<--version> Print version and exit.
+
+The second dash is optional, options can be shortened, as long as there are
+no ambiguities.
+
+=head1 PRIVOXY CONFIGURATION
+
+In Privoxy's configuration file the line:
+
+ actionsfile user-agent.action
+
+should be added after:
+
+ actionfile default.action
+
+and before:
+
+ actionfile user.action
+
+This way the user can still use custom User-Agents
+in I<user.action>. I<user-agent> has to be the name
+of the generated action file.
+
+If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
+
+=head1 EXAMPLES
+
+Without any options, B<uagen> creates an action file like:
+
+ {+hide-accept-language{en-ca} \
+ +hide-user-agent{Mozilla/5.0 (X11; U; OpenBSD i386; en-CA; rv:1.8.0.4) Gecko/20060628 Firefox/1.5.0.4} \
+ }
+ /
+
+with the --no-accept-language option the generated file
+could look like this one:
+
+ {+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD i386; de-DE; rv:1.8.0.4) Gecko/20060720 Firefox/1.5.0.4} \
+ }
+ /
+
+=head1 CAVEATS
+
+If the browser opens an encrypted connection, Privoxy can't inspect
+the content and the browser's headers reach the server unmodified.
+It is the user's job to use Privoxy's limit-connect action to make sure
+there are no encrypted connections to untrusted sites.
+
+Mozilla users can alter the browser's User-Agent with the
+B<--prefs-file> option. But note that the preference file is only read
+on startup. If the browser is already running, B<uagen's> changes will be ignored.
+
+Hiding the User-Agent is pointless if the browser accepts all
+cookies or even is configured for remote maintenance through Flash,
+JavaScript, Java or similar security problems.
+
+=head1 BUGS
+
+Some parameters can't be specified at the command line.
+
+=head1 SEE ALSO
+
+privoxy(1)
+
+=head1 AUTHOR
+
+Fabian Keil <fk@fabiankeil.de>
+
+http://www.fabiankeil.de/sourcecode/uagen/
+
+http://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)
+
+=cut
+