sd-loginでログインセッションのStateの監視をする
loginctl
やsystemd/sd-login.h
のAPIを用いてログインセッションなどの現在状態が取得できる。加えて、monitor APIが用意されていて、各種変更の検知/通知の受け取りが可能となっている。
sd_login_monitor_new
で監視オブジェクトを生成poll
でイベントを待ち受ける
印象としてはかなり荒い単位で通知がくる。poll
が返ったら自分が監視対象としたい値を取得し、現在どんな値か、変化があったかを自分で
判別する必要がある。
環境
準備
systemdの開発用パッケージが必要なのでインストールする。
$ sudo yum install systemd-devel
ビルドするときはsystemdをリンクする。
$ g++ -Wall main.cpp -lsystemd
ログインStateの監視
sd_login_monitor_new
の第一引数で監視する値を絞れる。ここではユーザーのログイン状態を監視するのに"uid"
を渡している。
この値は他の関数群と関係していそうで、"uid"
ならsd_uid_get_***
系の関数で取れる値を監視できるんじゃなかろうかと思う(が確証なし)。
#include <systemd/sd-login.h> #include <poll.h> #include <unistd.h> #include <cstdlib> #include <iostream> int main() { // create monitor instance sd_login_monitor *mon = 0; const int ret = sd_login_monitor_new("uid", &mon); if (ret >= 0 && mon) { // set up pollfd struct pollfd pfd = {}; pfd.fd = sd_login_monitor_get_fd(mon); pfd.events = sd_login_monitor_get_events(mon); const uid_t uid = getuid(); for (;;) { // reset the wakeup state sd_login_monitor_flush(mon); // wait infinitely const int r = poll(&pfd, (nfds_t)1, -1); if (r > 0 && pfd.revents != 0) { std::cout << "poll: " << r << ", revents: " << pfd.revents << std::endl; // uid state char *state = 0; const int ret = sd_uid_get_state(uid, &state); if (ret >= 0 && state) { std::cout << "State is: " << state << std::endl; free(state); } } } sd_login_monitor_unref(mon); } }
プログラムを実行して、「ユーザースイッチ -> ユーザー切り替えを行わずに再度ログイン」したところ、以下のように出力された。監視対象の状態が多いので、(イメージよりも)不意に呼ばれたりする。
$ ./a.out poll: 1, revents: 1 State is: online poll: 1, revents: 1 State is: online poll: 1, revents: 1 State is: online poll: 1, revents: 1 State is: active poll: 1, revents: 1 State is: active
タイムアウト時間の設定値
ドキュメントを見るとsd_login_monitor_get_timeout
で返る値から計算するようになっているけど、これはイマイチ動作確認できず。試した環境だと計算上-1になって、結局無限に待つことになった。
参考
wの元utmp
Linuxのログイン情報の取得について調べているときに、w
コマンドの実装について気になった。巡り巡って/var/run/utmp
とgetutent
にたどり着くんだけど、ファイルについてはw
コマンドのman pageに記載があった。
ファイルが特定できたときに、まあ、とりあえずとcat /var/run/utmp
打ったらコンソールが化けました。バイナリやんか...tmuxから打って大変なことになった。
getutxentでログインユーザーの情報をえる
getutent
とgetutxent
があるがドキュメントの通りgetutxent
を使う。
#include <utmpx.h> #include <iostream> int main() { setutxent(); struct utmpx * ut = 0; while((ut = getutxent()) != NULL) { std::cout << ut->ut_user << ", type:" << ut->ut_type << ", session:" << ut->ut_session << ", line:" << ut->ut_line << ", id:" << ut->ut_id << std::endl; } endutxent(); }
ビルドして実行すると以下のように出力される。ut_sessionはなんでか0でした。ログイン中のユーザー名やpidなどが取得できる。
$ ./a.out reboot, type:2, session:0, line:~, id:~~ runlevel, type:1, session:0, line:~, id:~~ ayataka, type:7, session:0, line::0, id: ayataka, type:7, session:0, line:pts/0, id:/0 test, type:7, session:0, line::1, id: test, type:8, session:0, line:pts/4, id:/4 test, type:7, session:0, line:pts/6, id:/6 ayataka, type:7, session:0, line:pts/8, id:/8
systemdのsd-loginでGUIセッション情報を取得する
loginctl
で色々とセッション情報が取得できるのはわかったけど、じゃあプログラムから参照する方法はなんだと調べて行ったらsystemdのsd-loginに行き着いた。
ライセンスはLGPL v2.1。GitHubの表示がGPL 2.0になっているけどよくよく見ると初期のころ(?)にLGPLに変更されている。公式ページの方で確認できる。
環境
準備とビルド
開発用パッケージが必要なのでインストールする。
$ sudo yum install systemd-devel
ビルドするときはsystemdをリンクする。
$ g++ -Wall main.cpp -lsystemd
セッションのアクティブ状態を取得する
セッションIDが必要だけどgetsid
の値とは別物。sd-loginのAPIを使って、(ここでは)pidから取得している。
sd_session_is_active
の値をファイルに書き出しつつ、ユーザーを変更すると、得られる値が変化することが観察できる。
#include <systemd/sd-login.h> #include <sys/types.h> #include <unistd.h> #include <cstdlib> #include <iostream> int main() { const pid_t pid = getpid(); char *session = 0; const int err = sd_pid_get_session(pid, &session); if (err >= 0 && session) { std::cout << "session: " << session << ", is_active: " << sd_session_is_active(session) << std::endl; free(session); } }
ユーザーIDからセッションのStateを取得する
もう一つ。こちらはユーザーIDからStateの値を取得する。ログイン中でアクティブならactive、ログインしたままユーザーを変更するとonlineが返る。
こちらもStateの値をファイルに書き出しつつ、ユーザーを変更することで値の変化を観察できる。
#include <systemd/sd-login.h> #include <sys/types.h> #include <unistd.h> #include <cstdlib> #include <iostream> int main() { const uid_t uid = getuid(); char *state = 0; const int err = sd_uid_get_state(uid, &state); if (err >= 0 && state) { // forgraound user's state is "active" // background user's state is "online" std::cout << "uid: " << uid << ", state: " << state << std::endl; free(state); } }
参考
loginctlでGUIセッションの情報を取得する
LinuxでGUI表示中のユーザーかどうか(セッションがアクティブかどうか)を調べる方法がないかと調べて行ったらloginctl
に行き着いた。Linuxの(というかsystemdのか?)セッションに詳しくないのでフレーズの使い方が微妙なんだけど、概ね必要な情報は取れてたのでまとめておく。
loginctl list-sessions
でGUIログイン中のセッションリストを取得できるloginctl show-session [ID...]
で指定したセッションの詳細が表示できる/run/systemd/sessions/
以下のファイルからshow-session
の情報が取得できる- が、「# This is private data. Do not parse.」
環境
- CentOS 7.6.1810
- systemd 219 (loginctl --version)
systemdに依存しているので、systemdのシステムなら通じるはず。逆にsystemdではないシステムでは全く通じない。
system-logindのloginctlコマンド
loginctl
コマンドで情報を取得できる。サブコマンド形式になっていて、セッションに関するものは以下。このうちlist-sessions
とshow-session
あたりで見ていく。
$ loginctl -h ... Session Commands: list-sessions List sessions session-status [ID...] Show session status show-session [ID...] Show properties of sessions or the manager activate [ID] Activate a session lock-session [ID...] Screen lock one or more sessions unlock-session [ID...] Screen unlock one or more sessions lock-sessions Screen lock all current sessions unlock-sessions Screen unlock all current sessions terminate-session ID... Terminate one or more sessions kill-session ID... Send signal to processes of a session ...
loginctl list-sessions
でセッションのリストを表示する
セッションのリストを表示する。以下はayatakaユーザーとtestユーザーで同時にログインしている状態。
$ loginctl list-sessions SESSION UID USER SEAT 1 1000 ayataka seat0 47 1001 test seat0 c8 42 gdm seat0
loginctl show-session
でセッション情報を取得する
list-sessions
でセッションIDを調べたら、show-session
で詳細情報を取得する。この中のActiveがyesだったりStateがactiveだと表示中。非表示中だとnoとかonlineとかになる。
詳細な情報とか意味は公式ドキュメントで。今回は細かいところは理解していない。
$ loginctl show-session 1 Id=1 User=1000 Name=ayataka Timestamp=Sat 2019-03-02 21:56:39 JST TimestampMonotonic=151218846 VTNr=1 Seat=seat0 Display=:0 Remote=no Service=gdm-password Scope=session-1.scope Leader=30172 Audit=1 Type=x11 Class=user Active=yes # <- yes State=active # <- active IdleHint=no IdleSinceHint=1551587104930805 IdleSinceHintMonotonic=19749233850 LockedHint=no
/run/systemd/sessions/
以下のファイル
セッションの情報は/run/systemd/sessions/
以下にセッションIDごとにファイルが存在していて、一応そこから参照できる。ただし「# This is private data. Do not parse.」。
$ cat /run/systemd/sessions/1 # This is private data. Do not parse. UID=1000 USER=ayataka ACTIVE=1 STATE=active REMOTE=0 STOPPING=0 TYPE=x11 CLASS=user SCOPE=session-1.scope FIFO=/run/systemd/sessions/1.ref SEAT=seat0 DISPLAY=:0 SERVICE=gdm-password VTNR=1 LEADER=30172 AUDIT=1 REALTIME=1551531399727614 MONOTONIC=151218846
ログアウト済みのセッション情報
実は記事作成中、list-sessions
の結果は以下のようになっていた。testユーザーが2つある...
$ loginctl list-sessions SESSION UID USER SEAT 1 1000 ayataka seat0 47 1001 test seat0 c8 42 gdm seat0 29 1001 test seat0 # <- なんぞこれ?
中身を表示したところ、どうやら一度ログインしてログアウトした時の情報が残ってるっぽい。
$ loginctl show-session 29 Id=29 User=1001 Name=test Timestamp=Sun 2019-03-03 09:57:15 JST TimestampMonotonic=12154020744 VTNr=2 Seat=seat0 Display=:1 Remote=no Service=gdm-password Scope=session-29.scope Leader=35515 Audit=29 Type=x11 Class=user Active=no State=closing # <- closing IdleHint=no IdleSinceHint=1551586915841192 IdleSinceHintMonotonic=19560144236 LockedHint=no
OpenSSLを使った暗号化
- 本記事に記載のOpenSSLはバージョンが古いです
- v1.1.1dのAPIについてはこちらの記事をどうぞ
OpenSSLを使ってAESで暗号化。
暗号化
#include <openssl/evp.h> EVP_CIPHER_CTX ctx = {}; EVP_CIPHER_CTX_init(&ctx); // 暗号化の設定で、EVP_aes_128_ecb等いろいろ const unsigned char iv[16] = {}; EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, key, iv); // ブロックサイズで割り切れる部分の処理 int outl = 0; EVP_EncryptUpdate(&ctx, encrypted, &outl, data, data_size); // 最後のブロックの処理で、PKCSパディングされる。 int pad = 0; EVP_EncryptFinal_ex(&ctx, encrypted + outl, &pad); EVP_CIPHER_CTX_cleanup(&ctx);
AESはブロック暗号で、16バイトづつ処理される。
16の倍数分はEVP_EncryptUpdate
で処理されて、パディングを含んだ最後の部分はEVP_EncryptFinal_ex
で処理される。
OpenSSLはPKCSでパディングする。PKCSのパディング処理は足りないバイトの値で足りない分を埋めるんだけど、足りないバイトが0(データサイズが16の倍数)の場合は、1ブロックまるまるパディングが追加されるので、暗号化データのサイズには注意が必要になる。
復号化
EVP_CIPHER_CTX ctx = {}; EVP_CIPHER_CTX_init(&ctx); // 暗号化の設定 const unsigned char iv[16] = {}; EVP_DecryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, key, iv); // ブロックサイズで割り切れる部分の処理 int outl = 0; EVP_DecryptUpdate(&ctx, decrypted, &outl, data, data_size); // 最後のブロックの処理で、PKCSパディングのサイズを返す // パディングのデータは処理されないので、後処理は自分で行う int last = 0; EVP_DecryptFinal_ex(&ctx, decrypted + outl, &last); memset(decrypted + outl + last, 0, decrypted_size - outl - last); // remove padding EVP_CIPHER_CTX_cleanup(&ctx);
復号もEVP_DecryptUpdate
とEVP_DecryptFinal_ex
に分かれていて、EVP_DecryptFinal_ex
はパディングの値を返す。ただ、サイズを返すだけでデータ自体は処理されないので適宜処理してやる。
参考
Win32 APIを使ってOSを再起動する
ちょっと必要になって調べてた。呼び出す関数はExitWindowsEx
なんだけど特権の調整が必要で単純に呼び出すだけでは成功しない。
ExitWindowsEx
から調べていって、LUID_AND_ATTRIBUTES
のところで...
Its meaning is dependent on the definition and use of the LUID
といわれて、SE_SHUTDOWN_NAME
で調べたらサンプルが出てきたというオチ。以下はサンプルベースでは無くて、それまでに作っていたコード。
#include <Windows.h> int main() { HANDLE token = NULL; OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token); if (token) { LUID luid = {}; LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &luid); LUID_AND_ATTRIBUTES attr = { luid, SE_PRIVILEGE_ENABLED }; TOKEN_PRIVILEGES p = {}; p.PrivilegeCount = 1; p.Privileges[0] = attr; AdjustTokenPrivileges(token, FALSE, &p, 0, 0, 0); CloseHandle(token); } // 第2引数は値が豊富な上に、推奨される組み合わせがある ExitWindowsEx(EWX_REBOOT, SHTDN_REASON_MAJOR_SYSTEM | SHTDN_REASON_MINOR_NETWORK_CONNECTIVITY); }
ExitWindowsEx
の第2引数はパターンが豊富なのできちっとリファレンスを参照したほうが良い。
ExitWindowsEx
にはなじみが無かったんだけど、手札として持っておくと何かあったときに使えそうな感じ。
結局SE_SHUTDOWN_NAME
とSE_PRIVILEGE_ENABLED
を結びつける文書って見つからなかったんだけど、他の値を使うときはどうなるのか...
参考
C#のラムダをWin32呼び出しで使う
あたり前っちゃーあたり前なんですが、Win32を呼び出す際の選択肢として、なぜかラムダを使うという選択肢が思いつかなかった。
delegate Boolean EnumWindowProc(IntPtr hwnd, IntPtr lp); [DllImport("User32.dll")] private static extern Boolean EnumWindows(EnumWindowProc handler, IntPtr lparam); public void Enum() { int count = 0; EnumWindows( (hwnd, lp) => { ++count; return true; }, IntPtr.Zero); Console.WriteLine(count); }
処理内容が小さい場合はもちろん、呼び出し処理を完全にラップして抽象化する場合にも使える。コールバック関数がそこだけ必要ってケースも多いので利便性高い。