Nextcloud 14 の Telegram を使った2要素認証

先日ファイル共有ソフトの Nextcloud 14がリリースされました.

新機能のうちSignal/Telegram/SMS による2要素認証のサポートが気になります.

Signal/Telegram/SMS 2FA support
A new 2-factor authentication provider named ‘gateway’ was introduced which allows users to use the secure messaging apps Signal and Telegram as well as various SMS gateways as second factor to secure their authentication.

SMSはplaySMS, websms.deを利用するようです.SignalもTelegramもセキュアなメッセージングサービスなのでこういう用途に向いていそうです.どちらも公式でLinux x86_64のデスクトップアプリが提供されています.アプリの出来は今の所Telegramのほうが上だと思います.Arm Linuxだと公式のものはないのですが,TelegramについてはCLIのアプリがあり,Raspberry Pi の Raspbian stretch でも要patchで動作しました.(Webアプリもあるのでそちらでも動作すると思うが未確認)

SMSはやったことがあるし,Telegram が使いやすい.てことでTelegramの設定をしてみました.まだ未実装な機能が多くちょっと面倒なのでメモしておきます.

Two-Factor Gateway の導入

Nextcloud 14に Two-Factor Gateway アプリを導入します.管理者アカウントで「アプリ」の「セキュリティ」から導入するのが楽でしょう.

導入するとNextcloudの「設定」の「セキュリティ」の中に「Message gateway second-factor auth」という項目が現れますが,設定が出来ません.

GitHubのドキュメントをみると未だ未実装の機能が多いようです.

Telegram Bot の作成

TOTPコードの送信する Telegram Bot を作成します.作成はTelegram のドキュメントを参照して作成します.

BotFather と会話してbot作成

BotFather という bot 管理用の bot が居るので,会話してbot を作成します.

アイコンちょっと怖い

44062365934 5aabe8f64d m

/newbot コマンドで新しい bot の作成が始まります.botの名前を効かれるので答えます.続いて bot の username を求められます.この username は最後に bot とつける必要があります.
成功するとアクセストークンが表示されるのでメモして次の手旬に移ります.以下の例では 640093430:AAFTa_pSAcKCZWeFoVDt-l7h7ewqzNe0Luo がトークンです.

Done! Congratulations on your new bot. You will find it at t.me/matoken_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.

Use this token to access the HTTP API:
640093430:AAFTa_pSAcKCZWeFoVDt-l7h7ewqzNe0Luo

For a description of the Bot API, see this page: https://core.telegram.org/bots/api

30910879338 4609b7a707
44781979421 aac86c72de

occ コマンドで Telegram bot のトークン設定

Nextcloud のインストールされている場所に Nextcloud 管理用の occ コマンドがあります.このコマンドで Telegram bot のトークンを設定します.
トークンの 640093430: 部分は省いてその後ろだけを入力します.

$ sudo -u www-data php ./occ twofactorauth:gateway:configure telegram
Please enter your Telegram bot token: AAFTa_pSAcKCZWeFoVDt-l7h7ewqzNe0Luo
Using AAFTa_pSAcKCZWeFoVDt-l7h7ewqzNe0Luo.
$ sudo -u www-data php ./occ twofactorauth:gateway:status
Signal gateway: not configured
SMS gateway: not configured
Telegram gateway: configured

ひとまずこれで管理者側の設定は終了です.

Telegram bot の chat_id を調べて登録する

ここからはNextcloud の利用アカウントでの操作になります.

Telegram
In order to receive authentication codes via Telegram, you first have to start a new chat with the bot set up by your admin.
Secondly, you have to obtain your Telegram ID via the ID Bot. Enter this ID to receive your verification code below.

You are not using Telegram for two-factor authentication at the moment. Enable

Telegram アプリを利用して,Telegram の bot から TOTPコードを受け取りたい Telegram アカウントに適当なメッセージを投げます.

@matoken hello

その後,Telegram API を利用してメッセージを取得して chat_id を調べます.
以下の例では 475721977 です.

$ curl https://api.telegram.org/bot640093430:AAHu9u_c12KC2PY8g22QZoA94u4tAJvxsvY/getUpdates | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   358  100   358    0     0    315      0  0:00:01  0:00:01 --:--:--   315
{
  "ok": true,
  "result": [
    {
      "update_id": 612249686,
      "message": {
        "message_id": 9,
        "from": {
          "id": 475721977,
          "is_bot": false,
          "first_name": "matoken",
          "username": "matoken",
          "language_code": "En"
        },
        "chat": {
          "id": 475721977,
          "first_name": "matoken",
          "username": "matoken",
          "type": "private"
        },
        "date": 1537282406,
        "text": "@matoken hello",
        "entities": [
          {
            "offset": 0,
            "length": 8,
            "type": "mention"
          }
        ]
      }
    }
  ]
}

試しに送信してみて Telegram アプリに bot からメッセージが飛んできたら chat id が正しいはず.

$ curl -X POST "https://api.telegram.org/bot640093430:AAHu9u_c12KC2PY8g22QZoA94u4tAJvxsvY" -d "chat_id=475721977&text=hello" | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   274  100   246  100    28    179     20  0:00:01  0:00:01 --:--:--   200
{
  "ok": true,
  "result": {
    "message_id": 15,
    "from": {
      "id": 640093430,
      "is_bot": true,
      "first_name": "matoken_bot",
      "username": "matoken_bot"
    },
    "chat": {
      "id": 475721977,
      "first_name": "matoken",
      "username": "matoken",
      "type": "private"
    },
    "date": 1537312183,
    "text": "hello"
  }
}

Nextcloud にログインして,「設定」→「セキュリティ」の下部に「Message gateway second-factor auth」という項目が出来ているので「Enable」を押す.

Telegram
In order to receive authentication codes via Telegram, you first have to start a new chat with the bot set up by your admin.
Secondly, you have to obtain your Telegram ID via the ID Bot. Enter this ID to receive your verification code below.

You are not using Telegram for two-factor authentication at the moment. Enable

以下のメッセージが表示されたら chat_id を入力して Verify します.

Enter your identification (e.g. phone number to start the verification):
 Verify

Telegram で TOTPコードが飛んで来るのでそれを入力したらOKです.
一旦ログアウトして試してみましょう.

Telegram 認証を試す

いつものようにユーザ名,パスワードで認証すると以下のような画面に遷移します.
「Authenticate via Telegram」を押します.

44781965201 94bd9ca33e

Telegram から認証コードが飛んでくるのでそれを入力して認証完了です.

30910872108 2d5e8c21eb

めんどくさい……

未だ手順が面倒で一般ユーザに試してくれと言える状態ではないですね.でも次のリリースあたりでは簡単になるんじゃないでしょうか.

環境

$ sudo -u www-data php ./occ app:list|grep twofactor_|grep :
  - twofactor_backupcodes: 1.3.1
  - twofactor_gateway: 0.9.0
  - twofactor_totp: 1.5.0
$ sudo -u www-data php ./occ -V
Nextcloud 14.0.0
$ lsb_release -d
Description:    Ubuntu 16.04.5 LTS
$ uname -m
x86_64

Telegram の非公式cli client の telegram-cli を試す

Arm でも動くかな?と試してみました.

必要パッケージの導入
$ sudo apt install libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev libjansson-dev libpython-dev make libssl1.0-dev
sourceをcloneする
$ git clone --recursive https://github.com/vysheng/tg.git && cd tg
configure & make
$ ./configure
$ make
build error1
tgl/crypto/rsa_pem_openssl.c: In function ‘TGLC_rsa_new’:
tgl/crypto/rsa_pem_openssl.c:41:6: error: dereferencing pointer to incomplete type ‘RSA {aka struct rsa_st}’
   ret->e = unwrap_bn (TGLC_bn_new ());
      ^~
tgl/crypto/rsa_pem_openssl.c: In function ‘TGLC_rsa_n’:
tgl/crypto/rsa_pem_openssl.c:52:1: error: control reaches end of non-void function [-Werror=return-type]
 RSA_GETTER(n);
 ^~~~~~~~~~
tgl/crypto/rsa_pem_openssl.c: In function ‘TGLC_rsa_e’:
tgl/crypto/rsa_pem_openssl.c:53:1: error: control reaches end of non-void function [-Werror=return-type]
 RSA_GETTER(e);
 ^~~~~~~~~~
cc1: all warnings being treated as errors
Makefile.tgl:20: recipe for target 'objs/crypto/rsa_pem_openssl.o' failed
make: *** [objs/crypto/rsa_pem_openssl.o] Error 1

libssl-dev(1.1.1)では動作しないのでlibssl1.0-devに入れ替える. sudo apt install libssl1.0-dev

build error2
gcc -I. -I. -I./tgl -w  -I/usr/local/include -I/usr/include -I/usr/include -I/usr/include/lua5.2  -DHAVE_CONFIG_H -Wall -Werror -Wextra -Wno-missing-field-initializers -Wno-deprecated-declarations -fno-strict-aliasing -fno-omit-frame-pointer -ggdb -Wno-unused-parameter -fPIC -iquote ./tgl/tl-parser -c -MP -MD -MF dep/tl-parser.d -MQ objs/tl-parser.o -o objs/tl-parser.o tgl/tl-parser/tl-parser.c
tgl/tl-parser/tl-parser.c:37:10: fatal error: zlib.h: そのようなファイルやディレクトリはありません
 #include <zlib.h>
          ^~~~~~~~
compilation terminated.
make: *** [Makefile.tl-parser:4: objs/tl-parser.o] エラー 1

zlib1g-dev を導入する. sudo apt install zlib1g-dev

Raspbian stretchでの実行時エラー(32bit環境での不具合?)
$ bin/telegram-cli
Telegram-cli version 1.4.1, Copyright (C) 2013-2015 Vitaly Valtman
Telegram-cli comes with ABSOLUTELY NO WARRANTY; for details type `show_license'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show_license' for details.
Telegram-cli uses libtgl version 2.1.0
Telegram-cli includes software developed by the OpenSSL Project
for use in the OpenSSL Toolkit. (http://www.openssl.org/)
I: config dir=[/home/pi/.telegram-cli]
> telegram-cli: tgl/mtproto-utils.c:101: BN2ull: Assertion `0' failed.
SIGNAL received

以下の修正を行い, make clean して build しなおす.

$ diff -ud tgl/mtproto-utils.c.org tgl/mtproto-utils.c
--- tgl/mtproto-utils.c.org     2018-09-19 21:01:44.878560077 +0900
+++ tgl/mtproto-utils.c 2018-09-19 20:59:55.778295550 +0900
@@ -98,13 +98,13 @@
   if (sizeof (unsigned long) == 8) {
	 return TGLC_bn_get_word (b);
   } else if (sizeof (unsigned long long) == 8) {
-    assert (0); // As long as nobody ever uses this code, assume it is broken.
+    assert (0);  As long as nobody ever uses this code, assume it is broken.
	 unsigned long long tmp;
	 /* Here be dragons, but it should be okay due to be64toh */
	 TGLC_bn_bn2bin (b, (unsigned char *) &tmp);
	 return be64toh (tmp);
   } else {
-    assert (0);
+//    assert (0);
   }
 }
