JS

2013년 11월 14일 목요일

넥서스 7 (2013)에 kitkat 올리는 방법 (OTA)

롤리팝의 경우:
http://sigpoll.blogspot.kr/2014/11/nexus-7-2013-android-50-lollipop-ota.html


1. 우선 android sdk와 usb driver를 설치해야 된다. http://sigpoll.blogspot.kr/2013/09/adb.html을 참고하면 된다.

2. OTA 파일을 다운받아야 한다. 다음 주소에서 다운로드 받으면 된다.

http://android.clients.google.com/packages/ota/google_razor/3a3951e8243c82bc01a620ab858f4454c8ebd65c.signed-razor-KRT16O-from-JSS15R.3a3951e8.zip

링크 주소의 출처는 http://forum.xda-developers.com/showthread.php?t=2380113&page=7이다.

3. 그리고 adb sideload를 이용해서 업데이트를 하는데 그 방법은 http://forum.xda-developers.com/showthread.php?p=44882205#post44882205에 적혀있다. (영어)

대충 설명하면,
a. 전원을 끄고,
b. 전원 버튼과 볼륨+ 버튼을 동시에 눌러서 recovery 모드로 들어 간 뒤,
c. 누워 있는 안드로이드가 보이면 전원 버튼을 계속 누르고 있는 상태에서, 볼륨+ 버튼을 눌렀다가 떼고, 전원 버튼에서 손을 떼면,
d. 메뉴가 보일 것이다.
e. 메뉴에서 Apply update from ADB를 선택하고,
f. 컴퓨터에서 adb sideload <filename> 명령으로 업데이트 파일을 전달하면 된다.
g. 이제 업데이트가 완료될 때까지 구경하면서(?) 대기한다.

2013년 9월 28일 토요일

기기 관리자 문제 해결

문제가 발생하는 원인:
* OTA를 빨리 받기 위한 방법 중에 Google Services Framework의 데이터를 지우는 방법이 있다. 그런데 이 방법을 적용하면 몇 가지 문제가 발생하기도 한다.

안드로이드 기기 관리자에 내 기기가 안 보이는 경우

참고: https://support.google.com/accounts/answer/3265955?hl=ko

1. Google 설정에서 원격 기기 초기화 허용을 비활성화한다.
2. 설정 > 애플리케이션에서 Google Play 서비스의 데이터를 지운다.
3. Google 설정에서 원격 기기 초기화 허용을 활성화한다.
4. 기기를 리부팅하고 기다린다.

구글 플레이 스토어에서 내 기기가 안 보이는 경우 (앱 원격 설치 불가능)

참고: https://support.google.com/googleplay/answer/1141080?hl=en

1. 설정 > 애플리케이션에서 Google Play 스토어의 데이터를 지운다.
2. 플레이 스토어를 다시 실행하면 약관 동의를 묻는 창이 다시 나올 것이다. 여기에 동의하고 기다린다.

2013년 9월 27일 금요일

넥서스 7 (2013) OTA 실패

넥서스 7 (2013)을 루팅 등을 하지 않고 순정 상태로 사용 중이었다. 그런데 시스템 업데이트 과정에서 오류가 발생하였다. 처음에는 일시적인 오류라고 착각을 했는데, 같은 오류가 두세번 반복되고 난 후, 내 넥서스 7에 문제가 있다는 것을 알게 되었다. 오류가 발생한 화면에서 volume up key + power key를 누르면 recovery mode가 나오면서 어디서 오류가 발생했는지도 볼 수 있다. 에러 메시지는 다음과 같았다.

Finding update package...
Opening update package...
Verifying update package...
Installing update...
Verifying current system...
assert failed: apply_patch_check("/system/framework/framework-res.apk", "d6ff2a1767bec4e906ee8498fc852d74c16e328d", "b92dd7bde4e18298ee24053ba36436f4f03fcb79")
E:Error in /cache/58a0583c950458ab96dc1fc29b020c6782493c3b.signed-razor-JSS15R-from-JSS15Q.58a0583c.zip
(Status 7)
Installation aborted.

