Skip to content

Instantly share code, notes, and snippets.

@romainguinot
Last active December 14, 2015 09:50
Show Gist options
  • Save romainguinot/5068069 to your computer and use it in GitHub Desktop.
Save romainguinot/5068069 to your computer and use it in GitHub Desktop.
checkgmail (http://checkgmail.sourceforge.net/) patch for two factor authentication (a.k.a two step) support (config, UI, integration), login refactoring (requests, UI), refactored kwallet integration, + additional refactoring/bugfixing. Applies against latest available revision (r47). Note : old version. Updated version with CSRF support here : h…
--- checkgmail 2013-02-28 14:24:44.614055113 +0100
+++ checkgmail.current 2013-03-01 22:22:30.211693526 +0100
@@ -33,13 +33,18 @@
# global variables (can't be set global in the BEGIN block)
my ($version, $silent, $nocrypt, $update, $notsexy, $profile, $disable_monitors_check,
- $private, $cookies, $popup_size, $hosted_tmp, $show_popup_delay,
- $popup_persistence, $usekwallet, $libsexy, $nologin, $mailno, $debug);
+ $private, $cookies, $two_factor_auth_switch, $popup_size, $hosted_tmp, $show_popup_delay,
+ $popup_persistence, $use_kwallet, $libsexy, $nologin, $mailno, $debug);
+
+# global name of the 3rd party CLI binary used to access kwallet (if kwallet integration is used).
+my $kwalletcli;
+
BEGIN {
- $version = "1.14pre2-svn";
+ $version = "1.14pre2-svn+two_factor+support+improved_kwallet_integration";
$silent = 1;
$profile = "";
$cookies = 1;
+ $two_factor_auth_switch = 0; # command-line switch, default off.
$nologin = 1;
$show_popup_delay = 250;
$popup_persistence = 100;
@@ -80,6 +85,10 @@
$cookies = 0;
last };
+ /two_factor/ && do {
+ $two_factor_auth_switch = 1;
+ last };
+
# /label=(.*),(\d+)/ && do {
# $label_tmp{$1} = $2;
# last };
@@ -126,7 +135,7 @@
print "CheckGmail v$version\nCopyright © 2005-10 Owen Marshall\n\n";
- print "usage: checkgmail [-profile=profile_name] [-popup_delay=millisecs] [-hosted=hosted_domain] [-no_cookies] [-popup_persistence=millisecs] [-private] [-v | -verbose] [-nocrypt] [-no-libsexy] [-disable-monitors-check] [-numbers] [-update] [-h]\n\n";
+ print "usage: checkgmail [-profile=profile_name] [-popup_delay=millisecs] [-hosted=hosted_domain] [-no_cookies] [-two_factor] [-popup_persistence=millisecs] [-private] [-v | -verbose] [-nocrypt] [-no-libsexy] [-disable-monitors-check] [-numbers] [-update] [-h]\n\n";
exit 1;
}
@@ -147,25 +156,42 @@
# impossible to store the password safely without requiring a user-entered
# passphrase otherwise, which defeats the purpose of it all. The
# passphrase used is based on the MAC address of the users ethernet
- # setup if it exists and the entire info from uname - this is the
- # most unique info I can think of to use here, though other suggestions
- # are welcome!
- #
- # (obviously uname info isn't that unique unless you're running a kernel that
- # you compiled yourself - which I do, but many don't ...)
-
- my $uname = `uname -a`;
- chomp($uname);
-
- $_ = `whereis -b ifconfig`;
- my ($ifconfig_path) = m/ifconfig:\s+([\w\/]+)/;
- $ifconfig_path ||= "ifconfig";
-
- my $mac = `$ifconfig_path -a | grep -m 1 HWaddr | sed 's/ //g' | tail -18c`;
- chomp($mac);
-
- $passphrase = "$mac$uname";
+ # setup if it exists, the machine board name (not unique, but a somewhat limited set),
+ # and now also the root partition UUID, if it exists.
+
+ # The intent of the original design is not changed, i.e. this only protects remote/offline copies of the prefs file,
+ # but does not protect against compromised access to the machine checkgmail is running on.
+ # Instead of building/maintaining within checkgmail a passphrase unlocking mechanism, with length/pattern/strength constraints,
+ # it would make more sense to write integrations with other pwd mgt tools, as required, if KWallet is not used.
+
+ # ifconfig output has changed, and kernel updates are frequent...
+ # Therefore fixing/replacing the previous passphrase generation with hopefully more static identifiers.
+ # Note : Use of kwallet integration or similar is still highly recommended instead.
+
+ my $field_separator='-';
+
+ my $running_interfaces_macs=`ifconfig | grep -A4 RUNNING | grep ether | awk '{print \$2}' | xargs`;
+ chomp $running_interfaces_macs;
+
+ # using serial numbers from dmidecode would be nice, but reading them requires root access...
+ my $board_name_file="/sys/class/dmi/id/board_name";
+ my $board_name="";
+ if (-e $board_name_file) {
+ $board_name = `cat $board_name_file`;
+ $board_name = $field_separator.$board_name if($board_name ne "" && $running_interfaces_macs ne "");
+ }
+
+ my $root_partition_uuid="";
+ my $fstab = "/etc/fstab";
+ if (-e $fstab) {
+ # get the non-commented root partition UUID in fstab
+ $root_partition_uuid = `cat $fstab | grep ^UUID | awk '\$2 == "/"' | awk '{print \$1}' | sed -e 's/UUID=//g'`;
+ $root_partition_uuid = $field_separator.$root_partition_uuid if($root_partition_uuid ne "" && ($running_interfaces_macs ne "" || $board_name ne ""));
+ }
+
+ $passphrase = "_checkgmail_".$running_interfaces_macs . $board_name . $root_partition_uuid;
$passphrase =~ s/\s+//g;
+
}
@@ -176,6 +202,7 @@
BEGIN {
# A modular package checking routine ...
my $failed_packages;
+ my $please_download_error_message = "Please download and install from CPAN (http://search.cpan.org) or from your distribution";
my $eval_sub = sub {
print "$_\n" if $debug;
@@ -241,16 +268,24 @@
}
- print "\nCheckGmail requires the above packages to run\nPlease download and install from CPAN (http://search.cpan.org) and try again ...\n\n";
+ print "\nCheckGmail requires the above packages to run\n$please_download_error_message and try again ...\n\n";
exit 1;
}
- # Use kwallet if available
- if (`which kwallet 2>/dev/null`) {
- $usekwallet = 1;
+ # Name of the 3rd party CLI binary used to access kwallet. This needs to be installed separately for the kwallet integration to be enabled.
+ $kwalletcli="kwalletcli";
+ # Use kwallet if available. Expand detection as appropriate if other password management systems are integrated.
+ my $kwalletcli_path=`which $kwalletcli 2>/dev/null`;
+ chomp $kwalletcli_path;
+ if ($kwalletcli_path ne "") {
+ $use_kwallet = 1;
+ print "$kwalletcli has been found in the PATH ($kwalletcli_path), kwallet integration has been enabled...\n" unless $silent;
$nocrypt = 1;
+ } else {
+ $use_kwallet = 0;
+ print "$kwalletcli has not been found in the PATH, therefore kwallet integration will not be enabled...\n" unless $silent;
}
-
+
# optional packages for encryption
unless ($nocrypt) {
foreach (split("\n","
@@ -262,7 +297,7 @@
use MIME::Base64;
")) {&$eval_sub($_)};
if ($failed_packages) {
- print "\nCheckGmail requires the above packages for password encryption\nPlease download and install from CPAN (http://search.cpan.org) if you want to use this feature ...\n\n";
+ print "\nCheckGmail requires the above packages for password encryption\n$please_download_error_message if you want to use this feature ...\n\n";
$nocrypt = 1;
}
}
@@ -272,7 +307,7 @@
use Gtk2::Sexy;
")) {&$eval_sub($_)};
if (($failed_packages) && ($failed_packages =~ m/Sexy/i)) {
- print "\nCheckGmail uses Gtk2::Sexy for clickable URLs in mail messages\nPlease download and install from CPAN (http://search.cpan.org) if you want to use this feature ...\n\n";
+ print "\nCheckGmail uses Gtk2::Sexy for clickable URLs in mail messages\nn$please_download_error_message if you want to use this feature ...\n\n";
$libsexy = 0;
} else { $libsexy = 1 unless $notsexy; }
}
@@ -286,7 +321,7 @@
}
# Show big fat warning if Crypt::Simple not found ...
-if ($nocrypt && !$silent && !$usekwallet) {
+if ($nocrypt && !$silent && !$use_kwallet) {
print <<EOF;
*** Crypt::Simple not found, not working or disabled ***
*** Passwords will be saved in plain text only ... ***\n
@@ -348,18 +383,47 @@
my $gmail_address : shared;
my $user : shared;
my $passwd : shared;
+
+# two factor support (config, UI, integration), login refactoring (requests, UI), + additional refactoring/bugfixing added by @romainguinot on feb 23rd, 2013.
+# kwallet integration refactored by @romainguinot, based on the existing patch available at : http://sourceforge.net/tracker/?func=detail&aid=3175987&group_id=137480&atid=738663.
+
+# $web_passwd is your google main/'real' password, used to access e.g. the GMail web interface.
+my $web_passwd : shared;
+# $verification_code is the 2 factor PIN code, obtained through text message, a phone app, etc... This value will change at each verification so no point persisting it in the config.
+my $verification_code : shared;
+# cookie stored when machine is "trusted". using this cookie will mean prompting for a PIN code will not be required.
+my $two_factor_trust_cookie : shared;
+# preference setting value to enable or disable two-factor authentication. Can be set either in the preferences or with the command-line -two_factor switch.
+my $use_two_factor_auth_pref : shared;
+# $use_two_factor_auth will be the logical OR combination of preference setting and command line switch.
+my $use_two_factor_auth : shared;
+
+# variables for decrypted tokens, if local encryption is used
my $passwd_decrypt : shared;
+my $web_passwd_decrypt : shared;
+my $two_factor_trust_cookie_decrypt : shared;
+# flags indicating whether or not a particular token is saved / persisted
my $save_passwd : shared;
+my $save_web_passwd : shared;
+my $save_two_factor_trust_cookie : shared;
+
+# ui management variables
my $translations : shared;
my %trans : shared;
my $language : shared;
my $HOME : shared;
my $icons_dir : shared;
+
+# various gmail cookies
my $gmail_at : shared;
my $gmail_ik : shared;
my $gmail_hid : shared;
my $gmail_sid : shared;
my $gmail_gausr : shared;
+# SMSV is the cookie stored if you decide to "trust" this computer, i.e. not ask for the PIN code for a certain amount of time.
+my $gmail_smsv : shared;
+my $gmail_smsv_expiration_date : shared;
+
my $delay : shared;
my %label_delay : shared;
# my @labels : shared;
@@ -372,6 +436,19 @@
$escapes{chr($_)} = sprintf("%%%02X", $_);
}
+
+# This will be the "folder" in the default wallet where the passwords will be stored, if kwallet integration is used.
+# If a profile is specified, the folder name will be suffixed with "$profile" (already with a leading '-').
+my $kwallet_folder = "checkgmail" . $profile;
+# This will be either the general password, or the application-specific password (for the atom feed) if two-factor (a.k.a. two-step) authentication is used.
+my $kwallet_default_password_key = "gmailOrAppPassword";
+# This will be the main google account password, needed if 2-factor authentication is used.
+# See comments in http_check for more details about these passwords.
+my $kwallet_main_google_password_key = "mainGooglePassword";
+# trust cookie kwallet key
+my $kwallet_two_factor_trust_cookie_key = "twoFactorTrustCookie";
+
+
# Thread controls
my $request = new Thread::Queue;
my $request_results = new Thread::Queue;
@@ -380,11 +457,6 @@
my $fat_lady = new Thread::Semaphore(0);
my $child_exit : shared = 0; # to signal exit to child
-print "About to start new thread ...\n" if $debug;
-# Start http checking thread ...
-my $http_check = new threads(\&http_check);
-print "Parent: Process now continues ...\n" if $debug;
-
#######################
# Prefs and Variables
@@ -395,8 +467,13 @@
my %pref_variables = (
user => \$user,
passwd => \$passwd,
+ web_passwd => \$web_passwd,
+ two_factor_trust_cookie => \$two_factor_trust_cookie,
+ use_two_factor_auth => \$use_two_factor_auth_pref,
hosted => \$hosted,
save_passwd => \$save_passwd,
+ save_web_passwd => \$save_web_passwd,
+ save_two_factor_trust_cookie => \$save_two_factor_trust_cookie,
atomfeed_address => \$gmail_address,
language => \$language,
delay => \$delay,
@@ -422,6 +499,8 @@
$delay = 120000;
$popup_delay = 6000;
$save_passwd = 0;
+$save_web_passwd = 0;
+$save_two_factor_trust_cookie = 0;
$time_24 = 0;
$archive_as_read = 0;
$gmail_command = 'firefox %u';
@@ -430,12 +509,13 @@
# Global variables
$HOME = (getpwuid($<))[7];
my $gmail_web_address = "https://mail.google.com/mail";
+$gmail_address = gen_prefix_url()."/feed/atom";
+#$gmail_address = $hosted ? "mail.google.com/a/$hosted/feed/atom" : "mail.google.com/mail/feed/atom";
+
my $prefs_dir = "$HOME/.checkgmail";
$icons_dir = "$prefs_dir/attachment_icons";
my $prefs_file_nonxml = "$prefs_dir/prefs$profile";
my $prefs_file = "$prefs_file_nonxml.xml";
-$gmail_address = gen_prefix_url()."/feed/atom";
-# $gmail_address = $hosted ? "mail.google.com/a/$hosted/feed/atom" : "mail.google.com/mail/feed/atom";
# for every gmail action ...
my %gmail_act = (
@@ -463,6 +543,9 @@
my $status_label;
# my $message_flag;
+
+# global stuff initialised, starting child process...
+
print "Parent: Checking the existence of ~/.checkgmail ...\n" if $debug;
# Create the default .checkgmail directory and migrate prefs from users of older versions
unless (-d $prefs_dir) {
@@ -551,28 +634,32 @@
show_prefs();
}
-# kdewallet integration if present - thanks to Joechen Hoenicke for this ...
-if (($usekwallet) && ($save_passwd)) {
- $passwd = `kwallet -get checkgmail`;
- chomp $passwd;
-}
+# $use_two_factor_auth can be set either in the preferences or with the command-line -two_factor switch.
+$use_two_factor_auth = $use_two_factor_auth_pref || $two_factor_auth_switch ;
-# remove passwd from the pref_variables hash if the user requests it and prompt for login
-unless ($save_passwd && !$usekwallet) {
- delete $pref_variables{passwd};
- login($trans{login_title}) unless $passwd;
-}
+# get or set passwd and associated variables, as required.
+($save_passwd, $passwd, $passwd_decrypt) = handle_auth_token(\$save_passwd, "passwd", \$passwd,\$passwd_decrypt, $kwallet_default_password_key, \&login, $trans{login_title});
+
+# Prompt for the web password and PIN code, if both cookies and two-factor authentication are enabled.
+# If the trust cookie is persisted, no need to prompt for the PIN code.
+# If two-factor is not used, no need for either of those.
+# If cookies are not used, the password already provided will be either the app-specific password (which will work against the atom feed),
+# or the general password, which will work for both the atom feed and web UI.
+if ($cookies && $use_two_factor_auth) {
+
+ # get or set web_passwd and associated variables, as required.
+ ($save_web_passwd, $web_passwd, $web_passwd_decrypt) = handle_auth_token(\$save_web_passwd, "web_passwd", \$web_passwd,\$web_passwd_decrypt, $kwallet_main_google_password_key, \&get_web_passwd, undef);
+ # get or set two_factor_trust_cookie and associated variables, as required.
+ # We may have stored the SMSV "trust this computer" cookie. If that's the case, don't prompt for the verification code.
+ # Otherwise, now prompt for the PIN code. (The PIN code itself does not need to be persisted in any way).
+ ($save_two_factor_trust_cookie, $two_factor_trust_cookie, $two_factor_trust_cookie_decrypt) = handle_auth_token(\$save_two_factor_trust_cookie, "two_factor_trust_cookie", \$two_factor_trust_cookie, \$two_factor_trust_cookie_decrypt, $kwallet_two_factor_trust_cookie_key, \&get_verification_code, undef);
-# changing the passphrase causes Crypt::Simple to die horribly -
-# here we use an eval routine to catch this and ask the user to login again
-# - this will only happen if you change your network interface card or
-# recompile your kernel ...
-unless ((eval ('$passwd_decrypt = decrypt_real($passwd);'))) {
- login($trans{login_title});
}
-chomp($passwd_decrypt) if $passwd_decrypt;
+# update the preferences with regards to passwords / auth token storage.
+write_prefs();
+
# Continue building tray ...
if ($background) {
@@ -584,6 +671,18 @@
$tray->show_all;
print "Parent: System tray now complete ...\n" if $debug;
+
+# starting http_check thread...
+###############################
+
+
+print "About to start new thread ...\n" if $debug;
+# Start http checking thread ...
+my $http_check = new threads(\&http_check);
+print "Parent: Process now continues ...\n" if $debug;
+
+
+
############################
# enter/leave notification
#
@@ -711,6 +810,114 @@
# Subroutines start here ...
#
+# Convenience rountine to set the global vars attached to a particular password. It is expecting references for :
+# - $save_auth_token_flag_ref : the reference to the variable indicating whether or not that particular password is saved
+# - $auth_token_variable_name : the token variable name, to update the preferences if needed
+# - $auth_token_value_ref : the reference to the password variable
+# - $auth_token_decrypted_value_ref : the reference to the decrypted password variable, if kwallet integration is not used, and if local lencryption is used
+# - $kwallet_key : store the token under that key, in kwallet, if kwallet integration is in use
+# - $dialog_function : the dialog function to call, if prompting for the token is required
+# - $translation_key : the optional translation key to pass to the dialog function
+sub handle_auth_token {
+
+ #my ($pref_variables, $save_auth_token_flag_ref, $auth_token_variable_name, $auth_token_value_ref, $auth_token_decrypted_value_ref, $kwallet_key, $dialog_function, $translation_key) = @_;
+ my ($save_auth_token_flag_ref, $auth_token_variable_name, $auth_token_value_ref, $auth_token_decrypted_value_ref, $kwallet_key, $dialog_function, $translation_key) = @_;
+
+ # dereference and associate human readable variables to the pointed values
+ my $save_auth_token_flag = $$save_auth_token_flag_ref;
+ my $auth_token_value = $$auth_token_value_ref;
+ my $auth_token_decrypted_value = $$auth_token_decrypted_value_ref;
+
+ my $decryption_mismatch=0;
+
+ # kdewallet integration if present - thanks to Joechen Hoenicke for this ...
+ if ($use_kwallet && $save_auth_token_flag) {
+ $auth_token_decrypted_value = get_auth_token_from_kwallet($kwallet_key);
+ $auth_token_value = $auth_token_decrypted_value; # same if using kwallet, i.e. not native encryption
+ chomp $auth_token_value;
+ }
+
+ # attempt to decrypt the token, if it was stored in the prefs...
+ if (!$use_kwallet && (defined $auth_token_value)) {
+ # changing the token causes Crypt::Simple to die horribly -
+ # here we use an eval routine to catch this and prompt the user for the appropriate token, if required
+ # - this will only happen if you change one of the items used to generate the local encryption passphrase (and are not using kwallet) ...
+ # See the passphrase generation block at the beginning for more details.
+ unless (eval ('$auth_token_decrypted_value = decrypt_real($auth_token_value);')) {
+ $decryption_mismatch = 1;
+ print "decryption mismatch for '$auth_token_variable_name', changed encryption passphrase ? reprompting for this token...\n";
+ }
+ }
+
+ if (!$save_auth_token_flag || (!defined $auth_token_value) || $auth_token_value eq "" || $decryption_mismatch) {
+ $dialog_function->($translation_key);
+ # Dialog_function may have directly acted on the referenced values, reset the local dereferenced values for the next check.
+ # two_factor_trust_cookie value is not obtained here, so do not store it
+ $save_auth_token_flag = $$save_auth_token_flag_ref;
+ if ($auth_token_variable_name ne "two_factor_trust_cookie") {
+ $auth_token_value = $$auth_token_value_ref; # no risk of overwriting the value set above, accessing here means it was empty above.
+ $auth_token_decrypted_value = $$auth_token_decrypted_value_ref;
+ }
+ }
+
+ chomp($auth_token_decrypted_value) if $auth_token_decrypted_value;
+ # remove $auth_token_variable_name from the pref_variables hash if the user requests it and call $dialog_function to prompt for it.
+ handle_auth_token_preference($save_auth_token_flag, $auth_token_variable_name, $auth_token_value);
+
+ return ($save_auth_token_flag, $auth_token_value, $auth_token_decrypted_value);
+
+}
+
+# for a given auth token item, add it to the prefs hash, or delete it from the prefs hash, depending on whether or not this auth token needs to be saved,
+# and whether or not kwallet integration is used.
+#
+# $save_auth_token_password_flag : does the auth token need to be saved (e.g. $save_passwd)
+# $prefs_item_name : preference hash item name
+# $prefs_item_value : preference hash item value
+sub handle_auth_token_preference {
+
+ my $save_auth_token_password_flag = shift,
+ my $prefs_item_name = shift;
+ my $prefs_item_value = shift;
+
+ if ($save_auth_token_password_flag && !$use_kwallet) {
+ $pref_variables{$prefs_item_name}=\$prefs_item_value;
+ } else {
+ delete $pref_variables{$prefs_item_name};
+ }
+}
+
+
+# store a key/value pair (entry,auth token) in the default kwallet, $kwallet_folder folder.
+sub store_auth_token_in_kwallet {
+
+ my ($auth_token_key_to_store, $auth_token_value_to_store) = @_;
+ if( (!defined $auth_token_value_to_store) || $auth_token_value_to_store eq "") {
+ my $empty_auth_token_value_error_msg="refusing to store empty auth token value in the wallet for key '$auth_token_key_to_store'.";
+ print $empty_auth_token_value_error_msg."\n";
+ return $empty_auth_token_value_error_msg;
+ }
+
+ open KWALLET, "|$kwalletcli -f $kwallet_folder -e $auth_token_key_to_store -P";
+ print KWALLET "$auth_token_value_to_store\n";
+ close KWALLET;
+
+}
+
+# get an auth token for a given key from the default kwallet, $kwallet_folder folder.
+sub get_auth_token_from_kwallet {
+
+ my ($auth_token_key_to_get) = @_;
+
+ my $returned_auth_token = `$kwalletcli -f $kwallet_folder -e $auth_token_key_to_get`;
+ chomp $returned_auth_token;
+
+ return $returned_auth_token;
+
+}
+
+
+
####################
# Checking thread
@@ -734,6 +941,11 @@
# set up the useragent ....
$ua = LWP::UserAgent->new();
+ # some debug handlers for the traffic, uncomment as required.
+ #$ua->add_handler("request_send", sub { shift->dump; return });
+ #$ua->add_handler("response_header", sub { shift->dump; return });
+ #$ua->add_handler("response_done", sub { shift->dump; return });
+
$ua->requests_redirectable (['GET', 'HEAD', 'POST']);
# push @{ $ua->requests_redirectable }, 'POST';
@@ -759,7 +971,15 @@
$http_status->enqueue($trans{notify_login});
my $URI_user = URI_escape($user);
- my $URI_passwd = URI_escape($passwd_decrypt);
+ my $URI_passwd;
+ # The script simulates a browser login to get the cookies required to interact with the inbox (e.g. mark as read etc...).
+ # Therefore, if two-factor authentication is used, the web password _must_ be used here.
+ # Otherwise the general password is used if two-factor is not used.
+ if ($use_two_factor_auth) {
+ $URI_passwd = URI_escape($web_passwd_decrypt);
+ } else {
+ $URI_passwd = URI_escape($passwd_decrypt);
+ }
# clumsy error detection code uses this variable to differentiate between unable to
# connect and unable to login - the Gmail login provides no unauthorised code if unsuccessful
@@ -767,38 +987,16 @@
# Thanks to that wonderful Firefox extension LiveHTTPHeaders for
# deciphering the login form! :)
- unless ($hosted) {
- # Normal Gmail login action ...
- $error = http_get("Email=$URI_user&Passwd=$URI_passwd", "LOGIN");
-
- # $cookie_jar->scan(\&scan_at);
- # unless ($error || !$gmail_sid || !$gmail_gausr) {
- # $error = http_get("https://mail.google.com/mail/?pli=1&auth=$gmail_sid&gausr=$gmail_gausr", 'LOGIN');
- # }
-
- # $error = http_get("https://mail.google.com/mail?nsr=0&auth=$gmail_sid&gausr=$gmail_gausr", "LOGIN");
-
- } else {
- # hosted domains work differently ...
- # First we POST a login
- # $error = http_get("https://www.google.com/a/$hosted/LoginAction|at=null&continue=http%3A%2F%2Fmail.google.com%2Fa%2F$hosted&service=mail&userName=$URI_user&password=$URI_passwd", "POST");
- # thanks to Olinto Neto for this fix for hosted domains:
- $error = http_get("https://www.google.com/a/$hosted/LoginAction2|at=null&continue=http%3A%2F%2Fmail.google.com%2Fa%2F$hosted&service=mail&Email=$URI_user&Passwd=$URI_passwd", "POST");
-
- # Then we grab the HID ("Hosted ID"?) cookie
- $cookie_jar->scan(\&scan_at);
-
- # And now we login with that cookie, which will give us the GMAIL_AT cookie!
- unless ($error || !$gmail_hid) {
- $error = http_get("https://mail.google.com/a/$hosted?AuthEventSource=Internal&auth=$gmail_hid", 'GET');
- }
- }
+
+ # now using the same call for both hosted and non-hosted accounts. the small differences between the two will be handled there.
+ $error = http_get("Email=$URI_user&Passwd=$URI_passwd", "LOGIN");
$cookie_jar->scan(\&scan_at);
unless ($gmail_at) {
unless ($error) {
- $http_status->enqueue("Error: 401 Unauthorised");
+ # in reality a code-thrown 401 error...
+ $http_status->enqueue("Error : 401 Unauthorized");
$error_block->down;
} else {
# simple block to prevent checkgmail hogging CPU if not connected!
@@ -810,8 +1008,31 @@
}
print "Logged in ... AT = $gmail_at\n" unless $silent;
+
+ # persist smsv cookie if required
+ #################################
+ $two_factor_trust_cookie_decrypt = $gmail_smsv;
+
+ # If kwallet integration is available, and password storage is requested, store it in KWallet. Otherwise encrypt it locally and save it in the preferences.
+ if ($save_two_factor_trust_cookie) {
+ if ((defined $two_factor_trust_cookie_decrypt) && $two_factor_trust_cookie_decrypt ne "") {
+ if ($use_kwallet) {
+ store_auth_token_in_kwallet($kwallet_two_factor_trust_cookie_key, $two_factor_trust_cookie_decrypt);
+ } else {
+ $two_factor_trust_cookie = encrypt_real($two_factor_trust_cookie_decrypt);
+ }
+ }
+
+ handle_auth_token_preference($save_two_factor_trust_cookie, "two_factor_trust_cookie", $two_factor_trust_cookie);
+
+ if ($save_two_factor_trust_cookie) {
+ write_prefs();
+ }
+ }
}
+ print "http_check : Entering while loop...\n" unless $silent;
+
while ((my $address = $request->dequeue) && ($child_exit==0)) {
# this is a clumsy hack to allow POST methods to do things like mark messages as spam using the same queue
# (can't send anonymous arrays down a queue, unfortunately!)
@@ -819,14 +1040,23 @@
my ($method, $address_real, $label) = ($address =~ /(.*?):([^\s]*)\s*(.*)/);
my $logon_string = "";
- unless ($cookies) {
+ # The following credentials are used for the atom feed login :
+ # - If two-factor authentication is used, the password used here must be the application-specific password.
+ # - If two-factor authentication is not used, this is the same, general, password as the gmail web UI password.
+ # In both cases (app-specific or general), this is the first password prompted for, and this is also the one that may be stored and optionally encrypted.
+ # The logon string below is therefore required if cookies are not used, or if two factor is used.
+ if (!$cookies || $use_two_factor_auth) {
my $URI_user = URI_escape($user);
- my $URI_passwd = URI_escape($passwd_decrypt);
+ my $URI_passwd = URI_escape($passwd_decrypt);
$logon_string = "$URI_user:$URI_passwd\@";
+ print "http_check : set logon_string to '$user:***'.\n" unless $silent;
+ } else {
+ print "http_check : cookies are used, and two factor is not used, therefore logon_string not required for the atom feed...\n" unless $silent;
}
- $request_results->enqueue(http_get("https://$logon_string$address_real", $method, $label))
- }
+ $request_results->enqueue(http_get("https://$logon_string$address_real", $method, $label));
+
+ }
}
@@ -834,9 +1064,9 @@
my $cookie_ref = \@_;
unless ($silent) {
- # use Data::Dumper;
- # print Dumper(\@_);
- print "Saved cookie: ",$cookie_ref->[1],"\n",$cookie_ref->[2],"\n\n";
+ #use Data::Dumper;
+ #print Dumper(\@_);
+ print "scan_at : Saved cookie: ",$cookie_ref->[1],"\n",$cookie_ref->[2],"\n\n";
}
# This sub is invoked for each cookie in the cookie jar.
@@ -859,6 +1089,11 @@
if ($cookie_ref->[1] =~ m/SID/) {
$gmail_sid = $cookie_ref->[2];
}
+
+ if ($cookie_ref->[1] =~ m/SMSV/) {
+ $gmail_smsv = $cookie_ref->[2];
+ $gmail_smsv_expiration_date = $cookie_ref->[8];
+ }
}
@@ -903,51 +1138,89 @@
# Well, we did get a URL here, but it doesn't make any sense to send both LOGIN and the URL to this function.
# So, this URL is just the username and password addition.
- my $req = HTTP::Request->new('GET' => "https://www.google.com/accounts/ServiceLogin?service=mail");
- my $response = $ua->request($req);
- if($response->is_error) {
- my $code = $response->code;
- my $message = $response->message;
- $error = "Error: $code $message";
- $http_status->enqueue($error);
- return $error;
+
+ print "http_get : entering LOGIN...\n" unless $silent;
+
+ # start address
+ ###############
+
+ my $start_address;
+ # different start address depending on whether or not a hosted domain is used.
+ if (!$hosted) {
+ $start_address = "https://accounts.google.com/ServiceLogin?service=mail";
+ } else {
+ $start_address = "https://mail.google.com/a/$hosted/"
}
- my $http = $response->content;
+
+ my $start_req = HTTP::Request->new('GET' => $start_address);
+
+
+ my $start_response = $ua->request($start_req);
+ if ($start_response->is_error) {
+ (my $code, $error) = format_error_message($start_response);
+ $http_status->enqueue($error);
+ return $error;
+ }
+ my $start_response_content = $start_response->content;
# Find the value of the GALX input field
- my ($post_galx) = ($http =~ m/"GALX".*?value="(.*?)"/ismg);
+ my ($post_galx) = ($start_response_content =~ m/"GALX".*?value="(.*?)"/ismg);
unless ($post_galx) {
- print "Error: No GALX input field found\n";
- return "Error: No GALX input field found";
+ my $galx_error_msg = "Error: No GALX input field found\n";
+ print $galx_error_msg;
+ return $galx_error_msg;
}
- $post_galx = URI_escape(URI_unescape($1));
-
+ my $galx = URI_unescape($1);
+ $post_galx = URI_escape($galx);
+
+ print "http_get : got GALX : '$post_galx'\n" unless $silent;
+
+ # post authentication data
+ ##########################
+
# Find the data to post
my $post_data;
- $post_data = "ltmpl=default&ltmplcache=2&continue=http://mail.google.com/mail/?ui%3Dhtml&service=mail&rm=false&scc=1&GALX=$post_galx&$address&PersistentCookie=yes&rmShown=1&signIn=Sign+in&asts=";
+
+ my $post_data_address;
+ my $continue_address;
+
+ # central location to POST authentication data. works with non-hosted and hosted accounts.
+ $post_data_address="https://accounts.google.com/ServiceLoginAuth?service=mail";
+
+ # go to a different address if using a hosted account or not...
+ if (!$hosted) {
+ $continue_address="https://mail.google.com/mail/";
+ } else {
+ $continue_address="https://mail.google.com/a/$hosted/";
+ }
+
+ $post_data = "ltmpl=default&ltmplcache=2&continue=$continue_address?ui=html&service=mail&rm=false&scc=1&GALX=$post_galx&$address&PersistentCookie=yes&rmShown=1&signIn=Sign+in&asts=";
+ # be aware that $address is badly named, as it may actually be the username/password couple...
# Hide personal data from verbose display
my $post_display = $post_data;
- $post_display =~ s/Email=(.*?)&/Email=******/;
- $post_display =~ s/Passwd=(.*?)&/Passwd=******/;
- print "Logging in with post data $post_display\n" unless $silent;
+ $post_display =~ s/Email=(.*?)&/Email=******&/;
+ $post_display =~ s/Passwd=(.*?)&/Passwd=******&/;
+
+ print "http_get : Logging in with post data $post_display\n" unless $silent;
# Send the post data to the login URL
- my $post_req = HTTP::Request->new('POST' => "https://www.google.com/accounts/ServiceLoginAuth?service=mail");
+ #my $post_req = HTTP::Request->new('POST' => "https://accounts.google.com/ServiceLoginAuth?service=mail");
+ my $post_req = HTTP::Request->new('POST' => $post_data_address);
$post_req->content_type('application/x-www-form-urlencoded');
$post_req->content($post_data);
+ $post_req->header('Cookie' => "GALX=$galx");
+
my $post_response = $ua->request($post_req);
- if ($post_response->is_error) {
- my $code = $response->code;
- my $message = $response->message;
- $error = "Error: $code $message";
- $http_status->enqueue($error);
- return $error;
- }
- my $post_http = $post_response->content;
+ if ($post_response->is_error) {
+ (my $code, $error) = format_error_message($post_response);
+ $http_status->enqueue($error);
+ return $error;
+ }
+ my $post_response_content = $post_response->content;
# Find the location we're directed to, if any
- if ($post_http =~ m/location\.replace\("(.*)"\)/) {
+ if ($post_response_content =~ m/location\.replace\("(.*)"\)/) {
# Rewrite the redirect URI.
# This URI uses \xXX. Replace those, and just to be sure \\. \" we don't handle, though.
my $redirect_address = $1;
@@ -956,41 +1229,99 @@
print "Redirecting to ".$redirect_address."\n" unless $silent;
# And request the actual URL
- my $req = HTTP::Request->new('GET' => $redirect_address);
- my $response = $ua->request($req);
- if($response->is_error) {
- my $code = $response->code;
- my $message = $response->message;
- $error = "Error: $code $message";
- $http_status->enqueue($error);
- return $error;
- }
+ my $redirect_req = HTTP::Request->new('GET' => $redirect_address);
+ my $redirect_response = $ua->request($redirect_req);
+ if ($redirect_response->is_error) {
+ (my $code, $error) = format_error_message($redirect_response);
+ $http_status->enqueue($error);
+ return $error;
+ }
} else {
print "No location.replace found in HTML\n" unless $silent;
}
+ # if two-factor authentication is used, get the smsToken value, obtained after a successful 2 factor PIN code verification.
+ #######################################
+
+ my $two_factor_post_response_content;
+ #if ($use_two_factor_auth && !$save_two_factor_trust_cookie) {
+ #if ($use_two_factor_auth) {
+ if ($use_two_factor_auth) {
+
+ # first attempt to access gmail, needed to trigger the validation check
+ my $initial_target_req = HTTP::Request->new('GET' => "https://mail.google.com/mail/?shva=1");
+
+ my $initial_target_response = $ua->request($initial_target_req);
+ if ($initial_target_response->is_error) {
+ (my $code, $error) = format_error_message($initial_target_response);
+ $http_status->enqueue($error);
+ return $error;
+ }
+ my $initial_target_response_content = $initial_target_response->content;
+
+ if (!$save_two_factor_trust_cookie || ($save_two_factor_trust_cookie && !$two_factor_trust_cookie_decrypt)) {
+ print "http_get : two_factor : getting smsToken value..." unless $silent;
+ # Could in theory ask for the pin code here, but testing showed that receiving texts in this context was unreliable, and would sometimes hit captchas / bot-prevention
+ # mechanisms. It is therefore recommended to use the google authenticator app on a smartphone to get a code without relying on receiving a text.
+ # Waiting for the text message to enter the code in the dialog is not either the best user experience...
+ #get_verification_code();
+
+ my $sleep_delay = 2; # a small sleep is sometimes required before making this next POST (pin code), and after returning from the previous get...
+ print "sleeping $sleep_delay sec(s) before calling two_factor_check...\n" unless $silent;
+ sleep $sleep_delay;
+ my $extracted_smsToken = two_factor_check();
+ print "called two_factor_check, obtained : '$extracted_smsToken'\n" unless $silent;
+
+ $post_data = $post_data . "&" . $extracted_smsToken;
+ }
+
+ #Send the post data to the login URL a second time, now with the SMS token or the SMSV cookie, if it was saved
+ my $two_factor_post_req = HTTP::Request->new('POST' => "https://accounts.google.com/ServiceLoginAuth?service=mail");
+ $post_req->content_type('application/x-www-form-urlencoded');
+ $post_req->content($post_data);
+ $post_req->header('Cookie' => "GALX=$galx");
+
+ if ($save_two_factor_trust_cookie && $two_factor_trust_cookie_decrypt) {
+ print "http_get : adding SMSV cookie to bypass PIN code...\n" unless $silent;
+ $post_req->header('Cookie' => "SMSV=$two_factor_trust_cookie_decrypt");
+ }
+
+ my $two_factor_post_response = $ua->request($post_req);
+ if ($two_factor_post_response->is_error) {
+ (my $code, $error) = format_error_message($two_factor_post_response);
+ $http_status->enqueue($error);
+ return $error;
+ }
+
+ $two_factor_post_response_content = $two_factor_post_response->content;
+ }
+
+ # now go to the web UI
+ ##########################
+
+ print "http_get : accessing gmail...\n" unless $silent;
# Last part of the login process, in order to get Gmail's globar variables
# (including the all-important ik parameter)
- $req = HTTP::Request->new('GET' => "https://mail.google.com/mail/?shva=1");
- $response = $ua->request($req);
- if($response->is_error) {
- my $code = $response->code;
- my $message = $response->message;
- $error = "Error: $code $message";
- $http_status->enqueue($error);
- return $error;
- }
- $http = $response->content;
-
+ my $target_req = HTTP::Request->new('GET' => "https://mail.google.com/mail/?shva=1");
+
+ my $target_response = $ua->request($target_req);
+ if ($target_response->is_error) {
+ (my $code, $error) = format_error_message($target_response);
+ $http_status->enqueue($error);
+ return $error;
+ }
+ my $target_response_content = $target_response->content;
+
# The globals. Lots of goodies here, which we don't need right now
# but which might come in handy later ...
- if ($http =~ m/var GLOBALS=\[,.*?,.*?,.*?,.*?,.*?,.*?,.*?,.*?,"(.*?)"/) {
+ if ($target_response_content =~ m/var GLOBALS=\[,.*?,.*?,.*?,.*?,.*?,.*?,.*?,.*?,"(.*?)"/) {
$gmail_ik = $1;
+ print "Found gmail_ik:'$gmail_ik'\n\n" unless $silent;
} else {
- print "Unable to find gmail_ik ... full message text won't work :(\n\n";
+ print "Failed login or unable to find gmail_ik ... full message text won't work :(\n\n";
}
-
+ # return the error if there was one, otherwise return null
return $error;
} elsif ($method eq 'LOGOUT') {
@@ -1026,14 +1357,12 @@
$http_status->enqueue($trans{notify_check});
-
+
my $req = HTTP::Request->new($method => "$address$label");
my $response = $ua->request($req);
if ($response->is_error) {
- my $code = $response->code;
- my $message = $response->message;
- $error = "Error: $code $message";
+ (my $code, $error) = format_error_message($response);
$http_status->enqueue($error);
# Incorrect username/password??
@@ -1055,6 +1384,50 @@
}
+sub format_error_message {
+
+ my ($http_request_response) = @_;
+
+ my $code = $http_request_response->code;
+ my $message = $http_request_response->message;
+ my $error_message = "Error: $code $message";
+
+ return ($code, $error_message);
+}
+
+# The HTTP POST required after web login, to provide the PIN validation code obtained either by text message, Google authenticator on Android / iOS, etc...
+# Extracts and returns the smsToken value obtained after successful validation.
+sub two_factor_check
+{
+ print "verification_code:'$verification_code'\n" unless $silent;
+
+ # headers here were obtained with charles proxy from http://www.charlesproxy.com/.
+ my $pin_post_data = "smsUserPin=$verification_code&smsVerifyPin=Verify&PersistentCookie=yes";
+ my $twofactor_req = HTTP::Request->new('POST' => "https://accounts.google.com/SmsAuth?continue=https%3A%2F%2Fmail.google.com%2Fmail%2F&hl=en&service=mail&ss=1&ltmpl=default");
+ $twofactor_req->content_type('application/x-www-form-urlencoded');
+ $twofactor_req->content($pin_post_data);
+
+ my $twofactor_response = $ua->request($twofactor_req);
+
+ if ($twofactor_response->is_error) {
+ my ($code, $error) = format_error_message($twofactor_response);
+ my $status = $http_status->enqueue($error);
+ return $error;
+ }
+
+ my $twofactor_response_content = $twofactor_response->content;
+ my ($post_smsToken) = ($twofactor_response_content =~ m/name="smsToken" value="(.*?)"/ismg);
+
+ unless ($post_smsToken) {
+ my $post_smsToken_error_msg = "Error: No SMS token response field found\n";
+ print $post_smsToken_error_msg;
+ return $post_smsToken_error_msg;
+ }
+
+ return "smsToken=$post_smsToken";
+}
+
+
############################
# Main thread checking ...
#
@@ -1071,8 +1444,20 @@
$image->set_from_pixbuf($error_pixbuf);
if ($status =~ m/401/) {
+ # Here there is a choice to make : either not reprompt for currently stored tokens (assuming a mistyped password, if it isn't stored),
+ # or reprompt for all of them in case a password or token has changed. Choosing/keeping the latter, for now.
+
# Unauthorised error
- login("Error: Incorrect username or password");
+ login($trans{login_err});
+
+ # Prompt _again_ for the web password and PIN code, if both cookies and two-factor authentication are enabled.
+ # If two-factor is not used, no need for either of those.
+ # If cookies are not used, the password already provided will be either the app-specific password (which will work against the atom feed),
+ # or the general password, which will work for both the atom feed and web UI.
+ if ($cookies && $use_two_factor_auth) {
+ get_web_passwd(); # prompt for the web password, currently it is not persisted, as opposed to the other password.
+ get_verification_code(); # prompt for the PIN code
+ }
Gtk2->main_iteration while (Gtk2->events_pending);
# queue a new request to check mail
@@ -1088,7 +1473,9 @@
# Return if there aren't any Atom feeds in the queue ...
return 1 unless my $atom = $request_results->dequeue_nb;
- if ($atom =~ m/while\(1\);/) {
+ #if ($atom =~ m/while\(1\);/) { # datapack has changd, adjust the regexp as this no longer works...
+ if ($atom =~ m/^\)]}'/m) {
+
# datapack shortcircuit
# we use this to grab the full text of messages ...
@@ -1123,14 +1510,17 @@
}
}
+ # Note that apparently the message text is currently not available in the datapack for hosted domains...
+ $mb = "" if (!$mb);
$mb = clean_text_body($mb);
print "cleaned text is\n$mb\n\n" unless $silent;
-
# cs is the message id
my ($cs) = ($atom =~ m/.*\["ms","(.*?)"/s);
- $messages_ext{$cs}->{text} = $mb;
- $messages_ext{$cs}->{shown} = 1;
- $messages_ext{$cs}->{attachment} = $ma;
+ if($cs) {
+ $messages_ext{$cs}->{text} = $mb;
+ $messages_ext{$cs}->{shown} = 1;
+ $messages_ext{$cs}->{attachment} = $ma;
+ }
notify();
@@ -1260,6 +1650,11 @@
# Note -- for some reason (?? why ??) the title does not need decoding; all other data apparently does. Very strange ...
sub clean_text_and_decode {
($_) = @_;
+
+ if(!defined $_ || $_ eq "") {
+ return $_;
+ }
+
# some basic replacements so that the text is readable ...
# (these aren't used by pango markup, unlike other HTML escapes)
s/&hellip;/\.\.\./g;
@@ -1286,6 +1681,11 @@
sub clean_text {
($_) = @_;
+
+ if(!defined $_ || $_ eq "") {
+ return $_;
+ }
+
# some basic replacements so that the text is readable ...
# (these aren't used by pango markup, unlike other HTML escapes)
s/&hellip;/\.\.\./g;
@@ -1304,6 +1704,11 @@
sub clean_text_body {
($_) = @_;
+
+ if(!defined $_ || $_ eq "") {
+ return $_;
+ }
+
# some basic replacements so that the text is readable ...
# (these aren't used by pango markup, unlike other HTML escapes)
s/&hellip;/\.\.\./g;
@@ -1396,8 +1801,13 @@
# lock shared variables
lock($user);
lock($passwd);
+ lock($web_passwd);
+ lock($two_factor_trust_cookie);
lock($save_passwd);
+ lock($save_web_passwd);
+ lock($save_two_factor_trust_cookie);
lock($gmail_address);
+ lock($use_two_factor_auth_pref);
while (<PREFS>) {
s/[\n\r]//g;
@@ -1417,9 +1827,14 @@
# lock shared variables
lock($user);
lock($passwd);
+ lock($web_passwd);
+ lock($two_factor_trust_cookie);
lock($save_passwd);
+ lock($save_web_passwd);
+ lock($save_two_factor_trust_cookie);
lock($gmail_address);
-
+ lock($use_two_factor_auth_pref);
+
my $prefs_xml = XMLin($prefs, ForceArray => 1);
## For debugging ...
@@ -1459,8 +1874,13 @@
# lock shared variables
lock($user);
lock($passwd);
+ lock($web_passwd);
+ lock($two_factor_trust_cookie);
lock($save_passwd);
+ lock($save_web_passwd);
+ lock($save_two_factor_trust_cookie);
lock($gmail_address);
+ lock($use_two_factor_auth_pref);
convert_labels_from_array();
@@ -1476,7 +1896,7 @@
$xml_out{$i} = {%$pointer}; # if defined(%$pointer);
last };
}
-
+
}
my $prefs = XMLout(\%xml_out, AttrIndent=>1);
@@ -2012,7 +2432,7 @@
s/@/%40/g; # @ needs to be escaped in gmail address
s/http([^s])/https$1/g; # let's make it secure!
- print "login command is: $_\n" unless $silent;
+ print "get_login_href : login command is: $_\n" unless $silent;
my $escaped_uri = URI_escape($_);
my $URI_user = URI_escape($user);
@@ -2022,7 +2442,7 @@
if ($hosted) {
$target_uri = "http://mail.google.com/a/$hosted/$options_uri";
} else {
- $target_uri = "https://www.google.com/accounts/ServiceLoginAuth?ltmpl=yj_wsad&ltmplcache=2&continue=$escaped_uri&service=mail&rm=false&ltmpl=yj_wsad&Email=$URI_user&Passwd=$URI_passwd&rmShown=1&null=Sign+in";
+ $target_uri = "https://accounts.google.com/ServiceLoginAuth?ltmpl=yj_wsad&ltmplcache=2&continue=$escaped_uri&service=mail&rm=false&ltmpl=yj_wsad&Email=$URI_user&Passwd=$URI_passwd&rmShown=1&null=Sign+in";
}
return $target_uri;
@@ -2441,12 +2861,12 @@
# The preferences dialogue ...
# Yes, I know, I know - it's getting seriously ugly, isn't it?? :)
- my $dialog = Gtk2::Dialog->new ($trans{prefs}, undef,
+ my $dialog = Gtk2::Dialog->new ($trans{prefs}, undef,
'destroy-with-parent',
'gtk-ok' => 'ok',
'gtk-cancel' => 'cancel',
- );
-
+ );
+
my $hbox = Gtk2::HBox->new (0, 0);
$hbox->set_border_width (4);
$dialog->vbox->pack_start ($hbox, 0, 0, 0);
@@ -2454,48 +2874,109 @@
my $vbox = Gtk2::VBox->new (0, 4);
$hbox->pack_start ($vbox, 0, 0, 4);
- my $frame_login = Gtk2::Frame->new ("$trans{prefs_login}");
+ my $frame_login = Gtk2::Frame->new ("$trans{prefs_login}");
$vbox->pack_start ($frame_login, 0, 0, 4);
-
+
my $table_login = Gtk2::Table->new (2, 3, 0);
$table_login->set_row_spacings (4);
$table_login->set_col_spacings (4);
- $table_login->set_border_width (5);
+ $table_login->set_border_width (5);
$frame_login->add($table_login);
- my $label_user = Gtk2::Label->new_with_mnemonic ($trans{prefs_login_user});
- $label_user->set_alignment (0, 0.5);
+ my $label_user = Gtk2::Label->new_with_mnemonic ($trans{prefs_login_user});
+ $label_user->set_alignment (0, 0.5);
$table_login->attach_defaults ($label_user, 0, 1, 0, 1);
-
- my $entry_user = Gtk2::Entry->new;
- $entry_user->set_width_chars(15);
- $entry_user->append_text($user) if $user;
+
+ my $entry_user = Gtk2::Entry->new;
+ $entry_user->set_width_chars(15);
+ $entry_user->append_text($user) if $user;
$table_login->attach_defaults ($entry_user, 1, 2, 0, 1);
$label_user->set_mnemonic_widget ($entry_user);
-
- my $label_pwd = Gtk2::Label->new_with_mnemonic ($trans{prefs_login_pass});
- $label_pwd->set_alignment (0, 0.5);
- $table_login->attach_defaults ($label_pwd, 0, 1, 1, 2);
-
- my $entry_pwd = Gtk2::Entry->new;
- $entry_pwd->set_width_chars(15);
- $entry_pwd->set_invisible_char('*');
- $entry_pwd->set_visibility(0);
- $entry_pwd->append_text($passwd_decrypt) if $passwd_decrypt;
- $table_login->attach_defaults ($entry_pwd, 1, 2, 1, 2);
- $label_pwd->set_mnemonic_widget ($entry_pwd);
- $entry_pwd->signal_connect(activate=>sub {$dialog->response('ok')});
+
+ my $pwd_label_text = $use_two_factor_auth ? $trans{prefs_login_pass}." (".$trans{two_factor_main_google_password_app_label}.")" : $trans{prefs_login_pass};
+ my $label_pwd = Gtk2::Label->new_with_mnemonic ($pwd_label_text);
+ $label_pwd->set_alignment (0, 0.5);
+ $table_login->attach_defaults ($label_pwd, 0, 1, 1, 2);
+
+ my $entry_pwd = Gtk2::Entry->new;
+ $entry_pwd->set_width_chars(15);
+ $entry_pwd->set_invisible_char('*');
+ $entry_pwd->set_visibility(0);
+ $entry_pwd->append_text($passwd_decrypt) if ($passwd_decrypt && $save_passwd);
+ $table_login->attach_defaults ($entry_pwd, 1, 2, 1, 2);
+ $label_pwd->set_mnemonic_widget ($entry_pwd);
+ $entry_pwd->signal_connect(activate=>sub {$dialog->response('ok')});
my $button_passwd = Gtk2::CheckButton->new_with_label($trans{prefs_login_save});
- $table_login->attach_defaults($button_passwd, 0, 2, 2, 3 );
- $button_passwd->set_active(1) if ($save_passwd);
- $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_plain})") if ($nocrypt && !$usekwallet);
- $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_kwallet})") if ($usekwallet);
- $button_passwd->signal_connect(toggled=>sub {
- $save_passwd = ($button_passwd->get_active) ? 1 : 0;
- }
- );
+ $table_login->attach_defaults($button_passwd, 0, 2, 2, 3 );
+ $button_passwd->set_active(1) if ($save_passwd);
+ $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_plain})") if ($nocrypt && !$use_kwallet);
+ $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_kwallet})") if ($use_kwallet);
+ $button_passwd->signal_connect(toggled=>sub {
+ $save_passwd = ($button_passwd->get_active) ? 1 : 0;
+ }
+ );
+
+
+ # two factor authentication preferences
+ my $frame_two_factor = Gtk2::Frame->new ("$trans{prefs_two_factor}");
+ $vbox->pack_start ($frame_two_factor, 0, 0, 4);
+
+ my $table_two_factor = Gtk2::Table->new (2, 3, 0);
+ $table_two_factor->set_row_spacings (4);
+ $table_two_factor->set_col_spacings (4);
+ $table_two_factor->set_border_width (5);
+
+ $frame_two_factor->add($table_two_factor);
+
+ # label : web password
+ my $label_web_password_two_factor = Gtk2::Label->new_with_mnemonic ($trans{prefs_two_factor_main_google_password});
+ $label_web_password_two_factor->set_alignment (0, 0.5);
+ $table_two_factor->attach_defaults ($label_web_password_two_factor, 0, 1, 0, 1);
+
+ # web password text field
+ my $entry_web_pwd_two_factor = Gtk2::Entry->new;
+ $entry_web_pwd_two_factor->set_width_chars(15);
+ $entry_web_pwd_two_factor->set_invisible_char('*');
+ $entry_web_pwd_two_factor->set_visibility(0);
+ $entry_web_pwd_two_factor->append_text($web_passwd_decrypt) if ($web_passwd_decrypt && $save_web_passwd);
+ $table_two_factor->attach_defaults ($entry_web_pwd_two_factor, 1, 2, 0, 1);
+ $label_web_password_two_factor->set_mnemonic_widget ($entry_web_pwd_two_factor);
+ $entry_web_pwd_two_factor->signal_connect(activate=>sub {$dialog->response('ok')});
+
+
+ # enable 2-factor authentication
+ my $button_enable_two_factor = Gtk2::CheckButton->new_with_label($trans{prefs_two_factor_enable});
+ $table_two_factor->attach_defaults($button_enable_two_factor, 0, 1, 1, 2 );
+ $button_enable_two_factor->set_active(1) if ($use_two_factor_auth_pref);
+ $button_enable_two_factor->signal_connect(toggled=>sub {
+ $use_two_factor_auth_pref = ($button_enable_two_factor->get_active) ? 1 : 0;
+ }
+ );
+
+ # save web password
+ my $button_two_factor_save_web_passwd = Gtk2::CheckButton->new_with_label($trans{prefs_two_factor_save_main_google_password});
+ $table_two_factor->attach_defaults($button_two_factor_save_web_passwd, 0, 2, 2, 3 );
+ $button_two_factor_save_web_passwd->set_active(1) if ($save_web_passwd);
+
+ $button_two_factor_save_web_passwd->set_label("$trans{prefs_two_factor_save_main_google_password} ($trans{prefs_login_save_plain})") if ($nocrypt && !$use_kwallet);
+ $button_two_factor_save_web_passwd->set_label("$trans{prefs_two_factor_save_main_google_password} ($trans{prefs_login_save_kwallet})") if ($use_kwallet);
+
+ $button_two_factor_save_web_passwd->signal_connect(toggled=>sub {
+ $save_web_passwd = ($button_two_factor_save_web_passwd->get_active) ? 1 : 0;
+ }
+ );
+
+ # save cookie / trust machine
+ my $button_two_factor_save_trust_cookie = Gtk2::CheckButton->new_with_label($trans{prefs_two_factor_save_trust_cookie});
+ $table_two_factor->attach_defaults($button_two_factor_save_trust_cookie, 0, 3, 4, 5 );
+ $button_two_factor_save_trust_cookie->set_active(1) if ($save_two_factor_trust_cookie);
+ $button_two_factor_save_trust_cookie->signal_connect(toggled=>sub {
+ $save_two_factor_trust_cookie = ($button_two_factor_save_trust_cookie->get_active) ? 1 : 0;
+ }
+ );
+
my $frame_lang = Gtk2::Frame->new ("$trans{prefs_lang}");
$vbox->pack_start ($frame_lang, 0, 0, 4);
@@ -2862,17 +3343,35 @@
my $response = $dialog->run;
if ($response eq 'ok') {
- # remove password from the hash if user requests it ...
- if ($save_passwd && !$usekwallet) {
- $pref_variables{passwd}=\$passwd;
- } else {
- delete $pref_variables{passwd};
- }
-
+ # remove passwd from the hash if user requests it ...
+ handle_auth_token_preference($save_passwd, "passwd", $passwd);
+ # remove passwd from the hash if user requests it ...
+ handle_auth_token_preference($save_web_passwd, "web_passwd", $web_passwd);
+ # remove trust cookie from the hash if user requests it ...
+ handle_auth_token_preference($save_two_factor_trust_cookie, "two_factor_trust_cookie", $two_factor_trust_cookie);
+
# grab all entry variables ...
$user = $entry_user->get_text;
- $passwd_decrypt = $entry_pwd->get_text;
- $passwd = encrypt_real($passwd_decrypt);
+
+ # set potentially new $use_two_factor_auth state
+ $use_two_factor_auth = $use_two_factor_auth_pref || $two_factor_auth_switch ;
+
+
+ if ($save_passwd) {
+ $passwd_decrypt = $entry_pwd->get_text;
+ $passwd = encrypt_real($passwd_decrypt);
+ }
+
+ if ($save_web_passwd) {
+ $web_passwd_decrypt = $entry_web_pwd_two_factor->get_text;
+ $web_passwd = encrypt_real($web_passwd_decrypt);
+ }
+
+ if ($save_two_factor_trust_cookie) {
+ # trust cookie is not entered in the prefs, it was set automatically earlier, returned from PIN code validation
+ $two_factor_trust_cookie = encrypt_real($two_factor_trust_cookie_decrypt);
+ }
+
$delay = ($entry_delay->get_text)*1000;
$popup_delay = ($entry_pdelay->get_text)*1000;
$gmail_address = $atom_entry->get_text;
@@ -2880,10 +3379,16 @@
$notify_command = $entry_notify->get_text;
$nomail_command = $entry_notify_none->get_text;
- if ($usekwallet && $save_passwd) {
- open KWALLET, "|kwallet -set checkgmail";
- print KWALLET "$passwd\n";
- close KWALLET;
+ if ($use_kwallet) {
+ if ($save_passwd && (defined $passwd) && $passwd ne "") {
+ store_auth_token_in_kwallet($kwallet_default_password_key, $passwd);
+ }
+ if ($save_web_passwd && (defined $web_passwd) && $web_passwd ne "") {
+ store_auth_token_in_kwallet($kwallet_main_google_password_key, $web_passwd);
+ }
+ if ($save_two_factor_trust_cookie && (defined $two_factor_trust_cookie) && $two_factor_trust_cookie ne "") {
+ store_auth_token_in_kwallet($kwallet_two_factor_trust_cookie_key, $two_factor_trust_cookie);
+ }
}
reinit_checks();
@@ -2946,7 +3451,7 @@
sub login {
# a login dialogue - just ripped from the prefs above ...
-
+
# lock shared variables
lock($user);
lock($passwd);
@@ -2975,56 +3480,57 @@
$hbox->add($table_login);
- my $label_user = Gtk2::Label->new_with_mnemonic ($trans{prefs_login_user});
- $label_user->set_alignment (0, 0.5);
+ my $label_user = Gtk2::Label->new_with_mnemonic ($trans{prefs_login_user});
+ $label_user->set_alignment (0, 0.5);
$table_login->attach_defaults ($label_user, 0, 1, 0, 1);
- my $entry_user = Gtk2::Entry->new;
- $entry_user->set_width_chars(12);
- $entry_user->append_text($user) if $user;
+ my $entry_user = Gtk2::Entry->new;
+ $entry_user->set_width_chars(18);
+ $entry_user->append_text($user) if $user;
$table_login->attach_defaults ($entry_user, 1, 2, 0, 1);
$label_user->set_mnemonic_widget ($entry_user);
-
- my $label_pwd = Gtk2::Label->new_with_mnemonic ($trans{prefs_login_pass});
- $label_pwd->set_alignment (0, 0.5);
+
+ my $pwd_label_text = $use_two_factor_auth ? $trans{prefs_login_pass}." (".$trans{two_factor_main_google_password_app_label}.")" : $trans{prefs_login_pass};
+ my $label_pwd = Gtk2::Label->new_with_mnemonic ($pwd_label_text);
+ $label_pwd->set_alignment (0, 0.5);
$table_login->attach_defaults ($label_pwd, 0, 1, 1, 2);
- my $entry_pwd = Gtk2::Entry->new;
- $entry_pwd->set_width_chars(12);
- $entry_pwd->set_invisible_char('*');
- $entry_pwd->set_visibility(0);
- $entry_pwd->append_text($passwd_decrypt) if $passwd_decrypt;
- $table_login->attach_defaults ($entry_pwd, 1, 2, 1, 2);
- $label_pwd->set_mnemonic_widget ($entry_pwd);
- $entry_pwd->signal_connect(activate=>sub {$dialog->response('ok')});
+ my $entry_pwd = Gtk2::Entry->new;
+ $entry_pwd->set_width_chars(18);
+ $entry_pwd->set_invisible_char('*');
+ $entry_pwd->set_visibility(0);
+ $entry_pwd->append_text($passwd_decrypt) if $passwd_decrypt;
+ $table_login->attach_defaults ($entry_pwd, 1, 2, 1, 2);
+ $label_pwd->set_mnemonic_widget ($entry_pwd);
+ $entry_pwd->signal_connect(activate=>sub {$dialog->response('ok')});
my $button_passwd = Gtk2::CheckButton->new_with_label($trans{prefs_login_save});
- $table_login->attach_defaults($button_passwd, 0, 2, 2, 3 );
- $button_passwd->set_active(1) if ($save_passwd);
- $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_plain})") if ($nocrypt && !$usekwallet);
- $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_kwallet})") if ($usekwallet);
- $button_passwd->signal_connect(toggled=>sub {
- $save_passwd = ($button_passwd->get_active) ? 1 : 0;
- }
- );
+ $table_login->attach_defaults($button_passwd, 0, 2, 2, 3 );
+ $button_passwd->set_active(1) if ($save_passwd);
+
+ $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_plain})") if ($nocrypt && !$use_kwallet);
+ $button_passwd->set_label("$trans{prefs_login_save} ($trans{prefs_login_save_kwallet})") if ($use_kwallet);
+
+ $button_passwd->signal_connect(toggled=>sub {
+ $save_passwd = ($button_passwd->get_active) ? 1 : 0;
+ }
+ );
$dialog->show_all;
- my $response = $dialog->run;
+ my $response = $dialog->run;
+
if ($response eq 'ok') {
- if (($save_passwd)) {
- $pref_variables{passwd}=\$passwd;
- } else {
- delete $pref_variables{passwd};
- }
+
$user = $entry_user->get_text;
$passwd_decrypt = $entry_pwd->get_text;
$passwd = encrypt_real($passwd_decrypt);
+
+ handle_auth_token_preference($save_passwd, "passwd", $passwd);
+ # save at least the username, and optionally the password
write_prefs();
- if ($usekwallet && $save_passwd) {
- open KWALLET, "|kwallet -set checkgmail";
- print KWALLET "$passwd\n";
- close KWALLET;
+ if ($use_kwallet && $save_passwd && (defined $passwd) && $passwd ne "") {
+ store_auth_token_in_kwallet($kwallet_default_password_key, $passwd);
}
} else {
@@ -3034,6 +3540,115 @@
$dialog->destroy;
}
+# prompt for the web password. This is the password used to access the GMail web UI.
+sub get_web_passwd {
+
+ lock($web_passwd);
+ lock($save_web_passwd);
+
+ my $previous_save_web_passwd = $save_web_passwd;
+ ($web_passwd_decrypt, my $return_value) = generic_field_dialog($trans{prefs_two_factor_main_google_password}, $trans{two_factor_main_google_password}, $trans{prefs_two_factor_save_main_google_password}, $previous_save_web_passwd);
+ $save_web_passwd = $return_value;
+ $web_passwd = encrypt_real($web_passwd_decrypt);
+
+ handle_auth_token_preference($save_web_passwd, "web_passwd", $web_passwd);
+
+ # write out prefs, in case password storage option has changed from the previous value.
+ # e.g. now storing the password, removing the stored password, or updated the stored password. Do not write prefs if password was not stored before,
+ # and storing it is still not requested.
+ if ($previous_save_web_passwd || $save_web_passwd) {
+ write_prefs();
+ }
+
+ # if kwallet integration is available, and password storage is requested, store it in KWallet.
+ if ($use_kwallet && $save_web_passwd && (defined $web_passwd) && $web_passwd ne "") {
+ store_auth_token_in_kwallet($kwallet_main_google_password_key, $web_passwd);
+ }
+
+}
+
+# prompt for the 2-factor verification PIN code, obtained either through text message, an app, etc...
+sub get_verification_code {
+
+ lock($verification_code);
+ lock($save_two_factor_trust_cookie);
+
+ my $previous_save_two_factor_trust_cookie = $save_two_factor_trust_cookie;
+ ($verification_code, my $return_value) = generic_field_dialog($trans{prefs_two_factor_verification_code},$trans{prefs_two_factor_pin}, $trans{prefs_two_factor_save_trust_cookie}, $previous_save_two_factor_trust_cookie);
+ $save_two_factor_trust_cookie = $return_value;
+
+ if ($previous_save_two_factor_trust_cookie || $save_two_factor_trust_cookie) {
+ write_prefs();
+ }
+
+ # the item that may be stored here is not the PIN code, but the returned cookie. The cookie storage will handled later, when logging in, if required...
+
+}
+
+# generic single text box popup, modeled after the one used for the pre-existing username / password popup. see the 'login' method.
+sub generic_field_dialog {
+
+ my ($title,$label,$checkbox_label,$checkbox_associated_value) = @_;
+ my $field_value;
+ my $dialog = Gtk2::Dialog->new ($title, undef,
+ 'destroy-with-parent',
+ 'gtk-ok' => 'ok',
+ 'gtk-cancel' => 'cancel',
+ );
+ # $dialog_login->set_default_response('ok');
+
+ my $hbox = Gtk2::HBox->new (0, 0);
+ $hbox->set_border_width (4);
+ $dialog->vbox->pack_start ($hbox, 0, 0, 0);
+
+ my $vbox = Gtk2::VBox->new (0, 4);
+ $hbox->pack_start ($vbox, 0, 0, 4);
+
+ my $table_field = Gtk2::Table->new (2, 3, 0);
+ $table_field->set_row_spacings (4);
+ $table_field->set_col_spacings (4);
+ $table_field->set_border_width (5);
+
+ $hbox->add($table_field);
+
+
+ my $label_field = Gtk2::Label->new_with_mnemonic ($label);
+ $label_field->set_alignment (0, 0.5);
+ $table_field->attach_defaults ($label_field, 0, 1, 1, 2);
+
+ my $entry_field = Gtk2::Entry->new;
+ $entry_field->set_width_chars(32);
+ $entry_field->set_invisible_char('*');
+ $entry_field->set_visibility(0);
+ $table_field->attach_defaults ($entry_field, 1, 2, 1, 2);
+ $label_field->set_mnemonic_widget ($entry_field);
+ $entry_field->signal_connect(activate=>sub {$dialog->response('ok')});
+
+ if (defined $checkbox_label && defined $checkbox_associated_value) {
+ my $button_field = Gtk2::CheckButton->new_with_label($checkbox_label);
+ $table_field->attach_defaults($button_field, 0, 2, 2, 3 );
+ $button_field->set_active(1) if ($checkbox_associated_value);
+ $button_field->signal_connect(toggled=>sub {
+ $checkbox_associated_value = ($button_field->get_active) ? 1 : 0;
+ }
+ );
+ }
+
+ $dialog->show_all;
+ my $response = $dialog->run;
+ if ($response eq 'ok') {
+ $field_value = $entry_field->get_text;
+ }
+ $dialog->destroy;
+
+ if (defined $checkbox_associated_value) {
+ return ($field_value, $checkbox_associated_value);
+ } else {
+ return $field_value;
+ }
+}
+
+
sub about {
my $text = <<EOF;
<b>CheckGmail v$version</b>
@@ -3505,6 +4120,8 @@
<Language name="English"
login_err="Error: Incorrect username or password"
login_title="Login to Gmail ..."
+ two_factor_main_google_password="Main password"
+ two_factor_main_google_password_app_label="App"
mail_archive="Archive"
mail_archiving="Archiving ..."
mail_delete="Delete"
@@ -3521,7 +4138,7 @@
menu_compose="Compose mail"
menu_prefs="_Preferences"
menu_undo="_Undo last action"
- menu_restart="Restart ..."
+ menu_restart="Restart ..."
notify_and="and"
notify_check="Checking Gmail ..."
notify_from="From:"
@@ -3560,6 +4177,13 @@
prefs_login_save_kwallet="in KDE wallet"
prefs_login_save_plain="as plain text"
prefs_login_user="_Username"
+ prefs_two_factor="Two-factor authentication"
+ prefs_two_factor_main_google_password="Main Google password"
+ prefs_two_factor_save_trust_cookie="Trust machine / save associated cookie"
+ prefs_two_factor_enable="Enable"
+ prefs_two_factor_save_main_google_password="Save main password"
+ prefs_two_factor_verification_code="Verification code"
+ prefs_two_factor_pin="PIN"
prefs_tray="System tray"
prefs_tray_bg="Set tray background ..."
prefs_tray_error_icon="Use custom error icon"
@@ -3634,6 +4258,8 @@
<Language name="Français"
login_err="Erreur: Nom d'utilisateur ou mot de passe incorrect"
login_title="Connexion à Gmail..."
+ two_factor_main_google_password="Mot de passe principal"
+ two_factor_main_google_password_app_label="App"
mail_archive="Archiver"
mail_archiving="Archivage..."
mail_delete="Supprimer"
@@ -3688,6 +4314,13 @@
prefs_login_save_kwallet="dans le portefeuille KDE"
prefs_login_save_plain="en clair"
prefs_login_user="_Nom d'utilisateur"
+ prefs_two_factor="Authentification en deux étapes"
+ prefs_two_factor_main_google_password="Mot de passe Google principal"
+ prefs_two_factor_save_trust_cookie="Machine de confiance / conserver le cookie associé"
+ prefs_two_factor_enable="Activer"
+ prefs_two_factor_save_main_google_password="Sauver le mot de passe principal"
+ prefs_two_factor_verification_code="Code de vérification"
+ prefs_two_factor_pin="PIN"
prefs_tray="Zone de notification"
prefs_tray_bg="Couleur de fond..."
prefs_tray_error_icon="Icône d'erreur personnalisée"
@@ -5132,4 +5765,3 @@
%trans = %{$xmlin->{Language}->{$language}};
}
-
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment