내 서명 원문…

많은 커뮤니티에서 글을 쓸때 ‘오랫동안 꿈을 그리는 사람은 그 꿈을 닮아간다.’ 라는 서명을 사용해왔습니다. 사실 전 ‘당신의 무대를 세계로 옮겨라.’ 라는 책에서 인용했던 말이었는데요 실제로는 앙드레 말로라는 프랑스 작가가 했던 말이라고 합니다.

영문으로는 다음과 같네요.

‘At last persons are resembling their dreams gradually, who long for them, I believe my dream will be realized without fail.’

제가 번역했다면 다음과 같이 했을 것 같네요. ‘오랫동안 바란다면 결국 그 꿈을 점점 닮아갈 거에요. 내 꿈도 언젠가는 현실이 될 거라고 믿습니다.’

오래 살지는 못했지만 삐그덕 거리고, 두려운 인생입니다. 그래도 언젠간 제가 그리고 바라던 것들을 이룰 수 있을거라는 사실을 믿어 의심치는 않습니다.

흉흉한 소식이 들려오는 하루하루지만 다들 힘내보아요~!

메모리 복사 코드 최적화…

kldp 를 눈팅하다가 오늘 재밌는 문서를 읽게 되었습니다. 2001년도에 AMD 에서 나온 문서였는데, 그 내용이 상당히 흥미로워서 몇 가지 내용을 옮겨볼까 합니다.
메모리에서 연속된 값들을 다른 곳으로 복사하기 위해 사용할 수 있는 가장 간단한 어셈블리 코드는 다음과 같습니다.

rep movsb 명령은 repeat move single byte (바이트 단위로 값들을 반복해서 옮긴다.)라는 의미를 가집니다. esi 가 가리키는 곳에 있는 값을 edi 로 ecx 에 있는 개수 만큼 복사하게 됩니다. 이렇게 할 경우 일초에 약 620MB 를 복사할 수 있다고 하네요.
이 코드를 byte 단위가 아니라 4 byte 단위로 반복해서 복사를 하도록 하면 어떻게 될까요? 우선 코드는 다음과 같이 변하겠네요.

shl 인스트럭션은 두개의 오퍼랜드를 가지며 (shl operand1 operand2) operand1 에 있는 값을 operand2 에 있는 값만큼 왼쪽으로 shift 를 시키게 됩니다. movsd 는 mov single dword[1] 라고 보시면 됩니다.
결과적으로 이렇게 코드를 수정함으로 인해 1초에 640MB 를 복사할 수 있게 됩니다. 3% 정도 성능 향상이 생기네요.
그런데 최근에 나온 프로세서들에서는 rep 같은 복잡한 인스트럭션을 내부적으로 RISC 명령으로 바꿔서 실행하다 보니, 그리 효율적이지 못하답니다. 그러므로 rep 를 사용하지 말고 반복문을 사용해보도록 합시다.

코드가 뭔가 좀 길어졌죠? 위 코드를 c로 표현하면 아래와 같습니다.

c로 표현하니 어디서 많이 쓰던 코드죠? 쨌든! 이렇게 하니 1초에 650MB 를 복사할수 있었고 결과적으로 1.5% 정도 성능이 향상되었답니다.
그럼 여기다가 Loop 코드를 최적화 하는 기법인 Loop Unrolling 을 적용해봅시다. [2]

자 룹을 펼쳤더니 1초에 640MB 를 복사하였고, 결과적으로 1.5% 만큼 성능이 떨어졌습니다. 하지만 다행히도 Loop Unrolling 을 적용하고 나니 최적화를 할 여지가 많아졌네요.
캐쉬를 좀 더 잘 활용할 수 있도록 코드 순서를 바꿔봅시다.

이젠 1초에 660MB 를 복사할 수 있게 되었고, 3% 만큼 성능 향상이 일어났습니다.
여기서 끝이 아닙니다. 첨에 movsb 대신 movd 를 사용해서 1byte씩이 아닌 4byte 씩 복사를 하는 방법을 통해 최적화를 진행했었는데요, MMX 를 사용할 경우 movq 등의 인스트럭션을 이용해서 한 번에 8byte 씩을 복사하는게 가능해집니다. 또한 mm0~7 이라는 특수한 레지스터를 활용할 수 있으니 8*8 = 64 즉 한번에 64byte 씩을 복사해봅시다.

MMX 용 레지스터들인 mm0~7 은 FPU stack 의 일부를 활용하게 되므로, 이 레지스터 값을 바꿔주게 될 경우 FPU 와 관련해서 문제를 일으킬 수 있습니다. EMMX 는 이를 방지하기 위해 사용해야 하는 인스트럭션이 되겠습니다. 하여튼 이렇게 바꾸니 1초에 705MB 를 복사할 수 있게 되었고, 7% 만큼 성능이 향상되었습니다.
이젠 movntq 라는 인스트럭션을 통해 cache 를 우회해서 writing 을 진행해봅시다.

movntq 를 활용한 다음에는 write buffer 를 비워주기 위해 sfence 를 사용해야 한다는군요. write 부분이 movq 에서 movntq 로 바뀌었고 emms 앞에 sfence 가 들어간 것을 제외하면 코드는 동일합니다. 하지만 성능 향상은 60% 로 굉장하네요. 1초에 1130MB 를 복사할 수 있었다고 합니다.
이젠 prefetch 도 활용해봅시다.

