Last active
December 14, 2015 09:50
-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- 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<mplcache=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<mplcache=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<mpl=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/…/\.\.\./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/…/\.\.\./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/…/\.\.\./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<mplcache=2&continue=$escaped_uri&service=mail&rm=false<mpl=yj_wsad&Email=$URI_user&Passwd=$URI_passwd&rmShown=1&null=Sign+in"; | |
+ $target_uri = "https://accounts.google.com/ServiceLoginAuth?ltmpl=yj_wsad<mplcache=2&continue=$escaped_uri&service=mail&rm=false<mpl=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