Linux で遊ぼう

これは個人的な覚え書きです。

はじめに

この文章の目的です。
古くなって余ったPCがあれば家庭内Linuxサーバの運用が楽しいと思います。
ディストリビューションはDebianを使うことにします。
ネットワーク構築、Webサービス、DNSサービス、Wikiサービス、MySQLデータベースをやっていきます。
Linuxの操作やプログラミングの知識はある程度ある前提です。
ある程度の知識がある人でせっかくだから何かやってみようという人が主な対象です。
家庭内LANを想定していますのでセキュリティについてはあまり考えません。

ネットワーク基礎

ネットワークの基礎知識について書きます。
ネットワークはルーターと呼ばれるハードウェアで繋がります。
ルーターにはMACアドレスと呼ばれる個別の番号がついていて他のルーターと接続し情報交換できます。
しかし、MACアドレスだとルーターが故障するなどした場合にMACアドレスの変更が面倒です。
よって、IPアドレスと呼ばれる固有のアドレスを結びつけます。
MACアドレスとIPアドレスを結び付けて使い、ルーターが壊れたら新しいルーターのMACアドレスとIPアドレスを結び付け直します。
こうすることでルーターを変更しても同じIPアドレスを使い続けることができます。
IPアドレスは現在IPv4が使われていますが、より多くのアドレスを扱えるIPv6も使われるようになりました。
IPv4はPPPoE方式で接続されますが、IPv6のIPoE方式はより高速です。
IPv4は「0.0.0.1」などと表記し、IPv6は「0000:0000:0000:0000:0000:0000:0000:0001」などと表記します。
IPv6では連続した0を省略するために1か所のみ::をつかうことができ、上記例は「::1」と表記することもできます。
IPv6も普及してきているのでIPv4とIPv6の両方でネットワークを考える必要があります。
さて、このIPアドレスですが、企業などの場合は固定のIPアドレスで構いませんが、個人のIPアドレスは変動します。
個人にIPアドレスが固定されないようにIPアドレスを与えるプロバイダーが意図的に行っています。
個人のIPアドレスが固定されると個人を特定してアクセス拒否を行ったり自由を奪うこともできるようになるからです。
企業のIPアドレスはアクセスされるのが前提ですが、個人のIPアドレスはアクセスするのが前提なので固定である必要はありません。
個人のIPアドレスは一定の期間によって自動的に変更され、そのつど個人のルーターのMACアドレスと結び付けられます。
これまでは個人のルーターによってプロバイダーが提供するIPアドレスを使い世界のネットワークとつながる方法について書きました。
ルーターとプロバイダーによって世界につながるネットワークをWANと呼びます。
一方、家庭内で繋がるネットワークをLANと呼びます。
WANとLANでは使用できるIPアドレスが異なります。
例えば、「192.168.0.0~192.168.255.255」はLANで使用することができるIPv4のIPアドレスです。
ルーターは内部のIPアドレスを自動で割り振る機能を持っておりこれをDHCPと呼びます。
よって、ルーターで機器を接続する場合にそれぞれの機器にIPアドレスを設定する必要はありません。
LAN内のIPアドレスは固定することもでき今回サーバにするPCはIPアドレスを固定することにします。
IPアドレスを固定するにはルーターの設定で固定するIPアドレスを自由に決めサーバにするPCのMACアドレスを登録します。
外部からアクセスされないか不安になるかもしれませんが、ルーターでアクセスできる設定にしない限り外部からアクセスはされません。

Linuxサーバの遠隔操作

TeraTermでの接続について。
Linuxはサーバとしては優秀なのですが、クライアントはWindowsが優秀なのでWindowsを使いたいと思います。
TeraTermというソフトを使うとWindows窓でネットワーク内のLinuxPCの画面を操作することができます。
ファイルの送受信や文字列のコピー&ペーストにも対応しています。
利点はLinuxPCにディスプレイ、キーボード、マウスが必要ないこと。
WindowsPCでネット検索をしながらLinuxサーバのテストができたりなど使い勝手が良いことです。
通信の暗号化もできますが家庭内LANなので平文でも構わないと思います。

コマンドを使う