위 코드에는 현재 복사할 차례의 512 바이트를 미리 읽어두라는 의미의 prefetchnta 인스트럭션이 추가되었습니다. 한 번에 복사하는건 64Byte 인데 왜 512Byte 를 읽으라고 했는지 살짝 의문이네요. 제 생각에는 문서를 작성하신 분이 버그를 낸거라고 생각합니다.
하여튼 이젠 1초에 1240MB 를 복사할 수 있게 되었고, 10% 만큼 더 성능 향상이 생겼네요.
지금까지의 방법만으로도 꽤 많은 성능 향상이 있었지만, 한 번에 한 캐쉬 라인[3]만을 활용하고 있습니다. 하지만 실제 CPU 에는 훨씬 많은 캐쉬 라인이 존재하므로 이를 더 잘 활용할 수 있도록 코드를 수정해보겠습니다.

여기서는 cache 가 1024 개의 cache line 을 가지고 있다고 가정했네요. (16진수인 400h 는 10진수로 바꿀 경어 1024 가 됩니다.) 미리 캐쉬 사이즈만큼 prefetch 명령들을 내려놓은 뒤 값들을 복사하게 될 때 쯤이면 이미 값들이 캐쉬에 올라와있게 되니 딜레이를 줄일 수 있게 되겠습니다.
효과가 있을까 싶지만, 실제 1초에 1976MB 를 복사할 수 있었고, 59% 의 성능향상이 추가로 발생했습니다. 초기 코드에 비하면 300% 의 성능 향상이라네요. 신기하죠. 😉
전 상당히 흥미롭게 읽었었는데, (비슷한 걸 해본 경험도 있고 해서) 재밌었는지 모르겠네요. sfence 나 movqnta, prefetchnta 같은 캐쉬와 관련된 명령들은 정확히 무슨 용도인지 이해하지 못하고 있었는데 이 문서를 통해 이해할 수 있었던 것 같네요. 관심이 있으신 분은 아래 문서를 읽어보시면 되겠습니다. FPU 관련된 최적화도 다루고 있는데, 관심이 없어서 그 부분은 옮기질 않았습니다. 그럼 다들 즐거운 주말 보내시길 😉

Click to access AMD_block_prefetch_paper.pdf


[1] x86 호환 아키텍쳐에서는 사이즈를 byte, word, dword, qword, dqword 식으로 표현합니다. 이는 각각 1, 2, 4, 8, 16 바이트를 의미하며, word 가 2바이트이고 나머지의 앞에 붙은 알파벳들은 각각 double(*2), quad(*4), double quad(*8)를 나타내는 것이죠.
[2] Loop Unrolling 은 Loop 을 펼쳐서 파이프라인의 덕을 더 많이 볼 수 있도록 코드를 수정하는 방법입니다. 예를 들어


위와 같은 코드를

이렇게 바꿀 경우 branch 로 인한 pipeline hazard 도 줄일 수 있고, add instruction 도 1/4 만큼만 사용하게 됨으로 인해 성능이 향상될 여지가 많습니다.
[3] 메모리에 어떤 값을 읽어들일 때 CPU 에서는 바이트 단위로 값을 읽지 않고, block 단위로 값들을 cache 에 복사하는데, 이 block 의 크기를 cache line 이라고 표현합니다. 펜티엄 계열의 경우 대부분 64byte 입니다.

exim 으로 메일 서버 구성하기…

왠지 모르게 점점 마이너 한 것들만 사용하게 되네요. postfix 나 exim 을 사용해보니 여러가지 장점이 눈에 보이는데 막상 how to 문서같은게 그리 잘 나와있질 않다보니 사람들이 사용하기를 꺼리는 것 같아 처음으로 howto 문서를 하나 작성해려 합니다.
이 문서는 젠투에서 exim 을 사용하는 방법을 기준으로 설명하고 있습니다.

Table of Contents

  1. 기본 구성
  2. 필요한 소프트웨어 설치하기
  3. 가상호스트 설정하기
  4. 바이러스 필터 설정
  5. 스팸 필터 설정
  6. dovecot 설정하기
  7. smtp 인증 설정하기
  8. 다른 배포판을 사용할 때 유의점

기본 구성

Mail Transfer Agent 로는 exim, 스팸 필터로는 bogofilter, 바이러스 필터는 clamav, pop3/imap server 로는 dovecot 을 사용하기로 하겠습니다. dovecot 이나 exim 이나 기본 적으로 실행하는 프로세스들이 많지 않기 때문에 프로세스 리스트를 확인할 때 깔끔해서 좋더군요.

필요한 소프트웨어 설치하기

저 같은 경우는 대강 다음과 같은 USE 플래그를 이용해서 설치를 진행했습니다.

domainkeys 는 야후나 구글에서 사용하고 있는 domain key 를 제대로 검증하기 위한 USE 플래그가 되겠구요, dovecot-sasl 은 smtp 인증에 dovecot 을 이용하기 위한 플래그입니다.
spf, srs, domainkey 같은 플래그는 해당 기능을 사용하지 않겠다면 빼주셔도 무방합니다. CentOS 등에서는 dovecot-sasl 도 기본으로 지원이 안되더라구요. 하튼 관련해서 자세한 건 뒤에서 설명하도록 하겠습니다.
살펴보니 별로 특이한 플래그를 사용하지는 않네요. -_;; 젠투 유져라면 다들 아시겠지만 실제 설치는 위의 예제에서 -pv 를 제거하고 실행시키시면 됩니다.

가상호스트 설정하기

