PHP 5.2.8 Extension 개발하기

이번에 SafeNet DataSecure (Ingrain) 제품을 사용하여 데이터 암호화를 사용해야했다.

이 제품은 별도의 하드웨어(어플라이언스)로 구성되어 있으며 이 어플라이언스가 실제 암호화 및 복호화를 하도록 되어있다.

웹 어플리케이션 서버에서는 해당 서버로 TCP/IP 또는 SSL을 사용하여 API를 주고 받도록 되어있다.

문제는 Ingrain에서는 C API와 Java API는 제공하지만 PHP용은 제공하지 않는다는 점이다.

이 문제점을 해결하기 위해 제공되는 C API를 사용하는 PHP Extension을 개발해야 했다.

무척 오랜만에 C로 코딩을 하니 Java나 C#과 같은 언어를 사용할 때 참 쉽게 개발했다는 것이 느껴졌다.

환경은 Red Hat Enterprise Linux ES release 4 (Nahant Update 5), 커널 2.6.9-55.ELsmp이다.

1. PHP extension 만들기

PHP에서는 확장 모듈을 쉽게 개발할 수 있도록 ext_skel 도구를 제공한다.

php-5.2.8 $ cd php-5.2.8/ext
php-5.2.8/ext $ ext_skel --extname=ingrian
Creating directory ingrain
Creating basic files: config.m4 config.w32 .cvsignore ingrain.c php_ingrain.h CREDITS EXPERIMENTAL tests/001.phpt ingrain.php [done].

To use your new extension, you will have to execute the following steps:

1.  $ cd ..
2.  $ vi ext/ingrain/config.m4
3.  $ ./buildconf
4.  $ ./configure --[with|enable]-ingrain
5.  $ make
6.  $ ./php -f ext/ingrain/ingrain.php
7.  $ vi ext/ingrain/ingrain.c
8.  $ make

Repeat steps 3-6 until you are satisfied with ext/ingrain/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.

이와 같이 하면 8개의 파일과 1개의 디렉토리가 생성된다.
사용법까지 출력해주니 참 편하다.

중요한 것만 보면 다음과 같다.

ingrain.c 확장모듈 예제 C 소스
ingrain.php 확장 모듈 확인용 PHP
config.m4 autoconf 용 설정 파일
php_ingrain.h 확장모귤 예제 헤더

2. 설정 파일 수정

제대로 컴파일을 하기 위해서 config.m4 을 설정해야하다.
이 확장 모듈은 설치된 CAPI가 있어야 하므로 enable-ingrain 대신 with-ingrain을 사용하도록 했다.

php-5.2.8 $ vi ext/ingrian/config.m4
dnl $Id$
dnl
dnl @(#)config.m4
dnl Author: Barney Kim
dnl
dnl PHP Extension for SafeNet DataSecure (Ingrian) ICAPI
dnl
dnl config.m4 for extension ingrian

PHP_ARG_WITH(ingrian, for Ingrian(SafeNet DataSecure) support,
[  --with-ingrian             Include Ingrian support])

if test "$PHP_INGRIAN" != "no"; then
  if test "$PHP_INGRIAN" != "yes"; then
  	SEARCH_PATH=$PHP_INGRIAN
  else
  	SEARCH_PATH="/usr/local /usr"
  fi

  dnl 헤더 파일 검사
  SEARCH_FOR="/include/icapi.h"
  if test -r $PHP_INGRIAN/$SEARCH_FOR; then
  	 AC_MSG_CHECKING([for ingrian files in custom path])
     INGRIAN_DIR=$PHP_INGRIAN
  else
     AC_MSG_CHECKING([for ingrian files in default path])
     for i in $SEARCH_PATH ; do
       if test -r $i/$SEARCH_FOR; then
         INGRIAN_DIR=$i
         AC_MSG_RESULT(found in $i)
       fi
     done
  fi
  dnl
  if test -z "$INGRIAN_DIR"; then
     AC_MSG_RESULT([not found])
     AC_MSG_ERROR([Please reinstall the ingrian distribution])
  fi

  PHP_ADD_INCLUDE($INGRIAN_DIR/include)

  dnl 추가적인 CFLAG 설정
  INGRIAN_ARCH="-DARCH=64"

  dnl 라이브러리명 (libIngICAPI)
  LIBNAME=IngICAPI
  dnl 라이브러리가 가지고 있는 API 중 1개 선택
  LIBSYMBOL=I_C_Initialize

  PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
   [
     PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $INGRIAN_DIR/lib, INGRIAN_SHARED_LIBADD)
     AC_DEFINE(HAVE_INGRIANLIB,1,[ ])
    ],[
      AC_MSG_ERROR([wrong ingrian lib version or lib not found])
    ],[
      -L$INGRIAN_DIR/lib -lm -ldl
    ])
  PHP_SUBST(INGRIAN_SHARED_LIBADD)

  PHP_NEW_EXTENSION(ingrian, ingrian.c, $ext_shared,,$INGRIAN_ARCH)
