プログラム」カテゴリーアーカイブ

WindowModePatchに最前面管理か…

なんかタイトルがおかしいですが、掲示板への返信ではなくこちらで書いてみたいと思います。

 

WindowModePatchをしばらく放置していたので少し修正してみた

掲示板の要望を見たから、というのもあるのですが、修正したものを作成してみました。一応最前面管理のような機能を追加して、アクティブウィンドウに関する処理を追加しています。処理を行わせた場合に不具合が出る、というわけではないのですが、そのオプションがないと不具合が出るプログラムがあれば詳細が詰められるのに…という状態で、まだ試作レベルとなってしまっています。今現在は設定には直接設定ファイルを書き換える必要があります。

 

ただし、まだ正式リリースはしない予定

WindowModePatchがWindowsDefenderによりウイルス扱いされた件があることもあり、様子見です。公開の準備はできている(Ver 0.72Alpha相当)のですが、このまま公開せずにスキップするかもしれません。いろいろと気に入らない点が見つかって、「直したいんだけれども…」といったところでしょうか。ここまで来るとプログラム上の処理の数が多すぎて追いかけるのが大変なんですよね…。まるでコンピュータウイルスの解析をしているような感覚に襲われたこの改良作業でした。

 

CMSサイトの構築とSELinux

というわけで今回はこういう話題です。ほとんどの解説サイトがSELinuxを前提にしていない書き方をしているためにこの系の権限変更がちゃんと行われず、サイトの初期設定すらまともに行えない、という現象にはまることが多いような気がするのでそれについて書いてみます。

 

SELinuxが持っているhttp系の初期権限

これを知らないと大変なことになります。SELinuxでは、http系で操作される各種ファイルについて以下のようなラベルを割り当てて管理しています。大まかに必要な分だけですが…。

権限名 意味 権限
httpd_sys_content_t 通常コンテンツ 読み込み専用
httpd_sys_rw_content_t 通常コンテンツ(読み書きあり) 読み書き
httpd_sys_script_exec_t 実行可能コンテンツ(CGIなど) 読み込みおよび実行
httpd_var_lib_t /var以下に存在するhttpの動作に関わる補助ファイル(phpのキャッシュなど) 読み書き
httpd_var_run_t /var以下に存在するhttp上で実行されるコンテンツの補助ファイル(phpのセッションなど) 読み書き

下二つはあまり関係しませんが、php-fpmなどの設定時に関わってくることがあります。で、問題は上の3つ。

 

SELinuxがhttp系ファイルにつける初期ラベルはhttpd_sys_content_tになる

これが要注意ポイント。つまり読み込み専用になるわけです。この状態は通常のコンテンツをアップロードしたときには正しいのですが、CMSサイトのようにディレクトリ内にキャッシュを持ったり、自分自身でファイルのアップデートを行うコンテンツにおいては非常に相性が悪い(というかこの状態だとうまく使えないこと)になってしまいます。

 

CMSサイトのディレクトリにはhttp_sys_rw_content_tをつけないとインストールできないことも

この件について調べるのにかなり時間がかかってしまいました…。特に内部的にキャッシュディレクトリを持つ場合はそのキャッシュディレクトリにhttp_sys_rw_content_tを設定しておかないとキャッシュが動かず実行できません。これは大変です。また、CMSの場合たまにあるのが、アップデート時に何らかのスクリプトをCGI権限で動かすパターンがあるのですが、その場合は個別にhttpd_sys_script_exec_tを設定しないとたとえchmodによる実行権があってもSELinuxにより実行が拒否されますので対応する必要があります。

 

CMSサイトのディレクトリには必要な部分にhttp_sys_rw_content_tを設定しよう

という結論になります。もちろん、全域に設定するとセキュリティが弱くなるので必要な部分だけ、というのはあるのでコンテンツに関するディレクトリだけです。例えば/var/www/cms以下にコンテンツをインストールして、/var/www/cms/webroot以下を書き換え可能にするとするなら

# semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/cms/webroot(/.*)?"
# restorecon -R -v /var/www/cms/webroot

のような処理が必要になります。この処理はターミナル上からしかできないのでSELinuxを設定している時は要注意になります。また、実行権が必要になるファイルにもhttpd_sys_script_exec_tの設定処理が必要になりますので、これを参考にやっておきましょう。

 

SELinuxがセキュリティに貢献していることがよくわかる…

chmodによる権限変更だけでは受け入れない堅さが自慢です、というところですか。面倒ですが、少しずつ覚えていって使いこなせるようにならないとまずいような気はしますね。

 

Fedora32のPHPは初期設定でphp-fpmになるのか…

そして今回の出来事の中で気がついてしまったこと。昔はApacheの場合はphpがhttpd上で動く組み込みパターンになるのでphpの実行者はhttpdの実行者と同じになっていたのですが、Fedora32の場合は初期設定がphp-fpmによる外部動作なので、phpの実行者がphp-fpmで設定されている実行者になる、という「suexecを考えるよりはわかりやすいのか?」という状態になっているようです。このあたりも気をつける必要がある人は気をつけましょう。

