筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.coins.tsukuba.ac.jp/~syspro/2008/No9.html
あるいは、次のページから手繰っていくこともできます。
	http://www.coins.tsukuba.ac.jp/~syspro/2008/
	http://www.coins.tsukuba.ac.jp/~yas/
サーバのように、長時間動くプログラムや停止しないプログラムにメモリ・リー クの問題があると、時間経過とともにメモリが不足して、そのうち停止するか 仮想記憶のページ置き換えが頻発して、性能が著しく低下する。
すぐに終了するプログラムの場合、メモリ・リークが許されることもある。
C言語でプログラムを書く限り、メモリ・リークがないプログラムを書こうと思 えば書ける技術を身につけたい。
f( char *base )
{
	char *name ;
	int fd;
	name = make_filename( base );
	fd = open( name,O_RDONLY );
	...
}
#define MAX_STRING	1024
char *make_filename( char *s )
{
    char *buf ;
    	buf = malloc( MAX_STRING );
	snprintf(buf,MAX_STRING,"%s.html",s );
	return( buf );
}
デバッグ。
f( char *base )
{
    char *name ;
    int fd;
	name = make_filename( base );
	fd = open( name,O_RDONLY );
	...
	free( name ); // これが抜けていた
}
malloc() をやめて局所変数(auto変数)にする。
#define MAX_STRING	1024
f( char *base )
{
    char name[MAX_STRING] ;
    int fd;
	make_filename( base, name, MAX_STRING );
	fd = open( name,O_RDONLY );
	...
	// 局所変数/auto変数では、free は不要。
}
char *make_filename( char *s, char *buf, int buflen )
{
	snprintf( buf,buflen,"%s.html",s );
	return( buf );
}
static変数に入れて返す。
f( char *base )
{
	char *name ;
	int fd;
	name = make_filename( base );
	fd = open( name,O_RDONLY );
	...
	// free は不要。
}
#define MAX_STRING	1024
char *make_filename( char *s )
{
    static char buf[MAX_STRING] ; // static は必要。
	snprintf( buf,MAX_STRING,"%s.html",s );
	return( buf );
}
char *make_filename( char *s )
{
    char buf[MAX_STRING] ;
	snprintf( buf,MAX_STRING,"%s.html",s );
	return( buf );
}
ローカル変数用のメモリは、関数から return したら、別の関数が利用する。
main() {
       f();
       g();
}
図? ローカル変数用のメモリ
関数から文字列を return する方法
f() {
    auto int x;// auto は省略可能。
    ...
}
スタックの構造については、
前半の「バッファオーバーフロー」の説明参照。
バグ入りのプログラム
char *make_filename( char *s )
{
    char *buf ;
    	buf = malloc( MAX_STRING );
	snprintf(buf,MAX_STRING,"%s.html",s );
	free( buf );
	return( buf );
}
少し込み入ったバグ入りのプログラム。free_string_vector() の中で free()
が呼ばれている。
char *make_filename( char *line )
{
    int count;
    char **vec;
	string_split(line, ' ', &count, &vec);
	...
	free_string_vector(count, vec);
	return( vec[1] );
}
修正方法1。strdup() (内部で malloc()) してコピーして返す。
呼出し側で free() が必要になる。
char *make_filename( char *line )
{
    int count;
    char **vec;
    char *ret;
	string_split(line, ' ', &count, &vec);
	...
	ret = strdup( vec[1] ); // free する前にコピー。
	free_string_vector(count, vec);
	return( ret );
}
修正方法2。引数に返す。
char *make_filename( char *line, char *buf, int buflen )
{
    int count;
    char **vec;
    char *ret;
	string_split(line, ' ', &count, &vec);
	...
	snprintf(buf,buflen,"%s",vec[1]); // free する前にコピー。
	free_string_vector(count, vec);
	return( buf );
}
修正方法3。static 変数にコピーして返す。
修正方法4。大域変数を使う。
Gateway とは、WWW で使われている HTTP というプロトコルへの入り口という 意味である。 プログラムの実行結果は、普通は、HTML にすることが多いが、普通のテキス トであることもイメージであることもある。
CGI の利用例
情報学類には、2つのサーバ・プロセスが動作している。
CGI になるファイル名のパタンの例(設定により、これ以外も可能)。
/cgi-bin/name /dir/name.cgi
   1:	/*
   2:	        cgi-hello.c --簡単な CGI のプログラム
   3:	        ~yas/syspro/www/cgi-hello.c
   4:	        Created on: 2002/06/23 18:21:34
   5:	*/
   6:	
   7:	#include <stdlib.h>     /* exit() */
   8:	#include <stdio.h>      /* printf() */
   9:	
  10:	extern void print_header();
  11:	extern void print_content();
  12:	
  13:	main()
  14:	{
  15:	        print_header();
  16:	        print_content();
  17:	}
  18:	
  19:	void print_header()
  20:	{
  21:	        printf("Content-Type: text/html\n");
  22:	        printf("\n");
  23:	}
  24:	
  25:	void print_content()
  26:	{
  27:	        printf("<HTML><HEAD></HEAD><BODY>\n");
  28:	        printf("hello.\n");
  29:	        printf("</BODY></HTML>\n");
  30:	}