정상적인 방법으로 OTA를 시도한 경우에도 실패했고, adb sideload를 이용한 방법도 실패했다. 공장 초기화를 한 후 다시 시스템 업데이트를 시도한 경우에도 실패했다. 모든 경우에 위와 같은 assert failed 메시지가 나타났다.

* 루팅을 시도하지도 않았고, 커스톰 롬을 설치하지도 않았다.

* 설정의 메뉴에서 공장 초기화를 해도 문제는 해결되지 않았다.

* 리커버리 모드에서 공장 초기화를 해도 문제는 해결되지 않았다.

* 위 메시지에서 assert failed에 해당하는 부분은 OTA update 파일에서 META-INF/com/google/android/update-script의 line #196에 해당하는 내용이다.

* 루팅은 하지 않았고, 시스템 파티션에 read-only로 접근 가능하다. adb pull /system/framework/framework-res.apk로 JSS15Q 상태의 기기에서 framework-res.apk를 추출한 후 sha1sum을 해보니 결과는 b92dd7bd... 이였다.

* Factory image의 압축을 해제한 후 apk의 파일을 추출할 수 있다 (참고1, 참고2). 요약하면 sgs2toext4.jar을 이용하여 .img 파일을 .ext4.img 파일로 바꿀 수 있고, 그 파일은 DiskInternals Linux Reader에서 읽을 수 있다. 이렇게 해서 framework-res.apk를 추출했고, 그 파일의 sha1sum은 b92dd7bd... 이였다.

* 그리고 d6ff2a17...은 업데이트 적용 후의 sha1sum인 것 같다.

* OTA 파일에서 patch/system/framework/framework-res.apk.p가 바이너리 패치 파일인 것 같다.

* apply_patch_check가 정확히 무슨 일을 하는지 모르겠다. 내 시스템에서 framework-res.apk의 sha1sum은 분명히 문제가 없다. 그리고 OTA 파일도 아마 문제가 없을 것이다. 정상적인 방법(sideload 말고)을 이용했을 때도 같은 에러가 발생했으며, 에러 메시지에서 보이는 파일명(zip 파일)은 sideload를 위해 다운 받은 zip 파일의 파일명과 일치하기 때문이다.

안드로이드 adb를 설치하고 사용하는 방법

1. SDK 설치

여기를 클릭하여 Android SDK에 접속하여, 필요한 프로그램을 다운받아 설치해야 한다. 참고로 ADT bundle이라고 되어 있는 것은 이런 저런 프로그램이 통합되어 있어서 용량이 크다. 앱을 개발할 생각이 없다면 SDK tools only라고 되어 있는 것을 다운받아서 설치해도 된다. 본인에게 맞는 방법으로 설치하자. 참고로 JDK도 설치해야 된다. System requirements에 자세히 적혀 있다.


SDK 설치가 완료되면 Android SDK Manager를 실행하고 Tools/Android SDK Platform-tools를 설치한다. 앱을 개발할 계획이 없다면 Android 4.3 (API 18) 등은 설치하지 않아도 된다.



2. USB 드라이버 설치

단순히 파일 복사만 하는 경우에는 드라이버를 설치하지 않아도 되지만, USB 디버깅을 하려면 추가 드라이버를 설치해야 한다. 이 드라이버는 제품에 따라 다운 받는 위치가 다르다. 이 글에서는 윈도우에서 넥서스 7 (2013)을 사용하는 경우를 기준으로 설명하겠다. 그 이외의 상황이라면 윈도우를 쓰는 경우에는 win-usb.html 또는 oem-usb.html을 참고하고, 다른 운영체제를 쓰는 경우에는 device.html을 참고하자.

Android SDK Manager를 실행하고 Extras/Google USB Driver를 설치한다. 앱을 개발할 계획이 없다면 Android 4.3 (API 18) 등은 설치하지 않아도 된다.

