Android 8.0なスマートフォンでGoogleアカウントにログインできない問題を解消させる

Android 8.0をインストールしてあるスマートフォンをファクトリーリセットして保管してあった。 これを再度利用しようとしたがGoogleアカウントにログインすることができなかった。 何らかの方法でログインしようとすると、Google Play開発者サービスの画面が表示され、 以下のようなメッセージが表示されてしまう。

ログインできませんでした
Googleサーバーとの通信中に問題が発生しました。
しばらくしてからもう一度お試しください。

どうやってもログインできないという問題は以前にも遭遇したことはあって、 そのときはユーザーIDとパスワードを正しく入力しても、ログインに成功しなかった。 これは、古いAndroidの場合に、2要素認証を有効にしていてはいけないという問題だった。 だが、今回はそもそもユーザーIDを入力する段階にまでも進めないので、違う問題だ。

Google Play開発者サービスで止まっているのを考えると、現在のGoogleサーバー側の設定に 対応できないような古いバージョンが使われているというのが可能性としてはありそうである。 Google Play開発者サービスのバージョンを確認すると、12.6.89という非常に古いものだった。 Android 10のインストールされている環境で確認すると、22.42.12がインストールされていた。 ウェブで検索してみると、最新は22.43.12のようだ。

普通であれば、Play Store appからアップデートできると思うのだが、そもそもAndroidへの アカウント追加もできていないし、Play Store appもログインできず利用できないので、 普通の方法でアップデートすることはできそうにない。 Google Play開発者サービスのような重要なアプリケーションを出所の分からないウェブサイトから インストールしたくはなかったのだが、apkmirror.comから.apkファイルをダウンロードして ダウンロードしてみた。 apkmirror.comは、この端末のGoogle ChromeではCloudflareのcaptchaを突破できずアクセスが一切できなかった。 なので、PCのウェブブラウザーで.apkファイルをダウンロードして適当な場所にアップロードして、 それをこの端末のGoogle Chromeでダウンロードしてインストールした。この時、Google ChromeにはGoogle Play Store 以外からの.apkをインストールできるように設定しておいた。 Android 8.0では、.apkファイルしかインストールできないようなので、 com.google.android.gms_22.43.12_(020400-483592595)-224312010_minAPI21(arm64-v8a,armeabi-v7a)(nodpi)_apkmirror.com.apkというのをダウンロードして利用した。

これでGoogleアカウントでログインできるようになり、全てが正常に利用できるようになった。

Firefoxでクライアント証明書認証をするウェブサイトに接続する際に、SSL_ERROR_HANDSHAKE_FAILURE_ALERTが出る場合の対処法

Firefoxでクライアント証明書認証をするウェブサイトにアクセスすると、少なくとも初回では提出するクライアント証明書を選択することができる。 ここで提出したクライアント証明書がどれかを保存し、次回以降は聞かれないようにすることができる。 提出するクライアント証明書が間違っていた場合には、SSL_ERROR_HANDSHAKE_FAILURE_ALERTのエラーが発生し、ウェブサイトにアクセスすることはできない。

ここで、間違ったクライアント証明書を提出して、その設定を保存してしまうと、次回以降毎回SSL_ERROR_HANDSHAKE_FAILURE_ALERTが発生してしまうことになる。 どのクライアント証明書をどのウェブサイトに既定値として提出するかの情報は、設定(Settings)→プライバシーとセキュリティ(Privacy & Security)→証明書(Certificates) →証明書を表示(View certificates)→証明書マネージャー(Certificate Manager)の認証の決定(Authentication Decisions)で確認できる。間違った決定は削除(Delete)すれば 再度選択できるようになるので、正しいものを提出すれば、SSL_ERROR_HANDSHAKE_FAILURE_ALERTにはならなくなる。

Microsoft EdgeのIEモードのuser-agent string

Internet Explorer 11でしか動かないウェブサイトを利用しないといけないため、Microsoft EdgeのIEモードでアクセスする場合がある。 その場合のuser-agent stringを見てみると、Internet Explorer 11そのままだった。当たり前かもしれない。

Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko

ANAの機内Wi-Fiのゲートウェイ

航空機に乗る機会はめっきり少なくなってしまったのだが、仕事の都合でどうしても乗らないといけない場合というのは存在する。 ここ1年間くらいでは、ANAしか乗っていないような気がするが、小さな空港との往復便でも機内でWi-Fi接続ができるようになっている。

私が乗った便だと、205.220.148.137205.220.148.140がゲートウェイのIPアドレスとして見えていた。 これらのIPアドレスは、Panasonic Avionics Corporationに割り当てられている。 機内では、ana-panasonic.aeroというドメインがウェブサイトのホスト名の一部として見えていたが、このドメインは実在するものではないようだ。

