bash で任意の SMTP サーバへメールを送信する
呼称: Mailmail
目的: コマンドラインで使える「ちょっとした」メール送信クライアントを作成する
特徴: 任意のファイルを添付できる(たぶん)、bash で動く
用例: テストスクリプトに組み込んで、実行結果のログをメールに添付して送信する
備考: bash のソケット機能を使用している、SMTP 認証は未対応
hi_saito さんが gawk でメール送信クライアントを作成されていました。(最下部リファレンスを参照) それに対抗(?)して、bash で作ったメール送信クライアントも晒し、、、いや、公開してみます。bash しか使えないような、やや特殊な環境で役に立つかも?(^ ^;;
#!/bin/bash ########################################################### # GLOBAL (default value) ########################################################### PROG_NAME=$(basename $0) MAIL_SERVER="localhost" SMTP_PORT=25 FROM="${USER}@${HOSTNAME}" RCPT_TO=() HEADER_TO= SUBJECT="This mail is sent by ${PROG_NAME}" BODY=$(mktemp /tmp/${PROG_NAME}_body.XXXXXXXXXXXX) ATTACH_FILE= DATE=$(date +%Y%m%d%H%M%S) LOG_FILE="/tmp/$(echo ${PROG_NAME} | sed "s/\.sh//").log" FD_NUM=5 DEBUG_MODE="off" # for error check LAST_FUNCNAME= NORMAL_END=0 UNKNOWN_OPTION=1 MISSING_ADDRESS=2 NOT_NUMERIC=3 INVALID_PORT=4 INVALID_ATTACH=5 UNKNOWN_SMTP_250REPLY=6 UNKNOWN_SMTP_ERROR=7 ########################################################### # Trap ########################################################### trap do_exit 0 1 2 3 13 15 ########################################################### # Run Before exit, post-processing ########################################################### do_exit() { [ -f ${BODY} ] && rm -f ${BODY} if [ -S /dev/fd/${FD_NUM} ]; then log "*** Closing socket." eval "exec ${FD_NUM}<&-" fi } ########################################################### # Main ########################################################### do_main() { rm -f ${LOG_FILE} log "* Starting ${PROG_NAME} ${FROM} on ${DATE}" get_args "$@" || return $? send_mail || return $? return ${NORMAL_END} } ########################################################### # Usage ########################################################### usage() { echo -n "usage: bash-socket_mail.sh -t" echo -n " <to_address1 to_address2 ...>" echo -n " [ -m <mail_server> ] [ -p <smtp_port> ]" echo -n " [ -f <from_address> ] [ -s <subject> ]" echo -n " [ -b <mail_body> ]" echo " [ -a <attach_files1 attach_file2 ...> ] [-v]" echo -n " : '-v' option is used for DEBUG" echo "(just some log is displayed)" echo "" } ########################################################### # Output to logfile ########################################################### log() { if [ ${DEBUG_MODE} = "off" ]; then echo "$*" >> ${LOG_FILE} else echo "$*" | tee -a ${LOG_FILE} fi } ########################################################### # Parse script option/argument ########################################################### get_args() { LAST_FUNCNAME=${FUNCNAME} local opt= addr= afile= local pflg="off" tflg="off" aflg="off" while getopts m:p:f:t:s:b:a:v opt do case ${opt} in m) MAIL_SERVER=${OPTARG} ;; p) SMTP_PORT=${OPTARG} pflg="on" ;; f) FROM=${OPTARG} ;; t) RCPT_TO=($(echo ${OPTARG} | sed "s/,*\s\s*,*/ /g")) HEADER_TO=$(echo ${RCPT_TO[@]} | sed "s/\s/, /g") tflg="on" ;; s) SUBJECT=${OPTARG} ;; b) echo -en "${OPTARG}" > ${BODY} ;; a) ATTACH_FILE=${OPTARG} aflg="on" ;; v) DEBUG_MODE="on" ;; *) return ${UNKNOWN_OPTION} ;; esac done # destination address check [ ${tflg} = "off" ] && return ${MISSING_ADDRESS} # port number check if [ ${pflg} = "on" ]; then [ -z $(echo ${SMTP_PORT} | egrep "^[0-9]+$") ] && \ return ${NOT_NUMERIC} [ ${SMTP_PORT} -ne 25 ] && \ [ ${SMTP_PORT} -lt 1024 -o ${SMTP_PORT} -gt 65535 ] && \ return ${INVALID_PORT} fi # attach file check if [ ${aflg} = "on" ]; then for afile in ${ATTACH_FILE} do [ ! -f ${afile} ] && return ${INVALID_ATTACH} done fi return ${NORMAL_END} } ########################################################### # check all error pattern ########################################################### check_error() { local result=$1 if [ ${result} != ${NORMAL_END} ]; then log "*** Error function : ${LAST_FUNCNAME}" fi case ${result} in ${NORMAL_END}) log "*** ${PROG_NAME} is completed." ;; ${UNKNOWN_OPTION}) log "unknown_option." ;; ${MISSING_ADDRESS}) log "missing destination mail address." ;; ${NOT_NUMERIC}) log "port number is not numeric." ;; ${INVALID_PORT}) log "port number should be between 1024 and 655535." ;; ${INVALID_ATTACH}) log "cannot find attach file." ;; ${UNKNOWN_SMTP_250REPLY}) log "unkown pattern getting 250 reply via SMTP." ;; ${UNKNOWN_SMTP_ERROR}) log "unkown error on SMTP connection." ;; *) log "unknown error." ;; esac [ ${result} = ${UNKNOWN_OPTION} ] || [ ${result} = ${MISSING_ADDRESS} ] || [ ${result} = ${INVALID_ATTACH} ] && usage return ${result} } ########################################################### # send mail using bash socket ########################################################### send_mail() { LAST_FUNCNAME=${FUNCNAME} local i=0 j=0 local line= local d_num=$((${#RCPT_TO[@]} + 2)) local q_num=$((${d_num} + 1)) # open socket to mail server eval "exec ${FD_NUM}<>/dev/tcp/${MAIL_SERVER}/${SMTP_PORT}" while read line; do # logging response from mail server log ${line} set - ${line} case $1 in 220) # <domain> Service ready log "*** Connected to ${MAIL_SERVER}:${SMTP_PORT}." echo "EHLO localhost" >&${FD_NUM} ;; 250) # Requested mail action okay, completed let i++ log "*** get OK reply from mail server." if [ ${i} = 1 ]; then log "*** Sending recipient address." log " - mail from : ${FROM}" echo -en "mail from: ${FROM}\r\n" >&${FD_NUM} elif [ ${i} -ge 2 -a ${i} -lt ${d_num} ]; then log "*** Sending destination address." log " - rcpt to : ${RCPT_TO[${j}]}" echo -en "rcpt to: ${RCPT_TO[${j}]}\r\n" >&${FD_NUM} let j++ elif [ ${i} = ${d_num} ]; then # data starting... now! log "*** Starting data transfer." echo -en "data\r\n" >&${FD_NUM} elif [ ${i} = ${q_num} ]; then # data successfully received log "*** Sending quit." echo -en "quit\r\n" >&${FD_NUM} else # we don't expect more than 3 250-OK responses. log "*** Sending rset and quit." echo -en ".\r\nrset\r\nquit\r\n" >&${FD_NUM} return ${UNKNOWN_SMTP_250REPLY} fi ;; 354) # Start mail input; end with <CRLF>.<CRLF> if [ -z "${ATTACH_FILE}" ]; then make_mail_data else make_mail_data_with_attach fi ;; [0-9]*-*) # followup lines, don't bother true ;; 221) # <domain> Service closing transmission channel log "*** Closing transmission channe." true ;; *) # whoops, something happened . log "*** Error sending mail" echo -en ".\r\nrset\r\nquit\r\n" >&${FD_NUM} return ${UNKNOWN_SMTP_ERROR} ;; esac done <&${FD_NUM} return ${NORMAL_END} } ########################################################### # send simple mail data ########################################################### make_mail_data() { LAST_FUNCNAME=${FUNCNAME} local header= local data_end=".\r\n" local ctype="$(file -ib ${BODY})" local cencoding="$(get_encoding_bit "${ctype}")" # make simple mail header header="From: ${FROM}\r\nTo: ${HEADER_TO}\r\n" header="${header}Subject: ${SUBJECT}\r\nContent-Type: ${ctype}\r\n" header="${header}Content-Transfer-Encoding: ${cencoding}\r\n\r\n" log "*** Sending mail header." echo -en "${header}" >&${FD_NUM} log "*** Sending mail body." cat ${BODY} >&${FD_NUM} echo -en "\r\n" >&${FD_NUM} echo -en "${data_end}" >&${FD_NUM} return ${NORMAL_END} } ########################################################### # send attached mail data ########################################################### make_mail_data_with_attach() { LAST_FUNCNAME=${FUNCNAME} local header= f= fname= local data_end=".\r\n" local boundary="------------${DATE}.$(head -c10 /dev/urandom | md5sum | head -c15)" local ctype="multipart/mixed;\r boundary=\"${boundary}\"" local mime_ver="1.0" local mime_ctype= local mime_disposition= local mime_encoding="base64" local mime_comment="This is a multi-part message in MIME format.\r\n" local body_ctype="$(file -ib ${BODY})" local body_cencoding="$(get_encoding_bit "${body_ctype}")" local body_header= # make MIME mail header header="From: ${FROM}\r\nTo: ${HEADER_TO}\r\nSubject: ${SUBJECT}\r\n" header="${header}MIME-Version: ${mime_ver}\r\nContent-Type: ${ctype}\r\n\r\n" # make body boundary body_header="--${boundary}\r\nContent-Type: ${body_ctype}\r\n" body_header="${body_header}Content-Transfer-Encoding: ${body_cencoding}\r\n\r\n" log "*** Sending mail header." echo -en "${header}" >&${FD_NUM} echo -en "${mime_comment}" >&${FD_NUM} echo -en "${body_header}" >&${FD_NUM} log "*** Sending mail body." cat ${BODY} >&${FD_NUM} echo -en "\r\n\r\n" >&${FD_NUM} log "*** Sending attach file." for f in ${ATTACH_FILE} do fname=$(basename ${f}) mime_ctype="$(file -ib ${f})\r name=\"${fname}\"" mime_disposition="inline;\r filename=\"${fname}\"" log " - file : ${f}" echo -en "--${boundary}\r\nContent-Type: ${mime_ctype}\r\n" >&${FD_NUM} echo -en "Content-Transfer-Encoding: ${mime_encoding}\r\n" >&${FD_NUM} echo -en "Content-Disposition: ${mime_disposition}\r\n\r\n" >&${FD_NUM} base64 ${f} >&${FD_NUM} echo -en "\r\n" >&${FD_NUM} done echo -en "--${boundary}\r\n" >&${FD_NUM} echo -en "${data_end}" >&${FD_NUM} return ${NORMAL_END} } ########################################################### # get encode 7bit or 8bit(simple check) ########################################################### get_encoding_bit() { echo "$1" | grep "charset=us-ascii" > /dev/null 2>&1 [ $? -eq 0 ] && echo "7bit" || echo "8bit" } ########################################################### # Run Main ########################################################### do_main "$@" || check_error $? exit $?
実行結果。
$ sh mailmail.sh -m localhost -p 25 -t xxx@gmail.com \ -f yyy@zzz -s send_test -b body_test \ -a ttt.txt -v 220 localhost ESMTP Postfix *** Connected to localhost:25. 250-localhost 250-PIPELINING 250-SIZE 10240000 250-VRFY 250-ETRN 250-AUTH PLAIN LOGIN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250 DSN *** get OK reply from mail server. *** Sending recipient address. - mail from : yyy@zzz 250 2.1.0 Ok *** get OK reply from mail server. *** Sending destination address. - rcpt to : xxx@gmail.com 250 2.1.5 Ok *** get OK reply from mail server. *** Starting data transfer. 354 End data with. *** Sending mail header. *** Sending mail body. *** Sending attach file. - file : ttt.txt 250 2.0.0 Ok: queued as 16E8552E4F5 *** get OK reply from mail server. *** Sending quit. 221 2.0.0 Bye *** Closing transmission channe. *** Closing socket.
リファレンス:
Bash socket programming with /dev/tcp
gawk で SMTP を使ってメールを送信する
python で SMTP 認証を行ってメールを送信する