VBAの小技 Excelで指定した領域を除外した領域を作る

またまた何か変な小ネタですが…ちょっととあることをやっていると「Excelにおいて、指定した領域のうち、ある部分だけが含まれない状態を作りたい」という不思議な要求が出てきたので、それをかなえるべくVBAで処理を組んでみました。Excel限定ですので多分VBA以外でこんな処理は作れないでしょうし、作る意味もない、というか。

 

コードは…

こんな感じでしょうか。

Function ExcludeRange(rr As Range, re As Range) As Range
    Dim ra As Range, rt As Range, ri As Range, ret As Range

    For ai = 1 To rr.Areas.Count Step 1
        Set ra = rr.Areas(ai)
        For i = 1 To ra.Rows.Count Step 1
            For j = 1 To ra.Columns.Count Step 1
                Set rt = Range(Cells(ra.Row + i - 1, ra.Column + j - 1), Cells(ra.Row + i - 1, ra.Column + j - 1))
                Set ri = Application.Intersect(rt, re)
                If ri Is Nothing Then
                    If ret Is Nothing Then
                        Set ret = rt
                    Else
                        Set ret = Application.Union(ret, rt)
                    End If
                End If
            Next j
        Next i
    Next ai
    Set ExcludeRange = ret 
End Function

 

構想は…

考え方はこの通り。

  • Rangeから直接差のRangeを取り出すことはできないので、「重なっていない」セルだけをくっつけていくしかない
  • 仕方がないので、Forループで一つ一つ領域→行→列を巡回して指されたRangeに重ならないかどうかを調べる(Intersectでのチェック)
  • あとは結果となる領域にくっつけていく

になります。なお、はじめのRangeに含まれているすべてのセルを巡回する特性上、かなり遅いです。参考として出しているだけですので、本気に使いたいのであればもう少し最適化が必要だと思います。(行方向のみに限定したり、列方向に限定する、など)ちゃんとRangeオブジェクトのためにIntersectやらUnionやらOffsetやらの処理はあるのに…。

まあ、ゲームの描画でも、DirtyRect管理で重なっている文の検出やら最小の再描画領域になるようにRectを再構築するなどはありそうですが、指定した範囲の除外なんていうのはほぼ出てこないパターンでしょう。アルゴリズムを考えてみる分には面白いのかもしれませんが。

 

C言語の仕様書に従うならNULLは0もしくは(void *)0らしい

ちょっとtwitter上でNULLに関することをつぶやきましたが、ツッコミが帰ってきてしまったため気になって仕様書(C89およびC99のもの)を読み込んでみました。そしてすべての論理をつなげてみると次のような結論となりました。

整数値の0もしくはそれをvoid *にキャストしたものがNULLとして定義される、と。

 

仕様書にはどう書いてあったか、というと・・・

C89でもC99でも記述は同じなのでそれは置いておいて。まず、null pointerおよびそれに関わる用語の定義です。関わる部分だけざっくりと翻訳すると次のようになります。

  • 整数値の0、もしくはそれをvoid *にキャストして表したものをnull pointer constant(ヌルポインタ定数)と呼びます
  • null pointer constant(ヌルポインタ定数)をそれぞれのポインタに変換したものをnull pointerと呼びます
  • null pointerはほかのどのオブジェクトおよび関数とも一致しないことが保証されています
  • null pointerをほかのポインタ型に変換してもそれはその型でのnull pointerとなります
  • null pointer constant(ヌルポインタ定数)はstddef.hでNULLとして定義されます

というわけで、この部分からNULLの定義について読み取るなら、それは1行目と5行目をつなぎ合わせることになり、「NULLは整数値0もしくは(void *)0として定義する」という結論が得られるわけです。ただし、3行目の説明を読んでみると、「何も指さないことが保証されている」という参考書の説明とは微妙に食い違っているように見えるのですが…。細かいことをいうなら特定のメモリ空間をオブジェクトに含めるのかどうか?というところでしょうか。

 

いくつかのシステムにおける例外について

なお、以下はWikipediaの記述(+リンクされていた参考資料)から読み取っている項目ですが、いずれもかなり古いシステムにおいて

  • null pointerが指しているアドレスが0でないシステムもある
  • (void *)0をキャストしてアクセスすると特定の読み書きとして成立するシステムもある

とのことでした。前者は48bitアドレスというなかなか変わった方式のものですし、後者は仮想メモリやメモリ保護の概念がない時代のものですので、今の時代には全くそぐわないものですから無視してもよいかもしれません。

 

ちなみにif(!p){ }の是非について

これが「ポインタ変数pがnull pointerのとき」の意味になるのか?については、仕様書から読み取ると

  • !pという演算は0==pと同一となる
  • ポインタとnull pointer constant(ヌルポインタ定数)が比較される場合はポインタの型に合わせたnull pointerとの比較として扱われる
  • 0はnull pointer constant(ヌルポインタ定数)として扱われる

ため、if(!p){ } はちゃんと「ポインタ変数pがnull pointerのとき」の意味になるようです。

 