설치가 무사히 끝났다면 가상호스트를 설정해봅시다. 젠투에서는 기본으로 설정파일을 만들어주지 않습니다. 그렇기 때문에 우선 /etc/exim/exim.conf.dist 를 복사해서 exim.conf 를 만들어줍니다.
# cp /etc/exim/exim.conf.dist /etc/exim/exim.conf
그런 다음 자신이 좋아하는 편집기를 이용해서 exim.conf 파일을 열고, 아래와 같은 부분을 찾아서 자신의 환경에 맞게 수정을 한 뒤

router 부분에서 system_alias 바로 아래에 다음 코드를 추가해줍니다.

주 도메인이 있는 경우 localhost 대신 unfix.net 같은 도메인을 사용해도 무방합니다. 만약 위의 예제에서 localhost 대신 unfix.net 을 설정해주면 로컬에 있는 모든 계정은 id@unfix.net 형태의 메일계정으로 사용이 가능해집니다. 주 도메인이 여러개라면 @ : localhost : mydomain1 : mydomain2 식으로 콜론(:) 을 이용해서 여러 개를 모두 적어주면 되겠습니다.
이젠 가상 호스트를 설정해봅시다. 만약 test.com 과 test.net 을 위한 가상호스트를 설정하려 한다면 다음과 같이 하면 됩니다.

위에서 dsearch; … 으로 설정해놓은 디렉토리 안에다가 호스트 이름을 이용해서 alias 파일을 만들어주면 되니까 간단하죠? 각 파일에 들어가는 내용은 다음과 같습니다.

# test.com
test: aqua@localhost
melong: test@gmail.com

# test.net
aqua: test@localhost

test.com 파일에 들어가 있는 test: aqua@localhost 는 test@test.com 으로 온 메일을 aqua@localhost 로 포워드를 시키라는 거에요. 워낙 간단하니 다른 것들은 따로 설명 안해도 될 것 같네요. 단 위에서 localhost 대신 myhost.net 을 사용했다면 aqua@myhost.net 처럼 설정해줘야 합니다.
여기까지 따라하셨다면 가상 호스트 설정은 끝! 가상 호스트 파일을 수정했을 때는 alias 를 수정했을 때와 달리 newalias 를 실행시키실 필요가 없습니다.

바이러스 필터 설정

바이러스 필터로는 clamav 를 사용할 건데, USE 플래그로 exiscan 과 exiscan-acl 을 사용해서 설치했다면 clamd 와 연동하는건 아주 간단합니다.
exim.conf 에서 다음과 같은 줄을 찾아서 주석을 해제시키세요.

끝! clamd.sock 위치는 배포판에 따라 다를 수 있습니다. 자세한건 /etc/clamd.conf 를 참조하세요.

스팸 필터 설정

SPF

Exim에는 기본으로 SPF와 관련된 코드가 있으므로 SPF를 사용하기 위해선 EXPERIMENTAL_SPF=yes 환경변수와 함께 exim을 빌드하면 됩니다.
설정은 global 섹션에서 acl_smtp_rcpt 로 등록되어 있는 것에 다음과 같은 설정을 넣어주면 됩니다.

젠투에선 acl_smtp_rcpt가 acl_check_rcpt로 되어 있기 떄문에 acl_check_rcpt 부분에 위 설정을 추가해줬습니다.
만약 warn message = $spf_received 를 넣어주지 않으면 헤더에 SPF결과를 출력하지 않게됩니다. SPF헤더를 붙일 경우에는 SMTP Auth를 이용해서 메일을 보내는 경우에도 SPF헤더가 붙는 문제가 있습니다. 이를 피하기 위해 설정을 아래처럼 수정해주도록 합시다.

SRS

SRS를 설정하기 위해 우선 global 섹션에 다음과 같은 내용을 추가해줍니다.

여기서 somesecretkey는 본인이 원하는 임의의 문자열을 넣으면 되겠습니다. 그 후 다음과 같이 userforward에 ‘srs=forward’를 넣어주면 됩니다. 간단하죠.

Bogofilter

스팸 필터로는 베이시안 룰 기반의 스팸 필터인 bogofilter를 사용해봅시다.
bogofilter를 MTA와 연동시키는 데는 procmail을 이용할 수도 있지만, procmail은 로컬 유져에 한해서만 사용이 가능하고, alias 나 .forward 를 사용하게 되는 경우에는 사용할 수 없으므로 exim의 transport와 router를 이용해보겠습니다.
우선 transport를 다음과 같이 설정합니다.

그리고 이 transport 를 이용하는 router를 만들어야 합니다. 참고로 router 는 순서에 민감하므로 삽입할 위치를 잘 선택해야 합니다. 저같은 경우는 system_alias 다음에다가 넣어두었습니다.

여기까지만 하게 되면 bogofilter가 각각의 메일 헤더에 ‘이 메일이 스팸/햄이다.’ 라는 정보만을 넣어줄 뿐 스팸/햄을 다른 메일박스로 분류해주는 동작은 하질 않습니다.
그러므로 자동 분류를 위한 transport 와 router 를 또 추가해주도록 합니다.
transport 먼저…

