Cleaning Up Zimbra LDAP Ephemeral Data

Cleaning Up Zimbra LDAP Ephemeral Data

Large amounts of Zimbra LDAP ephemeral data can cause LDAP replica servers to fall out of sync with LDAP MMR servers, backups to fail and other issues.  Very large Zimbra installations often migrate Zimbra LDAP ephemeral data to an SSDB or Redis cluster to avoid these issues.  While Zimbra provides a migration tool to do this, such SSDB or Redis clusters are not covered by Zimbra Support. Further, if the cluster fails, every user is immediately logged out of Zimbra; no one can log back in until Redis or SSDB is back up and running.

mailboxd is supposed to remove CSRF and Auth tokens when they expire, but in our experience Zimbra doesn’t always do so, though it seems to do a better job of pruning expired zimbraCsrfTokenData entries than it does pruning expired zimbraAuthTokens entries.

We have found multiple smaller systems of even just a few hundred mailboxes with user accounts having thousands of expired zimbraAuthTokens entries (rarely have we found an excess of zimbraCsrfTokenData entries).

This blog post will help you understand what’s going on, and includes two bash scripts you can run: one to identify how big (or not) a problem you may have, and a second script to clean up expired zimbraAuthTokens entries from accounts identified by the first script having large numbers of such expired tokens.

Understanding zimbraAuthTokens and zimbraCsrfTokenData Creation

A new zimbraAuthTokens entry is added each time a user successfully authenticates to Zimbra via:

  • Web Client (Webmail) Login
  • Modern/Classic/Mobile web interface login
    • Each new browser session creates a new token
  • IMAP Authentication
    • Each IMAP connection that authenticates creates a new auth token – for example, when configuring Apple Mail with IMAP, multiple authentication events occur, each creating hundreds tokens on mailboxes with lots of folders
  • POP3 Authentication
    • Each successful POP3 login creates a token
  • SMTP Authentication
    • When users authenticate to send mail via SMTP (authenticated submission)
  • ActiveSync/EAS (Exchange ActiveSync)
    • Mobile device synchronization via ActiveSync protocol
  • CalDAV/CardDAV Authentication
    • Calendar and contact synchronization clients
  • Zimbra Connector for Outlook (ZCO)
  • EWS (Exchange Web Services)
    • Outlook for Mac and other EWS clients
  • API/SOAP Requests
    • Programmatic authentication via Zimbra’s SOAP API
    • Pre-authentication
    • Single sign-on integrations that create auth tokens

Key Points

  • Each successful authentication creates a new token, even if the user already has active sessions.
  • Tokens have a default lifetime of 2 days.
  • Tokens should be automatically cleaned up when they expire, but this cleanup mechanism can fail.
    IMAP clients in particular can create many tokens quickly – a single Mail.app configuration created multiple authentication events in rapid succession.

This explains why accounts with heavy IMAP usage (like users with multiple email clients or mobile devices) can accumulate hundreds or thousands of tokens if the automatic cleanup fails.

This article explains why having large numbers of expired tokens can cause issues, and lists several kinds of issues.

How Do I Know If I Have A Problem With Excessive zimbraAuthTokens Entries?

Simple!  Run the script below on any Zimbra mailstore, as the Zimbra user.  The script makes no changes to your system, runs “nicely” and generates a report.

 

#!/bin/bash
#
# Zimbra Auth Token Inventory Script
# This script scans all accounts and reports zimbraAuthTokens counts
#

set -e

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Step 1: Check that script is being run as zimbra user
if [ "$(whoami)" != "zimbra" ]; then
echo -e "${RED}ERROR: This script must be run as the 'zimbra' user.${NC}"
echo "Please run: su - zimbra -c '$0'"
exit 1
fi

echo -e "${GREEN}✓ Running as zimbra user${NC}"

# Step 2: Set nice priority for CPU and IO
# Renice the current process to run with low priority
renice -n 19 -p $$ > /dev/null 2>&1

# Set IO priority to idle class if ionice is available
if command -v ionice &> /dev/null; then
ionice -c 3 -p $$ > /dev/null 2>&1
echo -e "${GREEN}✓ Running with low CPU and IO priority${NC}"
else
echo -e "${YELLOW}⚠ ionice not available, running with low CPU priority only${NC}"
fi