意外と細かい仕様があってびっくり

というわけで「C言語の仕様書に従っている限りはNULLは0だし、null pointerによるifの判定にも誤りはない」という結論になりました。仕様書を読んでみるといろいろな動作についてちゃんと定義が書いてあるので、これに従ったコンパイラを完全に作るのは確かに大変だな、という感想もあります。ただし、逆にNULLをポインタにキャストしてアクセスした場合常にメモリ保護例外などのエラーとなるとは限らない、というのは調べていてびっくりした点です。今のシステムではほぼあり得ないことですが、昔のメモリ空間がカツカツだったりする環境のためなのでしょうかね。

 

C++にはnull pointerを表す定数が追加されていることを初めて知った

しばらく技術書を細かく眺めていなかったので知らなかったのですが、nullptrが追加されていて、C++11以降ではこれがnull pointerとして扱われるようです。NULLは上記の定義より整数型としての値を持つことがあり、引数として整数型とポインタ型のオーバーロードがあった場合にNULLを指定すると誤って整数型を引数とする方が呼び出されてしまう、という問題が起こってしまいます。それを防ぐために明示的にnull pointerで呼び出していることを示すものということでした。そういえば昔この手の問題で悩まされたことがあったな~と懐かしみながら。

 

WordPressのCaptchaを変更してみる(Advanced noCaptcha & invisible Captcha)

というわけで、Captchaが大分バグっている、というかとある事情で不具合が起こっていたようなので更新しました。その更新について少し書いておきたいと思います。一応コメントの書き込みテストはしているのでキャッシュ処理に関する問題はないはずですが…。

 

大量のスパムコメントがあった理由は…

4月までの数年間WordPressの更新は「緊急性の高いものを除きとりあえずセキュリティアップデートだけ」という状態だったので、一部の事態に鈍感になっていたことが大きかったです。で、本題の大量のスパムコメントですが、とりあえずコメントのフィルタシステムでスパム扱いされていたので半分無視していたのですが、妙に気になったのでCaptchaに関して調べてみると・・・。

SI Captcha Anti-SpamCaptchaは問題あり

という内容の記事があり、調べてみると開発者が変わって更新が止まっているとかバックドアが仕込まれているとかいろいろと情報がありました。ちなみに私が二年前の段階で導入していたのがSI Captcha Anti-Spamの方で、どうもこれがスパムコメントの原因だったようです。

 

別の種類のCaptchaを導入してみることに

さすがにCaptchaが一つもない、というのは微妙ということで調べてみていくつか導入候補を考えたのですが、それが

  • Google Captcha (reCAPTCHA)
  • Login No Captcha reCAPTCHA
  • Advanced noCaptcha & invisible Captcha

になりました。ところがどっこい。基本的にこれらはすべてGoogleのCaptchaシステムを借りて行うものなので変に所有者が変わらない限りはCaptchaとしての動きはほぼ変わりません。後は信頼度と設定がどこまでできるか?に依存するため、今回はAdvanced noCaptcha & invisible Captchaを使ってみることにしました。

 

導入の手順はおそらくこの3つともほとんど変わらない(と思う)

というのも、結局はGoogleのreCAPTCHAを借りることになるため、

  1. WordPressの管理画面からプラグインを検索してインストールする
  2. GoogleのreCAPTCHAの管理画面にGoogleアカウントでログインしてAPIキーとSecretキーを取得する
  3. 設定画面でAPIキーとSecretキーを登録して、ほか必要な設定を行う

となります。GoogleからAPIキーを取得するためのアドレスは大抵プラグインの設定画面にリンクが張られていると思うのでそれを参照すればよいと思います。ちょっと注意が必要なのが、reCAPTCHAのキーを取得するときに認証のバージョンなどを設定することができますが、そのバージョンにプラグイン側の設定も合わせる必要がある、ということです。v2(noCaptchaもしくはinvisible Captcha)で設定する場合とv3で設定する場合でそれぞれ方法が異なりますので注意しましょう。

あとはどの場所にCaptchaを設置するかどうかです。Advanced noCaptcha & invisible Captchaではいくつかの場所にチェックボックスだけで設定できるので、コメント投稿およびログイン画面に設定することにしました。これで安全性がかなり高まるかと思います。ちなみにAdvanced noCaptcha & invisible Captchaの場合はbbPressやBuddyPress、WooCommerceとも連動させてCaptchaをかけることができるので、これらのプラグインでサイトを作っている人には簡単に設定できるようになる分おすすめかもしれません。

 

Captchaのプラグインを変えるだけでスパムコメントが一切なくなった…

というわけで、Captchaのプラグインを変えて数日間たったわけですが、スパムコメントが一切なくなりました。まあ、このサイトこの2年間くらい記事更新がほとんどなかったこともありコメントらしいコメントもないので正しいコメントが受け付けられているかどうかはテストしたこと以上のことは不明なのですが、それでも管理の手間が大分減っているのでちょっと感動です。スパムコメントのフィルタリングに関してはIPフィルタ以上の効果ですね。