login
段々と世の中の動きが、せわしなくなってきている。もう、おせち料理の内覧会やら予約が始まってるな。競争相手に一歩でも先を行こうという魂胆。
カレンダーなんかは、普通12月に入ってから貰ったり、用意するものだろう。でも、今年は違った。早2種類のカレンダーが我が家には用意された。女房が通販で手配したもの。限定版だから、買っておかないと、12月じゃ手に入らないからと抜かす。踊らされていますなあ。
スケート連盟承認と印刷有り。ここまで書くと、分かる人には分かるだろう。
羽生君のカレンダー、卓上版と壁掛け用のやつ。ユーチューブ見て、過去のTVの録画を残すだけじゃ、ありきたりすぎるって事らしい。幸い、追っかけで、仙台に行きたいとかカナダまで見に行きたいと言い出さないだけ、ましか。
そんなものなんですかね? 某スケーターを追って、長野とか札幌とか函館まで遠征してた過去のある方。
init
shutdownを追いかけていた時、 /fastboot なんてのに出会った。何をしてるの? コードは、こんなの。
void doitfast(void) { int fastfd; if ((fastfd = open(_PATH_FASTBOOT, O_WRONLY|O_CREAT|O_TRUNC, 0664)) >= 0) { dprintf(fastfd, "fastboot file for fsck\n"); close(fastfd); } }
そしてrebootが呼ばれて、長い旅の果てに、/sbin/initにたどり着く。manには、色々書いてある。rcを見ろとかね。 /etc/rcを見るのが説明見るより早いか。
# Run filesystem check unless a /fastboot file exists. if [[ -e /fastboot ]]; then echo "Fast boot: skipping disk checks." elif [[ $1 == autoboot ]]; then echo "Automatic boot in progress: starting file system checks." do_fsck fi
この部分が、早い段階で呼び出される。何の事は無い。このファイルが存在すると、fsckを実行しませんて事だ。だから起動が速くなるのね。FreeBSDとかだと、ファイルシステムが進化してるので、裏でfsckが行われるから、基本この機構は無いはずだな。(要調査)
そして、initの大事な役目として、ユーザーがログインしてくる準備が有る。ログアウトした時の後処理もやってるのか。
In multi-user operation, init maintains processes for the terminal ports found in the file ttys(5). init reads this file, and executes the command found in the second field. This command is usually getty(8); getty opens and initializes the tty line and executes the login program. The login program, when a valid user logs in, executes a shell for that user. When this shell dies, either because the user logged out or an abnormal termination occurred (a signal), the init program wakes up, deletes the user from the utmp(5) file of current users and records the logout in the wtmp file. The cycle is then restarted by init executing a new getty for the line.
getty
そんなこんなで、追っかけていたら、gettyを見るはめになった。 /usr/src/libexec/getty/の中で、軽くloginを検索してみる。
ob6$ grep login *.[ch] init.c: { "lm", "login: " }, /* login message */ init.c: { "lo", _PATH_LOGIN }, /* login program */ main.c: login_tty(i); main.c: execle(LO, "login", "-p", "--", name, NULL, env); pathnames.h:#define _PATH_LOGIN "/usr/bin/login"
確かに、loginを起動してますなあ。でも、forkしてない。と言う事はgettyがloginに変身するんだな。何故かなと考えると、login中は、その回線を占有してるからだな。logoutとかすると、回線が空くんで、再びユーザーをその回線で受け入れ可能になる。それらの仕組みを実現してんのが、上の説明にもあるけど、initの仕事なんだな。
ごちゃごちゃ、回線の設定をしたり、リミット値を設定した上で、login: って言うプロンプトを出し、ユーザー名を入力させる。後は、loginプログラムにお任せって訳か。
loginに成功すれば、それで良し。失敗したら、syslogに記録を残してからgettyを終了って塩梅か。
login_tty(i) なんてのも有るな。manすると
The login_tty() function prepares for a login on the tty fd (which may be a real tty device, or the slave of a pseudo-tty as returned by openpty()) by creating a new session, making fd the controlling terminal for the current process, setting fd to be the standard input, output, and error streams of the current process, and closing fd. FILES /dev/pty[p-zP-T][0-9a-zA-Z] master pseudo terminals /dev/tty[p-zP-T][0-9a-zA-Z] slave pseudo terminals /dev/ptm pseudo terminal management device
これって、sshdとかで入ってくる時の入り口? ちょいと自信なし。今のターミナルは、/dev/ttyp2 なんで、slave pseudo terminals に相当。slave って、奴隷って意味あるから、使うの止そう、差別語だよと、パイソン方面で叫ばれているらしい。言葉狩りと思うんだけど、どうよ?
ちょっとttyの構造が分かっていないと理解が深まりそうにないので、先に進もう。
login
という事で、gettyから呼び出されるloginです。反対語はlogoutです。ユーザーが手打ちするlogoutは在りません。有ったとしてもそれはshellの仕業です。内部コマンドです。
その代わり、logout(3)は用意されてました。
The login() function updates the /var/run/utmp and /var/log/wtmp files with user information contained in ut. The logout() function removes the entry from /var/run/utmp corresponding to the device line.
回線に相当するutmpを削除するんだそうです。あくまでも、回線にまつわる仕組みになってるんだな。
login.c/mainを見ていくと
/* * login [ name ] * login -h hostname (for telnetd, etc.) * login -f name (for pre-authenticated login: datakit, xterm, etc.) * login -p (preserve existing environment; for getty) */
こんな注意書きが有った。昔は、telnetとかでよそのマシンに入ったな。よく行く所は、禁断のrloginとかで、手抜きしてた。
/* * If effective user is not root, just run su(1) to emulate login(1). */ if (geteuid() != 0) { : execv(_PATH_SU, av); warn("unable to exec %s", _PATH_SU); _exit(1); }
こんな事をしてた。何故って理由が示されていない。gettyから来た場合かな。
後は、だらだら(失礼)と、長編小説を読まされるがごとく続いていく。ちょいと読む気が失せた。
ああ、途中で、/etc/motd(今日のお知らせ)を出すのは、loginの仕業なのね。それから、/etc/login.confを見て、認証の仕方を変えたり、リミット値を設定したりしてる。最後は、
execlp(shell, tbuf, (char *)NULL); err(1, "%s", shell);
で、loginプログラムから、指定したshellに変身してる。これも、回線の保持を目的にしてるんだろうね。
ssh族
早々loginに見切りを付けて、多用してるssh系を見るか。族って書いたのは、色々なコマンドやユーティリティが提供されてるからだ。
ob6$ find /usr/src/usr.bin/ssh -type d /usr/src/usr.bin/ssh /usr/src/usr.bin/ssh/scp /usr/src/usr.bin/ssh/ssh /usr/src/usr.bin/ssh/moduli-gen /usr/src/usr.bin/ssh/sftp /usr/src/usr.bin/ssh/sftp-server /usr/src/usr.bin/ssh/ssh-add /usr/src/usr.bin/ssh/ssh-agent /usr/src/usr.bin/ssh/ssh-keygen /usr/src/usr.bin/ssh/ssh-keyscan /usr/src/usr.bin/ssh/ssh-keysign /usr/src/usr.bin/ssh/ssh-pkcs11-helper /usr/src/usr.bin/ssh/sshd
OpenSSH 10月に出るであろうOpenBSD6.4で新しいのになるのかな。
上で、dirだけを調べてみたけど、ソース類は、ssh/ の下に、260個余りが平置きと言うか、べた置きされてる。dirの中には、そのコマンドを作成する為のMakefileだけが置いてあるって寸法。
例えば、旧式のloginに変わる安全コマンドssh(remote login program)を作成するには、
.PATH: ${.CURDIR}/.. SRCS= ssh.c readconf.c clientloop.c sshtty.c sshconnect.c sshconnect2.c mux.c SRCS+= atomicio.c authfd.c bufaux.c buffer.c compat.c dns.c fatal.c \ hostfile.c key.c msg.c readpass.c utf8.c SRCS+= ${SRCS_BASE} ${SRCS_KEX} ${SRCS_KEXC} ${SRCS_KEY} ${SRCS_KEYP} \ ${SRCS_KRL} ${SRCS_PROT} ${SRCS_PKT} ${SRCS_UTL} ${SRCS_PKCS11} : .if (${OPENSSL:L} == "yes") LDADD+= -lcrypto DPADD+= ${LIBCRYPTO} .endif LDADD+= -lutil -lz DPADD+= ${LIBUTIL} ${LIBZ}
sshコマンドで、ケルベロス(地獄の番犬)認証も追加出来るのね。そんな大がかりじゃなければ、OPENSSLのライブラリィーを追加しとけとMakefileは言っています。
これだけのソースを読んで、どんな事をやってるか調べろ? 尻尾を巻いて逃げ出しましょ。 暗号やらネットワークやらターミナル関係やらが、きっと渦巻いていて降参するに決まってますから。
su doas sudo
sshは大変そうなので、loginの所に出て来た su を追いかけてみるか。suのお仲間にdoasが居る。おっとこれは、OpenBSD限定だった。一般的にはsudoだな。
いつもの通りに、su一式を/tmpに持ってきて、gdbにかけられるようにMakefileに小細工。 そして、出来たものに、少々、手を加える。
ob6$ doas chown root a.out doas (sakae@ob6.localdomain) password: ob6$ doas chmod 4555 a.out doas (sakae@ob6.localdomain) password:
suはオーナーさんがroot、そしてrootさんの権利で実行出来るように権限を付加する。 ちゃんと動くか、確認。
ob6$ ./a.out a.out: invalid script: /usr/libexec/auth/login_passwd Sorry ob6$ su Password: ob6# id uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest) ob6# exit ob6$ id uid=1000(sakae) gid=1000(sakae) groups=1000(sakae), 0(wheel), 5(operator)
自前で用意したa.out(su)では、見たことも無いエラーに見舞われた。ごめんと言われてもねぇ。一応、既存のsuを確認。ちゃんとrootになった。id関係を確認してもrootそのものである。 exitして元に戻ると、普通になった。あれ? 何気にexitしたけど、これってshellをおさらばする合図だよな。と言う事は、loginがsuを実行してもいいってのも頷けるな。
それより、不思議なエラーを何とかせい。
ob6$ ls /usr/libexec/auth/ ls: /usr/libexec/auth/: Permission denied ob6$ doas ls /usr/libexec/auth/ doas (sakae@ob6.localdomain) password: login_activ login_lchpass login_reject login_token login_chpass login_passwd login_skey login_yubikey login_crypto login_radius login_snk
auth領域って、一般ユーザー立ち入り禁止領域。その中に確かにエラーを出したlogin_passwdってのが存在してた。全くの言いがかりではなさそう。それはそうと、このauth領域に結集してるやつは、ログイン関係の管理スクリプトだな。昔で言うと関所のお役人。今風に言うと、イミグレにいる不愛想な入国審査官か。(あっ、ハワイのその人は、アロハシャツ着て、愛想がよかったか)
login_passwd(8)によると、login、su、ftpdとかに使われる標準の認証手順みたいだ。 どうせgdbにかけられるよう構成してるんで、gdbで追い詰めた方が早いか。
Breakpoint 2, verify_user (from=0x1172120a9310 "sakae", pwd=0x11721ac67b00, style=0x0, lc=0x1172c27a3ae0, as=0x1172bad96000) at su.c:357
378│ auth_verify(as, style, NULL, lc->lc_class, (char *)NULL); 379│ authok = auth_getstate(as); 380│ if ((authok & AUTH_ALLOW) == 0) { 381│ if ((cp = auth_getvalue(as, "errormsg")) != NULL) 382│ fprintf(stderr, "%s\n", cp); 383├───────────────> return(1); 384│ }
378行目を実行した時に、上記のエラーメッセージが出て来て、エラーリターンですよ。 なんか初見の事ばかりで、よく分からんなあと思いながら、何気にchmod(1)してたんだ。 そしたら、気になる記述が。
In addition to the file permission modes, the following mode bits are available: 4000 Set-user-ID on execution. 2000 Set-group-ID on execution. 1000 Enable sticky bit; see sticky(8) and chmod(2). The execute bit for a directory is often referred to as the "search" bit. In order to access a file, a user must have execute permission in each directory leading up to it in the filesystem hierarchy. For example, to access the file /bin/ls, execute permission is needed on /, /bin, and, of course, the ls binary itself.
このファイルが置かれる場所の記述。ひょっとして/tmp下なんて言う、公共施設にsuなんて言う 危険な代物を置いちゃいけないんじゃなかろうか。こういう連想が野生の勘で働いた。 もし、手心を加えたsuを、だれでもおける/tmpに設置して、実行出来ちゃったら。。。怖い事ですねぇ。
そんな事を思って、a.outを自dirに置いてみた。
ob6$ /home/sakae/a.out Password: ob6# id uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest) ob6# exit
やったーーー。
(gdb) r Starting program: /home/sakae/a.out a.out: invalid script: /usr/libexec/auth/login_passwd Sorry [Inferior 1 (process 14909) exited with code 01]
と思って、gdb上から走らせたら、また小癪なやつが出現。/var/log/messages上に出て来た報告。
Sep 26 15:36:57 ob6 su: cannot stat /usr/libexec/auth/login_passwd: Permission denied Sep 26 15:36:57 ob6 su: /usr/libexec/auth/login_passwd: path not secure
gdb上からの実行と常用shellからの実行で挙動が違うんだな。gdbはクリチカルな事をやってるので、怒りに触れたのでしょう。
取り合えず走らせて、流れを追うだけなら、rootになって行えばよいので、それで実行したよ。 我ながらトホホな所が有るけど、一般ユーザーがrootに化ける瞬間を捉えられました。
272├───────────────────────> if (setusercontext(lc, 273│ pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK) 274│ auth_err(as, 1, "unable to set user context 275│ }
これが要の部分。この後環境の整備が有って
336│ if (pwd->pw_uid && auth_approval(as, lc, pwd->pw_name, "su") <= 0) 337│ auth_err(as, 1, "approval failure"); 338│ auth_close(as); 339│ 340├───────> execv(shell, np); 341│ err(1, "%s", shell);
新しいshellに化ける瞬間。
ついでにdoasを見ておく。/etc/doas.confの解析と適用部分が、suより増えているけど、流れはsuと一緒。
if (setusercontext(NULL, pw, target, LOGIN_SETGROUP | LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK | LOGIN_SETUSER) != 0) errx(1, "failed to set user context for target"); : execvpe(cmd, argv, envp); if (errno == ENOENT) errx(1, "%s: command not found", cmd);
forkして実行って流れではなく、execvpeで変身するスタイルでした。
/etc/ttys
initが扱うデバイス
# name getty type status comments # console "/usr/libexec/getty std.9600" vt220 off secure ttyC0 "/usr/libexec/getty std.9600" vt220 on secure : ttyCb "/usr/libexec/getty std.9600" vt220 off secure tty00 "/usr/libexec/getty std.9600" unknown off tty01 "/usr/libexec/getty std.9600" unknown off : tty07 "/usr/libexec/getty std.9600" unknown off ttyp0 none network ttyp1 none network : ttyTZ none network
ttyCnが、コンソールに結び付いた仮想の端末 Ctl+Alt+Fn で切り替えられる。OSが提供するtmuxみたいなやつか。
tty00-07は、リアルな回線。普通はRS232Cで接続かな。
そして、ttyp0-TZまで多数あるやつは、いわゆるpty(4)になるのかな。疑似端末とかいうの。 sshとかで接続すると、これのお世話になる。 実体は、2本のパイプだと思う。パイプは信号が流れる方向が有るんで、それぞれ逆向きにながれるパイプ2本をセットにする。(エラー出力が有るから、実際は3本かな)
片方をマスター、もう一方をスレーブと称するのだな。そして多分マスター側に、流れをコントロールする制御機構が入っているのだろう。
雑な方法だけど、sshのソース群から、/dev/tty (スレーブ)を語句を探してみた。
ob6$ grep -l /dev/tty /usr/src/usr.bin/ssh/*.[ch] /usr/src/usr.bin/ssh/readpass.c /usr/src/usr.bin/ssh/sshlogin.c /usr/src/usr.bin/ssh/sshpty.c
スレーブ側がsshクライアントに接続。マスター側は、対向するsshd側に有るんだな。きっとそういう事だろう。
細かい事は、kern/tty_pty.c を見ろって事かな。冒頭に
/* * pts == /dev/tty[p-zP-T][0-9a-zA-Z] * ptc == /dev/pty[p-zP-T][0-9a-zA-Z] */
こんな事が書いてある1000行のソースでした。
emacs tips
tipsと言うより、オイラーが今まで知らなかった部分。この所、管理者が使うコマンドを見てる。rootじゃなければ動かないとか、時間制限があるとか、gdbにかけられるように再コンパイルして、gdbで追うって方法が取りにくい。
そこで、ソースをじっと眺める事になる。mainから読み下して、大体の流れを追うって方法ね。 途中で出会う関数をちょいと覗いてから、また元に戻る。
こういう事をemacsでやるには? 或る特定の場所でマークを設定すれば,後で容易にその位置へ移動できる。C-x C-x と入力すれば設定したマークへカーソルを移動でき,もう一度 C-x C-x と入力すれば元の位置へ戻る。
あるいは、C-SPCでマークして、何処かへ飛んで行く。とび先を堪能したら、C-u C-SPC で、戻ってくる。どちらでも可能。
それから、検索用単語を取り込んで検索。 カーソルが単語の先頭にあるなら、C-s C-w で、検索対象の単語を取り込んでくれる。 C-s C-w の後に C-w を入力するたびに、取り込む単語が長くなる。
C-sしてから、一文字づつ検索文字を入力してくと、そのたびに画面がパラパラして鬱陶しい。そういう時は、C-sRETsearchRET これで、迷わずsearchの所まで移動する。後は、C-sで、次を探させればよい。
正規表現での検索。C-M-s 関数名を探すなら、^w.*( ぐらいかな。面倒臭い。 こんな荒い指定でも大概は捉えてくれる。但し、int main() みたいに、型と関数名を同一行に書いたのはだめだ。