基本的なコマンドのまとめ。
ディレクトリ/etcへの移動
cd /etc
ひとつ上のディレクトリへの移動
cd ../
ひとつ下のディレクトリabcへの移動
cd ./abc
現在のディレクトリのファイル一覧を見る。
ls
ファイルの内容を表示
cat filename
ファイルを削除
rm filename
ファイルを強制削除
rm -r filename
ファイルを編集
nano filename

ネットワークの確認方法

ネットワークの確認方法。
まず、WAN側のIPアドレスですが、ルーターの設定画面で得るか、確認サイトで確認できます。
LAN側のLinuxサーバPCのMACアドレス、IPv4アドレス、IPv6アドレスは以下のコマンドで調べることができます。
ip a
ルーターのIPアドレスは以下のコマンドで調べます。
ip r
LAN側のWindowsPCのMACアドレス、IPv4アドレス、IPv6アドレスは以下のコマンドで調べることができます。
物理アドレスと書かれているのがMACアドレスのことです。
ディフォルトゲートウェイと書かれているのがルーターのIPアドレスのことです。
ipconfig /all
正常に通信が行えているの確認はLinuxもWindowsも同じコマンドです。
例えば「192.168.0.1」への通信を確認したい場合は以下のコマンドで調べます。
ping 192.168.0.1
どのPCがどのIPアドレスかは常に確認できるようにしておくと良いでしょう。

パッケージ管理

パッケージの管理について書きます。
Linuxには便利な管理ツールが揃っていますが、最初からインストールされているわけではありません。
パッケージ管理ツールのaptを使うと簡単に必要なツールがインストールできます。
例えば、ポートスキャンツールのnmapをインストールしたいとします。
以下のように入力するとnmapがインストールされます。
apt install nmap
逆にアンインストールは以下のようにします。
apt purge nmap
aptは便利ですが、aptでインストールできない場合もあります。
その場合はそれぞれのツールのインストール方法を調べてください。

サービスを使う

cronサービスを使ってみる。
cronは時間を設定して定期的にコマンドを実行できるサービスです。
cronが起動しているかどうかは以下のコマンドで調べます。
/etc/init.d/cron status
statusで状態を調べます。他に、startで起動、restartで再起動、stopで停止が可能です。
この位置のcronは単なるスクリプトでcatで中身を見ることができます。
実際に動作するcronは/usr/sbin/cronであり、これはバイナリファイルです。
現在起動しているサービス一覧は次のコマンドで見れます。
systemctl
システム起動時の設定ですが/etc/rc3.d/S01cronはランレベル3でcronを起動する設定になります。
サービスを起動したくなければファイル名のSをKに変えるとサービス停止の設定になります。
01を02にすると他の01のサービスを起動した後に02のサービスが起動するという優先度になります。
cronサービス自体の設定は次のコマンドからテキスト編集できます。
crontab -e
記述は 分、時、日、月、曜日、コマンド の順で記述します。
例えば、以下は月、水、金曜日の8時から16時まで2時間おきにディスク容量を記録します。
0 8-16/2 * * 1,3,5 date >> a.txt
0 8-16/2 * * 1,3,5 df >> a.txt

Webサービスを立ち上げる

apache2を使ってみる。
Webサービスをapache2で立ち上げてみます。
apt install pache2
apache2もサービスなので起動や停止の方法はcronと同じです。
WebサーバのIPアドレスが「192.168.0.2」の時、クライアントのブラウザから次のURLでアクセスできます。
http://192.168.0.2/
表示されるページがスタートページになるindex.htmlです。
これを読むとindex.htmlの位置と設定ファイルapache2.confの位置が書いてあります。
例えば、位置が/var/www/htmlだとすると、これ以降にHTMLファイルを配置すれば良いわけです。
Webサーバは80番のポートを使用しますのでポートが有効になっているかも調べたいです。
ネットワークの状況は次のコマンドで確認できます。
ss -atn
逆にクライアント側からnmapでサービスのポートを調べることができます。
ポートの調査はかならず家庭内のIPアドレスに対して行うようにしましょう。

Web APIを提供する

PHPを使ったWeb APIを利用できるようにする。
Web APIとはURLにアクセスすることでプログラムを実行し結果を得ることができる仕組みです。
事前にWebサービスを立ち上げておく必要があります。
次にプログラム言語のPHPを利用するのでインストールします。
apt install php
次のPHPのコードはファイル名をGETで送るとcatの結果をJSONで返します。
apiというディレクトリを作成しcat.phpという名前で保存しておくことにします。
<?php
$command = "cat ".$_GET["fn"];