CGI のプログラムは、Apache の場合、標準ではHTTP のヘッダ(一番最初の 
「HTTP/1.0 200 OK」 ではなく、2行目以降)を標準出力に出力する所から始
まる。Content-Type: 行は、必ず付ける。空行がヘッダと本分の区切りである。
本文には、この場合は、HTML の文書を置いている。
実行例:
% mkdir ~/public_html/syspro-cgi-examples/ 
% cd ~/public_html/syspro-cgi-examples/ 
% cp ~yas/syspro/www/cgi-hello.c . 
% cc cgi-hello.c -o cgi-hello.cgi 
% ./cgi-hello.cgi  
Content-Type: text/html
<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
% 
CGI のプログラムの場合、拡張子を .cgi にする。
CGI のプログラムを、シェルから実行することもできる。
引数の与え方については、後述する。
WWW ブラウザでは、次の URL を開く。
http://www.coins.tsukuba.ac.jp/~syspro/2008/No9_files/cgi-hello.cgi
telnet では、次のようにして実行する。
% telnet www.coins.tsukuba.ac.jp 80 
Trying 130.158.86.207...
Connected to www.coins.tsukuba.ac.jp.
Escape character is '^]'.
GET /~syspro/2008/No9_files/cgi-hello.cgi HTTP/1.0
HTTP/1.1 200 OK
Date: Tue, 12 Jun 2008 10:50:16 GMT
Server: Apache/2.0.55 (Unix) PHP/4.4.2
Connection: close
Content-Type: text/html
<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
Connection closed by foreign host.
% 
GET の代りに POST でもよい。
% telnet www.coins.tsukuba.ac.jp 80 
Trying 130.158.86.207...
Connected to www.coins.tsukuba.ac.jp.
Escape character is '^]'.
POST /~syspro/2008/No9_files/cgi-hello.cgi HTTP/1.0
HTTP/1.1 200 OK
Date: Tue, 12 Jun 2008 10:51:47 GMT
Server: Apache/2.0.55 (Unix) PHP/4.4.2
Connection: close
Content-Type: text/html
<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
Connection closed by foreign host.
% 
Status:という行を含めることで、ステータ
ス行を記述することもできる。Status:がない場合、Location: 
というヘッダがあれば、302 Found (redirect)、それ以外の場合は、200
OKが返される。それ以外のヘッダも、Apache が自動的に生成して返す。
ステータス行も含めて、完全なヘッダを CGI で作成して返すこともできる。 この方法を、Apache では、nph (non-parsed headers) と呼んでいる。
Apache で、nph を使うには、nph- で始まるファイル名を用いる。こ
れを使うと、Apache は、CGI プログラムが作成したヘッダを一切解釈(parse)
せず、そのままクライアントに返す。
HTML記述例:
<H2><A NAME="cgi-example-get">CGI の GET メソッドを使う例</A></H2>
<FORM ACTION="No9_files/cgi-printarg.cgi" method="get">
    <P>
    姓: <INPUT type="text" name="lastname">
    名: <INPUT type="text" name="firstname"><BR>
    <INPUT type="radio" name="sex" value="Male"> 男 <BR>
    <INPUT type="radio" name="sex" value="Female"> 女 <BR>
    <INPUT type="radio" name="sex" value="others"> その他 <BR>
    電子メール: <INPUT type="text" name="email"><BR>
    <INPUT type="submit" value="send"> <INPUT type="reset">
    </P>