그 다음에 윈도우의 장치 관리자를 열고 기타 장치에서 느낌표가 떠 있는 Nexus 7을 선택하여 드라이버 업데이트를 한다. 수동으로 설치하는 옵션을 선택하자. 그 다음 드라이버를 검색할 위치를 지정해야 한다. 이 위치를 C:\Program Files\Android\android-sdk\extras\google\usb_driver으로 지정한다. (정확한 위치는 android-sdk를 설치한 위치에 따라 알맞게 수정.)

설치가 제대로 되었는지 확인하려면 cmd 창에서 adb devices 명령을 내려보고 기기가 나타나는지 보면 된다. adb가 기본적으로 PATH에 없으므로 PATH를 수정하든가 C:\Program Files\Android\android-sdk\platform-tools에서 cmd를 열어야 한다.

3. Recovery 모드에서 adb 사용

USB 코드가 달라서 드라이버를 또 설치해야 한다. 방법은 위와 비슷한데, 드라이버를 검색하는 옵션 대신 드라이버 목록에서 직접 선택하는 옵션을 사용해야 한다. 그렇게 하지 않으면 설치가 안 된다. (수동 설치 -> 직접 선택 -> (모든 장치) -> 디스크 있음 -> Andorid ADB Interface 선택)




2013년 8월 10일 토요일

Create a pivot table using SQL

이글을 참고하면 좋다: http://stackoverflow.com/a/12599372
링크한 글에서는 MAX()를 사용하고 있는데 경우에 따라 SUM()이 필요할 수도 있을 것 같다.

SQLite 같은 프로그램은 @sql같은 것을 지원하지 않지만, query를 여러번에 나누어서 수행하면 된다.

Scrollable Table with Fixed Header

HTML/CSS만 사용하여 머리글(첫 행)이 고정된 표를 만들고 싶은 경우가 있다. 이것은 해결하는 방법은 여러 가지가 있는데, 그 중 하나는 표를 두 개 만드는 방법이다. 이 방법을 간단하게 설명하면, 이 방법은 머리글만 포함하는 표와 머리글 없이 내용만 포함하는 표를 각각 만들어서 위아래로 붙여 놓는 방법이다. 그러나 이렇게 할 경우 첫번째 문제는 겉보기에는 두 개의 표가 하나로 보이지만 실제로는 각각의 표가 별개의 객체로 존재하게 되기 때문에, 머리글과 내용 사이의 논리적인 연관성을 표현하기 어려워진다는 것이다. 그리고 이것보다 더 현실적인 문제도 있는데, 이 문제는 열의 너비를 강제로 지정하지 않을 경우 위의 표와 아래의 표의 열 너비가 서로 일치하지 않게 될 수 있다는 것이다.

아무튼 원하는 요구 사항은 다음과 같다.
  • 하나의 table tag를 사용할 것
  • HTML/CSS만을 사용할 것
  • 열 너비를 강제로 지정하지 않아도 사용 가능하게 할 것
  • 테이블의 border가 변경되어도 머리글이 어긋나지 않을 것
인터넷을 검색하여 이런 방법을 찾았다. http://salzerdesign.com/test/fixedTable.html
아니면 이런 글도 있는데 이건 원리를 모르겠다.
 http://www.imaputz.com/cssStuff/bigFourVersion.html

