前書き

先日、Rubyのコーディング練習がてらに、ユーザ情報を出力するコマンドの仕様をボンヤリと考えていました。

出力すべきユーザ情報には「ユーザが使用しているログインシェル」を含めようと考え、「/etc/passwd」を確認したら、ログインシェルを記載するセクションに予想外の記述がありました。その記述とは、「/usr/sbin/nologin」および「/bin/false」です。明らかに、シェルではありません。

結論から言えば、nologinコマンドとfalseコマンドはユーザログインを拒否するための記述です。システムユーザ(デーモンなど)がログインシェルを使用できる場合、セキュリティリスクがあるため、nologin/falseコマンドでログインを拒否します。

私が細かな仕様を知らなかったため、本記事ではnologin/falseコマンドの仕様に関する調査結果を説明します。

検証環境

       _,met$$$$$gg.          nao@debian 
    ,g$$$$$$$$$$$$$$$P.       ---------- 
  ,g$$P"     """Y$$.".        OS: Debian GNU/Linux 10 (buster) x86_64 
 ,$$P'              `$$$.     Kernel: 4.19.67 
',$$P       ,ggs.     `$$b:   Uptime: 5 days, 2 hours, 46 mins 
`d$$'     ,$P"'   .    $$$    Packages: 3805 (dpkg) 
 $$P      d$'     ,    $$P    Shell: fish 3.0.2 
 $$:      $$.   - ,d$$'    Resolution: 2560x1080 
 $$;      Y$b._   _,d$P'      DE: Cinnamon 3.8.8 
 Y$$.    `.`"Y$$$$P"'         WM: Mutter (Muffin) 
 `$$b      "-.__              WM Theme: (Albatross) 
  `Y$$                        Theme: Blackbird [GTK2/3] 
   `Y$$.                      Icons: hicolor [GTK2/3] 
     `$$b.                    Terminal: gnome-terminal 
       `Y$$b.                 CPU: AMD Ryzen 7 3800X 8- (16) @ 3.900GHz 
          `"Y$b._             GPU: NVIDIA NVIDIA Corporation TU107 
              `"""            Memory: 6818MiB / 64404MiB

前提:/etc/passwdの仕様

「/etc/passwd」は、ユーザ情報を管理しているファイルであり、下表に示す情報が保管されています。1行が1ユーザに対応し、下表の番号が小さいフィールドから順番に記載されています。フィールド間の区切り文字には":“が用いられます。

フィールド説明
第1フィールドユーザ名
第2フィールドパスワード用のフィールドであり、記載内容は以下のいずれか。 x:/etc/shadow にハッシュ化パスワードを記載する方式 *:アカウントを一時的に無効化 無記入:パスワード設定なし
第3フィールドユーザ番号(UID)
第4フィールドグループ番号(GID)
第5フィールドコメント(ユーザのフルネームや役割)
第6フィールドホームディレクトリのPATH
第7フィールドログインシェル(絶対PATH)

以下に、/etc/passwdの記載例を示します。

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin

nologinコマンドの仕様

nologinコマンドを実行した場合、ログインを試みたユーザが利用不可である旨のメッセージが出力されます。

$ sudo nologin
This account is currently not available.

より詳細な説明は、manページ("$ man 8 nologin"で表示するページ)に記載されています。アカウントが利用不可能な旨をメッセージで知らせるため、“politely refuse a login (ログインを丁寧に拒否する)“と説明されているのでしょう。

NOLOGIN(8)                            System Management Commands                           NOLOGIN(8)

NAME
       nologin - politely refuse a login

SYNOPSIS
       nologin

DESCRIPTION
       The nologin command displays a message that an account is not available and exits non-zero. It
       is intended as a replacement shell field for accounts that have been disabled.

       To disable all logins, investigate nologin(5).

SEE ALSO
       login(1), nologin(5).

HISTORY
       The nologin command appeared in BSD 4.4.

shadow-utils 4.5                              07/27/2018                                   NOLOGIN(8)

manには、「nologinコマンドは非0(失敗)の値を返して終了する」と記載されているので、実装を確認します。

nologinコマンドの実装

まず、ソースコードの取得方法を示し、その後にコードを示します。

(注釈):どのパッケージが、nologinコマンドを提供するかを確認
$ apt-file -F search /usr/sbin/nologin
login: /usr/sbin/nologin 

(注釈):nologinコマンドのソースコードを取得
$ apt source login

(注釈):取得したソースを確認。
    バイナリパッケージ名はloginだが、ソースパッケージ名はshadow。
$ ls
shadow-4.5  shadow_4.5-1.1.debian.tar.xz  shadow_4.5-1.1.dsc  shadow_4.5.orig.tar.xz

(注釈):取得したソースの表示
$ cat shadow-4.5/src/nologin.c
// BSDライセンスの記述は省略

#include 

#ident "$Id$"

#include 
#include 
#include 
#include 

int main (void)
{
	const char *user, *tty;

	tty = ttyname (0);
	if (NULL == tty) {
		tty = "UNKNOWN";
	}
	user = getlogin ();
	if (NULL == user) {
		user = "UNKNOWN";
	}
	openlog ("nologin", LOG_CONS, LOG_AUTH);
	syslog (LOG_CRIT, "Attempted login by %s on %s", us、er, tty);
	closelog ();

	printf ("%s", "This account is currently not available.\n");

	return EXIT_FAILURE;
}

上記の処理は、大したことをしていません。

  • ttyname()で端末デバイス名(例:/dev/pts/2)を取得
  • getlogin()でユーザ名(例:nao)を取得
  • syslog()で、ユーザがログインを試みた記録を端末名、ユーザ名付きで保存
  • 固定文字列"This account is currently not available.“を出力

syslog()は、システムからのメッセージをロギングする仕組みを利用しており、メッセージの格納先は「/etc/syslog.conf」もしくは「/etc/rsyslog.conf」の設定に従って変わります。基本的なログ出力先は「/var/log」以下であり、認証系の場合は「/var/log/auth.log」が格納先です。

実際に確認した所、以下のログが「/var/log/auth.log」に残っていました。

$ sudo cat /var/log/auth.log | grep Att
Apr 16 20:24:01 debian nologin: Attempted login by nao on /dev/pts/1

Debianはnologinコマンドのログイン拒否メッセージを変更不可

色々なサイトで、「/etc/nologin.txtを編集すれば、ログイン拒否メッセージを変更可能」と書かれています。しかし、前述の実装の限り、ログイン拒否メッセージは固定の文字列です。

	printf ("%s", "This account is currently not available.\n");

調査した結果、Debianのnologinコマンドは「/etc/nologin.txt」を確認しませんが、Linux Kernel Organizationが配布しているutil-linux版は「/etc/nologin.txt」を確認するようです(util-linux版ソースコードを参照)

[the_ad id=“598”]

falseコマンドの仕様

falseコマンドは、失敗を意味する値(1)を返して終了します。ログイン用のコマンドではなく、coreutilsパッケージが提供するコマンドの一つです。

メッセージも出さずに終了するため、nologinコマンドよりやや不親切です。しかし、悪意を持ったユーザが連続ログインを試みた際のシステム負荷はfalseコマンドの方が低く、その点でメリットがあります。

$ false       (注釈):実行しても何も表示されない

(注釈):終了ステータスの確認
$ echo $?
1

falseコマンドの実装

falseコマンドの実装方法は、トリッキーで面白いです。coreutils-8.30/src/false.cに実装がありますが、たったの2行です。

(注釈):どのパッケージが、falseコマンドを提供するかを確認
$ apt-file -F search /bin/false
coreutils: /bin/false         

(注釈):falseコマンドのソースコードを取得
$ apt source coreutils

(注釈):取得したソースの表示
$ cat coreutils-8.30/src/false.c
#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

#incldeはファイルを展開するだけなので、true.cファイルを#include行に展開しています。true.cはtrueコマンド用ソースコードであり、trueコマンドはfalseと逆の挙動をします。つまり、必ず成功を意味する値(0)を返して終了します。

true.cファイル内では、EXIT_STATUSが未定義の場合、「#define EXIT_STATUS EXIT_SUCCESS」と定義しています。

#ifndef EXIT_STATUS              
# define EXIT_STATUS EXIT_SUCCESS
#endif                           

つまり、true.cをコンパイルした場合はEXIT_STATUSは成功になり、false.cをコンパイルした場合はEXIT_STATUSは失敗となります。読めばすぐ理解できますが、このような実装は初めて見ました。

まとめ

nologin/falseコマンドの仕様差異

  • nologin/falseコマンドともに、セキュリティ対策で用いるログイン拒否コマンド。
  • nologinコマンドは、ログイン拒否メッセージ(Debian版は変更不可)を出力する。
  • falseコマンドは、失敗ステータスを返すだけで、メッセージを出力しない。