3 ##############################################################################################
4 # uagen (http://www.fabiankeil.de/sourcecode/uagen/)
6 # $Id: uagen.pl,v 1.14 2011/06/29 18:35:53 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);
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 )
99 and ( $mon == $rel_mon )
100 and ( $mday < $rel_day ) );
102 my @c_time = (0, 0, 0, $rel_day, $rel_mon - 1, $rel_year - 1900, 0, 0, 0);
103 my $c_seconds = &timelocal( @c_time );
105 $c_seconds = $now - (int rand ($now - $c_seconds));
106 @c_time = localtime $c_seconds;
107 (undef, undef, undef, $c_day, $c_mon, $c_year, undef, undef, undef) = @c_time;
112 die "Compilation year in the future" if ( $year < $c_year );
113 die "Compilation month in the future"
114 if ( ( $year == $c_year ) and ( $mon < $c_mon ) );
115 die "Compilation day in the future"
116 if ( ( $year == $c_year ) and ( $mon == $c_mon ) and ( $mday < $c_day ) );
118 return sprintf "%.2i%.2i%.2i", $c_year, $c_mon, $c_day;
121 sub generate_language_settings() {
125 my $language_i = int rand (@languages);
126 my $accept_language = $languages[$language_i];
127 $accept_language =~ tr/[A-Z]/[a-z]/;
129 return ($languages[$language_i], $accept_language);
132 sub generate_platform_and_os() {
138 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
139 order_is_inversed => 0,
144 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
145 order_is_inversed => 0,
150 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
151 order_is_inversed => 0,
156 architectures => [ 'i586', 'i686', 'x86_64' ],
157 order_is_inversed => 0,
162 architectures => [ 'i86pc', 'sun4u' ],
163 order_is_inversed => 0,
167 platform => 'Macintosh',
168 architectures => [ 'PPC', 'Intel' ],
169 order_is_inversed => 1,
173 platform => 'Windows',
174 architectures => [ 'NT 5.1' ],
175 order_is_inversed => 0,
181 foreach my $os_name ( keys %os_data ) {
182 push @os_names, ($os_name) x $os_data{$os_name}{'karma'}
183 if $os_data{$os_name}{'karma'};
186 my $os_i = int rand(@os_names);
187 my $os = $os_names[$os_i];
188 my $arch_i = int rand( @{ $os_data{$os}{'architectures'} } );
189 my $arch = $os_data{$os}{'architectures'}[$arch_i];
191 my $platform = $os_data{$os}{'platform'};
194 $os_or_cpu = sprintf "%s %s",
195 $os_data{$os}{'order_is_inversed'} ? ( $arch, $os ) : ( $os, $arch );
197 return $platform, $os_or_cpu;
200 sub generate_firefox_user_agent() {
203 our $browser_version;
204 our $browser_revision;
205 our $browser_release_date;
206 our $randomize_release_date;
208 my $mozillaversion = '5.0';
211 my $creation_time = $randomize_release_date ?
212 generate_creation_time($browser_release_date) : $browser_release_date;
213 my ( $locale, $accept_language ) = generate_language_settings();
214 my ( $platform, $os_or_cpu ) = generate_platform_and_os;
216 my $firefox_user_agent =
217 sprintf "Mozilla/%s (%s; %s; %s; %s; rv:%s) Gecko/%s Firefox/%s",
218 $mozillaversion, $platform, $security, $os_or_cpu, $locale, $browser_revision,
219 $creation_time, $browser_version;
221 return $accept_language, $firefox_user_agent;
231 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
235 my $logtime = sprintf "%i/%.2i/%.2i %.2i:%.2i", $year, $mon, $mday, $hour,
238 return if $no_logging;
240 open(my $log_fd, ">>" . $logfile) || die "Writing " . $logfile . " failed";
241 printf $log_fd UAGEN_VERSION . " ($logtime) $message\n";
249 $message = "Error: $message";
250 log_to_file($message);
256 sub write_action_file() {
260 our $accept_language;
261 our $no_hide_accept_language;
262 our $action_injection;
264 my $action_file_content = '';
266 if ($action_injection){
267 open(my $actionfile_fd, "<", $action_file)
268 or log_error "Reading action file $action_file failed!";
269 while (<$actionfile_fd>) {
270 s@(hide-accept-language\{).*?(\})@$1$accept_language$2@;
271 s@(hide-user-agent\{).*?(\})@$1$user_agent$2@;
272 $action_file_content .= $_;
274 close($actionfile_fd);
276 $action_file_content = "{";
277 $action_file_content .= sprintf "+hide-accept-language{%s} \\\n",
278 $accept_language unless $no_hide_accept_language;
279 $action_file_content .= sprintf " +hide-user-agent{%s} \\\n}\n/\n",
282 open(my $actionfile_fd, ">" . $action_file)
283 or log_error "Writing action file $action_file failed!";
284 print $actionfile_fd $action_file_content;
285 close($actionfile_fd);
290 sub write_prefs_file() {
292 our $mozilla_prefs_file;
294 our $accept_language;
297 my $prefs_file_content = '';
300 if (open($prefsfile_fd, $mozilla_prefs_file)) {
302 while (<$prefsfile_fd>) {
303 s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
304 s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
305 $prefs_file_content .= $_;
307 close($prefsfile_fd);
309 log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
312 $prefs_file_content .=
313 sprintf("user_pref(\"general.useragent.override\", \"%s\");\n", $user_agent) .
314 sprintf("user_pref(\"intl.accept_languages\", \"%s\");\n", $accept_language)
317 open($prefsfile_fd, ">" . $mozilla_prefs_file)
318 or log_error "Writing prefs file $mozilla_prefs_file failed!";
319 print $prefsfile_fd $prefs_file_content;
320 close($prefsfile_fd);
324 sub VersionMessage() {
325 printf UAGEN_VERSION . "\n" . 'Copyright (C) 2006-2011 Fabian Keil <fk@fabiankeil.de> ' .
326 "\nhttp://www.fabiankeil.de/sourcecode/uagen/\n";
333 our $browser_version;
334 our $browser_revision;
335 our $browser_release_date;
338 our $mozilla_prefs_file;
340 my $comma_separated_languages;
342 $loop = $loop ? ' ' . $loop : '';
343 $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
345 $comma_separated_languages .= $_ . ",";
347 chop $comma_separated_languages;
353 Options and their default values if there are any:
354 [--action-file $action_file]
356 [--browser-release-date $browser_release_date]
357 [--browser-revision $browser_revision]
358 [--browser-version $browser_version]
361 [--language-overwrite $comma_separated_languages]
365 [--no-hide-accept-language]
367 [--prefs-file$mozilla_prefs_file]
368 [--randomize-release-date]
371 [--sleeping-time $sleeping_time]
373 see "perldoc $0" for more information
382 my $no_action_file = NO_ACTION_FILE;
384 our $silent = SILENT;
385 our $no_logging = NO_LOGGING;
386 our $logfile = UAGEN_LOGFILE;
387 our $action_file = ACTION_FILE;
388 our $randomize_release_date = RANDOMIZE_RELEASE_DATE;
389 our $browser_version = BROWSER_VERSION;
390 our $browser_revision = BROWSER_REVISION;
391 our $browser_release_date = BROWSER_RELEASE_DATE;
392 our $sleeping_time = SLEEPING_TIME;
394 our $no_hide_accept_language = 0;
395 our $action_injection = 0;
398 our ( $accept_language, $user_agent );
399 our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
400 our $clean_prefs = 0;
402 GetOptions('logfile=s' => \$logfile,
403 'action-file=s' => \$action_file,
404 'language-overwrite=s@' => \@languages,
405 'silent|quiet' => \$silent,
406 'no-hide-accept-language' => \$no_hide_accept_language,
407 'no-logfile' => \$no_logging,
408 'no-action-file' => \$no_action_file,
409 'randomize-release-date' => \$randomize_release_date,
410 'browser-version=s' => \$browser_version,
411 'browser-revision=s' => \$browser_revision,
412 'browser-release-date=s' => \$browser_release_date,
413 'action-injection' => \$action_injection,
415 'sleeping-time' => \$sleeping_time,
416 'prefs-file=s' => \$mozilla_prefs_file,
417 'clean-prefs-file' => \$clean_prefs,
419 'version' => sub {VersionMessage() && exit(0)}
423 @languages = split(/,/,join(',',@languages));
425 @languages = LANGUAGES;
428 srand( time ^ ( $$ + ( $$ << 15 ) ) );
432 ( $accept_language, $user_agent ) = generate_firefox_user_agent();
434 print "$user_agent\n" unless $silent;
436 write_action_file() unless $no_action_file;
437 write_prefs_file() if $mozilla_prefs_file;
439 log_to_file "Generated User-Agent: $user_agent";
441 } while ($loop && sleep($sleeping_time * 60));
449 B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
453 B<uagen> [B<--action-file> I<action_file>] [B<--action-injection>]
454 [B<--browser-release-date> I<browser_release_date>]
455 [B<--browser-revision> I<browser_revision>]
456 [B<--browser-version> I<browser_version>]
457 [B<--clean-prefs-file>]
458 [B<--help>] [B<--language-overwrite> I<language(s)>]
459 [B<--logfile> I<logfile>] [B<--loop>] [B<--no-action-file>] [B<--no-logfile>]
460 [B<--prefs-file> I<prefs_file>] [B<--randomize-release-date>]
461 [B<--quiet>] [B<--sleeping-time> I<minutes>] [B<--silent>] [B<--version>]
465 B<uagen> generates a fake Firefox User-Agent and writes it into a Privoxy action file
466 as parameter for Privoxy's B<hide-user-agent> action. Operating system, architecture,
467 platform, language and, optionally, the build date are randomized.
469 The generated language is also used as parameter for the
470 B<hide-accept-language> action which is understood by Privoxy since
473 Additionally the User-Agent can be written into prefs.js files which are
474 used by many Mozilla browsers.
478 B<--action-file> I<action_file> Privoxy action file to write the
479 generated actions into. Default is /etc/privoxy/user-agent.action.
481 B<--action-injection> Don't generate a new action file from scratch,
482 but read an old one and just replace the action values. Useful
483 to keep custom URL patterns. For this to work, the action file
484 has to be already present. B<uagen> neither checks the syntax
485 nor cares if all actions are present. Garbage in, garbage out.
487 B<--browser-release-date> I<browser_release_date> Date to use.
488 The format is YYYYMMDD. Some sanity checks are done, but you
489 shouldn't rely on them.
491 B<--browser-revision> I<browser_revision> Use a custom revision.
492 B<uagen> will use it without any sanity checks.
494 B<--browser-version> I<browser_version> Use a custom browser version.
495 B<uagen> will use it without any sanity checks.
497 B<--clean-prefs-file> The I<prefs_file> is read and the variables
498 B<general.useragent.override> and B<intl.accept_languages> are removed.
499 Only effective if I<prefs_file> is set, and only useful if you want
500 to use the browser's defaults again.
502 B<--help> List command line options and exit.
504 B<--language-overwrite> I<language(s)> Comma separated list of language codes
505 to overwrite the default values. B<uagen> chooses one of them for the generated
506 User-Agent, by default the chosen language in lower cases is also used as
507 B<hide-accept-language> parameter.
509 B<--logfile> I<logfile> Logfile to save error messages and the generated
510 User-Agents. Default is /var/log/uagen.log.
512 B<--loop> Don't exit after the generation of the action file. Sleep for
513 a while and generate a new one instead. Useful if you don't have cron(8).
515 B<--no-logfile> Don't log anything.
517 B<--no-action-file> Don't write the action file.
519 B<--no-hide-accept-language> Stay compatible with Privoxy 3.0.3
520 and don't generate the B<hide-accept-language> action line. You should
521 really update your Privoxy version instead.
523 B<--prefs-file> I<prefs_file> Use the generated User-Agent to set the
524 B<general.useragent.override> variable in the Mozilla preference file
525 I<prefs_file>, The B<intl.accept_languages> variable will be set as well.
527 Firefox's preference file is usually located in
528 ~/.mozilla/firefox/*.default/prefs.js. Note that Firefox doesn't reread
529 the file once it is running.
531 B<--randomize-release-date> Randomly pick a date between the configured
532 release date and the actual date. Note that Firefox versions after 4.0
533 no longer provide the build date in the User-Agent header, so if you
534 randomize the date anyway, it will be obvious that the generated User-Agent
537 B<--quiet> Don't print the generated User-Agent to the console.
539 B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
541 B<--silent> Don't print the generated User-Agent to the console.
543 B<--version> Print version and exit.
545 The second dash is optional, options can be shortened, as long as there are
548 =head1 PRIVOXY CONFIGURATION
550 In Privoxy's configuration file the line:
552 actionsfile user-agent.action
554 should be added after:
556 actionfile default.action
560 actionfile user.action
562 This way the user can still use custom User-Agents
563 in I<user.action>. I<user-agent> has to be the name
564 of the generated action file.
566 If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
570 Without any options, B<uagen> creates an action file like:
572 {+hide-accept-language{en-ca} \
573 +hide-user-agent{Mozilla/5.0 (X11; U; OpenBSD i386; en-CA; rv:1.8.0.4) Gecko/20060628 Firefox/1.5.0.4} \
577 with the --no-accept-language option the generated file
578 could look like this one:
580 {+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD i386; de-DE; rv:1.8.0.4) Gecko/20060720 Firefox/1.5.0.4} \
586 If the browser opens an encrypted connection, Privoxy can't inspect
587 the content and the browser's headers reach the server unmodified.
588 It is the user's job to use Privoxy's limit-connect action to make sure
589 there are no encrypted connections to untrusted sites.
591 Mozilla users can alter the browser's User-Agent with the
592 B<--prefs-file> option. But note that the preference file is only read
593 on startup. If the browser is already running, B<uagen's> changes will be ignored.
595 Hiding the User-Agent is pointless if the browser accepts all
596 cookies or even is configured for remote maintenance through Flash,
597 JavaScript, Java or similar security problems.
601 Some parameters can't be specified at the command line.
609 Fabian Keil <fk@fabiankeil.de>
611 http://www.fabiankeil.de/sourcecode/uagen/
613 http://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)