그런데 위에서 링크한 방법은 열 너비를 맞추기 위해 머리글을 중복으로 넣어야 한다. 이렇게 하는 것이 조금 걸려서 내가 나름대로 다시 만들어 보았다. 원하는 박스가 정확한 위치에 있는지 쉽게 알아 보기 위해 일부러 border를 크게 만들어둔 상태이다. 실제 사용할 경우에는 약간 고쳐서 쓰면 될 것이다. 다음 링크에서 코드와 결과를 볼 수 있다. (http://jsfiddle.net/x72V3/3/) 그리고 남아 있는 문제점은 다음과 같다.
  • container에 가로 스크롤바가 발생할 경우, 내용만 좌우로 스크롤 되고 머리글은 좌우로 스크롤 되지 않는다. 이것은 position을 absolute로 했기 때문에 그렇다. 이 문제를 해결하기 위해 conationer를 하나 더 만드는 방법이 있긴 한데, 이렇게 하면 세로 스크롤바가 이상한 곳으로 숨어 버린다. (http://jsfiddle.net/JGeMg/3/)

2013년 8월 7일 수요일

Git subtree를 이용한 작업

다음과 같은 상황을 생각해보자. 현재 프로젝트 디렉토리 구조는 다음과 같다.
myproject/
        .git/
        app1/
                app1-files
        app2/
        others
그런데 여기서 app1만 떼어내서 프로젝트를 새로 만들고 싶다. 그리고 git 히스토리는 최대한 유지하고 싶다.
project-app1/
        .git/
        app1/
                app1-files

스택오버플로우에서 비슷한 질문을 찾았다.
http://stackoverflow.com/questions/359424/detach-subdirectory-into-separate-git-repository

처음에는 추천수를 가장 많이 받은 방법을 따라해보았다. (내가 실수해서 그렇게 되었을 가능성도 있지만) 저 방법을 그대로 따라하니 다음과 같이 되었다.
project-app1/
        .git/
        app1-files
그래서 디렉토리 구조를 또 바꿔야 하는데 이것도 filter-branch를 잘 쓰면 가능하겠지만 방법을 찾기 어려웠다. 어떻게 하다보니 찾게된 방법이 subtree를 이용하고 있었다.
http://stackoverflow.com/questions/1425892/how-do-you-merge-two-git-repositories

그래서 그냥 처음부터 subtree만 써서 다시 작업을 하면 좋겠다는 생각이 들었다. 최신 버전의 git을 설치하는 것도 문제인데 여기엔 적지 않겠다. (경우에 따라 소스를 직접 받아서 컴파일해야 할 수도 있다.)

디렉토리 분리

먼저 app1 디렉토리를 분리하는 여기를 참고해서 다음과 같이 하면 된다.
cd ~/path/myproject
git subtree split -P app1 -b app1-only
cd ~/path
여기서 -P 다음엔 디렉토리 이름을 적고, -b 다음에는 branch 이름을 적당히 적으면 된다.
mkdir ~/path/app1
cd ~/path/app1
git init
git pull ~/path/myproject app1-only
이렇게 하면 ~/path/app1 디렉토리에 app1의 내용만 분리된다. 원래의 myprojcet 디렉토리에서 app1을 삭제하는 방법은 참고한 글에 나온다. 그리고 참고한 글에 적힌 remote add는 생략한다.

디렉토리 가져오기

이제 디렉토리를 새로 만들어서 app1을 집어 넣어야 한다. 이것은 여기를 참고하였다. 다만 git subtree add를 하기 전에 뭔가 commit을 해두어야만 하는 것 같다.
mkdir ~/path/project-app1
cd ~/path/project-app1
git init
git add something
git commit something
git subtree add --prefix app1 ~/path/app1 master
이렇게 하면 처음에 원했던 작업이 완료된다. 다만 app1/somefile을 수정한 후 subtree add 이전의 커밋과 비교하는 것은 불가능하다. 처음 디렉토리에 대한 상대 경로가 변경되어서 그런 것 같다.

전체 히스토리에서 파일 삭제

생각을 더 해봤는데 더 좋은 방법이 있는 것 같다. 일단 myproject를 통채로 복사하고 디렉토리 이름을 project-app1으로 바꾼다. 이 디렉토리는 .git의 상위 디렉토리이므로 이름을 마음대로 바꿀 수 있다. 그리고 project-app1 디렉토리 내에서 불필요한 파일과 그에 해당하는 히스토리를 통채로 삭제해 버리면 된다. 이렇게 하는 방법은 다음 글에 적혀 있다.

2013년 6월 1일 토요일

Linux Commands

Advanced Bash-Scripting Guide에 Linux commands를 정리해둔 챕터가 있다. 다음 명령에 대해 정리되어 있다. 참고로 굵은 글씨는 필자가 마음대로 표시한 것으로 원본과는 무관하다.

. (See also source) ac adduser agetty agrep ar arch at autoload awk (See also Using awk for math operations) badblocks banner basename batch bc bg bind bison builtin bzgrep bzip2 cal caller cat cd chattr chfn chgrp chkconfig chmod chown chroot cksum clear clock cmp col colrm column comm command compgen complete compress coproc cp cpio cron crypt csplit cu cut date dc dd debugfs declare depmod df dialog diff diff3 diffstat dig dirname dirs disown dmesg doexec dos2unix du dump dumpe2fs e2fsck echo egrep enable enscript env eqn eval exec exit (Related topic: exit status) expand export expr factor false fdformat fdisk fg fgrep file find finger flex flock fmt fold free fsck ftp fuser getfacl getopt getopts gettext getty gnome-mount grep groff groupmod groups (Related topic: the $GROUPS variable) gs gzip halt hash hdparm head help hexdump host hostid hostname (Related topic: the $HOSTNAME variable) hwclock iconv id (Related topic: the $UID variable) ifconfig info infocmp init insmod install ip ipcalc iptables iwconfig jobs join jot kill killall last lastcomm lastlog ldd less let lex lid ln locate lockfile logger logname logout logrotate look losetup lp ls lsdev lsmod lsof lspci lsusb ltrace lynx lzcat lzma m4 mail mailstats mailto make MAKEDEV man mapfile mcookie md5sum merge mesg mimencode mkbootdisk mkdir mke2fs mkfifo mkisofs mknod mkswap mktemp mmencode modinfo modprobe more mount msgfmt mv nc netconfig netstat newgrp nice nl nm nmap nohup nslookup objdump od openssl passwd paste patch (Related topic: diff) pathchk pax pgrep pidof ping pkill popd pr printenv printf procinfo ps pstree ptx pushd pwd (Related topic: the $PWD variable) quota rcp rdev rdist read readelf readlink readonly reboot recode renice reset resize restore rev rlogin rm rmdir rmmod route rpm rpm2cpio rsh rsync runlevel run-parts rx rz sar scp script sdiff sed seq service set setfacl setquota setserial setterm sha1sum shar shopt shred shutdown size skill sleep slocate snice sort source sox split sq ssh stat strace strings strip stty su sudo sum suspend swapoff swapon sx sync sz tac tail tar tbl tcpdump tee telinit telnet tex texexec time times tmpwatch top touch tput tr traceroute true tset tsort tty tune2fs type typeset ulimit umask umount uname unarc unarj uncompress unexpand uniq units unlzma unrar unset unsq unzip uptime usbmodules useradd userdel usermod users usleep uucp uudecode uuencode uux vacation vdir vmstat vrfy w wait wall watch wc wget whatis whereis which who whoami whois write xargs yacc yes zcat zdiff zdump zegrep zfgrep zgrep zip


  1. http://tldp.org/LDP/abs/html/part4.html

Apache HTTP Server Configuration

필요한 모듈들을 추가해야 한다. /etc/apache2/mods-enabled에서 /etc/apache2/mods-available로 심볼릭 링크를 거는 형태로 모듈을 추가한다. 설치시 기본으로 추가된 모듈 이외에 ssl, rewrite, userdir, php5 등 필요한 모듈이 있으면 잘 추가해준다.

AllowOverride의 경우 필요한 것을 잘 추가해준다.
FileInfo AuthConfig Limit

그리고 Options의 경우 Indexes가 기본으로 들어있는데 꼭 필요한 것이 아니라면 Indexes는 빼버린다. SymLinksIfOwnerMatch를 사용하여 심볼릭 링크가 아무데나 안 가도록 하는 옵션도 생각해볼 수 있다. 혹시 심볼릭 링크의 owner가 chown으로 안 바뀌어서 고민이라면 chown -h를 사용하면 된다.
Options FollowSymLinks MultiViews SymLinksIfOwnerMatch

그리고 ScriptAlias를 사용하지 않는다면 주석 처리해도 좋다.

SSL을 사용하기 위해 /etc/apache2/sites-availabe/default-ssl을 /etc/apache2/sites-enabled/에서 심볼릭 링크로 연결한다. 그리고 인증서 설정을 적절히 해준다.
SSLCertificateFile    /etc/ssl/certs/my2048.cer
SSLCertificateKeyFile /etc/ssl/private/my2048.key

SSLCertificateChainFile /etc/ssl/certs/chain.cer

SSLCACertificateFile /etc/ssl/certs/CA.cer

인증서 passphrase를 자동으로 입력하기 위해 /etc/apache2/mods-enabled/ssl.conf를 변경한다. 여기서 mypass.sh는 암호를 echo하는 스크립트이다.
SSLPassPhraseDialog  exec:/etc/ssl/private/mypass.sh

보통의 http 접속이 https로 접속되도록 rewrite condition도 설정해준다. 일부 페이지만 https를 강제하고 싶다면 rewrite를 써서 해결할 수 있다. Rewrite rule은 ? 뒤에 붙은 것을 기본적으로 무시하므로 %{QUERY_STRING}을 써야할 수도 있다. 그리고 QSA는 query string이 rewrite 이후에도 남아있게 만드는 옵션이다.
#### RewriteEngine related code
RewriteEngine on

# login pages
RewriteCond %{HTTPS} !=on
RewriteCond %{QUERY_STRING} do=login
RewriteRule ^.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L,QSA]

# internal pages
RewriteCond %{HTTPS} !=on
RewriteRule ^/my_internal/.*$ https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

혹시 모든 접속을 https로 보내는 것을 원한다면 redirect를 쓰면 된다.
ServerName www.example.com
Redirect / https://www.example.com/


더 필요한 것이 있으면 잘 해주면 된다.

Procmail Configuration

postfix나 dovecot도 전체 기능 중 일부만 이해하고 있지만, procmail은 특히 심하다. 필자는 procmail의 설정에 대해서는 잘 모르고 있다.

가장 중요한 것은 디렉토리를 정하는 것이다. 설정 파일 /etc/procmailrc에 다음과 같이 적으면 된다. 그리고 privileges를 박탈하는 것도 좋은 옵션이다. 참고로 아래 설정 파일은 자체 검증을 거치지 않았다.
DROPPRIVS=yes
DEFAULT="$HOME/Maildir/"   
MAILDIR="$HOME/Maildir/"

Dovecot Configuration

Dovecot version 2.x를 기준으로 작성하였다. 기본 기능만 사용하는 경우 어쩌면 version 1.x가 더 설정하기 쉬웠던 것 같다. 하지만 2.x도 wiki를 잘 보면 설정하기 쉬운 편이다.

가장 중요한 설정은 mail_location이다. /etc/dovecpt/10-mail.conf에서 변경해준다.
mail_location = maildir:~/Maildir

SSL 또는 TLS를 사용하기 위해 /etc/dovecot/10-ssl.conf에서 ssl = yes로 바꾸고, 인증서 설정도 잘 해준다. 혹시 인증서를 만들어야 할 경우 2048비트 이상의 인증서를 권장한다. 참고로 2048비트 인증서는 아마도 2030년 정도까지 사용할 수 있을 것으로 예상하고 있다고 한다.
ssl = yes

SASL을 위해 /etc/dovecot/10-main.conf를 다음과 같이 변경한다. MTA는 postfix를 쓴다고 가정하였다.
service auth {
  #...
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    # Assuming the default Postfix user and group
    user = postfix
    group = postfix        
  }
  #...
}

  1. http://wiki2.dovecot.org/
  2. http://wiki2.dovecot.org/SSL
  3. http://wiki2.dovecot.org/HowTo/PostfixAndDovecotSASL