exec($command,$out,$res);

if($res==0){
  header('Content-Type: application/json; charset=UTF-8');
  print json_encode($out, JSON_PRETTY_PRINT);
}
else{
  header('Content-Type: text/plain; charset=UTF-8');
  print $_GET["fn"]." is not found.";
}
cat.phpの位置が/var/www/html/api/cat.phpであるとき。
WebサーバのIPアドレスが「192.168.0.2」であれば、次のURLでアクセスします。
http://192.168.0.2/api/cat.php?fn=cat.php
Web APIは他言語からも結果を拾えるので応用範囲が広く便利です。

PHPで動的なWebサイトを作る

PHPを使ってファイルアップローダを作ります。
ファイルをブラウザからアップロードする仕組みをPHPで作ります。
アップロードするディレクトリの名前を./upとしてアクセス権を与えておきます。
mkdir ./up
chmod -R 777 ./up
次のPHPのコードはファイル名をup.phpとします。
<html>
  <head>
    <title>PHP UP LOADER</title>
  </head>
<body>

<form action="./up.php" enctype="multipart/form-data" method="post">
  <input name="file_name" type="file">
  <input type="submit" value="upload">
</form>

<?php
  ini_set('display_errors', "On");

  if(!empty($_FILES['file_name'])){
    $upload = './up/'.$_FILES['file_name']['name'];
    if(move_uploaded_file($_FILES['file_name']['tmp_name'], $upload)){
      header("Location:./up.php");
      exit();
    }else{
      print("<p>error.</p>");
    }
  }

  exec("ls ./up", $out);
  foreach($out as $index => $i){
    print("<p>$index: $i</p>");
  }
?>

</body>
</html>
大きなファイルを上げる場合に画面が止まるのでもう少し改善の余地はありそうです。

PHPのパフォーマンスを考える

速度や効率を検証します
PHPの実行速度を検証します。
例えばprintよりもechoが速いことを検証するのに使えます。
<?php 
  $start = microtime(true);
  $max = 100000;
  for($i=0; $i < $max; $i++){
    print('Hello');
  }
  $end = microtime(true);
  $tm = $end - $start;
  error_log("time:[$tm]sec\n");
?>
PHPのプログラムはオペコードに変換して動作を検証することができます。
オペコードとはアセンブラのようなものです。
test.phpを検証する場合には以下のコマンドを使用します。
phpdbg
exec test.php
print exec
PHPではコンパイル後の関数をモジュールとして使用することができます。
これをExtensionと呼び高速化に使えます。

DNSサービスを立ち上げる

bind9を使ってみる。
DNSとはIPアドレスをドメイン名から検索できるサービスです。
基本的に一般人はアドレスをIPアドレスではなくドメイン名で覚えている場合が多いはずです。
例えば、Googleにアクセスする場合にIPアドレスを調べるより先にドメイン名を調べているはずです。
DNSサーバは世界中に存在し協力しあってドメイン名からIPアドレスを割り出そうとします。
このときに.comや.co.jpなどの名前はDNSサーバ検索のヒントになります。
また、DNSサーバは一度覚えたIPアドレスをキャッシュすることもあり早く発見できる仕組みを持ちます。
今回はこのDNSサービスを自宅で立ち上げます。
DNSにbind9を使うのでインストールします。
apt install bind9
DNSは設定ファイルが多いので順に設定していきます。
まずは/etc/bind/named.confに以下の記述を付け加えます。
include "/etc/bind/named.conf.internal-zones";
新たに/etc/bind/named.conf.internal-zonesというファイルを作成します。
以下のようにゾーンを定義します。
このときabc.homeがドメイン名でこの部分は自由に決めてください。
zone "abc.home" IN {
        type master;
        file "/etc/bind/abc.home.lan";
        allow-update { none; };
};
新たに/etc/bind/abc.home.lanというファイルを作成します。
ここにドメイン名と結び付けるIPアドレスを記載します。
この時のIPアドレス「ip a」コマンドで調べることができます。
AはIPv4アドレス、AAAAはIPv6のアドレスを記載します。
$TTL 86400
@  IN  SOA  dlp.abc.home. root.abc.home. (
      2023031201;
      3600;
      1800;
      604800;
      86400;
)

        IN      NS      dlp.abc.home.
        IN      A       192.168.0.2