이렇게 하면 자신의 홈 디렉토리의 .maildir 아래 .Spam 이란 디렉토리를 만들고, 그 디렉토리에 스팸 메일을 저장하게 됩니다.
IMAP 으로 접속하면 Spam 메일들을 확인할 수 있기 때문에 이렇게 했는데, POP3 만 사용하는 거라면 그냥 제목에 [Spam] prefix 를 붙이게 하는 것도 나쁘지 않을 것 같습니다.
그 다음엔 이 transport 를 이용하는 router를 설정해야하는데, 이 때도 위치를 잘 생각해야 합니다. 저같은 경우엔 bogofilter router 바로 아래에 위치시켜 놓았습니다..

스팸 메일은 bogofilter 에 의해 X-Bogosity: Spam, test=bogofilter … 식의 헤더가 추가되기 때문에 이렇게 할 경우 스팸을 쉽게 분류해낼 수 있습니다.

dovecot 설정하기

우선 /etc/dovecot.conf 파일을 열어서 서비스할 프로토콜 리스트를 적어주세요. 그 다음에 plaintext 로 로그인할 수 있도록 설정해주고, pop3 나 imaps 를 위해 ssl 을 사용할거라고 명시해주고, 마지막으로 dovecot-sasl 을 사용할 수 있도록 auth-client 를 활성화시켜주세요.
이를 종합해보면 다음과 같은 설정파일이 만들어집니다. 볼드로 표시한 부분이 제가 수정한 부분이에요. 주석을 제거하고 보니 정말 간단하네요. -_-;

그리고 /etc/pam.d 로 가서 smtp 파일이 있는지를 체크해보시고 만약 없다면 system-login 이나 pop 등을 복사해서 smtp 파일을 만들어주세요. 이것 때문에 어제 혈압이 올랐던걸 생각하면 …!!!
여기까지 설정하셨음 90% 는 끝난겁니다.

smtp 인증 설정하기

자 다시 exim.conf 로 돌아와서 smtp 인증을 설정해봅시다. exim.conf 에서 authenticators 파트를 찾아서 아래와 같은 내용을 추가해줍시다.

이걸로 설정은 끝! 다음엔 데몬들을 띄워서 제대로 되나 확인을 해보세요.

다른 배포판을 사용할 때 유의점

제가 이것저것 확인해본 건 아니라 정확하게 알려드릴 순 없지만 CentOS 에서는 spf, srs, dovecot-sasl, domainkeys 등을 사용할 수가 없습니다. 그렇기 때문에 dovecot-auth 대신 saslauthd 를 사용해야 해요. saslauthd 를 사용할 때의 authenticator 파트는 다음과 같습니다.

server_condition 을 다음처럼 하면 pam 만으로도 인증을 할 수 있습니다. 하지만 pam_auth 모듈에서 auth request를 보낸 사용자의 uid와 username을 가지고 확인하는 절차가 있기 때문에 exim을 root 권한으로 실행시키지 않으면 제대로 인증이 되지 않습니다.

참고로 인증을 saslauthd 를 통해서 하려는 경우 saslauthd 가 실행 중에 있어야 합니다.

spam filtering with transport/router of exim

I used to classify spam mail with procmail, but procmail is excuted only for local users. So when you use the .forward file, spam mail is also forwarded to the other email addresses.
It’s not so quite import but I don’t like this behavior. so I change the exim configuration to run bogofilter with the transport/router of exim.
first let’s configure transport like this.
bogofilter:
driver = pipe
command = /usr/sbin/exim -oMr bogodone -bS
use_bsmtp = true
transport_filter = /usr/bin/bogofilter -d /etc/bogofilter/ -e -p
log_output = true
return_path_add = false
temp_errors = *
home_directory = "/tmp"
current_directory = "/tmp"
message_prefix = ""
message_suffix = ""

after that you need to declare the router which uses this transport. Order is important in router. I put it right after the system_alias router.
bogofilter:
domains = +local_domains
no_verify
condition = ${if !eq {$received_protocol}{bogodone} {1}{0}}
driver = accept
transport = bogofilter

With this configuration, bogofilter will be excuted but it put only an additional header like “X-bogofilter: …” so we need another transport/router to classify the spam mails.
Here’s the transport to do that.
spam_delivery:
driver = appendfile
directory = /home/$local_part/.maildir/.Spam
maildir_format
delivery_date_add
envelope_to_add
return_path_add

With this transport, we can put the spam mails to the .Spam folder within $HOME/.maildir. If you use the IMAP protocol, then you can simply check the spam mails by accessing Spam folder. But with POP3 protocol there’s no way to check it. So if you use POP3 then use the header filtering rules of client software instead.
And we also need to add a router which uses this transport. The order is important in here too. I put it below the bogofilter router.
removingspam:
driver = accept
check_local_user
condition = ${if match {$h_X-Bogosity:} {Spam, tests=bogofilter} {1}{0}}
transport = spam_delivery

Spam mail has the header, X-Bogosity: Spam, test=bogofilter …, so we can classify the spam mail easily.
To confirm it working, check the mail log. I checked /var/log/mail/current because I use metalog but in almosts linux distributions syslogd is included so check the /var/log/messages file.

$ # tail -f /var/log/mail/current |grep R=
Feb 26 16:48:31 [exim] 2008-02-26 16:48:31 1JRkT5-0001pz-Nx => mailaddr R=procmail T=procmail
Feb 26 16:48:35 [exim] 2008-02-26 16:48:35 1JTuY0-00081q-7u => mailaddr R=removingspam T=spam_delivery
Feb 26 16:48:44 [exim] 2008-02-26 16:48:44 1JQWAs-0002po-CR => mailaddr R=bogofilter T=bogofilter
Feb 26 16:48:45 [exim] 2008-02-26 16:48:45 1JTuY5-000826-EK => mailaddr R=removingspam T=spam_delivery