# Generate timestamp for output file
DATE=$(date +%Y%m%d)
TIME=$(date +%H%M%S)
OUTPUT_FILE="/tmp/AuthTokenInventory-${DATE}_${TIME}.txt"
TEMP_FILE=$(mktemp)
ZMPROV_CMD_FILE=$(mktemp)
ZMPROV_OUTPUT_FILE=$(mktemp)

# Cleanup temp files on exit
trap "rm -f $TEMP_FILE $ZMPROV_CMD_FILE $ZMPROV_OUTPUT_FILE" EXIT

echo ""
echo -e "${BLUE}=====================================================${NC}"
echo -e "${BLUE} Zimbra Auth Token Inventory Script${NC}"
echo -e "${BLUE}=====================================================${NC}"
echo ""
echo "Output file: $OUTPUT_FILE"
echo ""

# Step 3: Get all accounts (excluding system accounts)
echo -e "${YELLOW}Fetching all accounts...${NC}"
ACCOUNTS=$(zmprov -l gaa | grep -v "spam.\|ham.\|virus-\|galsync")

if [ -z "$ACCOUNTS" ]; then
echo -e "${RED}ERROR: No accounts found${NC}"
exit 1
fi

ACCOUNT_COUNT=$(echo "$ACCOUNTS" | wc -l)
echo -e "${GREEN}✓ Found $ACCOUNT_COUNT accounts${NC}"
echo ""

# Step 4: Create bulk command file for zmprov
echo -e "${YELLOW}Creating bulk provisioning command file...${NC}"

while IFS= read -r ACCOUNT; do
echo "ga $ACCOUNT zimbraAuthTokens" >> "$ZMPROV_CMD_FILE"
done <<< "$ACCOUNTS" echo -e "${GREEN}✓ Command file created with $ACCOUNT_COUNT queries${NC}" echo "" # Execute bulk zmprov command echo -e "${YELLOW}Executing bulk account query...${NC}" echo "This may take a while for large deployments." echo "" zmprov -f "$ZMPROV_CMD_FILE" > "$ZMPROV_OUTPUT_FILE" 2>&1

echo -e "${GREEN}✓ Bulk query complete${NC}"
echo ""

# Parse the output and count tokens per account
echo -e "${YELLOW}Parsing results and counting tokens...${NC}"

# Use awk for more robust parsing
awk '
BEGIN {
account = ""
count = 0
}
/^prov> # name / {
# Save previous account if exists
if (account != "") {
print count "|" account
}
# Extract new account name (everything after "# name ")
sub(/^prov> # name /, "")
account = $0
count = 0
next
}
/^zimbraAuthTokens:/ {
count++
next
}
/^prov> $/ {
# Empty prov> line indicates end of account with no tokens
if (account != "") {
print count "|" account
account = ""
count = 0
}
next
}
END {
# Print last account if exists
if (account != "") {
print count "|" account
}
}
' "$ZMPROV_OUTPUT_FILE" > "$TEMP_FILE"

echo -e "${GREEN}✓ Parsing complete${NC}"
echo ""

# Step 5 & 6: Sort results and write output
echo -e "${YELLOW}Sorting results...${NC}"

# Write header to output file
{
echo "======================================================"
echo "Zimbra Auth Token Inventory Report"
echo "======================================================"
echo "Generated: $(date '+%Y-%m-%d %H:%M:%S')"
echo "Total Accounts Scanned: $ACCOUNT_COUNT"
echo "======================================================"
echo ""
printf "%-10s %s\n" "TOKENS" "ACCOUNT"
printf "%-10s %s\n" "----------" "----------------------------------------"
} > "$OUTPUT_FILE"

# Sort by token count (numeric, descending) and format output
sort -t'|' -k1 -nr "$TEMP_FILE" | while IFS='|' read -r COUNT ACCOUNT; do
printf "%-10s %s\n" "$COUNT" "$ACCOUNT"
done >> "$OUTPUT_FILE"

# Calculate cutoff timestamp (48 hours ago in milliseconds)
CURRENT_TIME_SEC=$(date +%s)
CUTOFF_TIME_MS=$(( (CURRENT_TIME_SEC - 172800) * 1000 )) # 172800 seconds = 48 hours