fi

3. 컴파일 테스트

별도의 라이브러리 파일로 배포가 가능하도록 만들고, CAPI 디렉토리를 지정한다.

php-5.2.8 $ ./buildconf --force
php-5.2.8 $ ./configure --with-ingrian=shared,/usr/local/ingrian
php-5.2.8 $ make

4. 테스트

php-5.2.8 $ cd ext/ingrain
php-5.2.8/ext/ingrain $ php ingrain.php
Functions available in the test extension:
confirm_ingrian_compiled

Congratulations! You have successfully modified ext/ingrain/config.m4. Module ingrain is now compiled into PHP.

5. 소스 구현

이제 실제 소스를 수정하면서 확장 모듈을 구현한다.

php-5.2.8/ext/ingrain $ vi ingrain.c
php-5.2.8/ext/ingrain $ vi php_ingrain.h

아래는 간단한 예이다.

zend_function_entry ingrain_functions[] = {
  PHP_FE(ingrain_ex,	NULL)
  PHP_FE(confirm_ingrain_compiled,	NULL)
  {NULL, NULL, NULL}
};
...
PHP_FUNCTION(ingrian_ex) {
  char *s1, *s2, *s3, *result;
  int s1_len,s2_len,s3_len;
  ...
  // 인자 값 3개인지 확인
  if (ZEND_NUM_ARGS() < 3) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Wrong parameter count");
    return;
  }
  // 인자 값을 s1, s2, s3으로 지정
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
      "sss", &s1, &s1_len, &s2, &s2_len, &s3, &s3_len) == FAILURE) {
    return;
  }
  ...
  // 결과를 문자열로 리턴
  len = spprintf(&result, 0, "result=%s", some);
  RETURN_STRINGL(result, len, 0);
}

그리고 다시 테스트를 한다.
정상적으로 컴파일이 되었다면 라이브러리 파일이 생겼을 것이다.

find . -name "ingrian*" -print
./ext/ingrian
./ext/ingrian/ingrian.c
./ext/ingrian/ingrian.php
./ext/ingrian/ingrian.la
./ext/ingrian/.libs/ingrian.o
./ext/ingrian/.libs/ingrian.so
./ext/ingrian/.libs/ingrian.a
./ext/ingrian/.libs/ingrian.lai
./ext/ingrian/.libs/ingrian.la
./ext/ingrian/ingrian.o
./ext/ingrian/ingrian.lo
./modules/ingrian.so
./modules/ingrian.la
./modules/ingrian.a

6. 배포

테스트가 성공적이면 이제 해당 웹 서버의 PHP 확장 디렉토리에 배포하고 설정파일을 수정한다.

php-5.2.8/ext/ingrain $ ls `php-config  --extension-dir`
ls: /usr/local/php5/lib/extensions/some: No such file or directory
php-5.2.8/ext/ingrain $ mkdir -p `php-config  --extension-dir`
php-5.2.8/ext/ingrain $ cp modules/ingrain.so `php-config  --extension-dir`
php-5.2.8/ext/ingrain $ vi /etc/ld.so.conf.d/php.conf
/usr/local/ingrian/lib
php-5.2.8/ext/ingrain $ vi /usr/local/php5/lib/php.ini
;;;;;;;;;;;;;;;;;;;;;;
; Dynamic Extensions ;
;;;;;;;;;;;;;;;;;;;;;;
extension=ingrian.so

참고