You have mail!
이 글은 DJ 애덤스가 재버(Jabber) 기술과 프로토콜을 연구한 끝에 쓴 결과물이다. 그는 재버 기술과 프로토콜을 익히면서 인스턴트 메시징 뿐만 아니라, 더 많은 것을 할 수 있음을 알게 되었다.
이 글에서 나는 몇 가지 표준 툴과 펄, 그리고 재버로, 새 메일이 수신되면 호출기처럼 알려 주도록 단순한 시스템을 하나로 통합하는 것이 얼마나 쉬운지를 보여 줄 것이다.
배경
나는 여러 메일링 리스트에 들어가서, 아니 “잠복해서” 몇 가지 필터 장치로 메일이 알맞은 메일 폴더로 이동하게 해 놓았다.
London Perl Mongers로부터 온 메일은 london.pm 메일 폴더에, Squackers에서 온 메일은 squackers 폴더에 보내는 식으로 말이다. 새로운 메시지를 분류해서 여러 폴더로 보내면 내 인박스에는 남아 있는 메시지가 거의 없다. 메일의 속성에 따라 인박스에 있는 메일을 분류하는데, 새로운 메시지가 도착하면 그 사실을 알려 주는 소프트웨어가 필요하다는 생각이 들었다.
재버 실행하기
이제 재버를 실행해 보자. 나는 내 컴퓨터에 재버 클라이언트를 설치해 놓고 직장 동료들과 쪽지를 주고받는다. 그러면서 재버가 쪽지를 주고 받는 기능 외에도 내 인박스에 새 메일이 도착했다는 사실을 알려 줄 수도 있을 것이라고 생각하게 되었다.
재버 메시지 타입
재버 클라이언트 커뮤니케이션은 메시지라 불리는 컨테이너로 전송된다. 재버에는 수많은 메시지 타입이 있는데, 이 중에서 “normal"과 ”chat"은 메일이 도착했음을 알려준다. “normal” 타입은 이메일 메시지와 비슷하며, 메일을 재버 지원 클라이언트에서 자신의 재버 클라이언트 “인박스”로 전송한다. 그림 1은 나의 재버 클라이언트인 WinJab을 나타내는데, 내 인박스에는 “normal” 타입의 메시지가 있다. 내용은 Sabine을 내 주소록에 포함시키겠다는 요청에 대해 Sabine이 승인했음을 알리는 것이다. 여기에는 이메일처럼 주제(“요청이 승인되었음”), 날짜와 시간(“06/11/00 17:01“), 그리고 본문(”sabine@yak이 당신의 요청을 승인했습니다.”)이 있다.
그림 1: ‘보통’ 메시지를 보여주는 WinJab
두 번째로 “chat” 타입은 좀더 능동적이다. 재버 클라이언트가 메시지를 받으면, chat은 새 창을 띄워서 메시지의 내용을 보여준다. 그러면 사용자는 인터넷 릴레이 채팅(IRC)의 DCC CHAT나 AIM처럼, 새 창에서 응답하거나 메시지를 보낸 사람과 대화를 할 수 있다. 그림 2는 이러한 팝업 창(popup window)을 보여 주는데, JabberIM이라는 다른 재버 클라이언트에서 온 메시지이다.
그림 2: 채팅을 보여 주는 팝업 창
컴퓨터에서 작업할 때 이러한 팝업 창이 자주 뜨면 성가시므로, “normal” 타입이 새 메일 알림에 가장 적합하다.
새 메시지 알림 설치
새 메시지를 분류하고 조직화하기 위해 나는 프록메일(procmail)을 사용한다. 프록메일은 내 메일이 MTA나 센드메일로부터 받은 것을 말하는데, 나에게는 새 메일이 도착했음을 알려주고 어떤 목적을 가진 메일인지에 대한 규칙을 정해 놓은 “레시피 파일(recipe file)"이 있다.
레시피 파일은 내 홈 디렉토리에 있는데
.procmailrc이라고 불린다. 내 경우 레시피 파일에는 패턴 매치와 메일 폴더의 최종 위치를 지정해 주는 여러 가지 명령어가 있다:
# Create backup of all mails
:0 c
Mail/backup
# London PM
:0
* ^To:.*london-list@happyfunball.pm.org
Mail/london.pm
# Squackers
:0
* ^To:.*squackers@squack.com
Mail/squackers
|
프록메일 레시피
첫 번째 레시피는 나중의 레시피가 새 메일을 지워버릴 경우를 대비해서, 새 메일을 백업 폴더(
Mail/backup)에 복사(c)한다(프록메일을 따로 분류하지 않는 것이 낫다). 다른 두 레시피는 메일의 제목이 폴더의 제목과 일치하는 메일은 복사하지 않고 그냥 메일 폴더로 옮겨 놓는다.
만약 메일이 모든 레시피를 통과했지만, 다른 곳으로 옮겨지지 않았다면 이 메일은 내 인박스에 남아 있게 된다.
프록메일은 메일을 폴더로 복사하거나 옮기는 것은 물론, 지정한 프로그램으로 메일(제목과 본문 모두)을 보낼 수도 있다. 그래서 특정한(패턴에 부합하는) 메일을 처리하는 과정을 사용자가 편한대로 설정할 수 있으며, 재버 클라이언트에 새 메일이 도착했음을 알려 줄 수도 있다.
내
.procmailrc 파일에 새로운 레시피를 입력하면 인박스를 통과하는 각각의 메일을 볼 수 있다(이 프로그램이 메일 내용을 받을 수도 있다):
# Send notification
:0 c
| ~/notify.pl
|
펄 스크립트를 불러오는 프록메일 레시피
이 레시피에서 파이프를 나타내는 기호(“|”)는 프록메일이 지정한 프로그램으로 메일 메시지를 연결해 준다는 점에서 셸에서 쓰이는 기능과 같다. 위의 예에서는 내 홈 디렉토리에 있는 notify.pl이 내가 지정해 준 프로그램이다.
도착 알림 스크립트
이제는 다음과 같은 펄 스크립트를 불러들이는 프레임워크가 생겨났다:
1. 메일을 분석해서 알려야 할 정보를 선별하라
2. 재버 서버에 연결하라
3. 재버 메시지를 내 재버 아이디로 보내 나에게 새 메일이 왔음을 알려라
시작하기 전에
메일을 분석하려면
Mail::Internet 모듈을 사용해야 하는데, 이 모듈로 우리는 메일 헤더를 수신하는 편안한 환경을 제공받는다. 재버 서버에 연결해서 메시지를 보내려면,
Net::Jabber 모듈을 사용한다. 그러면 이러한 모듈로 다음과 같은 스크립트를 작성해 보자:
use strict;
use Mail::Internet;
use Net::Jabber;
use constant RECIPIENT => "dj@yak";
use constant SERVER => "yak";
use constant PORT => 5222;
use constant USER => "notify";
use constant PASSWORD => "notify";
use constant RESOURCE => "perlscript";
|
물론 이것은 예제 스크립트에 불과하며 여기에 나타난 값은 내 컴퓨터 환경에만 국한된 것이다. 사용자는 자신에게 알맞게 이러한 것을 변환시키면 된다. 야크(yak)는 내가 사용하는 재버 서버가 운영되는 곳으로 표준 재버 포트 5222에서 명령을 기다린다. 나는 재버 주소
dj@yak로 새 메일 알림을 수신하도록 해 놓았다.
메일 분석
위에서 언급했듯이 프록메일은 메일을 스크립트에 연결하는데, 이는 메일의 내용, 헤더와 본문을 STDIN으로 볼 수 있다는 뜻이다.
메일 헤더 정보를 직접 알아내는 수고를 덜기 위해선
Mail::Internet 모듈을 사용하면 된다. 그리고
Mail::Internet 모듈 사용에 대한 정보 입력을 몇 줄로 줄여서 나머지 스크립트가 실행되지 않는 상황을 방지할 것이다:
my $header = Mail::Internet->new(*STDIN)->head()->header_hashref();
chomp $header->{$_}[0] foreach keys(%{$header});
|
새로운
Mail::Internet 개체를 생성하면서 시작하는데, 이는 분석할 메일이 STDIN을 통과하고 있다는 것을 나타낸다. 즉 프록메일 레시피에 있는 스크립트에 연결되었다는 뜻이다.
우리는 곧장 새로 생성된 개체에 대해
head() 메소드를 요청하는데, 그러면
Mail::Header 개체가 되돌아오고, 이에 대해 다시
header_hashref() 메소드를 요청하면, (아주 놀랍게도) 메일 헤더의 세부 내용을 담은 해시 레퍼런스가 되돌아온다.
두 번째 줄은 각각의 헤더 값에서 뉴라인을 제거한다. 헤더 해시의 실제 값은 무작위로 배열되므로(예를 들면,
To: recipients처럼 된다) 사용자의 환경에서 첫 번째에 있는 하나의 값만을 생각해야 된다. 그래서
$header->{$_}[0]에 있는
index [0]이 되는 것이다.
이 때 hashref는 다음과 같다:
{
"From" => [ "sabine@yak.local.net" ],
"Date" => [ "Sat, 4 Nov 2000 14:51:45 GMT" ],
"Subject" => [ "Shopping list for this afternoon" ],
...
}
|
재버 서버에 연결하기
메일 헤더에서 알림을 작성하는 방법까지 필요한 정보를 모두 알았으므로, 이제는 재버 서버에 연결해서 실제 메시지를 작성하여 보내도록 하자.
재버 ID
재버 메시지는 개인에서 개인으로, 우리의 경우에는 프로그램에서 개인으로도 전달된다. 여기에서 눈여겨볼 사항은 메시지를 받는 쪽과 보내는 쪽 모두 재버 ID(JID)를 가지고 있어야 한다는 점이다. 내 JID는
dj@yak이다. 우리의 알림 프로그램은 아직 JID가 없지만, 간소화하기 위해 이전에 생성해 놓은 JID(normal 타입의 재버 클라이언트)를 사용하려고 한다. 그리고 알림 프로그램용으로도 사용할 것이다.
알림 프로그램용으로 만든 내 JID는
notify@yak이며,
notify 라는 패스워드가 있다.
연결하기
이제는 재버 서버에 연결해 보자:
my $connection = Net::Jabber::Client->new();
$connection->Connect( "hostname" => SERVER,
"port" => PORT )
or die "Cannot connect ($!)n";
|
ID 발급과 리소스
일단 연결되면 ID 발급-인증 절차를 밟아야 한다. 이 때 스크립트는 야크의 재버 서버에 “알리라”는 것으로 인식한다.
재버 ID는 단순히
user@host로 이뤄지지 않는다. 같은 ID지만 다른 재버 디바이스에서 여러 명이 접속하려면(재버 서버를 노트북, PDA, 워크스테이션에 재버 서버를 연결한다고 생각해 보자), 리소스라는 제 3의 요소가 필요하다. 이 스크립트에서는 리소스 자리에 임의로 “perlscript"라는 단어를 선택했다.
그러면 이 스크립트의 ID값, 즉 3개의 요소로 된 ID는
notify@yak/perlscript가 된다.
어떤 재버 클라이언트는 자신의 이름을 리소스로 사용하는데, 야크의 재버 서버에 연결한 클라이언트로 JabberIM을 사용하는 Sabine이란 클라이언트는
sabine@yak/JabberIM이 ID이다.
그래서 ID 발급과 인증은 다음과 같다:
my @result = $connection->AuthSend( "username" => USER,
"password" => PASSWORD,
"resource" => RESOURCE );
if ($result[0] ne "ok") {
die "Ident/Auth with server failed: $result[0] - $result[1]n";
}
|
AuthSend() 메소드는 가능한 결과를 모두 나열한다는 것을 주의하자. 첫 번째 줄은 일반적인 성공/실패 플래그이고 두 번째 줄은 좀더 자세한 내용을 나타낸다.
메시지 작성
이번엔 메시지를 작성해 보자. 알림 기능을 사용하려면 “normal" 타입의 메시지를 사용해야 한다고 이미 말한 바 있다.
Net::Jabber 계층의
Net::Jabber::Message를 사용하는데,
Net::Jabber::Message는 재버 메시지라는 것을 나타내 준다. 메시지는 다음처럼 생성된다:
my $message = Net::Jabber::Message->new();
$message->SetMessage( "to" => RECIPIENT,
"subject" => "Email from $header->{From}[0]",
"body" => join("n", "Subject: $header->{Subject}[0]",
"Date: $header->{Date}[0]") );
|
물론 알림의 주제와 본문은 사용자가 원하는 대로 설정할 수 있다. 여기에서는
Mail::Internet 과
Mail::Header 모듈로 보낸 메일을 다시 편집해서 보낸다.
이 메시지 타입은
SetMessage 메소드에 "type"이라는 변수로 설정한 것으로, 디폴트값은 "normal"이다.