</FORM>
<H2><A NAME="cgi-example-post">CGI の POST メソッドを使う例</A></H2>
<FORM ACTION="No9_files/cgi-printarg.cgi" method="post">
    <P>
    姓: <INPUT type="text" name="lastname">
    名: <INPUT type="text" name="firstname"><BR>
    <INPUT type="radio" name="sex" value="Male"> 男 <BR>
    <INPUT type="radio" name="sex" value="Female"> 女 <BR>
    <INPUT type="radio" name="sex" value="others"> その他 <BR>
    電子メール: <INPUT type="text" name="email"><BR>
    <INPUT type="submit" value="send"> <INPUT type="reset">
    </P>
</FORM>
表示例:
<FORM>と</FORM>で括られた部
分が1つのフォームになる。(1つのページに複数のフォームを置くことがで
きる。)
<INPUT>の部分が、ブラウザからサーバに送られるデー
タを表わす。
「send」ボタンが押されると、ブラウザは、指定された
メソッドを使ってサーバにデータを送る。
メソッドとして GETが使われた時、Web ブラウザは、
普通のファイルを指定する時と同じように、URL に含めて送る。
GET /~syspro/2008/No9_files/cgi-printarg.cgi?lastname=姓&firstname=名&sex=Male&email=who@u-ust.ac.jp HTTP/1.0
URL の長さの上限で制限されるので、長いデータは送れない。
メソッドとして POST が使われた時、Web ブラウザは、データ
をHTTP の本文(ヘッダではない部分、空行の後)に入れて送る。
POST /~syspro/2008/No9_files/cgi-printarg.cgi HTTP/1.0
lastname=姓&firstname=名&sex=Male&email=who@u-ust.ac.jp
WWW サーバは、それを受け取ると、指定されたプログラムを実行する。実行さ れたプログラムは、 環境変数と 標準入力 からデータを受け取ることができる。
| 環境変数名 | 内容 | 
|---|---|
REQUEST_METHOD | 	データを送るのに使われたメソッド。POST か GET。 | 
QUERY_STRING | 	URLの指定で「?」以下に指定された文字列。 | 
SCRIPT_NAME | 	プログラムの名前。 | 
CONTENT_LENGTH | 	POSTメソッドが使われた時、標準入力から読み込めるデータのバイト数 | 
&」で区切られた
「フィールド名=値」の並びになっている。
例:
	lastname=姓&firstname=名&sex=Male&email=who@u-ust.ac.jp
GETメソッドが使われた場合、CGI のプログラムは、環境変数
QUERY_STRING からデータを得る。
POSTメソッドが使われた場合、CGI のプログラムは、データを標準入力から受
け取る。データのバイト数は、環境変数CONTENT_LENGTH に指定
されている。
送られてくるパラメタの中に漢字、
(&自身)、
URLで使えない文字が含まれていた場合、
「%hh( hh は2桁の16進数)」という形になっている。
これを元にもどすにはcgiparse というプログラム、
Ruby の CGI::unescape() や pack('H*')、
perl の命令 hex() が使われる。
   1:	
   2:	/*
   3:	        cgi-printarg.c -- CGI プログラムに対する引数を表示するプログラム
   4:	        ~yas/syspro/www/cgi-printarg.c
   5:	        Created on: 2002/06/23 18:21:34
   6:	*/
   7:	
   8:	#include <stdlib.h>     /* getenv(), malloc() */
   9:	#include <stdio.h>      /* printf() */
  10:	#include <string.h>     /* strlen() */
  11:	
  12:	extern void print_header(void);
  13:	extern void print_content(void);
  14:	extern char *get_query_string();
  15:	extern char *read_query_string();
  16:	extern void safe_printenv( char *name );
  17:	extern void safe_print_string( char *str );
  18:	extern char *html_escape( char *str );
  19:	extern char *decode_hex( char *str );
  20:	int string_split( char *str, char del, int *countp, char ***vecp  );
  21:	void free_string_vector( int qc, char **vec );
  22:	int countchr( char *s, char c );
  23:	
  24:	main()
  25:	{
  26:	        print_header();
  27:	        print_content();
  28:	}
  29:	
  30:	void print_header()
  31:	{
  32:	        printf("Content-Type: text/html\n");
  33:	        printf("\n");
  34:	}
  35:	
  36:	void print_content()
  37:	{
  38:	    char  *query_string ;
  39:	    char **qv ;
  40:	    int    qc ;
  41:	    int    i ;
  42:	
  43:	        printf("<HTML><HEAD></HEAD><BODY><PRE>\n");
  44:	
  45:	        safe_printenv("REQUEST_METHOD");
  46:	        safe_printenv("SCRIPT_NAME");
  47:	        safe_printenv("QUERY_STRING");
  48:	        safe_printenv("CONTENT_LENGTH");
  49:	
  50:	        query_string = get_query_string();
  51:	        printf("query_string:\n");
  52:	        safe_print_string( query_string ); printf("\n");
  53:	
  54:	        if(  string_split( query_string,'&',&qc, &qv ) < 0 )
  55:	        {
  56:	            printf("Error while parsing query string\n");
  57:	            printf("</PRE></BODY></HTML>\n");
  58:	            free( query_string );
  59:	            exit( -1 );
  60:	        }
  61:	        for( i=0 ; i<qc ; i++ )
  62:	        {
  63:	            char *decoded ;
  64:	            printf("qv[%d]: ",i);
  65:	            safe_print_string(qv[i]);
  66:	            decoded = decode_hex( qv[i] );
  67:	            printf("("); safe_print_string( decoded ); printf(")\n");
  68:	            if( decoded )
  69:	                free( decoded );
  70:	        }
  71:	
  72:	        printf("</PRE></BODY></HTML>\n");
  73:	        free_string_vector( qc, qv );
  74:	        free( query_string );
  75:	}
  76:	