echo -e "${YELLOW}Analyzing token ages (48 hour expiration threshold)...${NC}"

# Create temp file for expired token counts
EXPIRED_TEMP=$(mktemp)
trap "rm -f $TEMP_FILE $ZMPROV_CMD_FILE $ZMPROV_OUTPUT_FILE $EXPIRED_TEMP" EXIT

# Parse tokens again to count expired ones per account
CURRENT_ACCOUNT=""
TOTAL_COUNT=0
EXPIRED_COUNT=0

while IFS= read -r line; do
# Check if this is an account name line
if [[ "$line" =~ ^prov\>\ \#\ name\ (.+)$ ]]; then
# Save previous account's counts if we have one
if [ -n "$CURRENT_ACCOUNT" ]; then
echo "$TOTAL_COUNT|$EXPIRED_COUNT|$CURRENT_ACCOUNT" >> "$EXPIRED_TEMP"
fi

# Start tracking new account
CURRENT_ACCOUNT="${BASH_REMATCH[1]}"
TOTAL_COUNT=0
EXPIRED_COUNT=0

# Check if this is a token line
elif [[ "$line" =~ ^zimbraAuthTokens:\ (.+)$ ]]; then
TOTAL_COUNT=$((TOTAL_COUNT + 1))
TOKEN="${BASH_REMATCH[1]}"
# Extract timestamp (2nd field)
TIMESTAMP=$(echo "$TOKEN" | cut -d'|' -f2)
if [ "$TIMESTAMP" -lt "$CUTOFF_TIME_MS" ]; then
EXPIRED_COUNT=$((EXPIRED_COUNT + 1))
fi
fi
done < "$ZMPROV_OUTPUT_FILE" # Don't forget the last account if [ -n "$CURRENT_ACCOUNT" ]; then echo "$TOTAL_COUNT|$EXPIRED_COUNT|$CURRENT_ACCOUNT" >> "$EXPIRED_TEMP"
fi

echo -e "${GREEN}✓ Age analysis complete${NC}"
echo ""

# Add summary statistics
echo "" >> "$OUTPUT_FILE"
echo "=====================================================" >> "$OUTPUT_FILE"
echo "Summary Statistics:" >> "$OUTPUT_FILE"
echo "=====================================================" >> "$OUTPUT_FILE"

# Calculate statistics
TOTAL_TOKENS=$(awk -F'|' '{sum+=$1} END {print sum}' "$TEMP_FILE")
TOTAL_EXPIRED=$(awk -F'|' '{sum+=$2} END {print sum}' "$EXPIRED_TEMP")
TOTAL_VALID=$((TOTAL_TOKENS - TOTAL_EXPIRED))
MAX_TOKENS=$(sort -t'|' -k1 -nr "$TEMP_FILE" | head -1 | cut -d'|' -f1)
ACCOUNTS_WITH_TOKENS=$(awk -F'|' '$1 > 0 {count++} END {print count}' "$TEMP_FILE")
ACCOUNTS_WITH_EXPIRED=$(awk -F'|' '$2 > 0 {count++} END {print count}' "$EXPIRED_TEMP")
ACCOUNTS_OVER_100=$(awk -F'|' '$1 > 100 {count++} END {print count}' "$TEMP_FILE")
ACCOUNTS_OVER_1000=$(awk -F'|' '$1 > 1000 {count++} END {print count}' "$TEMP_FILE")
ACCOUNTS_EXPIRED_OVER_100=$(awk -F'|' '$2 > 100 {count++} END {print count}' "$EXPIRED_TEMP")

{
echo "Cutoff timestamp: $CUTOFF_TIME_MS (48 hours ago)"
echo "Current time: $(date)"
echo ""
echo "Total Auth Tokens (all accounts): $TOTAL_TOKENS"
echo " Valid tokens (< 48 hours): $TOTAL_VALID" echo " Expired tokens (>= 48 hours): $TOTAL_EXPIRED"
echo ""
echo "Maximum Tokens (single account): $MAX_TOKENS"
echo "Accounts with tokens: $ACCOUNTS_WITH_TOKENS"
echo "Accounts with expired tokens: $ACCOUNTS_WITH_EXPIRED"
echo "Accounts with >100 total tokens: $ACCOUNTS_OVER_100"
echo "Accounts with >100 expired tokens: $ACCOUNTS_EXPIRED_OVER_100"
echo "Accounts with >1000 total tokens: $ACCOUNTS_OVER_1000"
echo ""
echo "Top 10 accounts by total token count:"
echo "--------------------------------------"
} >> "$OUTPUT_FILE"

sort -t'|' -k1 -nr "$TEMP_FILE" | head -10 | while IFS='|' read -r COUNT ACCOUNT; do
printf " %6s tokens: %s\n" "$COUNT" "$ACCOUNT"
done >> "$OUTPUT_FILE"

{
echo ""
echo "Top 10 accounts by expired token count:"
echo "----------------------------------------"
} >> "$OUTPUT_FILE"

sort -t'|' -k2 -nr "$EXPIRED_TEMP" | head -10 | while IFS='|' read -r TOTAL EXPIRED ACCOUNT; do
printf " %6s expired (%s total): %s\n" "$EXPIRED" "$TOTAL" "$ACCOUNT"
done >> "$OUTPUT_FILE"

echo "" >> "$OUTPUT_FILE"
echo "=====================================================" >> "$OUTPUT_FILE"
echo "End of Report" >> "$OUTPUT_FILE"
echo "=====================================================" >> "$OUTPUT_FILE"

# Display results
echo -e "${GREEN}✓ Results written to: $OUTPUT_FILE${NC}"
echo ""
echo -e "${BLUE}Summary:${NC}"
echo " Total Accounts: $ACCOUNT_COUNT"
echo " Accounts with tokens: $ACCOUNTS_WITH_TOKENS"
echo " Accounts with expired tokens: $ACCOUNTS_WITH_EXPIRED"
echo " Accounts with >100 total tokens: $ACCOUNTS_OVER_100"
echo " Accounts with >100 expired tokens: $ACCOUNTS_EXPIRED_OVER_100"
echo " Accounts with >1000 total tokens: $ACCOUNTS_OVER_1000"
echo ""
echo -e "${BLUE}Token Statistics:${NC}"
echo " Total tokens: $TOTAL_TOKENS"
echo " Valid tokens (< 48 hours): $TOTAL_VALID" echo " Expired tokens (>= 48 hours): $TOTAL_EXPIRED"
echo ""
echo -e "${BLUE}Top 5 accounts by expired token count:${NC}"
sort -t'|' -k2 -nr "$EXPIRED_TEMP" | head -5 | while IFS='|' read -r TOTAL EXPIRED ACCOUNT; do
echo " $EXPIRED expired ($TOTAL total): $ACCOUNT"
done
echo ""
echo -e "${GREEN}Full report available at: $OUTPUT_FILE${NC}"

 

OK, So I Have A Gazillion Expired Tokens; How Can I fix the problem?

Simple!  Run the script below on any Zimbra mailstore, as the Zimbra user.  Input the email address of a user identified by the report from the script above.  This script will re-identify expired auth token entries, and then ask you to confirm that you want the script to delete them.

 

#!/bin/bash
#
# Zimbra Auth Token Cleanup Script
# This script removes expired zimbraAuthTokens (older than 48 hours)
#

set -e

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Step 1: Check that script is being run as zimbra user
if [ "$(whoami)" != "zimbra" ]; then
echo -e "${RED}ERROR: This script must be run as the 'zimbra' user.${NC}"
echo "Please run: su - zimbra -c '$0'"
exit 1
fi

echo -e "${GREEN}✓ Running as zimbra user${NC}"

# Step 2: Set nice priority for CPU and IO
# Renice the current process to run with low priority
renice -n 19 -p $$ > /dev/null 2>&1

# Set IO priority to idle class if ionice is available
if command -v ionice &> /dev/null; then
ionice -c 3 -p $$ > /dev/null 2>&1
echo -e "${GREEN}✓ Running with low CPU and IO priority${NC}"
else
echo -e "${YELLOW}⚠ ionice not available, running with low CPU priority only${NC}"
fi

# Step 3: Ask for the account to scan
echo ""
read -p "Enter the email account to clean (user@domain format): " ACCOUNT

# Validate email format
if [[ ! "$ACCOUNT" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo -e "${RED}ERROR: Invalid email format${NC}"
exit 1
fi

# Check if account exists
if ! zmprov ga "$ACCOUNT" &>/dev/null; then
echo -e "${RED}ERROR: Account '$ACCOUNT' not found${NC}"
exit 1
fi

echo -e "${GREEN}✓ Account found: $ACCOUNT${NC}"
echo ""

# Step 4: Execute zmprov ga and get auth tokens
echo "Fetching auth tokens..."
TOKENS=$(zmprov ga "$ACCOUNT" zimbraAuthTokens | grep "^zimbraAuthTokens:" | sed 's/^zimbraAuthTokens: //')

# Count tokens
TOKEN_COUNT=$(echo "$TOKENS" | wc -l)

if [ -z "$TOKENS" ] || [ "$TOKEN_COUNT" -eq 0 ]; then
echo -e "${YELLOW}No auth tokens found for this account.${NC}"
exit 0
fi

echo -e "${YELLOW}Found $TOKEN_COUNT auth tokens${NC}"

# Step 5: Calculate cutoff timestamp (48 hours ago in milliseconds)
CURRENT_TIME_SEC=$(date +%s)
CUTOFF_TIME_MS=$(( (CURRENT_TIME_SEC - 172800) * 1000 )) # 172800 seconds = 48 hours

echo "Current time: $(date)"
echo "Cutoff time: $(date -d @$((CURRENT_TIME_SEC - 172800)) 2>/dev/null || date -r $((CURRENT_TIME_SEC - 172800)))"
echo "Cutoff timestamp (ms): $CUTOFF_TIME_MS"
echo ""

# Create temporary file to store valid (non-expired) tokens
TEMP_FILE=$(mktemp)
trap "rm -f $TEMP_FILE" EXIT

# Filter tokens - keep only those with timestamp >= cutoff
echo "Analyzing tokens..."
VALID_COUNT=0
EXPIRED_COUNT=0

# Show first few tokens for debugging
echo ""
echo "Sample of first 3 tokens with timestamps:"
echo "$TOKENS" | head -3 | while IFS= read -r line; do
TIMESTAMP=$(echo "$line" | cut -d'|' -f2)
TOKEN_DATE=$(date -d @$((TIMESTAMP / 1000)) 2>/dev/null || date -r $((TIMESTAMP / 1000)) 2>/dev/null || echo "Unable to parse")
echo " Timestamp: $TIMESTAMP => $TOKEN_DATE"
if [ "$TIMESTAMP" -ge "$CUTOFF_TIME_MS" ]; then
echo " Status: VALID (>= $CUTOFF_TIME_MS)"
else
echo " Status: EXPIRED (< $CUTOFF_TIME_MS)" fi done echo "" while IFS= read -r line; do # Extract timestamp (2nd field, separated by |) TIMESTAMP=$(echo "$line" | cut -d'|' -f2) # Compare timestamp with cutoff if [ "$TIMESTAMP" -ge "$CUTOFF_TIME_MS" ]; then echo "$line" >> "$TEMP_FILE"
VALID_COUNT=$((VALID_COUNT + 1))
else
EXPIRED_COUNT=$((EXPIRED_COUNT + 1))
fi
done <<< "$TOKENS"

echo -e "${GREEN}✓ Analysis complete${NC}"
echo " Valid tokens (< 48 hours old): $VALID_COUNT" echo " Expired tokens (>= 48 hours old): $EXPIRED_COUNT"
echo ""

# If no expired tokens, nothing to do
if [ "$EXPIRED_COUNT" -eq 0 ]; then
echo -e "${GREEN}No expired tokens found. All tokens are valid.${NC}"
exit 0
fi

# If all tokens are expired, warn user
if [ "$VALID_COUNT" -eq 0 ]; then
echo -e "${YELLOW}WARNING: All tokens are expired!${NC}"
echo "This will remove all expired tokens."
echo ""
fi

# Show sample of tokens to be kept
echo "Sample of tokens to be preserved (newest first):"
sort -t'|' -k2 -nr "$TEMP_FILE" | head -5 | while IFS='|' read -r token_id timestamp version; do
TOKEN_DATE=$(date -d @$((timestamp / 1000)) 2>/dev/null || date -r $((timestamp / 1000)))
echo " $TOKEN_DATE (timestamp: $timestamp)"
done
echo ""

# Confirm before proceeding
echo -e "${YELLOW}This will DELETE $EXPIRED_COUNT expired tokens and keep $VALID_COUNT valid tokens.${NC}"
read -p "Do you want to proceed? (yes/no): " CONFIRM

if [ "$CONFIRM" != "yes" ]; then
echo "Operation cancelled."
exit 0
fi

# Step 6 & 7: Replace tokens with valid ones only
echo ""
echo "Updating auth tokens..."

if [ "$VALID_COUNT" -eq 0 ]; then
# Remove all tokens by setting a single dummy token, then removing it
echo "Removing all expired tokens..."
# First, get one existing token to use as a template
SAMPLE_TOKEN=$(echo "$TOKENS" | head -1)

# Set to just one token
if ! zmprov ma "$ACCOUNT" zimbraAuthTokens "$SAMPLE_TOKEN"; then
echo -e "${RED}ERROR: Failed to set temporary token${NC}"
exit 1
fi

# Now remove that token
if ! zmprov ma "$ACCOUNT" -zimbraAuthTokens "$SAMPLE_TOKEN"; then
echo -e "${RED}ERROR: Failed to remove token${NC}"
exit 1
fi
else
# Read tokens into array
mapfile -t TOKEN_ARRAY < "$TEMP_FILE"

# First token: replace (ma) to clear all existing tokens
FIRST_TOKEN="${TOKEN_ARRAY[0]}"
echo "Setting first token (replacing all)..."
if ! zmprov ma "$ACCOUNT" zimbraAuthTokens "$FIRST_TOKEN"; then
echo -e "${RED}ERROR: Failed to set first token${NC}"
exit 1
fi

# Remaining tokens: append (+zimbraAuthTokens)
for i in $(seq 1 $((VALID_COUNT - 1))); do
TOKEN="${TOKEN_ARRAY[$i]}"
echo "Adding token $((i + 1))/$VALID_COUNT..."
if ! zmprov ma "$ACCOUNT" +zimbraAuthTokens "$TOKEN"; then
echo -e "${RED}ERROR: Failed to add token $((i + 1))${NC}"
exit 1
fi
done
fi

# Flush account cache
echo ""
echo "Flushing account cache..."
if ! zmprov fc account "$ACCOUNT"; then
echo -e "${YELLOW}WARNING: Failed to flush cache, but tokens were updated${NC}"
fi

echo ""
echo -e "${GREEN}✓ SUCCESS: Auth tokens cleaned up successfully!${NC}"
echo -e "${GREEN} Deleted: $EXPIRED_COUNT expired tokens${NC}"
echo -e "${GREEN} Kept: $VALID_COUNT valid tokens${NC}"
echo ""

# Show final count
FINAL_COUNT=$(zmprov ga "$ACCOUNT" zimbraAuthTokens | grep -c "^zimbraAuthTokens:" || true)
echo "Final token count: $FINAL_COUNT"

 

Am I Done?

Maybe not.  As explained at the end of the Github article, just deleting expired tokens does not reduce Free Pages.  You may need to dump and restore the LDAP databases to fix that issue.  This wiki article can show you how — but we STRONGLY recommend you open a Zimbra Support Case first if you feel that an LDAP export/import is justified.

If you need help with this, or any other Zimbra LDAP issue, just fill out this form and we’ll be back to you!

Go back

Your message has been sent

Warning
Warning
Warning
Warning

Warning.

 

Hope that helps,
L. Mark Stone
Mission Critical Email LLC
1 October 2025

The information provided in this blog is intended for informational and educational purposes only. The views expressed herein are those of Mr. Stone personally. The contents of this site are not intended as advice for any purpose and are subject to change without notice. Mission Critical Email makes no warranties of any kind regarding the accuracy or completeness of any information on this site, and we make no representations regarding whether such information is up-to-date or applicable to any particular situation. All copyrights are reserved by Mr. Stone. Any portion of the material on this site may be used for personal or educational purposes provided appropriate attribution is given to Mr. Stone and this blog.

 

Leave a Reply