WindowsのサービスをPowerShellで再起動させる

Windows Server 2012 R2 x86_64上に、定期的に再起動しないと動作がおかしくなるサービスがあって、タスクスケジューラーで夜中に再起動したい。 PowerShellのRestart-Serviceで比較的容易にできるようだったので、スクリプトを作成し、タスクスケジューラーに設定した。

サービスのNameを取得する

Restart-ServiceにはNameかDisplayNameを指定してサービスを再起動できるのだが、Nameを利用する方が良さそうな気がする、 今回再起動したいサービスでは該当しないが、DisplayNameには日本語が含まれている場合もあるし、ホワイトスペースが含まれている場合もあり、 いつか困ることになりそうな気がする。 Get-Serviceを使うとNameとDisplayNameを表示できるのだが、長いNameは最後が省略されてしまい、Restart-Serviceに 厳密に指定できない。Restart-Serviceではワイルドカードも利用できるのだが、今回はサービスの再起動の順序はあらかじめ決めてあるし、 Get-Serviceで表示が省略されるからといってワイルドカードを利用するのは良くない。 以下のようにすると、長いNameも省略されずに一覧できる。

> Get-Service | Sort-Object | Format-List -Property Name

これで、目当てのサービスのNameが、ServiceNameだと分かった場合には、以下のように実行すれば、サービスが再起動される。 スクリプト中に複数行で複数の再起動指示を記載しておくと、1つのサービスが再起動されるまで次の再起動の実行開始はブロックされる。

> Restart-Service -Name 'ServiceName'

How to use Google Workspace OAuth2 with mbsync (from isync) and msmtp on NetBSD

Google will remove old OAuth out-of-band (OOB) support on 2022-10-04. And desktop clients for Google Workspace should use Loopback IP address flow instead. So I must change my script to support Loopback IP address flow. My previous setup using OOB is described in How to use Google G suite OAuth2 with mbsync (from isync) and msmtp on NetBSD post.

I have searched the web and found How to continue using msmtp OAuth 2.0 for Gmail in mutt after oob deprecation? on StackExchange. The answer is to use getmail-gmail-xoauth-tokens script from getmail6 to get refresh token and access tokens. The answer says "- which are what we've been looking for, but that 1h expiry is prohibitively awkward...". It is absolutely unacceptable for me. I am away from GUI or web browsers in most time. I have not read getmail-gmail-xoauth-tokens script. However I feel that it may be the answer's misunderstanding or misuse. Anyway I do not have enough time to investigate the potentially hopeless script.

As a result, I found that the access tokens will expire in 3600 seconds and the refresh token has much longer life time like before. I have create two scripts to confirm this result. And I can reuse the scripts for my mbsync from isync and msmtp setups.

Get refresh token and save it

At first, you must download your client secret file as client_secret_*.json. Just rename it as client_secret.json

Google provides Google Auth Python Library and related libraries for Python. For further changes, I should use common methods to get tokens. I am a pkgsrc user and I have installed required libraries as follows:

# cd /usr/pkgsrc/www/py-google-api-python-client
# make install
# cd /usr/pkgsrc/security/py-google-auth-oauthlib
# make install

To get refresh token, you can use google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file() with the client_secret.json file. My source code (google-oauth2-loopback-ip.py) is as follows:

#!/usr/pkg/bin/python3.10

# Get refresh token and save it as ~/.refresh_token.pickle.
# Saved information will be used to refresh access token hourly.

# Follow:
# https://googleapis.github.io/google-api-python-client/docs/oauth-installed.html

import os
import pickle
from google_auth_oauthlib.flow import InstalledAppFlow

# Constants
CLIENT_SECRETS_FILE = 'client_secret.json'
CREDENTIAL_CACHE_FILE = '.refresh_token.pickle'

## Configurations
### To identify scopes and services, See:
### https://developers.google.com/identity/protocols/oauth2/scopes#gmail
SCOPES=['https://mail.google.com/']
API_SERVICE_NAME = 'mail'
API_VERSION = 'v1'

## Get credentials
def get_credentials():
    ## Create client object
    flow = InstalledAppFlow.from_client_secrets_file(
        CLIENT_SECRETS_FILE,
        scopes=SCOPES)

    ## Open with local web browser, input credential manually
    flow.run_local_server(host='localhost',
        port=8091,
        authorization_prompt_message='Please visit this URI: {url}',
        success_message='The auth flow is complete; you may close this web browser window.',
        open_browser=True)

    return flow.credentials