The name of router/transport is located after R= and T= . The name of router appears after R=, and the name of transport appears after T=. According to this log, we can say that removespam or procmail router is used after the bogofilter excuted.

exim 의 transport, router 를 이용한 스팸 필터링

익숙한 걸 사용하려다보니 procmail 을 이용해서 bogofilter 를 수행하는 방법을 사용해 왔지만, procmail 은 로컬 유져에 한해서 실행되게 되므로, alias 나 .forward 를 사용하게 되는 경우 스팸 필터링을 하지 않게 된다.
하여튼 이게 좀 신경쓰여서 transport 와 router 를 이용해서 bogofilter 를 수행하도록 설정해봤다.
우선 transport 를 다음과 같이 설정해보자.
bogofilter:
driver = pipe
command = /usr/sbin/exim -oMr bogodone -bS
use_bsmtp = true
transport_filter = /usr/bin/bogofilter -d /etc/bogofilter/ -e -p
log_output = true
return_path_add = false
temp_errors = *
home_directory = "/tmp"
current_directory = "/tmp"
message_prefix = ""
message_suffix = ""

그리고 이 transport 를 이용하는 router 를 만든다. 참고로 router 는 순서에 민감하므로 삽입할 위치를 잘 조절해야 한다. 나같은 경우는 system_alias 다음에 선언해두었다. (alias 를 사용하는 주소들중 로컬 유져에게 전달되지 않는 건 mailman 과 관련된 것들 밖에 없는데 이거야 뭐 어짜피 인증된 사용자가 보낸 메일만 받으니 상관 없겠다는 마음으로…-_-;; )
bogofilter:
domains = +local_domains
no_verify
condition = ${if !eq {$received_protocol}{bogodone} {1}{0}}
driver = accept
transport = bogofilter

여기까지만 하게 되면 bogofilter 를 수행하기는 하지만 이를 이용해서 메일을 옮긴다거나 하는 동작은 하지 않게 된다. 그러므로 이런 동작을 시키기 위한 transport 와 router 를 또 추가해주자.
역시나 transport 먼저…
spam_delivery:
driver = appendfile
directory = /home/$local_part/.maildir/.Spam
maildir_format
delivery_date_add
envelope_to_add
return_path_add

이렇게 하면 자신의 홈 디렉토리의 .maildir 아래 .Spam 이란 디렉토리를 만들고, 그 디렉토리에 스팸 메일을 저장하게 된다. IMAP 으로 접속하면 Spam 메일들을 확인할 수 있기 때문에 이렇게 했는데, POP3 만 사용하는 거라면 그냥 제목에 [Spam] prefix 를 붙이게 하는 것도 나쁘지 않을 듯…
그 다음엔 이 transport 를 이용하는 router! 역시나 어디다 위치시킬지 잘 생각해야 한다. 나같은 경우엔 bogofilter router 바로 아래에 이걸 위치시켜놓았다.
removingspam:
driver = accept
check_local_user
condition = ${if match {$h_X-Bogosity:} {Spam, tests=bogofilter} {1}{0}}
transport = spam_delivery

스팸 메일은 bogofilter 에 의해 X-Bogosity: Spam, test=bogofilter … 식의 헤더가 추가되기 때문에 이렇게 할 경우 스팸을 쉽게 분류해낼 수 있다.
잘 됐는지 확인은 메일로그를 이용해서 확인하면 된다. 나같은 경우는 metalog 를 사용하니 /var/log/mail/current 를 이용해서 확인해야 했는데 대부분의 경우 syslogd 를 사용할테니 /var/log/message 를 확인하면 될 것 같다.

$ # tail -f /var/log/mail/current |grep R=
Feb 26 16:48:31 [exim] 2008-02-26 16:48:31 1JRkT5-0001pz-Nx => 메일주소 R=procmail T=procmail
Feb 26 16:48:35 [exim] 2008-02-26 16:48:35 1JTuY0-00081q-7u => 메일주소 R=removingspam T=spam_delivery
Feb 26 16:48:44 [exim] 2008-02-26 16:48:44 1JQWAs-0002po-CR => 메일주소 R=bogofilter T=bogofilter
Feb 26 16:48:45 [exim] 2008-02-26 16:48:45 1JTuY5-000826-EK => 메일주소 R=removingspam T=spam_delivery

유심해서 봐야할건 R=, T= 다음에 나오는 것들이다. R= 다음에 나오는 것은 사용된 router 를 의미하고, T= 다음에 나오는건 transport 를 의미한다. 위의 로그를 보면 bogofilter 를 수행한 뒤 removingspam router 를 이용해서 spam_delivery trasport 가 수행되기도 하고 혹은 이를 통과해서 procmail transport 가 수행되기도 하는 걸 확인할 수 있다.
기본으로는 procmail transport 가 없으니 원랜 local_delivery 가 나올 수도 있겠고 뭐 하여튼 router 나 transport 이름은 사용자가 맘대로 지으면 되는거라 상황에 따라 다 다를 듯…
스팸 없는 세상이 올 때까지 ㅠ.ㅠ 오늘도 삽질…

CentOS 삽질 후기: 잘못된 정보여 안녕!