dlp     IN      A       192.168.0.2
www     IN      A       192.168.0.2
dlp     IN      AAAA    fe80::2
www     IN      AAAA    fe80::2
これでDNSサーバを再起動です。
/etc/init.d/named restart
ただし、この状態ではPCがDNSサーバーを見つけられません。
/etc/resolv.confに「ip a」で調べたIPアドレスの記載を追加します。
nameserver 192.168.0.2
nameserver fe80::2
クライアントのWindowsPCもIPv4、IPv6でそれぞれDNSサーバのIPアドレスを登録します。
以下のコマンドで利用したDNSサーバのIPアドレスとドメイン名から検索したIPアドレスが表示されます。
nslookup abc.home
結果としてブラウザからも次のようなURLでWebサービスにアクセスが可能になっているはずです。
http://www.abc.home/

Wikiサービスを立ち上げる

PukiWikiを使ってみる。
Wikiとはブラウザを使用したメモサービスです。
ブラウザ上で文章を編集でき、検索機能もついています。
WebサービスとPHPプログラムが動作する環境が必要です。
PukiWikiはzipで圧縮されたPHPプログラム群です。
これを例えば/var/www/html/wikiなどに展開します。
DNSでwww.abc.homeが検索できるようになっていれば以下のURLでWikiが使えるようになっています。
http://www.abc.home/wiki/

データベースサーバを立ち上げる

MySQL派生のMariaDBを使います。
データベースを管理するMariaDBサーバをインストールします。
名前はMariaDBですがほとんどMySQLと同じような使い方ができます。
apt install mariadb-server
起動の仕方などは他のサーバと同じです。
/etc/init.d/mariadb start
データベース操作は以下のコマンドからスタートします。
mysql
これで対話形式のMySQLのコマンドラインが開始します。
test@localhostというユーザ、testdbというデータベースを作ってすべてのテーブルにすべての権限を与えます。
create user 'test'@'localhost' identified by 'pass';
create databese testdb;
grant all on testdb.* to test@localhost;
データベースに入りcommentというテーブルを作成します。
use testdb;
create table comment(id int auto_increment, str varchar(30), index(id));
以上でデータベースを作成しデータベースを操作できるユーザを作成。
データベース内にテーブルを作成し、データを入れる手前まで準備しました。
実際にデータを操作する場合にはいちいちSQLコマンドを打つのは面倒なのでプログラムで行うことにします。
次のコマンドでMySQLのコマンドラインを終了します。
exit

PHPでデータベースを操作

データベースにコメント投稿をしてみます
まずはPHPからSQLを使えるようにしておきます。
apt install php-mysqli
POSTでコメントを投稿するサイトをPHPで書いてみました。
ファイル名はcomment.phpとします。
SQLサーバは前に作成したデータベース、ユーザの設定を使用しています。
以下のコードは、不適切なパスワードおよびSQLインジェクションに無対応などセキュリティ上問題があります。
<html>
  <head>
    <title>PHP COMMENT LIST</title>
  </head>
<body>

<form action="./comment.php" method="post">
  <input type="text" name="comment">
  <input type="submit" value="send">
</form>

<?php

$link = mysqli_connect('localhost', 'test', 'pass');
if(!$link){
  die('mysqli_connect error.'.mysqli_error($link));
}

print("<p>connected.</p>\n");

$db_selected = mysqli_select_db($link,'testdb');
if(!$db_selected){
  die('mysqli_select_db error.'.mysqli_error($link));
}

if(!empty($_POST["comment"])){
  $result = mysqli_query($link,'insert into comment set str="'.$_POST["comment"].'"');
  if(!$result){
    die('mysqli_query error.'.mysqli_error($link));
  }
  header("Location:./comment.php");
  exit();
}

if(!empty($_POST["id"])){
  $result = mysqli_query($link,'delete from comment where id='.$_POST["id"]);
  if(!$result){
    die('mysqli_query error.'.mysqli_error($link));
  }
  header("Location:./comment.php");
  exit();
}

