2011年1月27日木曜日

DOMのhashプロパティ×2

URLを構成する文字列のうち、"#…"の部分はフォーマルには "fragment identifier" と呼ばれるが、「フラグメント・アイデンティファイア」と発音しても他人に通じることは稀。"#"は一般的に「ハッシュマーク」や「シャープ」と呼ばれるから、日常会話では「ハッシュ以下」とか「シャープなんとか」などと言えば通じる。

JavaScriptでこの fragment identifier を参照する方法を整理しておく。

window.location.hash

ブラウザで現在表示しているURLの fragment identifier を知りたい場合、JavaScriptでは window.location.hash と書けばよいことが知られている(ちなみに、Mozillaのドキュメント window.location - MDC Doc Center には "DOM Level 0. Not part of any standard." と書いてあるので、W3Cで標準化されたものではない)。

HTMLAnchorElement.hash

location以外に fragment identifier が関係してくる場面としては、a要素のhref属性値を処理する場面がある。実はつい最近までhref属性値を文字列処理して"#…"の部分を取り出す、なんてことをしていたのだがその必要はない。アンカーオブジェクトにもhashプロパティが存在しているので、getElementしてhashプロパティを参照するだけで済む。次の例はアンカーのhref属性値"http://dminor11th.blogspot.com/index.html#foo"のうちの"#foo"をアラートで表示する。

<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p><a id="a1" href="http://dminor11th.blogspot.com/index.html#foo">anchor1</a></p>
    <script type="text/javascript">
      alert(document.getElementById('a1').hash);
    </script>
  </body>
</html>

ちなみに、このhashプロパティ、つまりHTMLAnchorElementのhashプロパティも、W3Cで標準化されたものではないようだ(DOM Level 2 HTML Spec.等に記載されていない)。Mozillaの HTMLAnchorElement - MDC Doc Center によるとHTML5には記載されるらしい。

2011年1月16日日曜日

setuid なスクリプト

シェルやPerlで書かれたスクリプトも、setuidプログラムのように動作して欲しいときがある。その場合にどうすればいいか、という話(セキュリティの問題があるため仕事ではほとんど役に立たない、教養レベルの話)。

いろいろと方法があるだろうけれど、ここではそのうちの2つ記録しておく。

その1. setuid(0)system()を用いたラッパープログラム

setuid on shell scripts に書いてあるとおり、シェルやPerl等のスクリプトファイルの場合は、setuidビットをセットして実行しても無視される。そこで、ラッパーとしてELFの実行ファイルをC言語で作成してsetuidビットをセットするのだが、その内部でただ単にsystem(…)関数でスクリプトを実行するだけでは足りない。system(…)の前に、setuid(0)を実行しないといけない。

…
int main()
{
   setuid( 0 );
   system( "/path/to/script.sh" );

   return 0;
}
  

ここで、setuid(0)が必要となる理由は、"man 3 system", "man sh" を調べると分かる。

man 3 system:
DESCRIPTION system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored.
man sh:
If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, no startup files are read, shell functions are not inherited from the environment, the SHELLOPTS variable, if it appears in the environment, is ignored, and the effective user id is set to the real user id. If the -p option is supplied at invocation, the startup behavior is the same, but the effective user id is not reset.

整理すると、system("/path/to/script.sh")/bin/sh -c "/path/to/script.sh" に変換される。この/bin/shは、effective use id=root で real user id=一般ユーザーの状態で実行されるから、effective user id が一般ユーザーのidで上書きされ、結局はsetuidプログラムのクレデンシャル(credential)が失われてしまう、という流れ。

これを防ぐには、effective use id と real user id が一致した状態でsystem(…)を呼べばよいから、事前にsetuid(0)を呼ぶことになる(setuid(0)により、effective use id と real use id の両方に 0 つまりrootが設定される)。

※UNIXプロセスのクレデンシャル関係の用語は、邦訳が揺らいでいて曖昧なので英語のままがいい。ちなみに、手元にある「Amazon.co.jp: 詳解UNIXプログラミング: W.リチャード スティーヴンス, W.Richard Stevens, 大木 敦雄: 本」では"real user id"が「実ユーザID」、"effective user id"が「実効ユーザID」と訳されている。

その2. execve() を用いたラッパープログラム

ここから別解。system()setuid(0)の使用をやめて、exec()系のexecve()を用いる方法。

なぜexecve()を使うかと言えば、man 3 systemおよび「IPA ISEC セキュア・プログラミング講座:C/C++言語編 第10章 著名な脆弱性対策:コマンド注入攻撃対策」で推奨されているから。要するに、スクリプトに引き継がれる環境変数を制御できるから。

