PHP로 구현한 게시판 자동 등록기 클래스입니다.

01 14, 2007 02:10


프리랜서로 작업을 하다가 업체에서 광고를 할 수 있는 게시판 등록기를 구해달라고 해서
몇가지 찾아봤습니다.
그렇지만...인터넷에서 구할 수 있는 자동등록기가 얼마없고(무료로^^) 사용해보니 안되는 곳들이 많더라구요.
추가변수 및 값들을 함께 전송할 수 있나 봤더니 그것이 안되어서 등록이 안되더라구요...
그래서 직접 소켓을 이용해서 한번 만들어 봤습니다.
결과는 일단은 만족적입니다. 모든 게시판에 다 쓸 수는 없었으나...
일반적인 홈페이지의 게시판들에는 쓰는게 가능하더라구요...
여기까지는 만든 계기구요...

아직은 초기버전이라 레버퍼 체크 및 쿠기 체크 등의 간단한 제약만 뚫고 글을 쓰는 정도입니다만 공개하는 이유는 스쿨에 와서 가끔보면 게시판 등록기에 대한 예기들이 있어서입니다.
이런 게시판 등록기의 기본 작동원리인 게시판에서 글쓰기 시에 어떤 요청이 오가고 어떤식으로 로봇이 들어와서 작업을 하는 것인지, 레퍼러 변조 및 쿠키 값 변조는 어떻게 하는 것인지 대략적으로나마 알려드리기 위해서입니다.
물론 이 방법 외의 방법을 사용하는 로봇도 있고 이걸 알고 계시는 고수분들도 많겠지만... 아직 모르시는 분들도 있기에 공개합니다.


일단 아직은 게시판 정보는 수동으로 폼을 분석해서 넣습니다.
자동으로 게시판을 분석하여 자동으로 게시판 정보를 추가하는 프로그램은 작업중에 있습니다.
자동으로 입력하고자 하는 게시판을 분석하여 아래의 정보를 DB에 넣어줍니다.
아래에 넣는 항목 중 추가변수(ff_etc)는 db=test&action=write와 같이 기본 필드외에 필요한 변수들을 적어주시면 됩니다.
그리고 formdata(boundary를 이용)와 urlencoded(GET 방식과 비슷함)의 전송방법은 요청헤더를 보고 차이점을 찾아보시기 바랍니다.
이것은 게시판에서 글쓰기시의 오가는 패킷을 분석해본 결과 위의 두가지중 하나로 처리가 되더라구요.보통의 게시판에서는 둘 중 아무것이나 해도 되는것이 있으나 어떤 게시판들은 저 둘중 하나만으로 들어와야지만 등록이 되더라구요...
이 부분에 대해서는 좀 더 찾아보고 다음 버전에서 알려드리겠습니다.
====== DB 스키마 ========================================
CREATE TABLE autopost (
  num int(6) NOT NULL auto_increment,
  title varchar(250) NOT NULL default '',       // 사이트 이름
  type varchar(30) default NULL,                // 전송방법 (formdata, urlencoded)
  site varchar(250) NOT NULL default '',        // 리스트 페이지 주소
  port int(5) default '80',                     // 접속 포트 (기본 80)
  referer varchar(250) NOT NULL default '',     // 글쓰기폼 페이지 주소
  cookie text,                                  // 글쓰기폼에서의 쿠키값
  method varchar(4) default 'POST',             // 전송방법 (POST,GET)
  target varchar(250) NOT NULL default '',      // 글쓰기 페이지의 주소
  ff_name varchar(40) NOT NULL default '',      // 작성자 필드 이름
  ff_password varchar(40) NOT NULL default '',  // 비밀번호 필드 이름
  ff_email varchar(40) default NULL,            // 이메일 필드 이름
  ff_homepage varchar(40) default NULL,         // 홈페이지 필드 이름
  ff_subject varchar(40) NOT NULL default '',   // 제목 필드 이름
  ff_content varchar(40) NOT NULL default '',   // 내용 필드 이름
  ff_etc text NOT NULL,                         // 추가변수
  count int(4) NOT NULL default '0',            // 전송횟수
  date datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (num)
) TYPE=MyISAM;


