3 ##############################################################################################
4 # uagen (https://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.2.2):
11 # Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
12 # Mozilla/5.0 (Macintosh; PPC Mac OS X; rv:78.0) Gecko/20100101 Firefox/78.0
13 # Mozilla/5.0 (X11; NetBSD i386; rv:78.0) Gecko/20100101 Firefox/78.0
14 # Mozilla/5.0 (X11; OpenBSD alpha; rv:78.0) Gecko/20100101 Firefox/78.0
15 # Mozilla/5.0 (X11; FreeBSD amd64; rv:78.0) Gecko/20100101 Firefox/78.0
16 # Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
17 # Mozilla/5.0 (X11; ElectroBSD amd64; rv:78.0) Gecko/20100101 Firefox/78.0
18 # Mozilla/5.0 (X11; FreeBSD i386; rv:78.0) Gecko/20100101 Firefox/78.0
20 # Copyright (c) 2006-2023 Fabian Keil <fk@fabiankeil.de>
22 # Permission to use, copy, modify, and distribute this software for any
23 # purpose with or without fee is hereby granted, provided that the above
24 # copyright notice and this permission notice appear in all copies.
26 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
27 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
28 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
29 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
30 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
31 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
32 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33 ##############################################################################################
42 UAGEN_VERSION => 'uagen 1.2.5',
44 UAGEN_LOGFILE => '/var/log/uagen.log',
45 ACTION_FILE => '/etc/privoxy/user-agent.action',
46 MOZILLA_PREFS_FILE => '',
53 # As of Firefox 4, the "Gecko token" has been frozen
54 # https://hacks.mozilla.org/2010/09/final-user-agent-string-for-firefox-4/
55 RANDOMIZE_RELEASE_DATE => 0,
57 # These variables belong together. If you only change one of them, the generated
58 # User-Agent might be invalid. If you're not sure which values make sense,
59 # are too lazy to check, but want to change them anyway, take the values you
60 # see in the "Help/About Mozilla Firefox" menu.
62 BROWSER_VERSION => "115.0",
63 BROWSER_REVISION => '109.0',
64 BROWSER_RELEASE_DATE => '20100101',
67 use constant LANGUAGES => qw(
68 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
71 #######################################################################################
73 sub generate_creation_time($) {
74 my $release_date = shift;
76 my ($rel_year, $rel_mon, $rel_day);
77 my ($c_day, $c_mon, $c_year);
79 my (undef, undef, undef, $mday, $mon, $year, undef, undef, undef) = localtime($now);
83 unless ($release_date =~ m/\d{6}/) {
84 log_error("Invalid release date format: $release_date. Using "
85 . BROWSER_RELEASE_DATE . " instead.");
86 $release_date = BROWSER_RELEASE_DATE;
88 $rel_year = substr($release_date, 0, 4);
89 $rel_mon = substr($release_date, 4, 2);
90 $rel_day = substr($release_date, 6, 2);
93 die "release year in the future" if ($year < $rel_year);
94 die "release month in the future"
95 if (($year == $rel_year) and ($mon < $rel_mon));
96 die "release day in the future"
97 if (($year == $rel_year) and ($mon == $rel_mon) and ($mday < $rel_day));
99 my @c_time = (0, 0, 0, $rel_day, $rel_mon - 1, $rel_year - 1900, 0, 0, 0);
100 my $c_seconds = timelocal(@c_time);
102 $c_seconds = $now - (int rand ($now - $c_seconds));
103 @c_time = localtime($c_seconds);
104 (undef, undef, undef, $c_day, $c_mon, $c_year, undef, undef, undef) = @c_time;
109 die "Compilation year in the future" if ($year < $c_year);
110 die "Compilation month in the future"
111 if (($year == $c_year) and ($mon < $c_mon));
112 die "Compilation day in the future"
113 if (($year == $c_year) and ($mon == $c_mon) and ($mday < $c_day));
115 return sprintf("%.2i%.2i%.2i", $c_year, $c_mon, $c_day);
118 sub generate_language_settings() {
122 my $language_i = int rand (@languages);
123 my $accept_language = $languages[$language_i];
124 $accept_language =~ tr/[A-Z]/[a-z]/;
126 return ($languages[$language_i], $accept_language);
129 sub generate_platform_and_os() {
135 architectures => [ 'i386', 'amd64' ],
136 order_is_inversed => 0,
141 architectures => [ 'i386', 'amd64' ],
142 order_is_inversed => 0,
147 architectures => [ 'arm64', 'i386', 'amd64', 'sparc64', 'alpha' ],
148 order_is_inversed => 0,
153 architectures => [ 'i386', 'amd64', 'sparc64', 'alpha' ],
154 order_is_inversed => 0,
159 architectures => [ 'aarch64', 'i586', 'i686', 'x86_64' ],
160 order_is_inversed => 0,
165 architectures => [ 'i86pc', 'sun4u' ],
166 order_is_inversed => 0,
170 platform => 'Macintosh',
171 architectures => [ 'PPC', 'Intel' ],
172 order_is_inversed => 1,
176 platform => 'Windows',
177 architectures => [ 'NT 5.1' ],
178 order_is_inversed => 0,
184 foreach my $os_name ( keys %os_data ) {
185 push @os_names, ($os_name) x $os_data{$os_name}{'karma'}
186 if $os_data{$os_name}{'karma'};
189 my $os_i = int rand(@os_names);
190 my $os = $os_names[$os_i];
191 my $arch_i = int rand( @{ $os_data{$os}{'architectures'} } );
192 my $arch = $os_data{$os}{'architectures'}[$arch_i];
194 my $platform = $os_data{$os}{'platform'};
197 $os_or_cpu = sprintf "%s %s",
198 $os_data{$os}{'order_is_inversed'} ? ( $arch, $os ) : ( $os, $arch );
200 return $platform, $os_or_cpu;
203 sub generate_firefox_user_agent() {
206 our $browser_version;
207 our $browser_revision;
208 our $browser_release_date;
209 our $randomize_release_date;
211 my $mozillaversion = '5.0';
213 my $creation_time = $randomize_release_date ?
214 generate_creation_time($browser_release_date) : $browser_release_date;
215 my ( $locale, $accept_language ) = generate_language_settings();
216 my ( $platform, $os_or_cpu ) = generate_platform_and_os;
218 my $firefox_user_agent =
219 sprintf "Mozilla/%s (%s; %s; rv:%s) Gecko/%s Firefox/%s",
220 $mozillaversion, $platform, $os_or_cpu, $browser_revision,
221 $creation_time, $browser_version;
223 return $accept_language, $firefox_user_agent;
233 my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
237 my $logtime = sprintf "%i/%.2i/%.2i %.2i:%.2i", $year, $mon, $mday, $hour,
240 return if $no_logging;
242 open(my $log_fd, ">>", $logfile) || die "Writing " . $logfile . " failed";
243 printf $log_fd UAGEN_VERSION . " ($logtime) $message\n";
251 $message = "Error: $message";
252 log_to_file($message);
258 sub write_action_file() {
262 our $accept_language;
263 our $no_hide_accept_language;
264 our $action_injection;
266 my $action_file_content = '';
268 if ($action_injection){
269 open(my $actionfile_fd, "<", $action_file)
270 or log_error "Reading action file $action_file failed!";
271 while (<$actionfile_fd>) {
272 s@(hide-accept-language\{).*?(\})@$1$accept_language$2@;
273 s@(hide-user-agent\{).*?(\})@$1$user_agent$2@;
274 $action_file_content .= $_;
276 close($actionfile_fd);
278 $action_file_content = "{";
279 $action_file_content .= sprintf "+hide-accept-language{%s} \\\n",
280 $accept_language unless $no_hide_accept_language;
281 $action_file_content .= sprintf " +hide-user-agent{%s} \\\n}\n/\n",
284 open(my $actionfile_fd, ">", $action_file)
285 or log_error "Writing action file $action_file failed!";
286 print $actionfile_fd $action_file_content;
287 close($actionfile_fd);
292 sub write_prefs_file() {
294 our $mozilla_prefs_file;
296 our $accept_language;
299 my $prefs_file_content = '';
302 if (open($prefsfile_fd, "<", $mozilla_prefs_file)) {
304 while (<$prefsfile_fd>) {
305 s@user_pref\(\"general.useragent.override\",.*\);\n?@@;
306 s@user_pref\(\"intl.accept_languages\",.*\);\n?@@;
307 $prefs_file_content .= $_;
309 close($prefsfile_fd);
311 log_error "Reading prefs file $mozilla_prefs_file failed. Creating a new file!";
314 $prefs_file_content .=
315 sprintf("user_pref(\"general.useragent.override\", \"%s\");\n", $user_agent) .
316 sprintf("user_pref(\"intl.accept_languages\", \"%s\");\n", $accept_language)
319 open($prefsfile_fd, ">", $mozilla_prefs_file)
320 or log_error "Writing prefs file $mozilla_prefs_file failed!";
321 print $prefsfile_fd $prefs_file_content;
322 close($prefsfile_fd);
326 sub VersionMessage() {
327 printf UAGEN_VERSION . "\n" . 'Copyright (c) 2006-2022 Fabian Keil <fk@fabiankeil.de> ' .
328 "\nhttps://www.fabiankeil.de/sourcecode/uagen/\n";
335 our $browser_version;
336 our $browser_revision;
337 our $browser_release_date;
340 our $mozilla_prefs_file;
342 my $comma_separated_languages;
344 $loop = $loop ? ' ' . $loop : '';
345 $mozilla_prefs_file = $mozilla_prefs_file ? ' ' . $mozilla_prefs_file : '';
347 $comma_separated_languages .= $_ . ",";
349 chop $comma_separated_languages;
355 Options and their default values if there are any:
356 [--action-file $action_file]
358 [--browser-release-date $browser_release_date]
359 [--browser-revision $browser_revision]
360 [--browser-version $browser_version]
363 [--language-overwrite $comma_separated_languages]
367 [--no-hide-accept-language]
369 [--prefs-file$mozilla_prefs_file]
370 [--randomize-release-date]
373 [--sleeping-time $sleeping_time]
375 see "perldoc $0" for more information
384 my $no_action_file = NO_ACTION_FILE;
386 our $silent = SILENT;
387 our $no_logging = NO_LOGGING;
388 our $logfile = UAGEN_LOGFILE;
389 our $action_file = ACTION_FILE;
390 our $randomize_release_date = RANDOMIZE_RELEASE_DATE;
391 our $browser_version = BROWSER_VERSION;
392 our $browser_revision = BROWSER_REVISION;
393 our $browser_release_date = BROWSER_RELEASE_DATE;
394 our $sleeping_time = SLEEPING_TIME;
396 our $no_hide_accept_language = 0;
397 our $action_injection = 0;
400 our ( $accept_language, $user_agent );
401 our $mozilla_prefs_file = MOZILLA_PREFS_FILE;
402 our $clean_prefs = 0;
404 GetOptions('logfile=s' => \$logfile,
405 'action-file=s' => \$action_file,
406 'language-overwrite=s@' => \@languages,
407 'silent|quiet' => \$silent,
408 'no-hide-accept-language' => \$no_hide_accept_language,
409 'no-logfile' => \$no_logging,
410 'no-action-file' => \$no_action_file,
411 'randomize-release-date' => \$randomize_release_date,
412 'browser-version=s' => \$browser_version,
413 'browser-revision=s' => \$browser_revision,
414 'browser-release-date=s' => \$browser_release_date,
415 'action-injection' => \$action_injection,
417 'sleeping-time' => \$sleeping_time,
418 'prefs-file=s' => \$mozilla_prefs_file,
419 'clean-prefs-file' => \$clean_prefs,
421 'version' => sub {VersionMessage() && exit(0)}
425 @languages = split(/,/,join(',',@languages));
427 @languages = LANGUAGES;
430 srand( time ^ ( $$ + ( $$ << 15 ) ) );
434 ( $accept_language, $user_agent ) = generate_firefox_user_agent();
436 print "$user_agent\n" unless $silent;
438 write_action_file() unless $no_action_file;
439 write_prefs_file() if $mozilla_prefs_file;
441 log_to_file "Generated User-Agent: $user_agent";
443 } while ($loop && sleep($sleeping_time * 60));
450 B<uagen> - A Firefox User-Agent generator for Privoxy and Mozilla browsers
454 B<uagen> [B<--action-file> I<action_file>] [B<--action-injection>]
455 [B<--browser-release-date> I<browser_release_date>]
456 [B<--browser-revision> I<browser_revision>]
457 [B<--browser-version> I<browser_version>]
458 [B<--clean-prefs-file>]
459 [B<--help>] [B<--language-overwrite> I<language(s)>]
460 [B<--logfile> I<logfile>] [B<--loop>] [B<--no-action-file>] [B<--no-logfile>]
461 [B<--prefs-file> I<prefs_file>] [B<--randomize-release-date>]
462 [B<--quiet>] [B<--sleeping-time> I<minutes>] [B<--silent>] [B<--version>]
466 B<uagen> generates a fake Firefox User-Agent and writes it into a Privoxy action file
467 as parameter for Privoxy's B<hide-user-agent> action. Operating system, architecture,
468 platform, language and, optionally, the build date are randomized.
470 The generated language is also used as parameter for the
471 B<hide-accept-language> action which is understood by Privoxy since
474 Additionally the User-Agent can be written into prefs.js files which are
475 used by many Mozilla browsers.
479 B<--action-file> I<action_file> Privoxy action file to write the
480 generated actions into. Default is /etc/privoxy/user-agent.action.
482 B<--action-injection> Don't generate a new action file from scratch,
483 but read an old one and just replace the action values. Useful
484 to keep custom URL patterns. For this to work, the action file
485 has to be already present. B<uagen> neither checks the syntax
486 nor cares if all actions are present. Garbage in, garbage out.
488 B<--browser-release-date> I<browser_release_date> Date to use.
489 The format is YYYYMMDD. Some sanity checks are done, but you
490 shouldn't rely on them. Note that the Mozilla project has frozen
491 the "Gecko token" starting with Firefox 4 so using a different
492 one than the default is somewhat suspicious.
494 B<--browser-revision> I<browser_revision> Use a custom revision.
495 B<uagen> will use it without any sanity checks.
497 B<--browser-version> I<browser_version> Use a custom browser version.
498 B<uagen> will use it without any sanity checks.
500 B<--clean-prefs-file> The I<prefs_file> is read and the variables
501 B<general.useragent.override> and B<intl.accept_languages> are removed.
502 Only effective if I<prefs_file> is set, and only useful if you want
503 to use the browser's defaults again.
505 B<--help> List command line options and exit.
507 B<--language-overwrite> I<language(s)> Comma separated list of language codes
508 to overwrite the default values. B<uagen> chooses one of them for the generated
509 User-Agent, by default the chosen language in lower cases is also used as
510 B<hide-accept-language> parameter.
512 B<--logfile> I<logfile> Logfile to save error messages and the generated
513 User-Agents. Default is /var/log/uagen.log.
515 B<--loop> Don't exit after the generation of the action file. Sleep for
516 a while and generate a new one instead. Useful if you don't have cron(8).
518 B<--no-logfile> Don't log anything.
520 B<--no-action-file> Don't write the action file.
522 B<--no-hide-accept-language> Stay compatible with Privoxy 3.0.3
523 and don't generate the B<hide-accept-language> action line. You should
524 really update your Privoxy version instead.
526 B<--prefs-file> I<prefs_file> Use the generated User-Agent to set the
527 B<general.useragent.override> variable in the Mozilla preference file
528 I<prefs_file>, The B<intl.accept_languages> variable will be set as well.
530 Firefox's preference file is usually located in
531 ~/.mozilla/firefox/*.default/prefs.js. Note that Firefox doesn't reread
532 the file once it is running.
534 B<--randomize-release-date> Randomly pick a date between the configured
535 release date and the actual date. Note that Firefox versions after 4.0
536 no longer provide the build date in the User-Agent header, so if you
537 randomize the date anyway, it will be obvious that the generated User-Agent
540 B<--quiet> Don't print the generated User-Agent to the console.
542 B<--sleeping-time> I<minutes> Time to sleep. Only effective if used with B<--loop>.
544 B<--silent> Don't print the generated User-Agent to the console.
546 B<--version> Print version and exit.
548 The second dash is optional, options can be shortened, as long as there are
551 =head1 PRIVOXY CONFIGURATION
553 In Privoxy's configuration file the line:
555 actionsfile user-agent.action
557 should be added after:
559 actionfile default.action
563 actionfile user.action
565 This way the user can still use custom User-Agents
566 in I<user.action>. I<user-agent> has to be the name
567 of the generated action file.
569 If you are using Privoxy 3.0.6 or earlier, don't add the ".action" extension.
573 Without any options, B<uagen> creates an action file like:
575 {+hide-accept-language{en-ca} \
576 +hide-user-agent{Mozilla/5.0 (X11; U; OpenBSD i386; en-CA; rv:1.8.0.4) Gecko/20060628 Firefox/1.5.0.4} \
580 with the --no-accept-language option the generated file
581 could look like this one:
583 {+hide-user-agent{Mozilla/5.0 (X11; U; FreeBSD i386; de-DE; rv:1.8.0.4) Gecko/20060720 Firefox/1.5.0.4} \
589 Use the https-inspection action to make sure Privoxy can modify
590 the browser's headers for encrypted traffic as well.
592 Mozilla users can alter the browser's User-Agent with the
593 B<--prefs-file> option. But note that the preference file is only read
594 on startup. If the browser is already running, B<uagen's> changes will be ignored.
596 Hiding the User-Agent is pointless if the browser accepts all
597 cookies or even is configured for remote maintenance through Flash,
598 JavaScript, Java or similar security problems.
602 Some parameters can't be specified at the command line.
610 Fabian Keil <fk@fabiankeil.de>
612 https://www.fabiankeil.de/sourcecode/uagen/
614 https://www.fabiankeil.de/blog-surrogat/2006/01/26/firefox-user-agent-generator.html (German)