diff --git a/conf/maildir-last-login.conf.sample b/conf/maildir-last-login.conf.sample new file mode 100644 index 0000000..6a8c5e3 --- /dev/null +++ b/conf/maildir-last-login.conf.sample @@ -0,0 +1,22 @@ +# ----------------------------------------------------------- +# - Parameter Settings for script 'maildir-last-change.conf'. +# ----------------------------------------------------------- + +# MAILBOX_BASE_DIR +# +# Base directory containing the all the domains within their +# mailboxes +# +# Defaults to: +# MAILBOX_BASE_DIR="/var/vmail" +# +#MAILBOX_BASE_DIR="/var/vmail" + +# DST_DIR +# +# Directory containing the created statistic files. +# +# Defaults to: +# DST_DIR="/var/MAIL-STATS/LAST-LOGIN" +# +#DST_DIR="/var/MAIL-STATS/LAST-LOGIN" diff --git a/maildir-last-login.sh b/maildir-last-login.sh new file mode 100755 index 0000000..dcc29f5 --- /dev/null +++ b/maildir-last-login.sh @@ -0,0 +1,414 @@ +#!/usr/bin/env bash + +export LC_ALL=en_US.utf8 + +script_name="$(basename $(realpath $0))" +working_dir="$(dirname $(realpath $0))" +conf_file="${working_dir}/conf/${script_name%%.*}.conf" + +LOCK_DIR="/tmp/dovecot_last_login.LOCK" +error_log="${LOCK_DIR}/error.log" + +_CACHE_FILE="${LOCK_DIR}/tmp_last_login" +_CACHE_FILE_UNKNOWN="${LOCK_DIR}/tmp_unknown" + + + + +# ------------- +# --- Some defaut values +# ------------- + +DEFAULT_MAILBOX_BASE_DIR="/var/vmail" +DEFAULT_DST_DIR="/var/MAIL-STATS/LAST-LOGIN" +DEFAULT_DOMAIN="*" + +DEFAULT_USER="*" + + + +# ------------- +# --- Some functions +# ------------- + + +usage() { + + + [[ -n "$1" ]] && error "$1" + + + [[ $terminal ]] && echo -e " +\033[1mUsage:\033[m + + $(basename $0) [-d ] + +\033[1mDescription\033[m + + Script determins the 'last login' time (pop/imap) for each mailbox of a domain. + If no domain was given at the command line, statistics is written for all mailboxes + of all available domains. + + Note: + this script expects a file '.las-log' at homedir of each mailbox. the timestamp of this + file is used as 'lost-login' time of the concerning mailbox. + + Configure a dovecot service to touch '~/.last-login' at each pop/imap login. See also: + \033[1mhttps://wiki.dovecot.org/PostLoginScripting\033[m + + +\033[1mOptions\033[m + + -d + The domain for which the 'last login' statistic is written. The default is to + write statistics for all available mail domains. + +\033[1mFiles\033[m + + $conf_file: Configuration file + +\033[1mExample:\033[m + + Write statistics for domain mail36.net + + $(basename $0) .. + +" + + clean_up 1 + +} + + +clean_up() { + + # Perform program exit housekeeping + rm -rf "$LOCK_DIR" + blank_line + exit $1 +} + +echononl(){ + if $terminal ; then + echo X\\c > /tmp/shprompt$$ + if [[ $(wc -c /tmp/shprompt$$ | awk '{print $1}') -eq 1 ]]; then + echo -e -n " $*\\c" 1>&2 + else + echo -e -n " $*" 1>&2 + fi + rm /tmp/shprompt$$ + fi +} + +fatal(){ + echo "" + if $terminal ; then + echo -e " [ \033[31m\033[1mError\033[m ]: $*" + echo "" + echo -e "\033[15G\033[31m\033[1mScript was interupted\033[m!" + else + echo " [ Fatal ]: $*" + echo "" + echo " Script was terminated...." + fi + echo "" + clean_up 1 +} + +error(){ + blank_line + if $terminal ; then + echo -e " [ \033[31m\033[1mError\033[m ]: $*" + else + echo " [ Error ]: $*" + fi + blank_line +} + +warn (){ + if $terminal ; then + blank_line + echo -e " [ \033[33m\033[1mWarn\033[m ] $*" + blank_line + fi +} + +info (){ + if $terminal ; then + blank_line + echo -e " [ \033[32m\033[1mInfo\033[m ] $*" + blank_line + fi +} + +echo_done() { + if $terminal ; then + echo -e "\033[75G[ \033[32mdone\033[m ]" + fi +} +echo_failed(){ + if $terminal ; then + echo -e "\033[75G[ \033[1;31mfailed\033[m ]" + fi +} +echo_skipped() { + if $terminal ; then + echo -e "\033[75G[ \033[37m\033[1mskipped\033[m ]" + fi +} +echo_wait(){ + if $terminal ; then + echo -en "\033[75G[ \033[5m\033[1m...\033[m ]" + fi +} + +blank_line() { + if $terminal ; then + echo "" + fi +} + +# Check if a given array (parameter 2) contains a given string (parameter 1) +# +containsElement () { + local e + for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done + return 1 +} + + +# ------------- +# - Job is already running? +# ------------- + +# - If job already runs, stop execution.. +# - +if mkdir "$LOCK_DIR" 2> /dev/null ; then + + ## - Remove lockdir when the script finishes, or when it receives a signal + trap clean_up SIGHUP SIGINT SIGTERM + +else + + datum="$(date +"%d.%m.%Y %H:%M")" + + msg="A previos instance of \"${script_name}\" seems already be running." + + blank_line + + if $terminal ; then + echo -e " [ \033[31m\033[1mError\033[m ]: $msg" + echo "" + echo -e "\033[15G\033[31m\033[1mScript was interupted\033[m!" + else + echo " [ Fatal ]: $msg" + echo "" + echo " Script was terminated...." + fi + echo "" + + exit 1 + +fi + + + +# ------------- +# --- Check some prerequisites +# ------------- + +# - Running in a terminal? +# - +if [[ -t 1 ]] ; then + terminal=true +else + terminal=false +fi + + +# ========== +# - Begin Main Script +# ========== + +# ---------- +# - Headline +# ---------- + +if $terminal ; then + echo "" + echo -e "\033[1m----------\033[m" + echo -e "\033[32m\033[1mRunning script \033[m\033[1m$script_name\033[32m .. \033[m" + echo -e "\033[1m----------\033[m" +fi + + +# ---------- +# - Read Configurations from file '$conf_file' +# ---------- + +if [[ -f "$conf_file" ]]; then + source "$conf_file" +else + warn "No configuration file '$conf_file' present.\n + Loading default values.." +fi + +[[ -n "${MAILBOX_BASE_DIR}" ]] || MAILBOX_BASE_DIR="${DEFAULT_MAILBOX_BASE_DIR}" +[[ -n "${DST_DIR}" ]] || DST_DIR="${DEFAULT_DST_DIR}" + + +# ---------- +# - Read commandline parameter +# ---------- + +while getopts d:h opt ; do + case $opt in + d) DOMAIN="$OPTARG" + ;; + h) usage + ;; + \?) usage ;; + esac +done + +[[ -n "${DOMAIN}" ]] || DOMAIN="*" +USER="*" + + +echo -n '' > $_CACHE_FILE +chmod 600 $_CACHE_FILE + +declare -a _domain_arr=() + + +blank_line + +echononl "Create Directory '${DST_DIR}'.." +if [[ ! -d "$DST_DIR" ]] ; then + mkdir -p "$DST_DIR" > $error_log 2>&1 + if [[ $? -eq 0 ]]; then + echo_done + else + echo_failed + error "$(cat "$error_log")" + fi +else + echo_skipped +fi + +echononl "Remove old statistic files .." +if [[ "$DOMAIN" = "*" ]] ; then + rm -f ${DST_DIR}/* > $error_log 2>&1 + if [[ $? -eq 0 ]]; then + echo_done + else + echo_failed + error "$(cat "$error_log")" + fi +else + rm -f "${DST_DIR}/${DOMAIN}/*" + if [[ $? -eq 0 ]]; then + echo_done + else + echo_failed + error "$(cat "$error_log")" + fi +fi + + +echononl "Determin last login time for mailboxes.." +echo_wait +for _dir in $(ls -d ${MAILBOX_BASE_DIR}/${DOMAIN}/*); do + + DOMAIN="$(basename $(dirname "$_dir"))" + USER="$(basename "$_dir")" + + if [[ ! -f "$_dir/.last_login" ]]; then + + echo "No login yet - ${USER}@${DOMAIN}" >> $_CACHE_FILE_UNKNOWN + + else + + LAST_LOGIN="$(stat "${_dir}/.last_login" | grep 'Change: ' | cut -d' ' -f2)" + echo "$LAST_LOGIN ${USER}@${DOMAIN}" >> $_CACHE_FILE + + fi + + if ! containsElement "$DOMAIN" "${_domain_arr[@]}" ; then + _domain_arr+=("$DOMAIN") + fi + +done; +echo_done + +echononl "Write down over all last-login statistic file .." +if [[ ${#_domain_arr[@]} -gt 1 ]]; then + cat $_CACHE_FILE | sort -n > "${DST_DIR}/00-all-domains.stat" 2> "$error_log" + if [[ $? -eq 0 ]]; then + echo_done + else + echo_failed + error "$(cat "$error_log")" + fi +else + echo_skipped +fi + +echononl "Write down over all nologin statistic file .." +if [[ ${#_domain_arr[@]} -gt 1 ]]; then + cat $_CACHE_FILE_UNKNOWN | sort -n > "${DST_DIR}/01-nologin.stat" 2> "$error_log" + if [[ $? -eq 0 ]]; then + echo_done + else + echo_failed + error "$(cat "$error_log")" + fi +else + echo_skipped +fi + +echononl "Write down statistic files per domain .." +echo_wait +_failed=false +for _domain in "${_domain_arr[@]}" ; do + if [[ ${#_domain_arr[@]} -eq 1 ]] ; then + if grep -q "@$_domain" "$_CACHE_FILE"; then + cat $_CACHE_FILE | sort -n > "${DST_DIR}/${_domain}.stat" 2>> "$error_log" + if [[ $? -ne 0 ]]; then + _failed=true + fi + fi + else + if grep -q "@$_domain" "${DST_DIR}/00-all-domains.stat" ; then + cat "${DST_DIR}/00-all-domains.stat" | grep "@$_domain" > "${DST_DIR}/${_domain}.stat" 2>> "$error_log" + if [[ $? -ne 0 ]]; then + _failed=true + fi + fi + fi + if [[ ${#_domain_arr[@]} -eq 1 ]] ; then + if grep -q "@$_domain" "$_CACHE_FILE_UNKNOWN"; then + cat $_CACHE_FILE_UNKNOWN | sort -n > "${DST_DIR}/${_domain}-nologin.stat" 2> "$error_log" + if [[ $? -ne 0 ]]; then + _failed=true + fi + fi + else + if grep -q "@$_domain" "${DST_DIR}/01-nologin.stat" ; then + cat "${DST_DIR}/01-nologin.stat" | grep "@$_domain" > "${DST_DIR}/${_domain}-nologin.stat" 2> "$error_log" + if [[ $? -ne 0 ]]; then + _failed=true + fi + fi + fi +done +if $_failed ; then + echo_failed + error "$(cat "$error_log")" +else + echo_done +fi + +info "Statistic file(s) stored at directory '${DST_DIR}/'" + +clean_up 0;