2013년 5월 31일 금요일

Postfix Configuration

postconf -n은 /etc/postfix/main.cf에서 직접 바꾼 설정만 보여준다. 여기에 나오지 않는 것은 기본값을 쓰고 있다는 것이다. 그리고 일부 설정은 리눅스 배포판에서 바꿔서 배포하고 있을 것이다. 아래 코드는 현재 설정된 서버에서 postconf -n 명령의 출력을 적은 것이다. 다만 개인 정보를 담고 있는 부분은 적절히 고쳤다.

alias_database = hash:/etc/aliases
alias_maps = hash:/etc/aliases
append_dot_mydomain = no
biff = no
config_directory = /etc/postfix
inet_interfaces = all
inet_protocols = ipv4
## mailbox, MDA
home_mailbox = Maildir/
#mailbox_command = procmail -a "$EXTENSION"
## mailbox and message size limit
mailbox_size_limit = 0
message_size_limit = 102400000
## domain settings
mydestination = host.domainname, localhost.domainname, localhost
myhostname = host.domainname
#mynetworks_style = host
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
myorigin = /etc/mailname
readme_directory = no
recipient_delimiter = +
relayhost =
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
## Dovecot SASL
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_path = private/auth
smtpd_sasl_type = dovecot
## TLS
smtpd_tls_cert_file = /etc/ssl/my.pem
smtpd_tls_key_file = /etc/ssl/private/my.key
smtpd_tls_security_level = may
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_use_tls = yes

