Postfix mailbox

민정·2025년 9월 3일

mailbox

목표

  • mailbox가 무엇인지 이해
    • /var/mail/var/spool/mail이 뭔지 확인
    • `mbox, maildir 방식을 다시 확인
  • postfix의 mailbox가 무엇인지 확인
    • 일반적으로는 어떤 mailbox를 사용하는지
    • 어떻게 MDA와 연동하는지
    • MDA와 연동하면 mailbox가 어떻게 되는 것인지
  • postfix에서 mailbox를 사용하지 않고 바로 queue로 연결하는 방법이 무엇인지 확인
    • 현재 대략 pipe를 사용한다는 정도만 알고 있음
  • 최종 목표는 메일이 /var/mail이 아닌 GCP의 큐 서비스에 전송되도록 하는 것
    • 단, 시스템 메일은 내부 메일박스 /var/mail로 전달되어야 함

mailbox란?

/var/mail/var/spool/mail의 차이?

  • /var/spool/mail/var/mail의 심볼릭 링크
    • 두 디렉토리 내부에서 보이는 파일은 모두 일치
    • 그렇기때문에 둘 간의 차이는 별로 중요하지 않음
  • /var/spool/mail은 spooling 개념에서 온 디렉토리라고 함

mbox, maildir

  • mbox 방식
    • 사용자마다 자신의 이름으로 되어있는 파일이 하나씩 존재
    • 해당 파일에 자신에게 온 메일이 모두 저장됨
    • 모든 메일이 하나의 파일에 이어서 기록됨
  • maildir 방식
    • 사용자마다 자신의 홈 디렉토리에 maildir 디렉토리 존재
    • 해당 디렉토리로 자신에게 온 메일 파일이 저장됨
    • 각각의 메일이 별도의 파일로 저장되고, 해당 파일이 디렉토리 구조를 통해 관리됨

postfix와 mailbox

  • postfix는 별도의 MDA나 설정이 없다면 수신한 메일을 로컬 메일박스(/var/mail)에 저장
  • 기본적으로 메일박스는 mbox 방식을 사용

How Postfix receives mail

  • 네트워크 메일
    • smtpd, qmqpd 서버를 통해 네트워크 메일 유입
    • 메일 프로토콜의 캡슐화 제거, 메일 무결성 검사 수행
    • cleanup 서버로 메일 전달
  • 로컬 메일
    • sendmail 명령 등을 통해 로컬 메일이 제출
    • 해당 메일은 postdrop 명령에 의해 maildrop 큐에 저장
    • pickup 서버가 큐에 있는 로컬 메일을 가져와 무결성 검사 수행
    • cleanup 서버로 메일 전달
  • 내부 소스 메일
    • 아래의 메일이 내부 소스 메일에 해당
      • local 전송 에이전트가 전달한 메일
      • bounce 서버가 발신자에게 반송하는 메일
      • postmaster 알림
    • cleanup 서버로 직접 전달
  • cleanup 서버
    • 메일이 큐에 저장되기 전 최종 처리를 진행
      • 누락된 From: 헤더, 기타 메시지 헤더 추가, 주소 변환
      • 정규표현식을 통한 컨텐츠 검사 수행
    • 최종 처리 후 메일을 단일 파일로 incoming 큐에 저장
    • 새로운 메일이 도착했음을 큐 관리자에게 알림

How Postfix delivers mail

  • 메일을 수신한 후 위의 과정을 거쳐 메일이 incoming 큐에 도착한 상황
  • 해당 과정에서는 메일을 최종 목적지로 전달(delivers)
  • 큐 관리자 서버 qmgr
    • 메일 전달의 핵심 요소
    • incoming 큐의 메일을 smtp, lmtp, local, virtual, pipe 등의 다양한 전달 에이전트에게 전달
    • 메일을 누가 어떻게 보낼지를 결정
    • 만약 메일을 즉시 전달할 수 없는 경우 해당 메일은 deferred 큐에 따로 보관
  • 주소 재작성 서버 trivial-rewrite
    • qmgr이 전달 에이전트에게 요청을 보내기 전 trivial-rewrite가 각 수신자 주소가 로컬인지, 원격인지에 따라 분류 및 처리
  • 전달 에이전트
    • smtp: 네트워크를 통해 다른 SMTP 서버로 메일 전달
    • lmtp: smtp와 유사, 다른 서버로 메일 전달
    • virtual: UNIX 스타일 메일박스, qmail 스타일 maildir에만 메일 전달
    • local: UNIX 스타일 메일 박스, qmail 스타일 maildir, sendmail 스타일 aliases, .forward 파일 등을 처리 가능
      • local은 훅을 가지고 있어 메일박스 전달을 외부 명령이나 다른 postfix 전달 에이전트에게 위임하는 설정이 가능
      • 전통적인 /var/mail 메일박스에 전달하는 것이 아닌 사용자 정의 스크립트, 프로그램에 메일을 전달하고자 할 때 중요
    • pipe
      • 다른 메일 처리 시스템으로 메일을 보냄
      • 메일을 외부 스크립트, 프로그램으로 파이프함

local, pipe 전송 에이전트

  • local은 기본 설정에서 전통적인 /var/mail 메일박스로 메일을 전달
  • local의 훅 설정을 통해 외부 프로그램으로 메일 전달 및 처리 가능
  • 이때, pipe가 외부 프로그램과의 통신을 핵심적으로 담당

추가 목표

  • postfix의 메일 수신 및 최종 배달 과정을 확인한 후 앞으로 해야 할 일이 더 명확해졌다.
    • postfix의 mailbox 관련 파라미터 확인 및 조작
    • local, pipe 간의 관계 확인
    • pipe 설정 및 gcp queue와 연결

postfix의 파라미터

mailbox

  • 메일박스 위치, 스타일
    • home_mailbox: local 사용자 홈 디렉토리 내의 메일박스 경로 지정
    • mail_spool_directory: local UNIX 스타일 메일박스가 보관되는 디렉토리 지정
  • 메일박스 전송 명령 및 전송 방식
    • mailbox_command: local 전송 에이전트가 메일박스 전송에 사용할 외부 명령을 지정(전역적)
      • 유사한 파라미터 mailbox_command_maps가 존재(지역적, 수신자별 선택 가능)
    • mailbox_transport: local 전송 에이전트가 사용할 메시지 전송 방식을 지정(전역적)
      • 유사한 파라미터 mailbox_transport_maps가 존재(지역적, 수신자별 선택 가능)
  • mailbox_size_limit: 메일박스 크기 제한
  • 메일박스 잠금, 가상 메일박스 관련 파라미터 존재
  • mailbox 관련 파라미터는 local 전달 에이전트와 관련있는 파라미터
    • pipe는 메일박스를 관리하기보다는 메일을 외부 프로그램에 파이프하는 역할이기 때문에 mailbox 관련 파라미터와 무관함

전달 에이전트

전달 에이전트 간 관계

  • Postfix는 메일 전달 시
    • 모든 전달 에이전트를 다 사용 가능
    • 특정 전달 에이전트만 사용 가능
  • 큐 관리자 qmgr가 다양한 전달 에이전트에게 전달 요청이 가능하기 때문
  • 파라미터 설정을 통해 유연한 라우팅이 가능하기 때문
  • local, pipe는 밀접한 관계를 가짐
    • local이 직접, 간접적으로 pipe를 호출 가능

local

https://www.postfix.org/local.8.html

  • 로컬 수신자에게 메일을 전달
    • 서버 내의 사용자 계정, 파일, 외부 명령으로 메일을 전달
  • 다음 순서대로 전달 방법을 시도
    • aliases: 별칭 데이터 베이스 사용
    • .forward 파일
    • mailbox_transport_maps, mailbox_transport
    • mailbox_command_maps, mailbox_command
    • home_mailbox: 사용자 홈 디렉토리에 메일 저장
    • mail_spool_directory: UNIX 스타일 메일박스(/var/mail/user, /var/spool/mail/user)에 메일 저장
    • fallback_transport_maps, fallback_transport
    • luser_relay

pipe

https://www.postfix.org/pipe.8.html

  • 메일을 외부 명령으로 전달

pipe 설정 및 GCP queue와 연결

lookup table

  • postfix에서 액세스 제어, 컨텐츠 필터링, 라우팅 등에 조회 테이블(lookup table)을 사용
  • 조회 테이블은 항상 type:table의 구조(예시: hash:/etc/postfix/map)
    • type에는 hash, regexp 등의 단순한 구조부터 mysql, mongodb 등의 외부 데이터베이스까지 지원
    • table에는 해당 테이블의 경로 작성
  • 현재 hash 테이블을 사용해 메일 라우팅을 설정할 예정
    • 시스템 계정(root, daemon, postmaster)은 local이 배달
    • 그 외의 모든 계정으로부터 오는 메일은 pipe가 배달

transport_maps 설정

# /etc/postfix/transport
root    local:
daemon  local:
postmaster local:
* gcppipe:
  • 설정 후 postmap /etc/postfix/transport로 적용 필요
# /etc/postfix/main.cf
transport_maps = hash:/etc/postfix/transport

발신 오류 이슈

  • transport_maps는 수신과 발신 메일 모두에 적용됨
  • 내가 원하는 방식은
    • 발신 시 postfix가 알아서 메일을 발송, gcppipe로 메일이 전달되어서는 안됨
    • 수신 시 시스템 계정을 제외한 모든 계정의 메일이 gcppipe로 전달, gcppipe가 스크립트를 실행해 메일을 pub/sub으로 전송
  • 그러나 위의 설정은 다음과 같은 의미를 가짐
    • 메일 송수신 시 가장 먼저 transport_maps을 검사
    • 이를 통해 시스템 계정(root, daemon, postmaster) 메일은 local로 전달, 그 외의 모든 메일(*)은 gcppipe로 전달
    • 즉, 시스템 계정을 제외한 모든 송수신 메일이 gcppipe으로 전달
# /etc/postfix/transport
root    local:
daemon  local:
postmaster local:
# /etc/postfix/main.cf
local_transport = gcppipe:
transport_maps = hash:/etc/postfix/transport
  • 따라서 위와 같이 수정
    • 메일 발송의 경우
      • transport_maps을 검사
      • 이를 통해 시스템 계정(root, daemon, postmaster) 메일을 local로 전달
      • transport_maps에 명시되지 않은 계정은 smtp를 사용해 전송
    • 메일 수신의 경우
      • transport_maps을 검사
      • 이를 통해 시스템 계정(root, daemon, postmaster) 메일을 local로 전달
      • 이 외의 모든 메일이 local로 전달, local_transport에 명시된 gcppipe로 재전달
      • gcppipe의 스크립트 실행

pipe 스크립트 설정

https://velog.io/@mjttong/postfix-pipe-스크립트-작성하기

  • 해당 글에 스크립트 관련 내용을 별도로 작성함

pipe 설정

# /etc/postfix/master.cf
# service type  private unpriv  chroot  wakeup  maxproc command + args
gcppipe   unix  -       n       n       -       -       pipe 
  flags=FqX user=nobody argv=/usr/local/bin/gcppipe_script.go ${sender} ${recipient}
  • type: 프로세스 간 통신(IPC) 방법 정의
    • inet(TCP/IP), unix(UNIX 소켓) 등의 값을 사용 가능
  • private: 서비스가 내부에 위치하는지 여부
    • y/n 응답, -는 디폴트 값 사용
  • unprivileged(unpriv): 서비스가 root(또는 postfix 소유자) 권한으로 실행되는지 여부
    • y/n 응답
  • chroot: 서비스가 chroot 환경에서 실행되는지
    • y/n 응답
  • wakeup: 서비스가 정기적으로 깨어나야 하는 시간 설정
    • -: 필요할 때 알아서 실행
  • maxproc: 동시에 실행될 수 있는 최대 서비스 프로세스 수
    • -: master.cf에 정의된 기본 값 사용
  • command + args: 실행될 프로그램의 경로, 명령줄 인수

수신 메일 이슈

  • gmail에서 내 메일 서버로 메일을 전송하면서 테스트를 진행했다.

DNS 이슈

  • A 레코드를 설정할 때, 모두 cloudflare proxy를 거치도록 설정했더니 수신 메일이 전혀 도달하지 않는 것을 확인했다.
    • 수신 메일을 전송했을 때, 관련 방화벽과 포트를 개방했음에도 불구하고 postfix 로그에 메일이 도달했다는 내용이 아예 뜨지 않았다.
    • 즉, 네트워크 수준에서 메일이 가로막혀 postfix 애플리케이션에도 전달되지 못하는 상황임을 인지했다.
    • A 레코드 설정에서 프록시를 거치지 않는 DNS 전용으로 설정을 수정했다.
    • dig 명령어를 통해 MX 레코드와 A 레코드가 제대로 설정되어 있는지 확인했다.

SASL 이슈

Sep 02 09:08:49 mail postfix/smtpd[78508]: fatal: no SASL authentication mechanisms
  • 내 Postfix가 모든 외부 서버에게 SASL 인증을 요구도록 설정되어 있어 gmail이 SASL 인증에 실패해 연결을 거부했다.
  • 원래는 smtp가 아닌 submission에만 sasl이 적용되어야 한다.
    • 만약 smtp에도 sasl이 적용된다면 25번 포트로 진행되는 모든 통신에 sasl을 요구하게 되어 제대로된 통신이 진행되지 않는다.
    • 따라서 master.cf에서 submission에만 smtpd_sasl_auth_enable = yes로 오버라이딩 설정을 해야 한다.
  • 현재 main.cf의 smtpd_sasl_auth_enable = yes로 smtp, submission 등의 모든 통신에 있어 sasl을 요구하게 되어 메일 수신을 못하고 있는 상황이다.
  • 따라서 main.cf에서 smtpd_sasl_auth_enable를 주석 처리하고, master.cf의 submission에서만 설정했다.

Client host rejected

smtpd_client_restrictions = permit_mynetworks, reject
  • postfix의 접근 제어 규칙이 지나치게 엄격해 gmail 서버의 접근을 차단했다.
    • 위와 같은 smtpd_client_restrictions 설정 시 mynetworks에서 지정된 서버만 접근을 허용한다.
    • 그렇기 때문에 gmail, naver 등의 정상적인 메일 서버도 모두 거부하게 된다.
  • 보안은 매우 강력하지만, 외부 메일을 수신하는 메일 서버의 경우 위 설정을 사용해선 안된다.
smtpd_client_restrictions = permit_mynetworks,
   permit_sasl_authenticated,
   reject_unknown_client_hostname
  • 따라서 비정상적인 클라이언트만 차단하는 수준으로 설정을 완화했다.

pipe 스크립트 인증 이슈

Command output: 2025/09/02 10:14:57 Failed to publish message to pubsub: rpc error: code = PermissionDenied desc = Request had insufficient authentication scopes. error details: name = ErrorInfo reason = ACCESS_TOKEN_SCOPE_INSUFFICIENT domain = googleapis.com metadata = map[method:google.pubsub.v1.Publisher.Publish service:pubsub.googleapis.com]
  • 위 문제를 모두 수정해 postfix 내로 메일이 도착하고, gcppipe로 메일이 전달되어 스크립트를 실행하는 것까지는 성공했다.
  • 하지만 postfix가 실행되는 VM의 API 액세스가 제한되어 있어 메일을 pub/sub으로 전달하는 것까지는 실패했다.
    • VM의 API 및 ID 관리를 확인하니 Cloud Pub/Sub API 권한이 없다는 것을 확인했다.
gcloud compute instances set-service-account [instance-name] --scopes=pubsub --zone=[zone]
  • VM을 정지하고 위 명령어를 CLI에서 입력해 권한을 부여했다.
  • 다시 메일을 전송하니, 메일이 제대로 전송된다.
profile
시스템 + 리눅스 + 클라우드

0개의 댓글