얼마 전에 아는 형 부탁으로 또 하나의 서버를 살펴봐주게 되었다. 장기적으로 할건 아니고 단발성으로 잠깐 봐주게 될 일이었는데…
여기 설치되어 있던 리눅스는 Fedora core2! 아는 사람은 다 알다시피 Fedora core2 는 업데이트 지원이 끊긴지 이미 오래된 배포판이다. 하여튼 알려진 버그들로 무장된 상태를 유지하는건 알바로써의 자세가 아니기 때문에 최신 버젼으로의 업데이트를 시도해봤지만 core2->8 으로의 업데이트는 실패!! (core8 의 redhat-release를 설치한 뒤 yum upgrade 를 시도해봤지만 패키지 의존성이 해결되질 않는다. 7, 6, 5 도 마찬가지)
차례로 순서를 밟아가야 겠다는 생각이 들어서 3, 4, 5 순서로 시도를 해봤는데 8 까지 제대로 업데이트가 되지도 않았을 뿐더러 reboot 이 제대로 되지 않아 버렸다. 하하하하하하 (사고 쳤단 얘기) apache, mysql 등이 /usr/local/apache, /usr/local/mysql 등의 prefix 에 설치되어 있었고, init script 조차 제대로 없는 그런 암울한 상황이었는데 마침 잘됐다는 생각으로 CD 와 함께 IDC 에 방문했다. fedora 는 업데이트 주기가 너무 짧고, 내가 관리해야할 서버가 아닌 마당에 젠투를 깔아주는건 옳지 못하단 생각이 들었으며, 우분투 등을 깔아주기엔 내가 너무 무지했다. 결국 선택한건 CentOS 5.1!
불행히도 DVD rom 이 없었기 때문에 가져갔던 CentOS 5.1 dvd 는 사용할 수가 없었고, 혹시 몰라 구워갔던 CentOS 5.1 net install cd 를 사용해야 했다. 설치야 예전 레드햇 계열들과 별로 다르지 않으니 어렵지 않게 끝~
하지만 이게 왠 일! /home 계정 밑에 있는 파일들을 아파치에서 읽질 못한다! 혹시나 해서 apache 유져의 쉘을 /bin/bash 로 바꿔주고 apache 유져로 스위치한 뒤 파일들을 읽어보니 잘 읽어온다. -_-; 헉; 이젠 리눅스마저 날 농락하려 드는건가 싶은 마음이… ㅠ.ㅠ 정확히 뭐가 문젠지도 몰라서 검색어조차 찾을 수 없던 이 시점! selinux 라는 요상 야릇한 단어가 눈에 들어왔다. 하여튼! 검색을 통해 찾아낸 아래와 같은 명령어를 통해 이 문제를 해결할 수 있다.

acl 을 사용하는건지 아님 뭔가 다른 방법을 사용해서 저런 secure context 를 저장하는 건지 까지는 모르겠지만 하여튼 강화된 보안 정책 덕에 초보 리눅서만 고생했다. ㅠ.ㅠ 그나저나 하고 싶었던 얘기는 내가 천재라 저런 문제를 해결했다는게 아니고, 웹에 잘못된 문서가 너무 많다는 것!
저 문제를 해결하기 위해 CentOS apache home 403 등으로 검색했을 경우 상위 랭크로 나오는 페이지들은 대부분 아파치 소스를 가져다 빌드해서 저 에러를 피해가는 요상한 방법이다. 그나마 거기에 설명된 설치 법에는 CFLAGS 등을 설정하지도 않고, 무슨 의미인지도 모르는 옵션들로 가득차 있으며 그나마 효율적이지도 않다.
linux 는 배포판 별로 파일 레이아웃에 대한 정책이 있다. 약간씩은 다르지만

/usr – 실행파일, 라이브러리, man 페이지, 문서 등이 놓여짐
/etc – 설정파일이 놓여짐
/var – 런타임에 생성되는 데이타가 놓여짐 (로그, 데이타베이스 파일, 메일 파일 등등)
/tmp – 임시파일 저장장소

대강 위와 같은 정책을 사용하며 약간 부연하자면 /usr 과 / 에 대해 아래와 같은 디렉토리 구조가 더 사용된다.

/bin 실행파일
/sbin 관리자한테만 필요한 실행파일
/lib 프로그램이 실행될 때 사용하는 쉐어드 라이브러리등이 존재

이런 얘기를 왜 하냐면 배포판에서 제공하는 패키지를 사용할 경우 위의 정책대로 파일들이 놓여지며 설정파일을 찾기 위해선 /etc 밑을 뒤지면 되고, 실행은 /usr/local/apache/bin/apachectl start 와 같이 해야 하는게 아니라 apachectl start 와 같이 하면 된다.
그냥 소스를 가져다가 ./configure –prefix=/usr/local/apache 와 같이 설치할 경우 PATH 가 걸려있지 않아서 실행 파일의 full path 를 적어줘야 하는 불편함 외에도 man page 가 놓여있는 경로가 MANPATH 에 등록되질 않기 때문에 명령어 들의 man 페이지를 확인해볼 수도 없다. (사실 저렇게 설치하는 유져가 man 페이지를 참고할 거란 생각은 별로 들지 않지만) 거기다가 init 스크립트를 제공하지 않는 프로그램들도 많아서 프로그램의 실행 종료를 수동으로 해야하는 불편도 생긴다.
간단한 예로 mysql 을 패키지로 설치한 것과 소스로 /usr/local/mysql 에 설치한 것을 비교해보자.