필자가 기억하기로 mailbox_command가 기본값(empty)이어도, home_mailbox를 제대로 해두면 동작했던 것 같다. 원한다면 procmail을 쓸 수도 있는데 필자는 procmail에 대해 거의 아는 것이 없다.

여기서 Dovecot SASL은 설정하지 않아도 local에서 mutt 등의 MUA를 이용하면 메일을 보내거나 받는 것이 가능하다. 이 기능을 쓰려면 당연히 dovecot을 설치하고 적절히 설정해야 하는데, 이에 대해서는 별도의 게시물로 작성할 예정이다.

참고로 domainname이 없는 서버에서도 postfix를 사용하는 것이 가능하긴 하다. 그런데 IP 주소를 이용하여 메일을 보내려고 할 때 받는 사람 주소에 "user@127.0.0.1"처럼 적으면 메일이 가지 않을 것이다. 필자는 장기간의 검색을 통해 어렵게 이유를 알아내었는데, 결론은 IP 주소를 적을 때 [127.0.0.1]라고 적어야 한다는 것이다.

그리고 /etc/postfix/master.cf에서는 submission protocol 부분을 uncomment하였다. 이렇게 하면 587번 포트를 사용할 수 있다.

submission inet n       -       -       -       -       smtpd

문제가 있을 경우에는 /var/log/mail 등의 로그 파일을 참고하면 큰 도움이 되며, smtpd_tls_loglevel 등의 옵션을 사용하여 로그 수준을 변경하는 것도 도움이 된다. 그런데 필자가 삽질 다 해보고 최종 결론을 적은 것이라 위에 적힌대로만 하면 아마도 잘 동작할 것 같다.

  1. http://www.postfix.org/SASL_README.html
  2. http://www.postfix.org/TLS_README.html
  3. http://wiki2.dovecot.org/HowTo/PostfixAndDovecotSASL