if __name__ == '__main__':
    credentials = get_credentials()

    # Get home directory path and create a path for cache file
    homeDir = os.environ['HOME']
    if not homeDir:
        homeDir = '.'
    credPath = os.path.join(homeDir, CREDENTIAL_CACHE_FILE)

    # Save credentials as file
    with open(credPath, 'wb') as tokenCache:
        pickle.dump(credentials, tokenCache)

    # https://google-auth.readthedocs.io/en/stable/reference/google.oauth2.credentials.html#google.oauth2.credentials.Credentials
    print('Refresh Token:', credentials.refresh_token)

You will get your refresh token as Refresh Token: YOURREFRESHTOKENSTRING. This script get an access token simultaniously (See your ~/.refresh_token.pickle). However I will not use the access token in this script at all.

Get access token using the refresh token

The refresh token has longer life time than 3600 seconds (life time of the access token). And the refresh token is used to get refreshed access tokens without login via web browsers.

To get a refreshed access token, you should load saved credentials in ~/.refresh_token.pickle and invoke refresh() method. My source code (google-oauth2-refresh-access_token.py) is as follows:

#!/usr/pkg/bin/python3.10

# Get new access token using saved refresh token.

# Follow:
# https://googleapis.github.io/google-api-python-client/docs/oauth-installed.html

import os
import pickle
from google.auth.transport.requests import Request

# Constants
CREDENTIAL_CACHE_FILE = '.refresh_token.pickle'

## Configurations
### To identify scopes and services, See:
### https://developers.google.com/identity/protocols/oauth2/scopes#gmail
SCOPES=['https://mail.google.com/']
API_SERVICE_NAME = 'mail'
API_VERSION = 'v1'

## Refresh credentials and get new ones
def get_refreshed_credentials():
    # Get home directory path and create a path for cache file
    homeDir = os.environ['HOME']
    if not homeDir:
        homeDir = '.'
    credPath = os.path.join(homeDir, CREDENTIAL_CACHE_FILE)

    if os.path.exists(credPath):
        with open(credPath, 'rb') as tokenCache:
            credentials = pickle.load(tokenCache)

        if credentials:
            # Refresh access token with refresh token
            credentials.refresh(Request())

    return credentials

if __name__ == '__main__':
    credentials = get_refreshed_credentials()

    # https://google-auth.readthedocs.io/en/stable/reference/google.oauth2.credentials.html#google.oauth2.credentials.Credentials
    print('Access Token:', credentials.token)
    #print('Expiry:', credentials.expiry) # in UTC

You will get your access token as Access Token: YOURREFRESHEDACCESSTOKENSTRING.

For mbsync and msmtp

This part is almost identical to my previous post. I will include my current script and configuration files.

$ cat /opt/bin/get_teteraorg_token.sh
#!/bin/sh

python3.10 /opt/bin/google-oauth2-refresh-access_token.py | awk -F" " '{if(NR==1)print $3}'
$ cat ~/.mbsync
(snip)
IMAPAccount gmail
Host imap.gmail.com
User username@tetera.org
AuthMechs XOAUTH2
PassCmd "/opt/bin/get_teteraorg_token.sh"
SSLType IMAPS
CertificateFile /etc/openssl/certs/ca-certificates.crt
(snip)
$ cat ~/.msmtprc
(snip)
account teteraorg
tls on
tls_certcheck off
tls_starttls off
host smtp.gmail.com
port 465
protocol smtp
auth xoauth2
from username@tetera.org
user username@tetera.org
passwordeval "/opt/bin/get_teteraorg_token.sh"
(snip)

Windows 10で、スタートアップディレクトリーを開く

Windows 10にはスタートアップディレクトリーがあって、ログインすると、ここに入っているショートカット等が自動的に開かれる。 このフォルダーを開く方法を忘れないように書いておく。

スタートアップが自分のユーザー用と、全ユーザーに適用されるものの2つがある。 自分のユーザーだけに適用されるフォルダーを開くには、Windowsキー+Rで表示される「ファイル名を指定して実行」よりshell:startupを実行すれば良い。 全ユーザーに適用されるフォルダーを開くには、shell:common startupを実行すれば良い。

これは、「ファイル名を指定して実行」から実行する必要があり、コマンドプロンプトからは実行できない。

Android 8.0なスマートフォンでGoogleアカウントにログインできない問題を解消させる

Android 8.0をインストールしてあるスマートフォンをファクトリーリセットして保管してあった。 これを再度利用しようとしたがGoogleアカウントにログインすることができなかった。 何らかの方法でログインしようとすると、Google Play開発者サービスの画...