これは、標準(nphではない)の CGI プログラムな
ので、print_header()では、HTTP のヘッダのうち、Content-Type: 行だけを
出力している。
print_content() では、本文を出力している。
safe_printenv() で、環境変数 REQUEST_METHOD, SCRIPT_NAME, QUERY_STRING, CONTENT_LENGTH の内容を標準出力に出力している。
get_query_string() は、クライアントから送られてきたパラメタ(query string)を読込み、文字列として返す関数である。この結果を、 safe_print_string() で標準出力に出力している。
for ループでは、qv[] をsafe_print_string() で標準出力に出力している。
さらに、decode_hex() で、%hh
(%hhは16進数)の形式を、バイトに元に戻すものであ
る。
  77:	char *get_query_string()
  78:	{
  79:	    char *request_method, *query_string;
  80:	        request_method = getenv("REQUEST_METHOD");
  81:	        if( request_method == 0 )
  82:	            return( 0 );
  83:	        else if( strcmp(request_method,"GET") == 0 )
  84:	        {
  85:	            query_string = getenv("QUERY_STRING");
  86:	            if( query_string == 0 )
  87:	                return( 0 );
  88:	            else
  89:	                return( strdup(query_string) );
  90:	        }
  91:	        else if( strcmp(request_method,"POST") == 0 )
  92:	        {
  93:	            return( read_query_string() );
  94:	        }
  95:	        else
  96:	        {
  97:	            printf("Unknown method: ");
  98:	            safe_print_string( request_method );
  99:	            printf("\n");
 100:	        }
 101:	}
 102:	
 103:	char *read_query_string()
 104:	{
 105:	    int   clen ;
 106:	    char *content_length ;
 107:	    char *buf ;
 108:	
 109:	        content_length = getenv("CONTENT_LENGTH");
 110:	        if( content_length == 0 )
 111:	        {
 112:	            return( 0 );
 113:	        }
 114:	        else
 115:	        {
 116:	            clen = strtol( content_length,0,10 );
 117:	            buf = malloc( clen + 1 );
 118:	            if( buf == 0 )
 119:	            {
 120:	                printf("read_query_string(): no memory\n");
 121:	                exit( -1 );
 122:	            }
 123:	            if( read(0,buf,clen) != clen )
 124:	            {
 125:	                printf("read error.\n");
 126:	                exit( -1 );
 127:	            }
 128:	            buf[clen] = 0 ;
 129:	            return( buf );
 130:	        }
 131:	}
 132:	