Mail Agent Overview

필자도 자세한 것은 모르지만, 메일관련 프로그램은 5가지 정도로 분류된다. 아래 정리된 설명은 조금 불완전할 수도 있다. 설명이 짧은 항목은 위키에서 보고 적은 것이므로 대충 맞겠지만, 설명이 긴 항목일 수록 개인적인 의견이 많이 섞여 있고 틀린 내용이 있을지도 모른다.
  • Mail User Agent (MUA)
    사용자 클라이언트이다. MS 아웃룩, 모질라 썬더버드, mutt 등이 그 예이다.
  • Mail Transfer Agent (MTA)
    넓은 의미로는 MUA를 제외하고는 전부 MTA이지만, 세분화해서 말하면 MTA는 메일을 실제로 다른 서버 등과 주고 받는 프로그램이다. 예를 들면 sendmail, postfix, exim, qmail 등이 있다. SMTP를 사용하고 port 번호는 25번이다.
  • Mail Delivery Agent (MDA)
    받은 메일을 메일함(~/Maildir 등)에 전달하는 프로그램으로, procmail 등의 프로그램이 있다. 하지만 MTA가 간단한 MDA 역할을 하기도 한다. 즉, procmail을 서버에 설치하지 않아도 기본적인 기능은 동작한다.
  • Mail Submission Agent (MSA)
    MTA와 MSA의 구분을 필자도 정확히 모르겠다. MSA와 MTA를 언제부터 구분하기 시작했는지 잘 모르겠지만, 비교적 최근에 구분하기 시작한 것 같다. MUA에서 메일을 직접 받는 것은 MSA라고 하고, MSA에서 메일을 받는 것이 MTA이다. MSA는 SMTP와 비슷한 프로토콜을 사용하는데 포트 번호는 587번이다. MSA가 만들어진 것 자체는 어느정도 지난 것 같지만, 그동안 MUA에서 587번 포트를 안 쓰고 25번 포트를 써왔던 것으로 기억한다. 그런데 스팸 메일을 차단하는 것 등의 목적으로 25번 포트를 제한하게 되면서 587번 포트가 본격적으로 사용되기 시작한 것 같다.
  • Mail Retrieval Agent (MRA)
    원격 메일함에서 메일을 가져온다. 예를 들면 fetchmail, getmail 등이 있다. 필자는 사용해보지 않았다. 참고로 MRA는 RFC 5598에 없는 내용이고, Wikipedia에서는 메일 전달 과정에서 받는 쪽 MUA 직전의 단계를 MRA라고 하고 있다.