man 3 system
Do not use system() from a program with set-user-ID or set-group-ID privileges, because strange values for some environment variables might be used to subvert system integrity. Use the exec(3) family of functions instead, but not execlp(3) or execvp(3).
IPA ISEC セキュア・プログラミング講座より引用:
これらのうち使用を推奨するのは、 execle execve execvP の3つである。なぜならば、環境変数 PATH が改ざんされていても影響を受けず、起動するプログラムに与える環境変数を制御できるからである。

同時に、実行される側、スクリプトのほうにも一工夫必要となる。具体的には、man shに記載のとおり、shebang行に-pオプション( Turn on privileged mode)を加えて、子プロセスの effective user id の変更を防ぐ。

man sh:
-p Turn on privileged mode. In this mode, the $ENV and $BASH_ENV files are not processed, shell functions are not inherited from the environment, and the SHELLOPTS variable, if it appears in the environment, is ignored. If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, these actions are taken and the effective user id is set to the real user id. If the -p option is supplied at startup, the effective user id is not reset. Turning this option off causes the effective user and group ids to be set to the real user and group ids.

以上をまとめると、次のようなコードになる。

execve()を使った場合のコード(C)

fork()してexecve()する流れ。コンパイル後、chmod で所有者root の 4755 に変更しておく。

/* setuid_test.c */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *argv[]){
  int pid;
  if((pid=fork()) < 0){
    return 1;
  }else if(pid > 0){
    wait(NULL);
  }else{
    if(execve("/path/to/script.sh", NULL, NULL) == -1){
      return 1;
    }
    return 0;
  }
  return 0;
}
    

スクリプトのコード(shebang行の -p が必要)

スクリプトのほうは chmod で所有者が一般ユーザーの 744 にしておく。

#!/bin/bash -p
tail /var/log/maillog;  # rootだけが読み込めるログを表示してみる
    

実行例

以下のような感じ(CentOS5で確認)。

$ ./setuid_test
Jan 16 10:11:16 example postfix/smtp[9884]: D446F10C4F9B: host mx.example.com[999.999.999.999] refused to talk to me: 421 Message from (999.999.999.999) temporarily deferred - …
Jan 16 10:11:17 example postfix/smtp[9884]: D446F10C4F9B: host mx.example.com[999.999.999.999] refused to talk to me: 421 Message from (999.999.999.999) temporarily deferred - …
…
    

ちなみに、shebang行の-pを消して実行した場合、execve("/path/to/script.sh", NULL, NULL)の後にsetuid(x)setgid(x)が勝手に実行されて、setuidプログラムのクレデンシャルが失われることが分かる(ここで、x は一般ユーザーのID)。以下は、その現象を strace で記録したもの。

$ id
uid=505(testuser) gid=505(testuser) groups=505(testuser)
$ strace -f -o strace.txt ./setuid_test
$ cat strace.txt
…略…
25971 execve("/path/to/script.sh", [0], [/* 0 vars */]) = 0
…略…
25971 getuid()                          = 505
25971 getgid()                          = 505
25971 geteuid()                         = 0
25971 getegid()                         = 505
25971 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
25971 setuid(505)                       = 0
25971 setgid(505)                       = 0
25971 open("/proc/meminfo", O_RDONLY)   = 3
…略…
    

Macro-Defining Macros をEmacsで試した

Onlisp: Macro-Defining Macros に書いてある例題は、Emacs(GNU Emacs 23.1.1 (i386-mingw-nt6.1.7600))でも使えた。長い名前をもつマクロを省略表記するための、"abbrev"というマクロ。いわゆるマクロを定義するマクロ。