$sbh란 객체를 만들고 게시판에 입력할 이름,비밀번호,제목,내용,이메일 등등의 값을 설정합니다.
그런 다음 DB에 내용을 읽어 $sbh->setData($row)로 넘겨줍니다.
그런 다음 $result로 결과값을 받아옵니다.
응답 코드가 200인 것에 대해서 성공으로 처리합니다.
이것은 웹서버에서 별다른 에러없이 처리했다는 메세지이지 게시물의 등록이 성공적으로 되었다는 것을 의미하지는 않습니다.
====== write.php 파일 소스 ==============================
    include("send_board.inc");
    $sbh = new sendBoard($name,$password,$email,$subject,$content,$homepage);

    $i = 0;
    $total_success = 0;
    $total_fail = 0;
    $sql_order = " order by num desc";
    $query = mysql_query("select * from autopost where $sql_where $sql_order");
    while($row = mysql_fetch_array($query)){
            // 1이면 요청헤더와 응답헤더만 보여주고 데이타를 전송은 안함
            // 2이면 요청헤어돠 응답헤더를 보여주고 데이타를 전송함
            $sbh->setDebug(2);
            $result = $sbh->setData($row);
            $print_num[$i]     = $row[num];
            $print_title[$i]   = $row[title];
            $print_site[$i]    = $row[site];
            $print_message[$i] = $sbh->getHttpStatusName($result[code]);

            if($result[code] == 200){
                $print_result[$i] = "<font color=blue>성공</font>";
                $dbh->sql_update("autopost","count=count+1","num='$row[num]'");
                $print_count[$i] = $row[count] + 1;
                $total_success++;
            }else{
                $print_result[$i] = "<font color=red>실패</font>";
                $total_fail++;
                $print_count[$i] = $row[count];
            }
            $i++;
    }

====== send_board.inc 파일 소스 =========================
<?
class sendBoard{
    var $time_out = 60;
    var $debug_level;
    var $row;
    var $data_name;
    var $data_password;
    var $data_email;
    var $data_subject;
    var $data_content;
    var $data_homepage;

    // 입력해야할 값들을 설정함
    //{{{ function sendBoard()
    function sendBoard($name,$password,$email,$subject,$content,$homepage=""){
        $this->data_name     = $name;
        $this->data_password = $password;
        $this->data_email    = $email;
        $this->data_subject  = $subject;
        $this->data_content  = $content;
        $this->data_homepage = $homepage;
    }
    //}}}

    // 디버그 레벨을 설정함
    //{{{ function setDebug($debug_level=1);
    function setDebug($debug_level=1){
        $this->debug_level = $debug_level;
    }
    //}}}

    // 기본 게시판 설정 정보를 가공하여 설정하고 그 정보를 바탕으로 요청헤더를 만들어
    // sendData()함수에 넘겨서 전송작업을 하고 결과를 받아서 리턴함
    //{{{ function setData($row)
    function setData($row){
        $server = parse_url($row[site]);
        $row[server] = $server[host];
        if(!$row[referer]) $row[referer] = "http://".$server[host]."/";

        $this->row = $row;

        $request_header = $this->getRequestHeader($row[type]);
        $result = $this->sendData($row[server],$row[port],$this->time_out,$request_header);
        return $result;
    }
    //}}}

    // 요청헤더를 만드는 함수
    //{{{ function getRequestHeader($type="")
    function getRequestHeader($type=""){

        ## request header를 정의함
        $request_header  = $this->row[method]." ".$this->row[target]." HTTP/1.0\r\n";
        $request_header .= "Host: ".$this->row[server]."\r\n";
        $request_header .= "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*\r\n";
        $request_header .= "Accept-Language: ko\r\n";
        $request_header .= "Accept-Encoding: gzip, deflate\r\n";
        $request_header .= "Referer: ".$this->row[referer]."\r\n";
        $request_header .= "User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98)\r\n";
        $request_header .= "Cache-Control: no-cache\r\n";
        $request_header .= "Connection: Keep-Alive\r\n";
        if($this->row[cookie]) $request_header .= "Cookie: ".trim($this->row[cookie])."\r\n";

        if($type == "formdata"){
            $boundary = uniqid("");
            $boundary_header = "boundary=---------------------------".$boundary."\r\n";
            $boundary_body   = "-----------------------------".$boundary."\r\n";
            $boundary_footer = "-----------------------------".$boundary."--\r\n";
            $formdata = $this->getFormData($boundary_body);
            $content_length = strlen($formdata);

            $request_header .= "Content-Type: multipart/form-data; $boundary_header";
            $request_header .= "Content-Length: $content_length\r\n\r\n";
            $request_header .= $formdata;
            $request_header .= $boundary_footer;
        }else{
            $formdata = $this->getFormData();
            $content_length = strlen($formdata)."\r\n";

            $request_header .= "Content-Type: application/x-www-form-urlencoded\r\n";
            $request_header .= "Content-Length: $content_length\r\n";
            $request_header .= $formdata;
        }
        $request_header .= "\r\n";

        if($this->debug_level) $this->printDebug("요청 헤더",$request_header);
        return $request_header;
    }
    //}}}