$result = mysqli_query($link,'select * from comment');
if(!$result){
  die('mysqli_query error.'.mysqli_error($link));
}

while($row = mysqli_fetch_assoc($result)){
  print("<p>");
  print('<form action="./comment.php" method="post">');
  print('<input type="hidden" name="id" value="'.$row['id'].'">');
  print('<input type="submit" value="delete">');
  print(" ".$row['id'].":".$row['str']);
  print("</form>");
  print("</p>\n");
}

$close_flag = mysqli_close($link);
if($close_flag){
  print("<p>closed.</p>\n");
}

?>

</body>
</html>

セキュリティについて考える

XSS、CSRF、SQLインジェクションを再現する。
自宅サーバーだとセキュリティの実験がやりやすいところもいいところです。
これまでに作ったコメントを投稿するサービスで実験します。
以下のコメントが投稿できます。これはタグの埋め込みです。
<script>alert('A');</script>
このような任意のスクリプトはGETで外部サイトから投稿することもできます。
では外部からPOSTできないかというとそうでもありません。
次のようなサイトは読み込むだけで送信ボタンを押さなくてもPOSTをしてしまいます。
<html>
<body onload="document.fn.submit()">

<form name="fn" action="http://www.abc.home/comment.php" method="post">
  <input type="hidden" name="comment" value="test">
  <input type="submit" value="send">
</form>

</body>
</html>
よって、外部サイトからの勝手なPOSTにも対応しないといけません。
次に、次のようなサイトはすべての投稿を全て消してしまいます。
<html>
<body onload="document.fn.submit()">

<form name="fn" action="http://www.abc.home/comment.php" method="post">
  <input type="hidden" name="id" value="1 or '1'='1'">
  <input type="submit" value="send">
</form>

</body>
</html>
これは結果として「delete from comment where id=1 or '1'='1'」を実行しています。
このようにするとid=1だったらという条件が無効になってしまいます。
この方法で例えばフラグが1なら表示するところ0でも表示するような不具合が発生します。
これによって重要なパスワードが漏洩することもあります。
これはSQL文を改ざんするSQLインジェクションという有名な攻撃です。
安易に準備したサービスは自宅内で運用すべきです。外部に公開すると危険です。
その他の攻撃や、攻撃への対策については他のサイトを参照ください。

コマンドを便利に使う

複雑な処理を書いてみます。
awkを使うとコマンドライン内でawkの関数を使えます。
例えば次のように出力に行番号を付け加えることも簡単にできます。
ls | awk '{print NR-1, $0}'
もう少しよく使いそうな処理を書いてみます。
拡張子が.pngのファイルを探してprintfでffmpegを実行するシェルプログラムを記載し実行します。
$0が標準入力です。スペースを挟めば$1や$2も拾えます。
\047は'のエスケープシーケンスです。
ファイル名でひっかかりそうなときにはつけておくといいでしょう。
ffmpegではPNG画像をJPEG画像に変換しています。
find ./ -maxdepth 1 -name "*.png" | awk '{sub(".png","",$0); printf "ffmpeg -i \047%s.png\047 \047%s.jpg\047\n", $0, $0}' | sh
特定の拡張子のファイルのみ圧縮する処理も1行で書けてしまいます。
find ./ -maxdepth 1 -name "*.jpg" | awk '{if(NR==1) {printf "zip archive "}; printf "\047%s\047 ", $0}' | sh
他にも書き方はたくさんありなんとでもなります。
よく使う処理は例えば~/commandディレクトリなど作ってまとめておくといいかもしれません。
例えば、~/command/zipcdというファイルを作成し、以下の記述をしてchmodで実行権限を与えます。
#!/bin/sh

ls -F | grep -v / | awk '{if(NR==1) {printf "zip archive "}; printf "\047%s\047 ", $0}' | sh
mv archive.zip $1
すると、以下の記述で現在のディレクトリのすべてのファイルをa.zipで圧縮できると思います。
~/command/zipcd a.zip

ログの管理

ログイン履歴の確認方法について書きます。
ログは/var/log以降に記録されますがバイナリなので直接見ることはできません。
ログイン履歴を見るには以下のコマンドを使用します。
last
以下のコマンドではログイン失敗の履歴も見ることができます。
lastb
inserted by FC2 system