소스 패키지
실행 /usr/local/mysql/bin/mysql mysql
man page 사용불가능 사용가능
데몬 시작 /usr/local/mysql/bin/mysqld_safe & /etc/init.d/mysqld start
데몬 종료 ps ax|grep mysql 후 kill 해당pid /etc/init.d/mysqld stop
자동 실행 /etc/rc.local 에 /usr/local/mysql/mysqld_safe & 추가 ntsysv 에서 mysql 체크
파일 변조 확인 불가능 rpm -Va mysql
제거 rm -rf /usr/local/mysql rpm -e mysql
업데이트 체크 수동 yum check-update mysql
파일 리스트 find /usr/local/mysql rpm -ql mysql

이걸로 끝이 아니다. 만약 openssl 이 업데이트 되면서 소스로 가져다 설치한 mysql 이 실행되는데 필요한 libcrypto.so.4 란 파일이 제거되었다고 해보자. rpm 등을 사용하면 dependency check 과정이 있으므로 이런 황당 무계한 일이 벌어지지 않는다. 하지만 소스로 설치했다면 저 라이브러리를 찾을 수 없다는 에러를 만날 수밖에 없다.
그리고 실행파일 경로(PATH)나 man page 경로(MANPATH) 같은 건 ~/.bash_profile 이나 /etc/bashrc /etc/profile.d 등을 수정해주면 쉽게 등록해 줄 수 있다. 하지만 이런 것까지 설명하고 있는 문서는 본 적이 별로 없는 것 같다. (물론 이렇게 설치하는 유져가 man 페이지를 확인할 거란 생각은 하지 않는다.)
우선 패키지 관리자를 사용하면 업데이트가 나왔는지를 확인하는 작업과 업데이트를 수행하는 일이 아주 간단해진다. 또한 프로그램의 설치/제거 가 같은 인터페이스를 통해 이뤄지고, 데몬의 실행/종료도 비슷한 인터페이스를 통해 이뤄진다.
대부분의 데몬들은 /etc/init.d/데몬이름 start|stop 와 같이 시작/종료를 할 수 있으므로 뭔가를 새로 설치했을 때 어떤 식으로 실행해야할지, 어떤 식으로 종료시켜야할지 고민할 필요가 없다. 게다가 ntsysv 를 통해 gui 로 시동 스크립트들을 관리할 수도 있게 되고…
뭐 하튼 예전부터 주장하고 있는 바지만 괜히 a.p.m 을 구축하기 위해 이미 다 설치되어 있는 apache, php, mysql 등을 제거하고 libjpeg, libungif, libgd 등을 줏어다가 하나하나 빌드하는 방법을 설명한 문서는 다 사라졌음 좋겠다.
jpeg 헤더가 필요하면 yum install libjpeg-devel 을 설치하면 된다. gif 헤더는? yum install libungif-devel, gd 는 yum install gd-devel …
다들 삽질로 부터 해방되는 그 날까지! 꺄아악
p.s) 졸려서 날림 마무리

요 며칠 삽질기 -_-! with Exim

어째 요새 관리해야할 서버가 늘어버렸네요. (전 언픽스 하나로 족한데 ㅠ.ㅠ) 하여튼!! 요 며칠 사이 gentoo + exim + procmail + spf + srs + clamav + bogofilter + dovecot 를 시도해봤습니다.

사실 계속 제가 맡아서 할 게 아니라 길어야 일 년 정도 만져줄 서버기 때문에 젠투가 아닌 다른 배포판을 생각했었는데, spf 와 srs 를 지원할 수 있도록 하면서 기본으로 제공되는 패키지를 이용할 수 있는 조합이 몇 가지 되질 않더군요. exim 에서 spf 와 srs 는 experimental 로 되어 있기 때문에 바이너리 배포판에선 기본으로 적용이 되어 있질 않고, postfix + milter 조합에서는 srs 를 제공할 수 없기 때문에 남은 선택은 sendmail + milter 조합 밖에 없는데 sendmail 을 사용하기는 싫었거든요.

하여튼! exim 에 procmail 을 붙이는 방법은 아래와 같습니다.

Continue reading 요 며칠 삽질기 -_-! with Exim

exim 에 spf, srs 적용하기

unfix 서버를 구축할 땐 postfix 를 사용했지만 postfix 에선 srs 를 사용할 수가 없었기 때문에 새로 설치하게 된 서버들에선 exim 을 시도 중이다.
그런데 exim 을 사용하려고 하니 문제가 하나 있는데, 구글에서 검색을 해도 원하는 결과가 잘 나오질 않는다는 점이 바로 그것! exim 에는 spf 와 srs 등을 사용하기 위해서는 따로 패치가 필요 없이 그저 EXPERIMENTAL_SPF=yes EXPERIMENTAL_SRS=yes 이 두 가지 환경변수를 세팅한 채로 빌드를 진행하면 된다.
그 이후 어떻게 해야하는 지에 대한 설명이 없어서 한참 헤맸는데 exim 과 함께 설치되는 문서를 확인해야하는 것이었다. -_-; 보면 domain key 등도 위에서와 같이 환경변수만 세팅해주면 사용이 가능해진다.
doc/exim-4.68/experimental-spec.txt.bz2
문서를 확인해보니 이제 해야할 일은 spf 와 srs 설정을 추가해주는 것 뿐 후훗!
SPF 는 global 섹션에서 acl_smtp_rcpt 로 등록되어 있는 것에 다음과 같은 설정을 넣어주면 된다.
# Query the SPF information for the sender address domain, if any,
# to see if the sending host is authorized to deliver its mail.
# If not, reject the mail.
#
deny
message = [SPF] $sender_host_address is not allowed to send mail \
from $sender_address_domain
log_message = SPF check failed.
spf = fail
 