$ make clean
$ ./configure
$ make
初回実行時はSMS認証が必要
$ bin/telegram-cli
Telegram-cli version 1.4.1, Copyright (C) 2013-2015 Vitaly Valtman
Telegram-cli comes with ABSOLUTELY NO WARRANTY; for details type `show_license'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show_license' for details.
Telegram-cli uses libtgl version 2.1.0
Telegram-cli includes software developed by the OpenSSL Project
for use in the OpenSSL Toolkit. (http://www.openssl.org/)
I: config dir=[/home/mk/.telegram-cli]
[/home/mk/.telegram-cli] created
[/home/mk/.telegram-cli/downloads] created
phone number: +81-9000000000
code ('CALL' for phone code): 16802
User matoken updated flags
User matoken online (was online [2018/09/18 23:55:32])
User matoken offline (was online [2018/09/18 23:50:35])
認証を行うとメッセージが飛んでくる
matoken,

We detected a login into your account from a new device on 19/09/2018 at 12:28:55 UTC.

Device: Unix Console
Location: Shizuoka, Japan (IP = 180.131.110.140)

If this wasn't you, you can go to Settings - Privacy and Security - Sessions and terminate that session.

If you think that somebody logged in to your account against your will, you can enable two-step verification in Privacy and Security settings.

Sincerely,
The Telegram Team
メッセージを送信してみる
> msg @matoken hello
telegram-cli の help
$ bin/telegram-cli --help
telegram-cli Usage
  --phone/-u                           specify username (would not be asked during authorization)
  --rsa-key/-k                         specify location of public key (possible multiple entries)
  --verbosity/-v                       increase verbosity (0-ERROR 1-WARNIN 2-NOTICE 3+-DEBUG-levels)
  --enable-msg-id/-N                   message num mode
  --config/-c                          config file name
  --profile/-p                         use specified profile
  --log-level/-l                       log level
  --sync-from-start/-f                 during authorization fetch all messages since registration
  --disable-auto-accept/-E             disable auto accept of encrypted chats
  --lua-script/-s                      lua script file
  --wait-dialog-list/-W                send dialog_list query and wait for answer before reading input
  --disable-colors/-C                  disable color output
  --disable-readline/-R                disable readline
  --alert/-A                           enable bell notifications
  --daemonize/-d                       daemon mode
  --logname/-L <log-name>              log file name
  --username/-U <user-name>            change uid after start
  --groupname/-G <group-name>          change gid after start
  --disable-output/-D                  disable output
  --tcp-port/-P <port>                 port to listen for input commands
  --udp-socket/-S <socket-name>        unix socket to create
  --exec/-e <commands>                 make commands end exit
  --disable-names/-I                   use user and chat IDs in updates instead of names
  --enable-ipv6/-6                     use ipv6 (may be unstable)
  --help/-h                            prints this help
  --accept-any-tcp                     accepts tcp connections from any src (only loopback by default)
  --disable-link-preview               disables server-side previews to links
  --bot/-b                             bot mode
  --json                               prints answers and values in json format
  --permanent-msg-ids                  use permanent msg ids
  --permanent-peer-ids                 use permanent peer ids
telegram-cli 起動時のhelp
$ bin/telegram-cli -e help
Telegram-cli version 1.4.1, Copyright (C) 2013-2015 Vitaly Valtman
Telegram-cli comes with ABSOLUTELY NO WARRANTY; for details type `show_license'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show_license' for details.
Telegram-cli uses libtgl version 2.1.0
Telegram-cli includes software developed by the OpenSSL Project
for use in the OpenSSL Toolkit. (http://www.openssl.org/)
I: config dir=[/home/pi/.telegram-cli]
[33;1maccept_secret_chat <secret chat>  Accepts secret chat. Only useful with -E option
add_contact <phone> <first name> <last name>    Tries to add user to contact list
block_user <user>       Blocks user
broadcast <user>+ <text>        Sends text to several users at once
channel_get_admins <channel> [limit=100] [offset=0]     Gets channel admins
channel_get_members <channel> [limit=100] [offset=0]    Gets channel members
channel_info <channel>  Prints info about channel (id, members, admin, etc.)
channel_invite <channel> <user> Invites user to channel
channel_join <channel>  Joins to channel
channel_kick <channel> <user>   Kicks user from channel
channel_leave <channel> Leaves from channel
channel_list [limit=100] [offset=0]     List of last channels
channel_set_about <channel> <about>     Sets channel about info.
channel_set_admin <channel> <admin> <type>      Sets channel admin. 0 - not admin, 1 - moderator, 2 - editor
channel_set_username <channel> <username>       Sets channel username info.
channel_set_photo <channel> <filename>  Sets channel photo. Photo will be cropped to square
chat_add_user <chat> <user> [msgs-to-forward]   Adds user to chat. Sends him last msgs-to-forward message from this chat. Default 100
chat_del_user <chat> <user>     Deletes user from chat
chat_info <chat>        Prints info about chat (id, members, admin, etc.)
chat_set_photo <chat> <filename>        Sets chat photo. Photo will be cropped to square
chat_upgrade <chat>     Upgrades chat to megagroup
chat_with_peer <peer>   Interface option. All input will be treated as messages to this peer. Type /quit to end this mode
clear   Clears all data and exits. For debug.
contact_list    Prints contact list
contact_search username Searches user by username
create_channel <name> <about> <user>+   Creates channel with users
create_group_chat <name> <user>+        Creates group chat with users
create_secret_chat <user>       Starts creation of secret chat
del_contact <user>      Deletes contact from contact list
delete_msg <msg-id>     Deletes message
dialog_list [limit=100] [offset=0]      List of last conversations
export_card     Prints card that can be imported by another user with import_card method
export_channel_link     Prints channel link that can be used to join to channel
export_chat_link        Prints chat link that can be used to join to chat
fwd <peer> <msg-id>+    Forwards message to peer. Forward to secret chats is forbidden
fwd_media <peer> <msg-id>       Forwards message media to peer. Forward to secret chats is forbidden. Result slightly differs from fwd
get_terms_of_service    Prints telegram's terms of service
get_message <msg-id>    Get message by id
get_self        Get our user info
help [command]  Prints this help
history <peer> [limit] [offset] Prints messages with this peer (most recent message lower). Also marks messages as read
import_card <card>      Gets user by card and prints it name. You can then send messages to him as usual
import_chat_link <hash> Joins to chat by link
import_channel_link <hash>      Joins to channel by link
load_audio <msg-id>     Downloads file to downloads dirs. Prints file name after download end
load_channel_photo <channel>    Downloads file to downloads dirs. Prints file name after download end
load_chat_photo <chat>  Downloads file to downloads dirs. Prints file name after download end
load_document <msg-id>  Downloads file to downloads dirs. Prints file name after download end
load_document_thumb <msg-id>    Downloads file to downloads dirs. Prints file name after download end
load_file <msg-id>      Downloads file to downloads dirs. Prints file name after download end
load_file_thumb <msg-id>        Downloads file to downloads dirs. Prints file name after download end
load_photo <msg-id>     Downloads file to downloads dirs. Prints file name after download end
load_user_photo <user>  Downloads file to downloads dirs. Prints file name after download end
load_video <msg-id>     Downloads file to downloads dirs. Prints file name after download end
load_video_thumb <msg-id>       Downloads file to downloads dirs. Prints file name after download end
main_session    Sends updates to this connection (or terminal). Useful only with listening socket
mark_read <peer>        Marks messages with peer as read
msg <peer> <text>       Sends text message to peer
msg <peer> <kbd> <text> Sends text message to peer with custom kbd
post <peer> <text>      Sends text message to peer as admin
post_audio <peer> <file>        Posts audio to peer
post_document <peer> <file>     Posts document to peer
post_file <peer> <file> Sends document to peer
post_location <peer> <latitude> <longitude>     Sends geo location
post_photo <peer> <file>       Sends photo to peer
post_text <peer> <file> Sends contents of text file as plain text message
post_video <peer> <file>       Sends video to peer
quit    Quits immediately
rename_channel <channel> <new name>     Renames channel
rename_chat <chat> <new name>   Renames chat
rename_contact <user> <first name> <last name>  Renames contact
reply <msg-id> <text>   Sends text reply to message
reply_audio <msg-id> <file>     Sends audio to peer
reply_contact <msg-id> <phone> <first-name> <last-name> Sends contact (not necessary telegram user)
reply_document <msg-id> <file>  Sends document to peer
reply_file <msg-id> <file>      Sends document to peer
reply_location <msg-id> <latitude> <longitude>  Sends geo location
reply_photo <msg-id> <file>    Sends photo to peer
reply_video <msg-id> <file>     Sends video to peer
resolve_username username       Searches user by username
safe_quit       Waits for all queries to end, then quits
search [peer] [limit] [from] [to] [offset] pattern      Search for pattern in messages from date from to date to (unixtime) in messages with peer (if peer not
present, in all messages)
send_audio <peer> <file>        Sends audio to peer
send_contact <peer> <phone> <first-name> <last-name>    Sends contact (not necessary telegram user)
send_document <peer> <file>     Sends document to peer
send_file <peer> <file> Sends document to peer
send_location <peer> <latitude> <longitude>     Sends geo location
send_photo <peer> <file>       Sends photo to peer
send_text <peer> <file> Sends contents of text file as plain text message
send_typing <peer> [status]     Sends typing notification. You can supply a custom status (range 0-10): none, typing, cancel, record video, upload video, recor
d audio, upload audio, upload photo, upload document, geo, choose contact.
send_typing_abort <peer>        Sends typing notification abort
send_video <peer> <file>       Sends video to peer
set <param> <value>     Sets value of param. Currently available: log_level, debug_verbosity, alarm, msg_num
set_password <hint>     Sets password
set_profile_name <first-name> <last-name>       Sets profile name.
set_profile_photo <filename>    Sets profile photo. Photo will be cropped to square
set_ttl <secret chat>   Sets secret chat ttl. Client itself ignores ttl
set_username <name>     Sets username.
set_phone_number <phone>        Changes the phone number of this account
show_license    Prints contents of GPL license
start_bot <bot> <chat> <data>   Adds bot to chat
stats   For debug purpose
status_online   Sets status as online
status_offline  Sets status as offline
unblock_user <user>     Unblocks user
user_info <user>        Prints info about user (id, last online, phone)
version Prints client and library version
view_audio <msg-id>     Downloads file to downloads dirs. Then tries to open it with system default action
view_channel_photo <channel>    Downloads file to downloads dirs. Then tries to open it with system default action
view_chat_photo <chat>  Downloads file to downloads dirs. Then tries to open it with system default action
view_document <msg-id>  Downloads file to downloads dirs. Then tries to open it with system default action
view_document_thumb <msg-id>    Downloads file to downloads dirs. Then tries to open it with system default action
view_file <msg-id>      Downloads file to downloads dirs. Then tries to open it with system default action
view_file_thumb <msg-id>        Downloads file to downloads dirs. Then tries to open it with system default action
view_photo <msg-id>     Downloads file to downloads dirs. Then tries to open it with system default action
view_user_photo <user>  Downloads file to downloads dirs. Then tries to open it with system default action
view_video <msg-id>     Downloads file to downloads dirs. Then tries to open it with system default action
view_video_thumb <msg-id>       Downloads file to downloads dirs. Then tries to open it with system default action
view <msg-id>   Tries to view message contents
visualize_key <secret chat>     Prints visualization of encryption key (first 16 bytes sha1 of it in fact)
[0mAll done. Exit
halt

大抵のことは出来る感じです.

試した環境1
$ git log --decorate|head -7
commit 6547c0b21b977b327b3c5e8142963f4bc246187a (HEAD -> master, origin/master, origin/HEAD)
Merge: 443793d 160231b
Author: V V <vvaltman@aurum>
Date:   Wed Mar 23 14:42:53 2016 +0300

    Merge github.com:vysheng/tg

$ dpkg-query -W libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev libjansson-dev libpython-dev make libssl1.0-dev
libconfig-dev:armhf     1.5-0.3
libevent-dev    2.0.21-stable-3
libjansson-dev:armhf    2.9-1
liblua5.2-dev:armhf     5.2.4-1.1
libpython-dev:armhf     2.7.13-2
libreadline-dev:armhf   7.0-3
libssl-dev
libssl1.0-dev:armhf     1.0.2l-2+deb9u3
lua5.2  5.2.4-1.1
make    4.1-9.1
$ lsb_release -d
Description:    Raspbian GNU/Linux 9.4 (stretch)
$ uname -m
armv7l
$ cat /proc/device-tree/model ;echo
Raspberry Pi 2 Model B Rev 1.1
試した環境2
$ git log --decorate|head -7
commit 6547c0b21b977b327b3c5e8142963f4bc246187a (HEAD -> master, origin/master, origin/HEAD)
Merge: 443793d 160231b
Author: V V <vvaltman@aurum>
Date:   Wed Mar 23 14:42:53 2016 +0300

    Merge github.com:vysheng/tg

$ dpkg-query -W libreadline-dev libconfig-dev libssl-dev lua5.2 liblua5.2-dev libevent-dev libjansson-dev libpython-dev make libssl1.0-dev
libconfig-dev:amd64     1.5-0.4
libevent-dev    2.1.8-stable-4build1
libjansson-dev:amd64    2.11-1
liblua5.2-dev:amd64     5.2.4-1.1build1
libpython-dev:amd64     2.7.15~rc1-1
libreadline-dev:amd64   7.0-3
libssl-dev
libssl1.0-dev:amd64     1.0.2n-1ubuntu5.1
lua5.2  5.2.4-1.1build1
make    4.1-9.1ubuntu1
$ lsb_release -d
Description:    Ubuntu 18.04.1 LTS
(mk)-(jobs:0)-(~/src/tg)
$ uname -m
x86_64

デーモンの起動しないSMTPクライアントの msmtp を試す(sSMTP乗り換え)

sSMTP から msmtp へ

現在メールを受け取らずメールを送信するだけのホストではsSMTPをよく使っています.MTAはeximやPostfix等でも良いのですが,デーモンが起動しないのでその分セキュアで設定も楽です.
しかし,sSMTPは最近メンテされていません,Debian などのディストリビューションではまだメンテナンスされていますが新機能が入ったりはしないでしょう.

ssmtp is unmaintained. Consider using something like msmtp instead.
バージョンも据え置きでUpstreamも無さそう
$ w3m -dump https://packages.qa.debian.org/s/ssmtp.html|grep versions -A11
versions ... ...pool

o-o-stable
    save 2.64-7
oldstable
    save 2.64-8
stable
    save 2.64-8
unstable
    save 2.64-8
Ubuntu
    2.64-8ubuntu2
$ apt show ssmtp 2>/dev/null |grep Homepage
Homepage: http://packages.qa.debian.org/s/ssmtp.html

そこで,alternativeto.net で挙げられている msmtp を試してみました.

以下では色々試していますが,システムメールを送るだけなら msmtp, msmtp-mta pkg を導入,/etc/msmtprc の設定だけでokです.

msmtp の導入

Debian sid/Ubuntu 18.04 LTS などではパッケージがあったのでこれを導入しました.

$ sudo apt install msmtp

mstpの設定

設定例が /usr/share/doc/msmtp/examples/ 以下にあるので
システム設定の場合は msmtprc-system.example`/etc/msmtprc にcpして設定.
ユーザ設定の場合は msmtprc-user.example~/.msmtprc にcpして設定する.

システムでの設定例

以下の例では root が読み書きできて,一般ユーザのmatokenアカウントは読み込みが可能な設定ファイル /etc/msmtprc を作成している.
defaults セクションに共通設定を書き,account セクションにメールサーバ固有の設定を書く.account セクションは複数書けて,account default に規定account を指定する.以下の例ではgmail が規定値となる.

/etc/msmtprc の作成
$ sudo install /dev/null /etc/msmtprc -o root -g root -m 600
$ sudo setfacl -m u:matoken:r /etc/msmtprc
$ getfacl /etc/msmtprc
getfacl: Removing leading '/' from absolute path names
# file: etc/msmtprc
# owner: root
# group: root
user::rw-
user:matoken:r--
group::---
mask::r--
other::---

$ sudo vi /etc/msmtprc
$ sudo grep -v ^# /etc/msmtprc

defaults
syslog on
aliases /etc/aliases

tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
syslog LOG_MAIL

account gmail
host smtp.gmail.com
port 587
from example@gmail.com
user example
password porseypacdoadwif

account default : gmail

※gmailの多要素認証を利用している場合は以下からアプリパスワードを生成する.(使い回しができちゃうけれど流出したときの影響を考えて設定毎に発行する)

ユーザでの設定

ユーザの設定ファイルは`~/.msmtprc` 若しくは $XDG_CONFIG_HOME/msmtp/config でシステムと違い,alias file と log を適当なファイルにした.
アカウントも増やしてみた.
gmailのアプリパスワードも新たに発行した.

~/.msmtprc の作成
$ install /dev/null ~/.msmtprc -o matoken -g matoken -m 600
$ vi ~/.msmtprc
$ grep -v ^# ~/.msmtprc

defaults
logfile ~/.msmtp.log
aliases ~/.msmtp.aliases

tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
syslog LOG_MAIL

account gmail
host smtp.gmail.com
port 587
from example@gmail.com
user example
password igeinleedafchygy

account yandex
tls_starttls off
host smtp.yandex.com
port 465
from example@yandex.com
user example
password throjfeekdewxeib

account nifty
tls_starttls off
host smtp.nifty.com
port 465
from example@nifty.com
user aaa99999
password 9Z#9999X

account default : gmail

送信テスト

msmtp コマンドで送信テストが出来る.
-a オプションでアカウントが指定できる.
-C オプションで設定ファイルが指定できる.(システムとユーザの設定を両方テストする)

$ echo "hello system msmtp" | msmtp -C /etc/msmtprc matohara@gmail.com
$ echo "hello user msmtp" | msmtp -C ~/.msmtprc -a gmail matohara@gmail.com

大抵のMTAでは . でmail body 終了となるが,msmtpでは`.`は普通にbody に含まれる.Ctrl + d で終了.

$ msmtp -a gmail matohara@gmail.com
Subject: hello

msmtp test
.
dot では終了にならない
Ctrl+d で終了
^d

aliases file

msmtp設定ファイルの alias で設定できる.

$ grep alias ~/.msmtprc
aliases ~/.msmtp.alias
$ sudo grep alias /etc/msmtprc
aliases /etc/aliases

このalias fileは通常のaliases ファイルと同じ形式の plain text で newaliases コマンドでの変換は不要.

メールアドレスは複数指定できる.default という名前は規定アドレスとなる.

root: hoge@example.com, fuga@example.com
default: piyo@example.com

以前のaliases ファイルを流用して以下のようにしてみたところ,

# /etc/aliases
mailer-daemon: postmaster
postmaster: root
nobody: root
hostmaster: root
usenet: root
news: root
webmaster: root
www: root
ftp: root
abuse: root
noc: root
security: root
root: matohara@gmail.com
default: matohara@gmail.com

以下のようにエラーとなった.alias先はユーザ名ではエラーとなるよう.

$ echo hello | sudo msmtp -C /etc/msmtprc root
msmtp: /etc/aliases: line 2: invalid address 'postmaster'

以下のようにそれぞれにメールアドレスを指定すると期待した動作となった.

$ sudo sed -i -e 's/ root$\| postmaster$/matohara@gmail.com/' /etc/aliases
$ cat /etc/aliases
# /etc/aliases
mailer-daemon:matohara@gmail.com
postmaster:matohara@gmail.com
nobody:matohara@gmail.com
hostmaster:matohara@gmail.com
usenet:matohara@gmail.com
news:matohara@gmail.com
webmaster:matohara@gmail.com
www:matohara@gmail.com
ftp:matohara@gmail.com
abuse:matohara@gmail.com
noc:matohara@gmail.com
security:matohara@gmail.com
root: matohara@gmail.com
default: matohara@gmail.com

パスワードをGnuPG暗号化ファイルに格納して利用する

パスワードをgpgで暗号化したファイルに格納することが可能.この場合アカウントごとにファイルが必要なのが少し面倒.
password porseypacdoadwif の場合 porseypacdoadwif 部分だけを暗号化してファイルに格納する.(はじめpassword も含めてしまい失敗した)

$ grep ^password\  ~/.msmtprc | head -1 | awk {'print $2'} | gpg2 --default-recipient-self -e - > ~/.msmtp-password-gmail.gpg
$ grep ^password\  ~/.msmtprc | head -2 | tail -1 | awk {'print $2'} | gpg2 --default-recipient-self -e - > ~/.msmtp-password-yandex.gpg
$ grep ^password\  ~/.msmtprc | tail -1 | awk {'print $2'} | gpg2 --default-recipient-self -e - > ~/.msmtp-password-nifty.gpg

msmtprc の password を削除して,代わりに passwordeval gpg2 --no-tty -q -d 暗号ファイル と設定を変更する.

$ grep ^passworde ~/.msmtprc
passwordeval gpg2 --no-tty -q -d ~/.msmtp-password-gmail.gpg
passwordeval gpg2 --no-tty -q -d ~/.msmtp-password-yandex.gpg
passwordeval gpg2 --no-tty -q -d ~/.msmtp-password-nifty.gpg

後はこれまでのようにmsmtpを呼ぶとgpg-agentが起動する.

※システム側はシステムメールが送れなくなるのでこの設定をしないほうがいいと思う.

パスワードをopenssl暗号化ファイルに格納

システムメールでgpg agentを利用するのは多分無理なのでopensslで暗号化してみる.パスワードはNICのmac addressにしてある.気休めレベルだけど試しに設定.
マシンが乗っ取られた場合はどうしようもないけれど /etc/msmtprc, /etc/msmtp-password ファイル流出時の時間稼ぎくらいにはなる?
NIC が使っていないものも含め増減したり変更になった場合は復元できなくなるので注意.

mac address をパスワードとしてopensslで暗号化
$ umask 0077; grep ^password\  /etc/msmtprc | head -1 | awk {'print $2'} | sudo openssl enc -e -md sha256 -aes-256-cbc -a -A -salt -pbkdf2 -in /dev/stdin -out /etc/msmtp-password -pass pass:`hwinfo --network | grep 'Permanent HW Address' | sed 's/^.*: //' | sort -n | tr -d '\n'`
復元テスト
$ sudo openssl enc -d -md sha256 -aes-256-cbc -a -A -salt -pbkdf2 -in /etc/msmtp-password -pass pass:`hwinfo --network | grep 'Permanent HW Address' | sed 's/^.*: //' | sort -n | tr -d '\n'`
igeinleedafchygy
/etc/msmtprc の password を削除して passwordeval に書き換え
$ grep passworde /etc/msmtprc
passwordeval openssl enc -d -md sha256 -aes-256-cbc -a -A -salt -pbkdf2 -in /etc/msmtp-password -pass pass:`hwinfo --network | grep 'Permanent HW Address' | sed 's/^.*: //' | sort -n | tr -d '\n'`

※ifconfigやiproute2を使わずhwinfoを使ってPermanent HW Addressを使っているのはmac address偽装対策

MTA パッケージの導入

msmtp コマンドでメールが送信できるようになったら,msmtp-mta pkg を導入する.これでsendmailコマンドにシンボリックリンクが張られて同様に利用できるようになる.システムメールも飛ぶようになる.

$ sudo apt install msmtp-mta
$ ls -l /usr/sbin/sendmail
lrwxrwxrwx 1 root root 12 11月 29  2016 /usr/sbin/sendmail -> ../bin/msmtp

オフライン時にmsmtpを利用する

msmtpはオフライン時に実行するとエラーとなる.通常は常にオンラインなので問題はないが,持ち運びNotePCなどではオフラインオンラインが発生してメールの送信に失敗することがある.

$ echo 'hoge' | msmtp -a gmail matoken@gmail.com
msmtp: cannot locate host smtp.gmail.com: 名前またはサービスが不明です
msmtp: could not send mail (account gmail from /etc/msmtprc)

/usr/share/doc/msmtp/examples/ 以下の msmtpq 若しくは msmtpqueue に格納されているscriptを利用することでオフライン時にキューとして貯めておいてオンライン時に送信といったことが出来る.

どちらもsendmailと置き換えが可能なので,ネットワークダウン時に`/usr/sbin/sendmail`をこれに置き換えてオンライン時に戻してqueueをフラッシュするようにするといいかもしれない.

Debian/Ubuntuなら以下のあたりで,

  • /etc/network/if-down.d/
    /usr/sbin/sendmail を msmtpq or msmtpqueue に向ける

  • /etc/network/if-pre-up.d/
    /usr/sbin/sendmail を msmtp に向ける

  • /etc/network/if-up.d/
    queueをフラッシュ

NetworkManagerだと /etc/NetworkManager/dispatcher.d/ 以下で同じことが出来る.

msmtpq

圧縮されているmsmtpqを展開して実行権を付与する
$ sudo gunzip /usr/share/doc/msmtp/examples/msmtpq/msmtpq.gz
$ sudo chmod 755 /usr/share/doc/msmtp/examples/msmtpq/msmtpq
使いやすい場所にシンボリックリンクを張る
$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpq/msmtpq /usr/local/bin/msmtpq
$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpq/msmtp-queue /usr/local/bin/msmtp-queue
queueとlogディレクトリを作成
$ install -d -m 700 ~/.msmtp.queue
$ install -d -m 700 ~/log
オフライン時に送信しようとする
$ echo 'msmtpq' | msmtpq -C ~/.msmtprc -a gmail matoken@gmail.com
  mail for [ -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ] : couldn't be sent - host not connected
  enqueued mail as : [ 2018-09-18-06.21.48 ] ( -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ) : successful
queueを確認する
$ msmtp-queue -d

  mail  num=[ 1 ]  id=[ 2018-09-18-06.21.18 ]

  mail  num=[ 2 ]  id=[ 2018-09-18-06.21.48 ]
queueをフラッシュする
$ msmtp-queue -r
  mail [ 1 ] [ 2018-09-18-06.21.18 ] from queue ; send was successful ; purged from queue
  mail [ 2 ] [ 2018-09-18-06.21.48 ] from queue ; send was successful ; purged from queue
$ msmtp-queue -d

  no mail in queue

msmtpqueue

使いやすい場所にシンボリックリンクを張る
$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpqueue/msmtp-enqueue.sh /usr/local/bin/msmtp-enqueue.sh
$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpqueue/msmtp-listqueue.sh /usr/local/bin/msmtp-listqueue.sh
~$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpqueue/msmtp-runqueue.sh /usr/local/bin/msmtp-runqueue.sh
`msmtp`コマンドの代わりに`msmtp-enqueue.sh`を使う
$ echo 'queue' | msmtp-enqueue.sh -C ~/.msmtprc -a gmail matoken@gmail.com
queueを確認する
$ msmtp-listqueue.sh
No mail in queue
今回のようにメールの内容がbodyだけでFrom, To, Subject が存在しないとこのscriptはうまく機能しない><
         egrep -s --colour -h '(^From:|^To:|^Subject:)' "$i" || echo "No mail in queue";
直接`~/.msmtpqueue/`を確認するとqueueが溜まっているのがわかる
$ ls -lA ~/.msmtpqueue/
合計 8
-rw------- 1 matoken matoken  7  9月 18 00:15 2018-09-18-00.15.53.mail
-rw------- 1 matoken matoken 53  9月 18 00:15 2018-09-18-00.15.53.msmtp
Subjectの付いたメールがある時
$ msmtp-listqueue.sh
No mail in queue

Subject: offline><
オンライン時にqueueをフラッシュする
$ msmtp-runqueue.sh
*** Sending 2018-09-18-00.15.53.mail to -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ...
2018-09-18-00.15.53.mail sent successfully
*** Sending 2018-09-18-00.27.21.mail to -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ...
2018-09-18-00.27.21.mail sent successfully

#sudoの場合も ~root/.msmtpqueue/ だった.

tips

gmailで以下のエラーとなった場合 tls_starttls on ( tls onのときの既定値? )と設定すると送信できた
$ echo "hello msmtp" | sudo msmtp -a gmail matohara@gmail.com
msmtp: envelope from address matohara@gmail.com not accepted by the server
msmtp: server message: 530 5.7.0 Must issue a STARTTLS command first. k126-v6sm14991126pgk.26 - gsmtp
msmtp: could not send mail (account gmail from /etc/msmtprc)
Yandexでメール送信時に帰ってこない場合 tls_starttls off と設定すると送信できた

niftyで以下のエラーとなった場合 tls_starttls off と設定すると送信できた
msmtp: network read error: 接続が相手からリセットされました
msmtp: could not send mail (account nifty from /etc/msmtprc)

試した環境

環境1(Raspberry Pi 2B/Raspbian stretch)
$ dpkg-query -W msmtp msmtp-mta gnupg2 openssl
gnupg2  2.1.18-8~deb9u2
msmtp   1.6.6-1
msmtp-mta       1.6.6-1
openssl 1.1.0f-3+deb9u2
$ lsb_release -d
Description:    Raspbian GNU/Linux 9.4 (stretch)
$ uname -m
armv7l
環境2(Ubuntu 18.04 LTS)
$ dpkg-query -W msmtp msmtp-mta gnupg2 openssl
gnupg2  2.2.4-1ubuntu1.1
msmtp   1.6.6-1
msmtp-mta       1.6.6-1
openssl 1.1.0g-2ubuntu4.1
$ lsb_release -d
Description:    Ubuntu 18.04.1 LTS
$ uname -m
x86_64
環境3(Debian sid)
$ dpkg-query -W msmtp msmtp-mta gnupg2 openssl
gnupg2  2.2.10-1
msmtp   1.6.7-1
msmtp-mta
openssl 1.1.1-1
$ lsb_release -d
Description:    Debian GNU/Linux unstable (sid)
$ uname -m
x86_64

crontab から送信される電子メールの件名を指定したい

crontab で実行されたコマンドの出力はメールで送られますが,このときの件名は「Cron <ユーザ名@ホスト名> 実行コマンド」のようになります.

crontab 例
15 7 * * *  echo "cron subject"
メール例
Subject: Cron <matoken@T430s> echo "cron subject"

cron subject
1つのhostからこんなメール飛んで来るとどれがどれかわかりづらい
Cron <matoken@T430s> nice -n 19 ionice -c 3 rsync -avxze "ssh -i……
Cron <root@T430s> nice -n 19 ionice -c 3 rsync -avxze "ssh -i……
Cron <root@T430s> nice -n 19 ionice -c 3 rsync -avxze "ssh -i……

分かりづらいので自分で件名を付けたいところです.

: を利用する

: は shell の組み込み関数で何もせず正常終了します.
コマンドなので # と違い,; の後にコマンドを書くとその後のコマンドは解釈されます.

$ :           #何もしない
$ : hoge
$ : echo hoge
$ : rm -rf *
$ : $(hostname)  #コマンドも展開せず何もしない
$ : `whoami`
$ : hoge; echo fuga   #;の後は解釈される
fuga
$ # hoge
$ # hoge; echo fuga

これをcrontabの頭に書くとこういう感じになります.

crontab
29 6 * * *  : cron subject; echo "hello"
mail
Subject: Cron <matoken@T430s> : cron subject; echo "hello"

hello

少しわかりやすくなりました.

mailコマンドを利用する

これはちょっと反則な感じがするのですが件名にコマンド実行結果を入れたり,頭の部分も書き換えたい場合はcronだけではできなさそうなのでmailコマンド経由で送信してみます.今回mailutilsのmailコマンドを利用しましたが,bsd-mailxやmutt等々ももちろん使えます.

crontab
45 6 * * *  echo "cron test" | mail -s "$(hostname)@$(whoami) cron Subject : $(date +\%F)" matoken+cron@example.com
mail
Subject: T430s@matoken cron Subject : 2018-09-13
X-Mailer: mail (GNU Mailutils 3.4)

cron test

mail コマンドの -s オプション部分が件名になります.その後ろが宛先のメールアドレス.bodyはcronの出力をパイプから受け取ります.

STDERR が別メールで届く

STDERR の出力があった場合cronでメールが送られてしまいます.mailコマンドと合わせて1度に2通届くことに.
実行コマンドの後ろに 2>&1 を付けて STDERR を STDOUT に渡してmailコマンドだけにします.

crontab
45 6 * * *  echo "cron test" 2>&1 | mail -s "$(hostname)@$(whoami) cron Subject : $(date +\%F)" matoken+cron@example.com

出力がなくてもメールが届く

crontab の場合コマンドの出力がなければメールが送られませんが,このコマンドでは NULL でもメールが送信されます.
未解決.

余録

cronで一切メールを送りたくない

場合crontabで MAILTO=”” を設定する
MAILTO=""

指定したコマンドだけメールを送りたくない

STDOUT と STDERR を /dev/null に捨てる
20 6 * * *  echo "cron subject" > /dev/null 2>&1

メールが届かない

/var/spool/mail/ユーザ名 のローカルにはメールがどとくけど,インターネット上のメールアドレスを指定しても届かない場合メールサーバの設定が出来ていないかもしれません.メールサーバ(sSMTP/msmtp等はデーモンが起動しないのでメールを受け取らず送信するだけならおすすめ)を設定してから再度試してみましょう.

環境1
$ man cron | grep -m1 cron
       cron - daemon to execute scheduled commands (Vixie Cron)
$ dpkg-query -W cron mailutils
cron    3.0pl1-128.1ubuntu1
mailutils       1:3.4-1
$ lsb_release -d
Description:    Ubuntu 18.04.1 LTS
$ uname -m
x86_64
環境2
$ man cron | grep -m1 cron
       cron - 予定されたコマンドを実行するデーモン(Vixie Cron)
$ dpkg-query -W cron mailutils
cron    3.0pl1-130
mailutils       1:3.4-1+b1
$ lsb_release -d
Description:    Debian GNU/Linux unstable (sid)
$ uname -m
x86_64

awesome WM で Light を利用して輝度調整する

以前は awesome WM での輝度調整は xbacklight を利用していたのですが,動作しなくなっていました.

$ xbacklight -get
No outputs have backlight property

最近まで ThinkPad が1台だけになって持ち運びをしなくなっていたのもあり,以下のような感じで /sys/class/backlight/intel_backlight/brightness に投げていました.

$ echo 800 | sudo tee /sys/class/backlight/intel_backlight/brightness
800

しかし,最近 T430s/X201i のジャンクを入手して補修して持ち運べる ThinkPad が出来ました :)
そうなると場所により明るさが変わったり,輝度を下げてバッテリー持ちを良くしたりしたいという感じでこまめに起動調整がしたくなりました.
他のwmのpower managerを使うと一応輝度調整できるけどあまり嬉しくないなということでちょっと探してみると, Light というものを見つけました.

Arch Linux, Fedora にはパッケージがあるようです.今回はsourceからUbuntuとDebianに導入しました.

導入
$ git clone https://github.com/haikarainen/light
$ cd light
$ ./autogen.sh
$ ./configure
$ make
$ sudo checkinstall

とりあえずそのまま既定値でbuildしました.

実行例
$ light -G  #輝度取得
56.34
$ light -A 10 #輝度 10 Up
$ light -U 10 #輝度 10 Down

問題なく動きました.他にもオプションはありますが,とりあえずこれだけ動けば問題ありません.
awesome WM の ~/.config/awesome/lua.rc に書いてみます.

rc.lua
diff --git a/rc.lua b/rc.lua
index f9a7467..9f461b9 100644
--- a/rc.lua
+++ b/rc.lua
@@ -289,8 +289,12 @@ globalkeys = awful.util.table.join(

     -- Brightness Controle
---    awful.key({         }, "XF86MonBrightnessDown", function () awful.util.spawn("xbacklight - 5") end),
 --    awful.key({         }, "XF86MonBrightnessUp",   function () awful.util.spawn("xbacklight + 5") end),
+--    awful.key({         }, "XF86MonBrightnessDown", function () awful.util.spawn("xbacklight - 5") end),
+    -- 以下の light を利用
+    -- "haikarainen/light: GNU/Linux application to control backlights" https://github.com/haikarainen/light
+    awful.key({         }, "XF86MonBrightnessUp",   function () awful.util.spawn("light -A 10") end),
+    awful.key({         }, "XF86MonBrightnessDown", function () awful.util.spawn("light -U 10") end),

     -- lock screen
     awful.key({ "Mod1"  }, "l", function () awful.util.spawn( "xscreensaver-command -activate",false) end),

awesome WM の restart をして,輝度調整ボタンを押すとちゃんと輝度がUp/Downするようになりました.
これで外出先でも困りません :)

環境1
$ $ dpkg-query -W awesome*
awesome 4.2-4
awesome-doc
awesome-extra   2017110501
$ lsb_release -d
Description:    Ubuntu 18.04.1 LTS
$ uname -m
x86_64
環境2
$ dpkg-query -W awesome*
awesome 4.2-5
awesome-doc     4.2-5
awesome-extra   2018041201
$ lsb_release -d
Description:    Debian GNU/Linux unstable (sid)
$ uname -m
x86_64

たくさん時間のずれた写真の時計合わせ

DP1M0538

先日ポタリングに行ったのですが,その時持っていったカメラの時計が初期化されてしまっていてオフセットが6年半以上ありました.
(予備バッテリーが死んでバッテリー充電中に情報が初期化されてしまう)

これを正確な撮影時間に変更します.いつもはexiv2で設定していたのですが,今回これではうまく行かなかったのでexiftimeコマンドで設定しました.

先ずカメラ内時計と実際の時計の差分を求めます.正確な時間を撮影(今回はNICTのJST Clockを撮影)した結果はこんな感じでした.

$ ls --full-time DP1M0608.JPG
-rw-r--r-- 1 mk mk 9043186 2012-01-02 10:27:01.000000000 +0900 DP1M0608.JPG

写真に写った時間 -> 2018-07-17 07:32:00

それぞれUNIX EPOCに変換して引き算をすると差分は206312699秒でした.

$ date +%s -d '2012-01-02 10:27:01'
1325467621
$ date +%s -d '2018-07-17 07:32:00'
1531780320
$ expr 1531780320 - 1325467621
206312699

いつも使っているexiv2コマンドにこの秒を指定して修正しようとしたら日付がおかしくなってしまいました.

$ cp -p ./DP1M0608.JPG /tmp/DP1M0608.JPG
$ exiv2 -a +206312699 /tmp/DP1M0608.JPG
$ exiftime /tmp/DP1M0608.JPG
exiftime: field count mismatch (DateTime)
exiftime: field count mismatch (DateTimeOriginal)
exiftime: field count mismatch (DateTimeDigitized)
exiftime: field count mismatch (ImageUniqueID)
/tmp/DP1M0608.JPG: no timestamps available
$ exif /tmp/DP1M0608.JPG|grep -i date
Date and Time       |25548:01:17 21:27:01
Date and Time       |2012:01:02 10:27:01
Date and Time (Origi|25548:01:17 21:27:01
Date and Time (Digit|25548:01:17 21:27:01

manを確認すると,秒だけの指定は駄目でちゃんと計算して指定しないといけないようです.そして日またぎ以上は指定できないような感じです.これまではせいぜい数秒から数分しかずれなかったので気づきませんでした…….

       -a time
              Time  adjustment  in  the  format  [-]HH[:MM[:SS]]. This option is only used with the 'adjust' action. Examples: 1 adds one
              hour, 1:01 adds one hour and one minute, -0:00:30 subtracts 30 seconds.
 

計算するのが面倒なので差分を秒で指定できるツールはないだろうかと探してみると,exiftags packageのexiftimeが使えそうです.

              If val is numeric, one of either y, m, w, d, H, M, or S must be used to specify which part of the date is to be adjusted.
 

実際に試してみます.

念の為ファイルを/tmpにコピーして先ずはこれで試します.

$ cp -p ./DP1M0608.JPG /tmp/DP1M0608.JPG
$ exiftime /tmp/DP1M0608.JPG
exiftime: field count mismatch (ImageUniqueID)
Image Created: 2012:01:02 10:27:01
Image Generated: 2012:01:02 10:27:01
Image Digitized: 2012:01:02 10:27:01

-vでオフセットを指定します.今回は+206312699S206312699秒進めます.

$ exiftime -v +206312699S /tmp/DP1M0608.JPG
exiftime: field count mismatch (ImageUniqueID)
Image Created: 2018:07:17 07:32:00
Image Generated: 2018:07:17 07:32:00
Image Digitized: 2018:07:17 07:32:00

大丈夫そうです.
-vだけでは実際は書き換わりません-wで実際にexifを書き換えます.

$ exiftime -v +206312699S -w /tmp/DP1M0608.JPG
exiftime: field count mismatch (ImageUniqueID)
adjust time created in /tmp/DP1M0608.JPG from
  2012:01:02 10:27:01 to 2018:07:17 07:32:00? (y/n [n]) y
Image Created: 2012:01:02 10:27:01 -> 2018:07:17 07:32:00
adjust time generated in /tmp/DP1M0608.JPG from
  2012:01:02 10:27:01 to 2018:07:17 07:32:00? (y/n [n]) y
Image Generated: 2012:01:02 10:27:01 -> 2018:07:17 07:32:00
adjust time digitized in /tmp/DP1M0608.JPG from
  2012:01:02 10:27:01 to 2018:07:17 07:32:00? (y/n [n]) y
Image Digitized: 2012:01:02 10:27:01 -> 2018:07:17 07:32:00
$ exiftime /tmp/DP1M0608.JPG
exiftime: field count mismatch (ImageUniqueID)
Image Created: 2018:07:17 07:32:00
Image Generated: 2018:07:17 07:32:00
Image Digitized: 2018:07:17 07:32:00

exiftoolコマンドでファイルのタイムスタンプも修正します.

$ exiftool "-FileModifyDate<DateTimeOriginal" /tmp/DP1M0608.JPG
Warning: Bad PrintIM size - /tmp/DP1M0608.JPG
    1 image files updated
$ ls --full-time /tmp/DP1M0608.JPG
-rw-r--r-- 1 mk mk 9043186 2018-07-17 07:32:00.000000000 +0900 /tmp/DP1M0608.JPG

OKそうです.

本番のファイルを一気に書き換えてみます.
確認メッセージが出ないようexiftime-fオプションを付けています.

$ exiftime -v +206312699S -w -f ./*.JPG
./DP1M0537.JPG:
exiftime: field count mismatch (ImageUniqueID)
Image Created: 2012:01:01 08:46:51 -> 2018:07:16 05:51:50
Image Generated: 2012:01:01 08:46:51 -> 2018:07:16 05:51:50
Image Digitized: 2012:01:01 08:46:51 -> 2018:07:16 05:51:50
 
./DP1M0538.JPG:
exiftime: field count mismatch (ImageUniqueID)
Image Created: 2012:01:01 08:55:16 -> 2018:07:16 06:00:15
Image Generated: 2012:01:01 08:55:16 -> 2018:07:16 06:00:15
Image Digitized: 2012:01:01 08:55:16 -> 2018:07:16 06:00:15
 
   :
$ exiftime -v +206312699S -w -f ./*.X3F
   :

更に,gpscorrelateでジオタグを埋め込みます.

$ gpscorrelate -g ./2018-07-16鹿屋ポタ.gpx -z +9 -n *.JPG
$ gpscorrelate -g ./2018-07-16鹿屋ポタ.gpx -z +9 *.JPG

最後にファイルのタイムスタンプを修正します.

$ exiftool "-FileModifyDate<DateTimeOriginal" ./*

うまく行ったようです :)

環境

$ dpkg-query -W exiv2 exiftags exif
exif    0.6.21-2
exiftags        1.01-6build1
exiv2   0.25-3.1ubuntu0.18.04.1
$ lsb_release -d
Description:    Ubuntu 18.04.1 LTS
$ uname -m
x86_64

HDDのS.M.A.R.Tから温度を取得して表示してくれるhddtemp

hddtempというコマンドを知りました.熱いし熱が気になる時期だしお手軽に温度を知れるのは良さそうと試してみました.

導入

$ sudo apt install hddtemp

help

$ hddtemp -h
 Usage: hddtemp [OPTIONS] [TYPE:]DISK1 [[TYPE:]DISK2]...
 
   hddtemp displays the temperature of drives supplied in argument.
   Drives must support S.M.A.R.T.
 
  TYPE could be SATA, PATA or SCSI. If omitted hddtemp will try to guess.
 
  -b   --drivebase   :  display database file content that allow hddtemp to
                        recognize supported drives.
  -D   --debug       :  display various S.M.A.R.T. fields and their values.
                        Useful to find a value that seems to match the
                        temperature and/or to send me a report.
                        (done for every drive supplied).
  -d   --daemon      :  run hddtemp in TCP/IP daemon mode (port 7634 by default.)
  -f   --file=FILE   :  specify database file to use.
  -F   --foreground  :  don't daemonize, stay in foreground.
  -l   --listen=addr :  listen on a specific interface (in TCP/IP daemon mode).
  -n   --numeric     :  print only the temperature.
  -p   --port=#      :  port to listen to (in TCP/IP daemon mode).
  -s   --separator=C :  separator to use between fields (in TCP/IP daemon mode).
  -S   --syslog=s    :  log temperature to syslog every s seconds.
  -u   --unit=[C|F]  :  force output temperature either in Celsius or Fahrenheit.
  -q   --quiet       :  do not check if the drive is supported.
  -v   --version     :  display hddtemp version number.
  -w   --wake-up     :  wake-up the drive if need.
  -4                 :  listen on IPv4 sockets only.
  -6                 :  listen on IPv6 sockets only.
 
Report bugs or new drives to <hddtemp@guzu.net>.
hddtemp version 0.3-beta15

デバイスを指定すると温度が取得できます./dev/sd[a-z]とか/dev/sd?とかも使えました.
/dev/sdd, /dev/sdeはS.M.A.R.Tを使えないUSBアダプタ経由で繋いでいるので取得できませんでした.

$ sudo hddtemp /dev/sda
/dev/sda: VB0250EAVER: 37°C
$ sudo hddtemp /dev/sda /dev/sdb
/dev/sda: VB0250EAVER: 38°C
/dev/sdb: Hitachi HDS5C3030ALA630: 39°C
$ sudo hddtemp /dev/sd?
/dev/sda: VB0250EAVER: 37°C
/dev/sdb: Hitachi HDS5C3030ALA630: 37°C
/dev/sdc: WDC WD30EZRX-00MMMB0: 40°C
/dev/sdd: WDC WD30EZRX-00DC0B0: S.M.A.R.T. not available
/dev/sde: TOSHIBA DT01ACA300: S.M.A.R.T. not available

デーモン化もできます.tcp 7634にアクセスると値が取得できます.

$ sudo hddtemp -d /dev/sda
$ nc localhost 7634
|/dev/sda|VB0250EAVER|37|C|

停止

$ pgrep hddtemp
11832
$ sudo kill 11832

複数デバイスも行けるけどそのままでは見にくいですね.

$ sudo hddtemp -d /dev/sd?
$ nc localhost 7634
|/dev/sda|VB0250EAVER|38|C||/dev/sdb|Hitachi HDS5C3030ALA630|39|C||/dev/sdc|WDC WD30EZRX-00MMMB0|41|C||/dev/sdd|WDC WD30EZRX-00DC0B0|NA|*||/dev/sde|TOSHIBA DT01ACA300|NA|*|
$ nc localhost 7634|sed -e 's/||/|\n|/g'
|/dev/sda|VB0250EAVER|37|C|
|/dev/sdb|Hitachi HDS5C3030ALA630|38|C|
|/dev/sdc|WDC WD30EZRX-00MMMB0|40|C|
|/dev/sdd|WDC WD30EZRX-00DC0B0|NA|*|
|/dev/sde|TOSHIBA DT01ACA300|NA|*|

S.M.A.R.Tの値を元にしているのでこのツールを使わず大抵の環境で入っているsmartctlでも良い気もします.

$ sudo smartctl -a /dev/sda|grep -i temp
190 Airflow_Temperature_Cel 0x0022   063   056   045    Old_age   Always       -       37 (Min/Max 31/43)
194 Temperature_Celsius     0x0022   037   044   000    Old_age   Always       -       37 (0 11 0 0 0)

環境

$ lsb_release -d
Description:    Ubuntu 16.04.4 LTS
$ uname -m
x86_64
$ dpkg-query -W hddtemp smartmontools
hddtemp 0.3-beta15-52
smartmontools   6.4+svn4214-1

StarDictからGoldenDictに移行した

The original StarDict project has recently been removed from SourceForge due to copyright infringement reports. Most of the files were lost with the demise of the project.

てことで後継のGoldenDictを試してみました.

StarDictでは辞書は英辞郎(テキストデータ版)とstardict-dic-jaを利用していました.

英辞郎の辞書は以下のページのscriptを利用してStarDict形式に変換しました.

eiji2sd_20160307.zipの中のPerl版(eiji2sd-text.pl)を利用して変換後StarDictの辞書ディレクトリにコピーしていました.以下は古めのマシンで実行しているので30分近く変換に掛かっていますが,通常はもっと短い時間で終わると思います.

$ unzip eiji2sd_20160307.zip
$ time perl eiji2sd-text.pl ../EIJIRO-1444.TXT
Now reading: yohimbine activation
Sorting...
Writing dictionary:  99% Zina
Done.

real    26m41.099s
user    3m3.413s
sys     0m7.541s
$ sudo install -b -D -g root -o root -m 0444 ./* /usr/share/stardict/dic/eijiro/

という状態でStarDictを利用していました.

GoldenDictの導入

20180511_17:05:38-1489

GoldenDictをディストリビューションパッケージで導入します.

$ sudo apt install goldendict

GoldenDictの辞書設定

導入したら早速起動して辞書の設定を行います.

メニューバーの「編集」->「辞書」で辞書ウィンドウを起動.
「ソース」タブの「ファイル」タブで「追加」ボタンを押し,辞書を追加します.GoldenDictもStarDictの辞書形式を利用できるのでStarDictの辞書ディレクトリを指定します.「/usr/share/stardict/dic」を指定しました.辞書データをGoldenDictの辞書ディレクトリに持っていってもokです.その場合は「`
追加したあと「再帰」チェックボックスにチェックをし,「今すぐ再スキャン」ボタンを押します.上部の「辞書」タブで辞書が反映されているか確認します.

20180511_20:05:16-26744

一旦ウィンドゥを閉じて,利用したいスキャンポップアップ機能を試します.タスクトレイのGoldenDictアイコンを右クリックして,「スキャン ポップアップ」にチェックが入っているのを確認して,適当な文字列を選択してポップアップウィンドウで辞書引きが出来るのを確認します.

20180511_17:05:24-20380

このままではちょっとポップアップが出る頻度が多くてうっとおしいですし,日本語入力が基本的に不可能になってしまいます.タスクトレイアイコンでOn/Offが出来ますが面倒です.

「編集」->「環境設定」の「スキャンポップアップ」タブの「すべての選択されたキーが押されているときのみポップアップを表示する」をチェックして有効にします.

20180608_14:06:17-1723

もう一つCtrl+c+cでもポップアップが表示されます.これは「ホットキー」タブの「クリップボードからの単語を翻訳するのに次のホットキーを使用します」が有効になっているためです.私はクリップボードへのコピー時につい2回cを押してこれを出してしまうので無効にしました.

20180530_05:05:23-12855

オンライン辞書の利用

「編集」->「辞書」の「ソース」タブの「Wikipedia」タブでWikipediaを引けるようになります.しかし,プリセットを有効にしても使えません.

20180511_18:05:19-17217

「アドレス」のURLをhttp://からhttps://に修正することで使えるようになりました.これが案外便利です.

20180511_18:05:40-14823shutter_18-05-11_19:35:24_001

Wikipediaが便利なので「Webサイト」タブで「ニコニコ大百科」と「はてなキーワード」も追加してみましたが表示されるまでの時間がかかりすぎるし,ヘッダーなどでコンテンツが見えづらいのでちょっとこのままでは使えなさそうです.

20180511_22:05:11-774120180511_23:05:11-26007

環境

Ubuntu 16.04 LTS arm64
Debian sid amd64

スワップファイルを作ってお手軽にスワップを増やす

RAMを大量に必要とするプログラムがあって遅くてもいいから一時的にスワップを増やしてやり過ごすことがあります.また,RAMの少ないマシンでディスクの構成をいじるのが面倒なときにもスワップファイルが使えます.ということで今回RAMが2GBでそこそこあるけど偶に使い切ってしまう(主にchromium!)のでスワップファイルを用意してみました.

スワップファイルを作成します.今回は/var/tmp/swap.imgというファイルで2GB用意しました.

$ sudo install -o root -g root -m 0600 /dev/null /var/tmp/swap.img
$ sudo dd if=/dev/zero of=/var/tmp/swap.img bs=1M count=2048
$ sudo mkswap /var/tmp/swap.img

とりあえずスワップファイルをアドホックに有効にします.

$ sudo swapon /var/tmp/swap.img 

.スワップが増えています.

$ swapon -s
Filename                                Type            Size    Used    Priority
/dev/zram0                              partition       254688  80024   5
/dev/zram1                              partition       254688  80016   5
/dev/zram2                              partition       254688  79940   5
/dev/zram3                              partition       254688  79928   5
/var/tmp/swap.img                       file            2097148 0       -1

一時的に必要な場合はこれで良いのですが,恒久的に利用したい場合はこのままでは再起動後には有効になりません./etc/fstabに設定を書いて起動時に有効になるようにします.

$ sudo vi /etc/fstab
$ grep -i swap.img /etc/fstab 
/var/tmp/swap.img     none    swap    sw      0       0

一旦swapoffでスワップを無効にしてswapon -aでfatabの設定が有効か試します.swapon -afstabのスワップの設定を全て有効にします.

$ sudo swapoff /var/tmp/swap.img
$ sudo swapon -a
$ swapon -s
Filename                                Type            Size    Used    Priority
/dev/zram0                              partition       254688  80020   5
/dev/zram1                              partition       254688  80016   5
/dev/zram2                              partition       254688  79940   5
/dev/zram3                              partition       254688  79928   5
/var/tmp/swap.img                       file            2097148 0       -1

念のため再起動してスワップファイルが有効かも試せばOKです :)

スワップファイルの利用はお手軽ですが,スワップパーティーションに比べると性能は少し落ちるので可能ならスワップパーティーションを利用したほうが良いです.RAMが増設できるならそれが一番です.

環境

$ dpkg-query -W mount
mount   2.27.1-6ubuntu3.6
$ lsb_release -d
Description:    Ubuntu 16.04.4 LTS
$ uname -m
aarch64

OpenJDK で JOSM

Javaで動作するOpenStreetMapエディタのJOSMをLinux上のOpenJDKで動かすメモです.
Debian sid amd64 / Ubuntu 16.04 LTS ARM64 で確認しています.

JOSMの入手

JOSMを次のページから入手します.josm-tested.jar をよく使っています.

OpenJDKインストール

2015年12月くらいにはこんなメッセージが出ていて現在はJava 8以上が必要になっています.

このバージョンのJavaはもうすぐサポート対象から外れます。 Java 8以上にアップグレードしてください!

ここでは OpenJDK 8 を導入

$ sudo apt install openjdk-8-jre

javaの確認

OpenJDK 8になっている.もし違うバージョンが表示されたら次の項目へ.

$ java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-8u151-b12-0ubuntu0.16.04.2-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)

javaが複数入っていてjavaコマンドの結果が想定しているバージョンではない場合切り替える

OpenJDK 8の場合はここはスキップする.

$ sudo update-alternatives --config java

JOSM起動

-Dawt.useSystemAAFontSettings=onはフォントのアンチエイリアスを有効にするオプション.LCDの場合onの代わりにlcd.

$ java -jar -Dawt.useSystemAAFontSettings=on ./josm-latest.jar
追記)
他の環境だとこの辺のどれかが使えそう(未確認)