get_query_string() は、クライアントから送られてきたパラメタを、1つの
文字列として受け取るものである。GET メソッドの場合()は、単に環境変数 
REQUEST_METHOD を読めばよい。ただし、文字列の長さの上限がきつい。
strdup() は、文字列をコピーするものである。使い終わったら、free() で解 放する。
POST メソッドの場合は、read_query_string() で続きの処理を行う。
 133:	void safe_printenv( char *name )
 134:	{
 135:	    char *val ;
 136:	    char *safe_val ;
 137:	
 138:	        printf("%s=",name );
 139:	        val = getenv( name );
 140:	        safe_print_string( val );
 141:	        printf("\n");
 142:	}
 143:	
 144:	
 145:	void safe_print_string( char *str )
 146:	{
 147:	    char *safe_str ;
 148:	
 149:	        if( str == 0 )
 150:	        {
 151:	            printf("(null)");
 152:	            return;
 153:	        }
 154:	        safe_str = html_escape( str );
 155:	        if( safe_str == 0 )
 156:	        {
 157:	            printf("(no memory)");
 158:	        }
 159:	        else
 160:	        {
 161:	            printf("%s",safe_str );
 162:	            free( safe_str );
 163:	        }
 164:	}
 165:	
 166:	char *html_escape( char *str )
 167:	{
 168:	    int len ;
 169:	    char c, *tmp, *p, *res ;
 170:	
 171:	        len = strlen( str );
 172:	        tmp = malloc( len * 6 + 1 );
 173:	        if( tmp == 0 )
 174:	            return( 0 );
 175:	        p = tmp ;
 176:	        while( c = *str++ )
 177:	        {
 178:	            switch( c )
 179:	            {
 180:	            case '&': memcpy(p,"&", 5); p += 5 ; break;
 181:	            case '<': memcpy(p,"<",  4); p += 4 ; break;
 182:	            case '>': memcpy(p,">",  4); p += 4 ; break;
 183:	            case '"': memcpy(p,""",6); p += 6 ; break;
 184:	            default:  *p = c ;              p++ ;    break;
 185:	            }
 186:	        }
 187:	        *p = 0 ;
 188:	        res = strdup( tmp );
 189:	        free( tmp );
 190:	        return( res );
 191:	}
 192:	
safe_printenv() は、環境変数をgetenv() で検索し、その値を
safe_print_string() で標準出力に出力している。
safe_print_string() は、次の html_escape() を使って文字列を安全なもの にして表示する。
html_escape() は、次の表の文字を置換えている。
画面表示 代りに送りだす文字列 ---------------------------------------------------------------------- & & < < > > " "これで、<SCRIPT> のような危険なスクリプトが送り込まれたとし ても「<SCRIPT>」と表示と表示されるだけで、スクリプト は実行されない。
len * 6 は、最大で元の文字列の6倍の長さになることに備えている。
193: char *decode_hex( char *str ) ... 228: int string_split( char *str, char del, int *countp, char ***vecp ) ... 273: void free_string_vector( int qc, char **vec ) ... 285: int countchr( char *s, char c ) ...decode_hex() は、「
%hh(2桁の16進数)」を元のバイトに
戻すものである。isxdigit() は、16進数の文字として正しいかを調べてい
る。strtol() で基底を 16 として変換している。
string_split() は、引数の文字列を、引数の区切り記号で分解する関数である。 分解した結果は、main() の引数である int argc, char *argv[] と同じ形になっ ている。qc が、argc, qv が argv に対応する。
free_string_vector() は、string_split() で malloc() したものを free() するための手続きである。
countchr() は、文字列の中でその文字がいくつ出てくるかを数えるものである。
実行例:
REQUEST_METHOD=GET SCRIPT_NAME=/~syspro/2008/No9_files/cgi-printarg.cgi QUERY_STRING=lastname=arg1&firstname=arg2&sex=Male&email=arg3 CONTENT_LENGTH=(null) query_string: lastname=arg1&firstname=arg2&sex=Male&email=arg3 qv[0]: lastname=arg1(lastname=arg1) qv[1]: firstname=arg2(firstname=arg2) qv[2]: sex=Male(sex=Male) qv[3]: email=arg3(email=arg3)
REQUEST_METHOD=POST SCRIPT_NAME=/~syspro/2008/No9_files/cgi-printarg.cgi QUERY_STRING= CONTENT_LENGTH=48 query_string: lastname=arg1&firstname=arg2&sex=Male&email=arg3 qv[0]: lastname=arg1(lastname=arg1) qv[1]: firstname=arg2(firstname=arg2) qv[2]: sex=Male(sex=Male) qv[3]: email=arg3(email=arg3)
/var/log/httpd/www.coins80/access_log.* /var/log/httpd/www.coins443/access_log.*この中で、数字が一番大きなもの、または、日付が新しいものが、 現在増加しているログである。
% cd /var/log/httpd/www.coins80 
% ls    access_log.* | tail -1 
% ls -t access_log.* | head -1 
% 
以下は、アクセスログの例である。
130.158.83.140 - - [10/Jun/2008:21:47:44 +0900] "GET /~syspro/2008/shui/common_files/Ctrl-Z.png HTTP/1.1" 200 189 130.158.83.140 - - [10/Jun/2008:21:53:52 +0900] "GET /~yas/coins/syspro-2008/No9.html HTTP/1.1" 200 570761行が1つのページの転送を意味する。左端は、クライアントの IP アドレス である。どのような要求が来たか、クライアントのIPアドレスがわかる。
アクセスログの他に、エラーが起きた時にもログが作られる。これをエラーロ グという。エラーログは、情報学類のサーバ www (orchid-nwd) では、次の場所にある。
/var/log/httpd/www.coins80/error_log.* /var/log/httpd/www.coins443/error_log.*CGI のプログラムをデバッグする時には、このエラーログを見て原因を探る。
その他に、次のようなファイルがある。
agent_log
referer_log
suexec_log
GET の場合、環境変数 REQUEST_METHOD に GETと設定し、環境変数 QUERY_STRING にパラメタを設定する。
% setenv REQUEST_METHOD GET 
% setenv QUERY_STRING 'lastname=name1&firstname=name2&sex=Male&email=who@dom' 
% ./cgi-printarg.cgi 
Content-Type: text/html
<HTML><HEAD></HEAD><BODY><PRE>
REQUEST_METHOD=GET
SCRIPT_NAME=(null)
QUERY_STRING=lastname=name1&firstname=name2&sex=Male&email=who@dom
CONTENT_LENGTH=(null)
query_string:
lastname=name1&firstname=name2&sex=Male&email=who@dom
qv[0]: lastname=name1(lastname=name1)
qv[1]: firstname=name2(firstname=name2)
qv[2]: sex=Male(sex=Male)
qv[3]: email=who@dom(email=who@dom)
</PRE></BODY></HTML>
% 
POST の場合、まず、データをファイルに保存しておく。そのバイト数を wc 
コマンドなどで調べておく。環境変数 REQUEST_METHOD に POST と設定する。
環境変数 CONTENT_LENGTH にデータファイルの大きさを設定する(ここでは最
後の改行を取り除いたバイト数を指定している)。
% echo 'lastname=name1&firstname=name2&sex=Male&email=who@dom' > data 
% wc data 
      1       1      54 data
% setenv REQUEST_METHOD POST 
% unsetenv QUERY_STRING 
% setenv CONTENT_LENGTH 53 
% ./cgi-printarg.cgi < data 
Content-Type: text/html
<HTML><HEAD></HEAD><BODY><PRE>
REQUEST_METHOD=POST
SCRIPT_NAME=(null)
QUERY_STRING=(null)
CONTENT_LENGTH=53
query_string:
lastname=name1&firstname=name2&sex=Male&email=who@dom
qv[0]: lastname=name1(lastname=name1)
qv[1]: firstname=name2(firstname=name2)
qv[2]: sex=Male(sex=Male)
qv[3]: email=who@dom(email=who@dom)
</PRE></BODY></HTML>
% 
rw-rw-rw-や 777
rwxrwxrwx にする)。このため、そのコンピュータにログイン
でき人ならば、そのファイルの名前を知っていれば、データを取り出したり内
容を破壊したりできる。
suEXEC を使う方法の場合、長所と短所は逆になる。
普通のプログラミング言語は、アプリケーション・プログラム本体を記述する 時に使われる。 これに対して、 スクリプト言語 は、アプリケーション本体で はなく、アプリケーションの細かな動作を変更したり、アプリケーション本体 を変更することなく機能を追加したりするために使われる言語である。 普通のプログラミング言語は、アプリケーション・プログラマにより使われ、 コンパイラで機械語に変換されるので、実行時には機械語しか残っていない。 これに対して、スクリプト言語は、アプリケーションのユーザや、システム管 理者などによって使われ、プログラムは、アプリケーションに組み込まれた インタプリタで解釈実行される。 スクリプト言語を使うと、単に変数を設定することに比べて、 高度な機能拡張が機能になる。
スクリプト言語の例:
% cat cgi-hello-csh.cgi 
#!/bin/csh -f
cat <<EOF
Content-Type: text/html
<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
EOF
% ./cgi-hello-csh.cgi 
Content-Type: text/html
<HTML><HEAD></HEAD><BODY>
hello.
</BODY></HTML>
% 
JavaScript は、文法が少し Java 言語に似ているが、Java とはまったく別の 言語である。WWW ページの中の Javaアプレットは、 「実行可能なインライン・イメージ」に似ている。これに対して JavaScript の記述は、「実行可能なインライン・テキスト」 に似ている。
JavaScrip のプログラムの例:
<SCRIPT LANGUAGE="JavaScript">
<!--
for( i=0 ; i<10; i++ )
  document.writeln("<P>hello,world</P>");
//-->
</SCRIPT>
これは、<P>hello,world</P> を 10 回書いた
のと同じ効果がある。<!--と
//->は、JavaScriptを知らないブラウザにはコメント
として扱われる。関数定義などは、ヘッダ部分
<HEAD></HEAD>に書くという方法もよく使われる。
for、while 、
if, else, continue,
break がある。ただし、switch は使えない。
function で、関数定義ができる。
var で宣言すれば、ローカル変数になる。
配列は、new Array(長さ) で確保する。
class というキーワードはない。
function に new を付けて呼べば、オブジェ
クトになる。オブジェクトのメソッドでは、this を使っ
て要素を参照できる。
JavaScript の記述は、CGI と似ているところもあるが、JavaScript でないと
できないものに、ブラウザの制御がある。たとえば、次の例では、ブラウザの
(戻る)ボタンと同じ動きをさせることができる。
<A HREF="javascript:history.back();">戻る</A>次の例は、
<FORM></FORM>からパラメタを受け取るも
のである。
<SCRIPT LANGUAGE="JavaScript">
function go(s,h,p)
{
	location.href = s.value + "://" + h.value + p.value ;
}
</SCRIPT>
<FORM NAME="form1">
<INPUT NAME="scheme" TYPE="text" VALUE="http"><BR>
<INPUT NAME="host"   TYPE="text"><BR>
<INPUT NAME="path"   TYPE="text" VALUE="/"><BR>
<INPUT TYPE="button" VALUE="go" onClick="go(form1.scheme,form1.host,form1.path)">
</FORM>
onClick 属性の値は、クリックした時に評
価される式であり、関数 go() が呼び出されている。引
数は、<FORM> の値である。関数 
go() の中では、
.value フィールドから値が読み出されている。
location.href に代入することで
そのページを表示させることができる。
この例では、<INPUT type="text">と
<INPUT type="button">が使われている。その他に、
<FORM>では、<INPUT
TYPE="radio">、<INPUT TYPE="checkbox">、
<SELECT>が使える。イベントとしては、
onClick が主に使われる。その他に、
onFocus が使われることもある。
JavaScript では、document.open() で新しく HTML の
ドキュメントを生成して、それをブラウザに表示させることもできる。
WWWブラウザでは、信頼しているサイトから送られてくるJavaScriptのプログ ラムだけを実行するようにし、攻撃サイトから送られてくるJavaScriptのプロ グラムを実行しないようにしたい。
脆弱性があるサイトでは、攻撃サイトから送られてきた JavaScript のプログ ラムを中継してしまう。
 図? JavaScripの送信元サイトの区別
CGI のプログラムをつくる時には、クロスサイトスクリプティング攻撃に気を
つける。これは、クライアントから送られてる文字列の中に
<SCRIPT>のようなタグが含まれていた場合、それをそのままクラ
イアントに送り返すと問題がある。
(さらに、
%hh
にも気をつける必要がある。)
クライアントから送られてきた文字列は、必ず検査し、安全な状態にして (sanitize)から使う。「<>&"」のようなタグが含まれている場合 には注意する。このような文字列を受け取った場合、不用意に送り返してはい けない。送り返す時には、html_escape() のよう な方法で必ずエスケープする。
他のプログラムを実行する時には、execve() のようなシステムコールを使い、
かつ、限られたプログラムしか実行しないようにすると安全性が高くなる。ク
ライアントから送られてきた文字列をsystem() や popen() に渡してプログラ
ムを実行する時には、必ず検査する。特にシェルが解釈する特殊な文字
「| & ; && || `」などが含
まれていた場合、意図しないプログラムが実行されることがある。
char *user ;
...
snprintf(cmd,BUFSIZE,"finger %s",user );
f = poepn(cmd,"r");
もし、user に ";" や "|" が含まれていたら、、、 
f = poepn("finger yas; /bin/sh","r");
Ruby の open() には、危険性がある。 C 言語のライブラリ関数 popen() と同じ動きをすることがある。
open("|cmd")
Ruby の注意すべき関数、式、クラス
perl の open() には、危険性がある。 C 言語のライブラリ関数 popen() と同じ動きをすることがある。
open(FILE, "|cmd")
Perl の注意すべき関数や式
産業技術総合研究所 グリッド研究センター セキュアプログラミングチーム
http://securit.gtrc.aist.go.jp/
)
IPA セキュア・プログラミング講座
http://www.ipa.go.jp/security/awareness/vendor/programming/
)
アクセスカウンタなどデータ・ファイルを書き換えるような CGI プログラム の場合、複数のプログラムが同時に実行された時のことを考慮する必要がある。 必要に応じて、ファイルのロックを行うなどして、一度に1つのプロセスしか プログラムを実行しないようにする。このことを、相互排除という。
相互排除について詳しくは、後半のマルチスレッド・プログラミングや 2学期のオペレーティング・システムで扱う。 特に、扱うべき資源が複数になった場合には、デッドロックに気をつける。
Unix では、ファイルに対するロックを扱うために、次のような機能を提供し ている。
セッション管理には、次のような方法がある。
セッション管理にクッキーを使う方法は、正しく使えば比較的安全である。し かし、クッキーはセッションを越えて有効なもの、異なるサイトに送られるも のも定義できる。また、クッキーにパスワードを埋め込むという幼稚で危険な 実装も時々見受けられる。
セッションは、認証とは別である。同じ人が複数のセッションを実行すること もある。
協調して動作しているプログラムの間で、ある一連の作業を識別するための数 を意味する。
RPC、HTTP(WWW)で使われる。
WWW ブラウザや telnet を使って得た結果と /var/www/htdocs/ 以下のファイ ルが同一であることをwww に ssh でログインして確認しなさい。
[azalea59:~] yas% ssh www
Last login: Tue Jun 17 15:03:11 2008 from sharon.hlla.is.
Welcome to Darwin!
[orchid-nwd:~] yas% cd /var/www/htdocs/
[orchid-nwd:/var/www/htdocs] yas% ls -l index.html
-rw-rw-r--   1 kawasima  c-kikaku  3441 May  2 21:03 index.html
[orchid-nwd:/var/www/htdocs] yas% lv index.html
[orchid-nwd:/var/www/htdocs] yas% 
同様に https でアクセスできるページについても調べなさい。 トップのディレクトリは、/var/www/secure_htdocs/ 以下にある。
/var/log/httpd/www.coins80/access_log.* というファイルにためられる。
これを、次のようにして観察しなさい。
% cd /var/log/httpd/www.coins80 
% ls access_log.* 
<ログファイル多数>
% ls access_log.* | tail -1   
access_log.200806100654
% ls -t access_log.* | head -1  
access_log.200806100654
% 
この例では、access_log.200806100654 が最新のものである。
% tail -f access_log.200806100654 
http://www.coins.tsukuba.ac.jp/や自分の WWW ページを開く。
次の CGI プログラムに対して、フォームを定義し、何かデータを送ってみなさい。
http://www.coins.tsukuba.ac.jp/~syspro/2008/No9_files/cgi-printarg.cgi
ヒント:
     char *arg1_s,arg2_s;
     arg1_s = getparam("arg1");
     arg2_s = getparam("arg2");
getparam() は、次のように自分で定義する。
char *getparam( int qc, char *qv[], char *name /*変数名*/ )
{
   int i;
	for( i=0; i<qc; i++ ) {
	    qv[i] の中で "変数名=値" となっているものを探す。
	    見つければ "値" の部分を返す。
	}
	見つからなければ、NULL を返す。
}
% setenv REQUEST_METHOD GET 
% setenv QUERY_STRING 'arg1=10&arg2=10' 
% ./a.out 
% 
一度行った setenv の結果は残っている。
% mkdir ~/public_html/syspro-cgi-examples/ 
% cp a.out ~/public_html/syspro-cgi-examples/calc.cgi 
余裕があれば、メニューやラジオボタンなどで足算以外の計算もできるように しなさい。
% cal 
     June 2008
 S  M Tu  W Th  F  S
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
% 
CGI プログラムを実行した月のカレンダーを表示しなさい。固定した表示する だけなら、CGI の意味はない。余計な表示は省略すること。
余裕があれば、今月のカレンダーではなく、任意の年・月のカレンダーを表示 しなさい。
この課題では、system() や popen() を用いてはならない。
CGI プログラムが、どのユーザの権限で動作しているかを調べるプログラムをつくりなさい。
ヒント:CGI のプログラムから id コマンドを実行させる。
レポートには、最初に脆弱生がある CGI プログラムを作成した年月日、 今回修正した場所、および、その修正箇所の説明をつけなさい。
レポートには、最初に脆弱生がある CGI プログラムを作成した年月日、 今回修正した場所、および、その修正箇所の説明をつけなさい。