abbrev
(defmacro abbrev (short long)
  `(defmacro ,short (&rest args)
     `(,',long ,@args)))
multiple-value-bind を mvbind に省略
(abbrev mvbind multiple-value-bind)
mvbind

;mvbindを強引に使ってみた例(与えられた数の商、剰余を出力)
((lambda (x y)
   (labels ((devide (x y) (list (/ x y) (% x y)))) ;商、剰余のリストを返す関数
     (mvbind (q r)
            (devide x y)
            (format "quotient: %d, remainder: %d" q r) )))
 ; 2011割る3を試してみる
 2011 3)
"quotient: 670, remainder: 1"
destructuring-bind を dbind に省略
(abbrev dbind destructuring-bind)
dbind

;dbindを強引に使ってみた例(与えられたリストからhtmlのaタグを生成)
((lambda (lst)
   (dbind
    (title href)
    lst
    (format "<a href=\"%s\" title=\"%s\">%s</a>" href blog blog))) ;リストの要素をaタグに変換
 '("title1" "http://dminor11th.blogspot.com/"))
"<a href=\"http://dminor11th.blogspot.com/\" title=\"blog\">blog</a>

2011年1月15日土曜日

document.anchors と document.links

今まで気にかけていなかった、DOMの"document.anchors"と"document.links"の違いを整理。

  • name属性を持つa要素はアンカーとみなされるので、document.anchors に属する。
  • href属性を持つa要素はリンクとみなされるので、document.links に属する。
  • name属性とhref属性を両方とも持っているa要素は、document.links, document.anchors の両方に属する。
  • id属性の有無はアンカー/リンクの判別には無関係(IEでは仕様が異なり、id属性を持つ場合はアンカーとみなされる)

確認のための例

アラートでa要素のテキストノードを表示。a1とa3が document.anchors、a2とa3が document.links として表示される。

<html>
<head>
  <title>test</title>
</head>
<body>

<ul>
  <li><a name="a1">a1</a></li> <!-- anchor -->
  <li><a href="foo">a2</a></li> <!-- link -->
  <li><a name="a3" href="bar">a3</a></li> <!-- anchor, link -->
</ul>

<script type="text/javascript">
  function collectText(a){
    var stack = new Array();
    for(var i=0, n=a.length; i<n; i++){
      stack.push(a[i].childNodes[0].nodeValue);
    }
    return stack.join(', ');
  }
  alert('document.anchors[' + collectText(document.anchors) + ']');
  alert('document.links[' + collectText(document.links) + ']');
</script>

</body>
</html>

※IE-6, IE-7, IE-8, Firefox-3.6.13, Chrome-8.0.552.224, Safari-5.0.2で期待通りに動いた(Windows7)。

2011年1月10日月曜日

PerlによるプログレッシブJPEGとベースラインJPEGの判別

JPEGファイルは、プログレッシブJPEGとベースラインJPEGに分類される(参考:Baseline JPEG and Progressive JPEG?)。

Webサイトで使用する画像の場合、プログレッシブのほうを採用するのが現在は一般的だと思われるが、携帯サイトの場合は事情が異なる。というのは、携帯電話の中にプログレッシブ非対応の機種があるため。

よって、この違いに意識的でないと、携帯サイトのテスト工程で「ある機種で写真が表示されません」ということが起こる。こうした状況、つまり「サーバーにあるプログレッシブJPEGを洗い出したい」場合に、Perlの Image::Info が使える(もちろん Perl以外の選択肢もある。参考:プログレッシブJPEGやCMYKモードのJPEGをUnix環境で判別(判定) | はやとも -hayatomo.com-

Image::Info インストール

CentOS5の場合、CPANシェルでインストールできた。

# perl -MCPAN -e shell
insatll Image::Info

Baseline/Progressive判別スクリプト

JPEGファイルのパスを引数として、単純に「ファイルパス: Baseline」「ファイルパス: Progressive」と出力するだけのプログラム。

#!/usr/bin/perl
use strict;
use warnings;
use Image::Info "image_info";

my $info = image_info($ARGV[0]);
print "$ARGV[0]: $info->{'JPEG_Type'}\n";

実行例

findでjpgを抽出してスクリプトに渡す(スクリプトは "./info_jpeg.pl" に置いてある)。

$ find /foo/bar/images/ -name '*jpg' -exec ./info_jpeg.pl {} \;
/foo/bar/images/pic01.jpg: Baseline
/foo/bar/images/pic02.jpg: Progressive
/foo/bar/images/pic03.jpg: Baseline

補足

Image::Info の image_info メソッドによって得られる連想配列の中身。ここで、「JPEG_Type」がBaseline/Progressiveを表す。

 $VAR1 = {
          'width' => 240,
          'file_media_type' => 'image/jpeg',
          'file_ext' => 'jpg',
          'color_type' => 'YCbCr',
          'AdobeTransformVersion' => 100,
          'ColorComponents' => [
                                 [
                                   'Y',
                                   34,
                                   0
                                 ],
                                 [
                                   'Cb',
                                   17,
                                   1
                                 ],
                                 [
                                   'Cr',
                                   17,
                                   1
                                 ]
                               ],
          'ColorComponentsDecoded' => [
                                        {
                                          'ComponentIdentifier' => 'Y',
                                          'HorizontalSamplingFactor' => 2,
                                          'VerticalSamplingFactor' => 2,
                                          'QuantizationTableDesignator' => 0
                                        },
                                        {
                                          'ComponentIdentifier' => 'Cb',
                                          'HorizontalSamplingFactor' => 1,
                                          'VerticalSamplingFactor' => 1,
                                          'QuantizationTableDesignator' => 1
                                        },
                                        {
                                          'ComponentIdentifier' => 'Cr',
                                          'HorizontalSamplingFactor' => 1,
                                          'VerticalSamplingFactor' => 1,
                                          'QuantizationTableDesignator' => 1
                                        }
                                      ],
          'BitsPerSample' => [
                               8,
                               8,
                               8
                             ],
          'SamplesPerPixel' => 3,
          'JFIF_Version' => '1.02',
          'App12-Ducky' => '2',
          'AdobeTransformFlags' => [
                                     49152,
                                     0
                                   ],
          'AdobeTransform' => 1,
          'height' => 180,
          'resolution' => '100/100',
          'JPEG_Type' => 'Baseline'
        };