    // 게시판에 입력될 정보들을 요청헤더에 함께 보낼 형식으로 가공
    //{{{ function getFormData()
    function getFormData($boundary_body=""){
        if($this->row[type] == "formdata"){
            $formdata .= $boundary_body."Content-Disposition: form-data; name=\"".$this->row[ff_name]."\"\r\n\r\n".$this->data_name."\r\n";
            $formdata .= $boundary_body."Content-Disposition: form-data; name=\"".$this->row[ff_password]."\"\r\n\r\n".$this->data_password."\r\n";
            $formdata .= $boundary_body."Content-Disposition: form-data; name=\"".$this->row[ff_email]."\"\r\n\r\n".$this->data_email."\r\n";
            $formdata .= $boundary_body."Content-Disposition: form-data; name=\"".$this->row[ff_homepage]."\"\r\n\r\n".$this->data_homepage."\r\n";
            $formdata .= $boundary_body."Content-Disposition: form-data; name=\"".$this->row[ff_subject]."\"\r\n\r\n".$this->data_subject."\r\n";
            $formdata .= $boundary_body."Content-Disposition: form-data; name=\"".$this->row[ff_content]."\"\r\n\r\n".$this->data_content."\r\n";
            $etc_data = explode("&",$this->row[ff_etc]);
            for($t=0; $t<sizeof($etc_data); $t++){
                $tmp = explode("=",$etc_data[$t]);
                $formdata .= $boundary_body."Content-Disposition: form-data; name=\"".$tmp[0]."\"\r\n\r\n".$tmp[1]."\r\n";
            }
        }else{
            if($this->row[ff_etc]) $formdata .= trim($this->row[ff_etc])."&";
            $formdata .= $this->row[ff_name]."=".$this->data_name."&";
            $formdata .= $this->row[ff_password]."=".$this->data_password."&";
            $formdata .= $this->row[ff_email]."=".$this->data_email."&";
            $formdata .= $this->row[ff_homepage]."=".$this->data_homepage."&";
            $formdata .= $this->row[ff_subject]."=".$this->data_subject."&";
            $formdata .= $this->row[ff_content]."=".$this->data_content;
            $formdata .= "\r\n";
        }
        return $formdata;
    }
    //}}}

    // 소켓을 이용하여 가공된 요청헤더를 보내고 결과를 리턴하는 함수
    //{{{ function sendData($server,$port,$time_out,$request_header)
    function sendData($server,$port,$time_out,$request_header){

        if($this->debug_level > 1){
            $header = "";
            $result = "";

            ## 소켓 요청 연결
            $socket = @fsockopen($server,$port,$errno,$errstr,$time_out);

            ## 소켓 연결이 성공했을 때
            if($socket){
                fwrite($socket,$request_header);

                ## response header 를 읽어옴
                do $header .= fread($socket,1); while (!preg_match('/\\r\\n\\r\\n$/',$header));

                ## chunked encoding 검사
                if (preg_match('/Transfer\\-Encoding:\\s+chunked\\r\\n/',$header))
                    do {
                        $byte = "";
                        $chunk_size="";
                        do {
                            $chunk_size.=$byte;
                            $byte=fread($socket,1);
                        } while ($byte!="\\r");

                        fread($socket, 1);
                        $chunk_size = hexdec($chunk_size);
                        $result .= fread($socket,$chunk_size);
                        fread($socket,2);
                    } while ($chunk_size);
                else {
                    ## content length 가 정의 되었는지 검사
                    if(preg_match('/Content\\-Length:\\s+([0-9]*)\\r\\n/',$header,$matches)) {
                        $result = fread($socket,$matches[1]);
                    }else{
                        while (!feof($socket)) $result .= fread($socket, 4096);
                    }
                }

                ## 소켓 닫기
                fclose($socket);
            ## 소켓 연결이 실패했을 때
            }else{
                errorMsg("$server:$port 서버로 연결할 수 없습니다.");
            }

            $return_value[header] = $header;
            $return_value[result] = $result;
            $return_value[code]   = substr($header,9,3);

            if($this->debug_level) $this->printDebug("응답 헤더",$return_value[header]);
            return $return_value;
        }
    }
    //}}}