# Add a SPF-Received: header to the message
warn
message = $spf_received

젠투에선 acl_smtp_rcpt = acl_check_rcpt 와 같이 되어 있기 떄문에 acl_check_rcpt 에 위와같은 설정을 추가해줬다. 만약 아래 warn message = $spf_received 를 넣어주지 않으면 spf 결과를 헤더에 추가해주지 않게된다. 생색을 내기 위해선 뭔가 출력될 필요가 있으므로 -_-! 난 헤더를 추가하도록 설정해줬다.
하지만 위에 대로만 할 경우 내가 메일을 보낼 경우에도 SPF-Received 헤더가 붙는 문제가 생긴다. smtp auth 를 설정해놓은 상황에서 이 서버를 통해 메일을 보낼려면 인증이 되어있어야 할테니 아래와 같이 수정해서 이런 문제를 피해주자.

# Add a SPF-Received: header to the message
warn
message = $spf_received
!authenticated = *

그리고 그 담은 SRS 복잡하게 하려면 복잡하겠지만 다 귀찮아서 hash 용 secretkey 하나만 넣어서 설정해버렸다. 아래 설정은 global 섹션에 들어가면 된다. (domainlist 같은게 들어가는 곳) 실제로 somesecretkey 를 적어주는건아니고 그냥 원하는 단어 하나를 적어주면 된다.
srs_config = somesecretkey
그 다음엔 userforward 트랜스포트에 srs 를 적용하도록 해주자 srs = forward 만이 내가 추가해준 부분;;
userforward:
driver = redirect
srs = forward
check_local_user
# local_part_suffix = +* : -*
# local_part_suffix_optional
file = $home/.forward
# allow_filter
no_verify
no_expn
check_ancestor
file_transport = address_file
pipe_transport = address_pipe
reply_transport = address_reply

이제 끝!!!
얼마 받지도 못하는 웍스테이션 조굔지 뭔지 때문에 지난 며칠간 고생한걸 생각하면 흑흑 ㅠ.ㅠ
p.s) exim 에 spf 적용하기와 관련해서 따라하기 편한 문서가 있는데 페이지 랭크가 낮은거 같아서 링크
http://slett.net/spam-filtering-for-mx/exim-spf.html

Sender Rewrite Scheme

spf 는 예전에 써놨던 글에서 충분히 설명해놨듯이 메일의 도메인값과 발송지 값을 이용해 스팸을 필터링해내기 위한 기술입니다. 예를 들어 From address 에 nospam@mytears.org 가 있을 경우 mytears.org 도메인의 txt 정보를 읽어오고, 거기에 적혀있는 61.109.245.78 에서 온 메일만을 정상적인 메일로 판단하게 되는데, 이렇게 spf 가 적용되어 있는 경우 .forward 나 alias 등을 이용해서 메일을 포워딩 시킬 경우 문제가 있을 수 있습니다.
.forwards 를 통해 nospam@mytears.org 를 nospam@gmail.com 으로 리다이렉션을 시킨 경우를 생각해봅시다.

  1. sender@somedomain 에서 nospam@mytears.org 로 메일을 보냄
  2. mytears 메일 서버에서 nospam@mytears.org 사용자에 대한 .forward 파일을 읽어서 거기 적혀있는 nospam@gmail.com 으로 메일을 포워딩 시킴
  3. gmail 메일 서버에서 from address 인 somedomain 의 txt 에서 spf 정보를 읽어옴
  4. spf 로 지정된 아이피와 메일을 포워딩시킨 서버의 아이피가 동일하지 않으므로 spf 정보가 맞지 않다고 판단함
  5. spf 정책에 따라 다르겠지만 대부분 스팸이라고 판단하게 됨
  6. 결국 제대로 메일이 전해지지 않음

이걸 해결하기 위한 방법으로 srs 라는 게 있습니다. 이걸 적극적으로 적용한 예로는 구글이 있습니다.
exim 의 경우 기본적으로 spf, srs 를 지원하기 때문에 빌드할 때 옵션을 잘 조절해주면 쉽게 적용할 수 있습니다. sendmail 의 경우 milter 를 이용하면 srs 를 적용할 수 있구요. 하지만 postfix 에서는 이를 사용할 수 없는 방법이 없습니다. (구버젼의 postfix 라면 패치를 통해 사용할 수 있지만 이 패치가 계속 유지되질 않고 있습니다.)
참고
spf: http://www.openspf.org/
srs: http://www.openspf.org/SRS
p.s) 그나저나 spf 가 나온지 꽤 오랜 시간이 흘렀음에도 대학 메일 서버들 중에 spf 를 지원하는 곳은 별로 없군요.

CG: gaussian blur

gaussian blur 는 간단하게 아래와 같은 gaussian function 을 이미지에 convolution 해주는 것을 통해 쉽게 구현할 수 있다.

시그마값을 0, 2, 4 로 변경시켜가며 가우시안 블러를 적용해보면 아래와 같은 결과를 얻을 수 있음.

코드:
https://github.com/Tee0125/snippet/tree/master/perspective_projection

p.s) 이걸 이용해서 구현해보려는 게 있는데, 막상 gaussian blur 와 difference of gaussian 모두 구현했지만 이상하게 관련된 실험은 손에 잡히질 않고 있다. -_-; 아악;;