Dovecot 등의 서버는 위 분류에서 어디에 들어갈지 모르겠다. Dovecot은 IMAP, POP 서버의 역할을 하는데, MRA로 분류되는 fetchmail은 IMAP 등을 통해서 메일을 가져오는 것이므로 dovecot과 성격이 다른 것 같다. 그런데 Wikipedia의 설명대로라면 Dovecot을 MRA인 것 같기도 하다.

또, dovecot은 Simple Authentication and Security Layer (SASL) 기능도 제공한다. 그리고 postfix나 exim에서 SASL을 통해 SMTP 인증(authentication)을 하게 된다. 적어도 내가 알기로는 postfix에서 SMTP 인증을 쓰기 위해 dovecot과 같은 별도의 프로그램이 필요한데, 왜 postfix에서 자체적으로 SASL 기능을 포함하지 않았는지 생각을 해보았다. 따지고 보면 postfix는 IMAP이나 POP도 지원하지 않고, 단순하게 들어오는 메일을 받거나, 나가야할 메일을 보내는 역할정도만 한다. Dovecot 없이 postfix만 설치해도 local에서 메일을 보내는 것은 잘 된다. 그런데 외부에서 SMTP로 들어오는 메일을 전부 처리하면 내 서버는 스팸 메일의 경유지가 될 것이므로, local에서 전송되는 메일 또는 local이 최종 목적지인 메일만 처리하는 편이 좋을 것이다. 즉, postfix 등의 MTA는 local 외부에서 사용되는 MUA를 제대로 지원하지 않고 있는 것이다. 그래서 dovecot 같은 것을 설치해야 IMAP 또는 POP을 통해 MUA가 메일을 가져갈 수 있고, SMTP를 통해 MUA가 메일을 보낼 수 있는 것이다. 여기서 SMTP의 경우 MUA는 MTA에 직접 접속하지만 dovecot이 없으면 SMTP 인증 기능은 사용할 수 없는 것으로 생각하면 될 것 같다.

결론적으로, IMAP이나 POP은 받는쪽 MTA와 받는 쪽 MUA를 매개하고, SASL은 보내는 쪽 MUA와 보내는 쪽 MTA의 연결에서 인증 기능을 담당하고 있다. 그러므로 dovecot 등의 프로그램은 MTA는 아니지만 MTA의 보조적인 기능을 담당하고 있는 것이라고 생각하면 될 것 같다.

마지막으로 dovecot은 Local Delivery Agent (LDA) 기능도 하는데 써보지는 않았다.

자세한 내용을 원하면 다음 문서를 읽어보면 된다. RFC 문서들은 링크한 것 이외에도 많이 있는데, 사실 난 거의 읽어보지 않았다. 기본 개념만 알기 원하는 것이라면 Wikipedia나 mutt, dovecot 사이트의 설명을 읽어보는 편이 도움이 될 것이다.