    // 에러메세지 출력 함수
    //{{{ function errorMsg($msg,$url="javascript:history.go(-1)",$frame="document")
    function errorMsg($msg,$url="javascript:history.go(-1)",$frame="document"){
    echo "
        <table width=100% height=350 border=0 cellpadding=0 cellspacing=0>
        <tr>
        <td>
            <table border=0 cellpadding=3 cellspacing=1 width=350 bgcolor=8d8d8d align=center>
            <tr bgcolor=e5e5e5>
            <td align=center height=30  style=font-family:Tahoma;font-size:8pt;><b>Error Message</font></td>
            </tr>
            <tr bgcolor=f5f5f5>
            <td align=center height=30 style=font-family:Tahoma;font-size:8pt;>
                <table width=90% border=0 cellpadding=0 cellspacing=0>
                <tr>
                <td>
                    <br>
                    <span style=line-height:150%;font-size=12;>$msg</span>
                </td>
                </tr>
                <tr>
                <td>
                    <br>
                    <center><input type=button value=\"   Move Back   \" onclick=\"$frame.location.href='$url'\" style=border-color:#b0b0b0;background-color:#3d3d3d;color:#ffffff;font-size:8pt;font-family:Tahoma;height:23px;>
                    <br>
                    <br>
                </td>
                </tr>
                </table>
            </td>
            </tr>
            </form>
            </table>
        </td>
        </tr>
        </table>
    ";
    exit;
    }
    //}}}

    // 디버그 정보를 출력하는 함수
    //{{{ function printDebug($title,$data)
    function printDebug($title,$data){
        $data = nl2br($data);
        $debug  = "==== $title ==============================================================================================================================\r\n<br>";
        $debug .= $data;
        $debug .= "============================================================================================================================================\r\n\r\n<br>";

        echo $debug;
    }
    //}}}

    // 소켓으로 요청한 후 넘어오는 응답코드를 알려줌
    //{{{ function getHttpStatusName($code)
    function getHttpStatusName($code){
        switch($code){
            case 100 : $name = "Continue";break;
            case 101 : $name = "Switching protocols";break;
            case 200 : $name = "Complete, 전송 성공";break;
            case 201 : $name = "Created, POST 명령 실행 및 성공";break;
            case 202 : $name = "Accepted, 서버가 클라이언트 명령을 받음";break;
            case 203 : $name = "Non-authoritative information, 서버가 클라이언트 요구 중 일부 만 전송";break;
            case 204 : $name = "No content, 클라언트 요구를 처리했으나 전송할 데이터가 없음";break;
            case 205 : $name = "Reset content";break;
            case 206 : $name = "Partial content";break;
            case 300 : $name = "Multiple choices, 최근에 옮겨진 데이터를 요청";break;
            case 301 : $name = "Moved permanently, 요구한 데이터를 변경된 임시 URL에서 찾았음";break;
            case 302 : $name = "Moved temporarily, 요구한 데이터가 변경된 URL에 있음을 명시";break;
            case 303 : $name = "See other, 요구한 데이터를 변경하지 않았기 때문에 문제가 있음";break;
            case 304 : $name = "Not modified";break;
            case 305 : $name = "Use proxy";break;
            case 400 : $name = "Bad request, 클라이언트의 잘못된 요청으로 처리할 수 없음";break;
            case 401 : $name = "Unauthorized, 클라이언트의 인증 실패";break;
            case 402 : $name = "Payment required, 예약됨";break;
            case 403 : $name = "Forbidden, 접근이 거부된 문서를 요청함";break;
            case 404 : $name = "Not found, 문서를 찾을 수 없음";break;
            case 405 : $name = "Method not allowed, 리소스를 허용안함";break;
            case 406 : $name = "Not acceptable, 허용할 수 없음";break;
            case 407 : $name = "Proxy authentication required, 프록시 인증 필요";break;
            case 408 : $name = "Request timeout, 요청시간이 지남";break;
            case 409 : $name = "Conflict";break;
            case 410 : $name = "Gone, 영구적으로 사용할 수 없음";break;
            case 411 : $name = "Length required";break;
            case 412 : $name = "Precondition failed, 전체조건 실패";break;
            case 413 : $name = "Request entity too large";break;
            case 414 : $name = "Request-URI too long, URL이 너무 김";break;
            case 415 : $name = "Unsupported media type";break;
            case 500 : $name = "Internal server error, 내부서버 오류";break;
            case 501 : $name = "Not implemented, 클라이언트에서 서버가 수행할 수 없는 행동을 요구함";break;
            case 502 : $name = "Bad gateway, 서버의 과부하 상태";break;
            case 503 : $name = "Service unavailable, 외부 서비스가 죽었거나 현재 멈춤 상태";break;
            case 504 : $name = "Gateway timeout";break;
            case 505 : $name = "HTTP version not supported";break;
        }
        $message = "[$code] $name";
        return $message;
    }
    //}}}
}
?>


http://www.phpschool.com/bbs2/inc_view.html?id=10043&code=tnt2

브니 Programs/Web Programs

01 14, 2007 02:10 01 14, 2007 02:10
Trackback Address:http://limcom.co.kr/blog/trackback/32
[로그인][오픈아이디란?]
오픈아이디로만 댓글을 남길 수 있습니다