# Copyright (C) 2019 Ian Kelling
# SPDX-License-Identifier: AGPL-3.0-or-later
+# Things I tend to forget. on MAIL_HOST, daemon runs with /etc/exim4/my.conf,
+# due to /etc/default/exim4 containing:
+# COMMONOPTIONS='-C /etc/exim4/my.conf'
+# UPEX4OPTS='-o /etc/exim4/my.conf'
+#
+# The non-daemon config
+# gets generated from this script calling update-exim4.conf -d /etc/myexim4
+# which has log path
+# log_file_path = /var/log/exim4/my%s
+#
+# On non bk|MAIL_HOST, the config and log file are all standard.
+#
+# eximbackup folder is /bu/md
+# it is cleaned up by mail-backup-clean, which is run by btrbk-run
+
+# shellcheck disable=SC2254 # makes for a lot of unneeded quotes
+
+
+# perusing through /el/mainlog without test messages:
+# &!testignore|jtuttle|
+#
+#&! testignore|jtuttle|eximbackup|/usr/sbin/exim4 -bpu
+
+# todo: this message seems to get dropped on the floor, it was due to a missing 2nd colon in
+# condition = ${if def:h_fdate:}
+# Figure out how to avoid this message being discarded.
+
+# 2023-09-12 01:41:43 [722371] 1qfw9f-0031v9-0S <= ian@iankelling.org U=iank P=local S=483 id=87cyyogd7t.fsf@iankelling.org T="iank2" from <ian@iankelling.org> for testignore@amnimal.ninja
+# 2023-09-12 01:41:43 [722373] 1qfw9f-0031v9-0S H=nn.b8.nz [10.173.8.2]: SMTP error from remote mail server after pipelined end of data: 451 Temporary local problem - please try later
+# 2023-09-12 01:41:43 [722372] 1qfw9f-0031v9-0S == testignore@amnimal.ninja R=smarthost T=remote_smtp_smarthost defer (-46) H=nn.b8.nz [10.173.8.2] DT=0s: SMTP error from remote mail server after pipelined end of data: 451 Temporary local problem - please try later
+
# todo: check new macro DKIM_TIMESTAMPS
# todo: check if REMOTE_SMTP_INTERFACE or REMOTE_SMTP_TRANSPORTS_HEADERS_REMOVE can simplify my or fsfs config
# * functions & constants
-pre="${0##*/}:"
+pre="${0##*/}:${SSH_CLIENT:+ $HOSTNAME:}"
m() { printf "$pre %s\n" "$*"; "$@"; }
e() { printf "$pre %s\n" "$*"; }
err() { printf "$pre %s\n" "$*" >&2; exit 1; }
done
}
sre() {
+ local enabled
for service; do
m systemctl restart $service
- m systemctl enable $service;
+ # Optimization for exim,
+ # is-enabled: 0m0.015s
+ # enable: 0m0.748s
+ # It is related to this message:
+ # exim4.service is not a native service, redirecting to systemd-sysv-install.
+ # Executing: /lib/systemd/systemd-sysv-install enable exim4
+ enabled=$(systemctl is-enabled $service 2>/dev/null ||:)
+ if [[ $enabled != enabled ]]; then
+ m systemctl enable $service
+ fi
done
}
mailhost() {
# * clamav
+# old file. remove when all hosts updated, 2023-09-11
+rm -fv /etc/exim4/conf.d/clamav_data_acl
+
m usermod -a -G Debian-exim clamav
u /etc/systemd/system/clamav-daemon.service.d/fix.conf <<EOF
# Aug 02 21:59:27 sy systemd[1]: wg-quick@wgmail.service: Failed with result 'exit-code'.
# Aug 02 21:59:27 sy systemd[1]: Failed to start WireGuard via wg-quick(8) for wgmail.
# Aug 02 21:59:47 sy systemd[1]: wg-quick@wgmail.service: Scheduled restart job, restart counter is at 1.
-# Aug 02 21:59:47 sy systemd[1]: Stopped WireGuard via wg-quick(8) for wgmail.
+# Aug 02 21:95:47 sy systemd[1]: Stopped WireGuard via wg-quick(8) for wgmail.
# Aug 02 21:59:47 sy systemd[1]: Starting WireGuard via wg-quick(8) for wgmail...
# Aug 02 21:59:47 sy wg-quick[3424]: wg-quick: `wgmail' already exists
# Aug 02 21:59:47 sy systemd[1]: wg-quick@wgmail.service: Main process exited, code=exited, status=1/FAILURE
u /etc/spamassassin/mylocal.cf <<'EOF'
# this is mylocal.cf because the normal local.cf has a bunch of upstream stuff i dont want to mess with
+
# /usr/share/doc/exim4-base/README.Debian.gz:
# SpamAssassin's default report should not be used in a add_header
# statement since it contains empty lines. (This triggers e.g. Amavis'
NICE="--nicelevel 15"
CRON=1
EOF
+
+case $HOSTNAME in
+ bk)
+ u /etc/spamassassin/my_thishost.cf <<'EOF'
+# note: these are duplicated in exim config
+# veth0/1 # bk bk_ip6
+internal_networks 10.173.8.1/32 10.173.8.2/32 85.119.83.50/32 2001:ba8:1f1:f0c9::2
+trusted_networks 10.173.8.1/32 10.173.8.2/32 85.119.83.50/32 2001:ba8:1f1:f0c9::2
+EOF
+
+ ;;
+ je)
+ u /etc/spamassassin/my_thishost.cf <<'EOF'
+# note: these are duplicated in exim config
+# veth0/1 # je je_ipv6
+internal_networks 10.173.8.1/32 10.173.8.2/32 85.119.82.128/32 2001:ba8:1f1:f09d::2/128
+trusted_networks 10.173.8.1/32 10.173.8.2/32 85.119.82.128/32 2001:ba8:1f1:f09d::2/128
+EOF
+ ;;
+ *)
+ u /etc/spamassassin/my_thishost.cf <<'EOF'
+# note: these are duplicated in exim config
+# veth0/1 # li li_ip6
+internal_networks 10.173.8.1/32 10.173.8.2/32 72.14.176.105/32 2600:3c00::f03c:91ff:fe6d:baf8/128
+trusted_networks 10.173.8.1/32 10.173.8.2/32 72.14.176.105/32 2600:3c00::f03c:91ff:fe6d:baf8/128
+EOF
+ ;;
+esac
+
##### end spamassassin config
domainlist local_hostnames = ! je.b8.nz : ! bk.b8.nz : *.b8.nz : b8.nz
+# note: most of these are duplicated in spamassassin config
hostlist iank_trusted = <; \
# veth0
10.173.8.1 ; \
# enable 587 in addition to the default 25, so that
# i can send mail where port 25 is firewalled by isp
-daemon_smtp_ports = 25 : 587
+daemon_smtp_ports = 25 : 587 : 10025
# default of 25, can get stuck when catching up on mail
smtp_accept_max = 400
smtp_accept_reserve = 100
# Rules that make receiving more liberal should be on backup hosts
# so that we dont reject mail accepted by MAIL_HOST
LOCAL_DENY_EXCEPTIONS_LOCAL_ACL_FILE = /etc/exim4/conf.d/local_deny_exceptions_acl
+
+acl_not_smtp = acl_check_not_smtp
EOF
if dpkg --compare-versions "$(dpkg-query -f='${Version}\n' --show exim4)" ge 4.94; then
EOF
fi
+cat >/etc/exim4/conf.d/main/30_local <<EOF
+freeze_tell =
+EOF
+
+
rm -fv /etc/exim4/rcpt_local_acl # old path
u /etc/exim4/conf.d/local_deny_exceptions_acl <<'EOF'
# dmarc_status = reject:quarantine
# add_header = Reply-to: dmarctest@iankelling.org
+# This allows us to delay sending an email until a specific time,
+# allowing us time to change our mind and also to appear to have
+# sent the message at a different time. In emacs copy the
+# automcatically date header add an f to make it fdate,
+# and then change the date to whenever you want to send it.
+# In the system-status script, I check once per minute
+# or more if it should be sent.
+
+warn
+ # fdate = future date.
+ condition = ${if def:h_fdate:}
+ remove_header = fdate:
+ remove_header = date:
+ add_header = date: $h_fdate
+ control = freeze
+EOF
+sed -i 's/^freeze_tell =.*/#\0/' /etc/exim4/conf.d/main/02_exim4-config_options
+
+u /etc/exim4/conf.d/acl/41_check_not_smtp <<'EOF'
+# todo: for non MAIL_HOST machines, i'd like
+# to send to the MAIL_HOST without freezing.
+# So, only do this if we are MAIL_HOST.
+
+acl_check_not_smtp:
+warn
+ # fdate = future date.
+ condition = ${if def:h_fdate:}
+ remove_header = fdate:
+ remove_header = date:
+ add_header = Date: $h_fdate
+ control = freeze
+accept
EOF
# # setup chgrp www-data in ./conflink
pi-nostart radicale
+ m usermod -a -G radicale iank
u /etc/systemd/system/radicale.service.d/override.conf <<EOF
[Unit]
### begin composer install
# https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md
- # cd $(mktemp -d)
- # sum="$(wget -q -O - https://composer.github.io/installer.sig)"
- # m php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
- # if [[ $sum != $(php -r "echo hash_file('sha384', 'composer-setup.php');") ]]; then
- # echo 'ERROR: Invalid composer installer checksum' >&2
- # rm -fv composer-setup.php
- # exit 1
- # fi
- # m php composer-setup.php --quiet
- # rm -fv composer-setup.php
- # m mv composer.phar /usr/local/bin
-
- # the above method gets composer2, carddav plugin at least doesnt work with that
- # yet, it was just released 10-24-2020.
- m cd /usr/local/bin
- m wget -nv -N https://getcomposer.org/composer-1.phar
- chmod +x composer-1.phar
+ cd /usr/local/bin
+ EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
+ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+ ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
+
+ if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
+ then
+ >&2 echo 'ERROR: Invalid installer checksum'
+ rm composer-setup.php
+ exit 1
+ fi
+
+ php composer-setup.php --quiet
+ rm composer-setup.php
+
+ # based on error when running composer
+ mkdir -p /var/www/.composer
+ chown www-data:www-data /var/www/.composer
+
### end composer install
rcdirs=(/usr/local/lib/rcexpertpath /usr/local/lib/rcninja)
- ncdirs=(/var/www/ncninja)
ncdirs=(/var/www/ncexpertpath /var/www/ncninja)
# point debian cronjob to our local install, preventing daily cron error
rcdir=${rcdirs[i]}
rcbase=${rcdir##*/}
ncdir=${ncdirs[i]}
- myncdir=/root/${ncdir##*/}
- mkdir -p $myncdir
# copied from debians cronjob
u /etc/cron.d/$rcbase <<EOF
verf=$rcdir/plugins/carddav/myversion
upgrade=false
install=false
- v=4.0.0
+ v=5.0.1
if [[ -e $verf ]]; then
if [[ $(cat $verf) != "$v" ]]; then
install=true
if $install; then
m rm -rf $rcdir/plugins/carddav
tmpd=$(mktemp -d)
- m wget -nv -O $tmpd/t.tgz https://github.com/blind-coder/rcmcarddav/releases/download/v$v/carddav-v$v.tgz
+ m wget -nv -O $tmpd/t.tgz https://github.com/blind-coder/rcmcarddav/releases/download/v$v/carddav-v$v.tar.gz
cd $rcdir/plugins
tar xzf $tmpd/t.tgz
rm -rf $tmpd
m chown -R www-data:www-data $rcdir/plugins/carddav
m cd $rcdir/plugins/carddav
if $upgrade; then
- m sudo -u www-data composer-1.phar update --no-dev
+ m sudo -u www-data composer.phar update --no-dev
else
- m sudo -u www-data composer-1.phar install --no-dev
+ m sudo -u www-data composer.phar install --no-dev
fi
m chown -R root:root $rcdir/plugins/carddav
echo $v >$verf
for ((i=0; i < ${#bkdomains[@]}; i++)); do
domain=${bkdomains[i]}
ncdir=${ncdirs[i]}
+ myncdir=/var/local/${ncdir##*/}
ncbase=${ncdir##*/}
+ mkdir -p $myncdir
m cd /var/www
if [[ ! -e $ncdir/index.php ]]; then
# if we wanted to only install a specific version, use something like
m touch $myncdir/done-install
fi
- # note, strange this happend where updater did not increment the version var,
- # mine was stuck on 20. I manually updated it.
m cd $ncdir/config
- if [[ ! -e $myncdir/config.php-orig ]]; then
- m cp -a config.php $myncdir/config.php-orig
- fi
- cat $myncdir/config.php-orig - >$myncdir/tmp.php <<EOF
+ # if we did this more than once, it would revert the
+ # version number to the original.
+ if [[ ! -e $myncdir/config.php-orig || ! -s config.php ]]; then
+ if [[ -s config.php ]]; then
+ m cp -a config.php $myncdir/config.php-orig
+ # keep the file so it keeps the same permissions.
+ truncate -s0 config.php
+ fi
+ cat $myncdir/config.php-orig - >$myncdir/tmp.php <<EOF
# https://docs.nextcloud.com/server/19/admin_manual/configuration_server/email_configuration.html
\$CONFIG["mail_smtpmode"] = "sendmail";
\$CONFIG["mail_smtphost"] = "127.0.0.1";
var_export(\$CONFIG);
fwrite(STDOUT, ";\n");
EOF
- e running php $myncdir/tmp.php
- # note: we leave it around place for debugging
- php $myncdir/tmp.php >config.php
+ e running php $myncdir/tmp.php
+ # note: we leave it around place for debugging
+ # shellcheck disable=SC2024 # intended
+ sudo -u www-data php $myncdir/tmp.php >config.php
+ fi
cd $ncdir
m sudo -u www-data php occ maintenance:update:htaccess
list=$(sudo -u www-data php $ncdir/occ --output=json_pretty app:list)
ncbase=$1
cd /var/www/$ncbase
# https://docs.nextcloud.com/server/22/admin_manual/maintenance/update.html?highlight=updater+phar
+# the docs claim this is all you need, which is not true.
+# You will go to the web ui and it will say that you need to click a button to update,
+# or that you can run occ upgrade
m php /var/www/$ncbase/updater/updater.phar -n
+# throw a sleep in just because who knows what else is undocumented
+sleep 5
+m php occ upgrade
EOFOUTER
chmod +x /usr/local/bin/ncup
# which will overwrite any existing file
u /etc/default/exim4 <<'EOF'
QUEUERUNNER='combined'
-QUEUEINTERVAL='30m'
+# note: this is duplicated in brc2, 10m here is -q10m there.
+QUEUEINTERVAL='10m'
COMMONOPTIONS='-C /etc/exim4/my.conf'
UPEX4OPTS='-o /etc/exim4/my.conf'
# i use epanic-clean for alerting if there are bad paniclog entries
# ** $MAIL_HOST|bk)
$MAIL_HOST|bk)
-
- # no clamav on je, it has 1.5g memory and clamav uses most of it
- u /etc/exim4/conf.d/clamav_data_acl <<'EOF'
-warn
-!hosts = +iank_trusted
-!authenticated = plain_server:login_server
-condition = ${if def:malware_name}
-remove_header = Subject:
-add_header = Subject: [Clamav warning: $malware_name] $h_subject
-log_message = heuristic malware warning: $malware_name
-EOF
-
- cat >>/etc/exim4/conf.d/main/000_local <<EOF
-# je.b8.nz will run out of memory with freshclam
-av_scanner = clamd:/var/run/clamav/clamd.ctl
+ cat >>/etc/exim4/update-exim4.conf.conf <<EOF
+dc_relay_nets='defaultnn.b8.nz'
EOF
- cat >> /etc/exim4/conf.d/data_local_acl <<'EOF'
-deny
- malware = */defer_ok
- !condition = ${if match {$malware_name}{\N^Heuristic\N}}
- message = This message was detected as possible malware ($malware_name).
-EOF
cat >/etc/exim4/conf.d/main/000_local-nn <<EOF
# MAIN_HARDCODE_PRIMARY_HOSTNAME might mess up the
debug_print = "R: smarthost for $local_part@$domain"
driver = manualroute
domains = ! +local_domains
-# send most mail through eggs, helps fsfs sender reputation.
+# comment senders to send most mail through eggs, helps fsfs sender reputation.
# uncomment and optionally move to 188 file to send through my own servers again
senders = *@gnu.org
transport = smarthost_dkim
# ** $MAIL_HOST)
$MAIL_HOST)
+
+
+ u /etc/exim4/conf.d/router/155_delay <<'EOF'
+By default, delay sending email by 30-40 minutes in case I
+change my mind.
+
+# Note, if we switch mail_host, the next queue run will
+# send the message to mail_host and the delay will be reset.
+# That is fine. I could probably set some header to track
+# the delay but it is not worth it.
+delay_iank:
+ driver = redirect
+ allow_defer
+ data = :defer:
+ # It hasnt been 30 minutes since we received the message.
+ # we can avoid delay by adding the header i: or putting the exim message id into a file,
+ # or pulling "all" into a file.
+ # note, true false at the end just for easier debugging when pasting into a exim -Mset ID -be.
+ condition = ${if and { \
+{< {$tod_epoch} {${eval10:$received_time + 60*30}}} \
+{!def:h_i:} \
+{!bool{${lookup{$message_exim_id}lsearch{/etc/exim4/no-delay-eximids}{true}}}} \
+{!bool{${lookup{all}lsearch{/etc/exim4/no-delay-eximids}{true}}}} \
+} {true}{false}}
+ headers_remove = <; i:
+ domains = ! +local_domains
+ # uncomment for testing delays to jtuttle
+ # local_parts = ! root : ! testignore : ! alerts : ! ian-pager : ! daylert
+ local_parts = ! root : ! testignore : ! alerts : ! jtuttle : ! ian-pager : ! daylert
+ ignore_target_hosts = ROUTER_DNSLOOKUP_IGNORE_TARGET_HOSTS
+EOF
+
u /etc/exim4/conf.d/router/195_dnslookup_vpn <<'EOF'
# copied from /etc/exim4/conf.d/router/200_exim4-config_primary, but
# use vpn transport. lower priority so it overrides the default route.
# note, to test this, i could temporarily allow testignore.
# alerts avoids potential mail loop. root is already
# redirected earlier, so that is just being overly cautious.
-local_parts = ! root : ! testignore : ! alerts
+local_parts = ! root : ! testignore : ! alerts : ! jtuttle : ! ian-pager : ! daylert
unseen = true
errors_to = alerts@iankelling.org
EOF
# man page: is used to build the local_domains list, together with "localhost"
# this is duplicated in a later router.
dc_other_hostnames='iankelling.org;zroe.org;r2e.iankelling.org;mx.iankelling.org;!je.b8.nz;!bk.b8.nz;*.b8.nz;b8.nz'
-dc_relay_nets='defaultnn.b8.nz'
EOF
## we use this host to monitor MAIL_HOST and host a mail server for someone
bk)
+ # No clamav on je, it has 1.5g memory and clamav uses most of it.
+ #
+ # No clamav on MAIL_HOST because it is just a waste of useful cpu
+ # time and memory when I'm running on an x200, and it takes 30
+ # seconds to shut down.
+
+ cat >>/etc/exim4/conf.d/main/000_local <<EOF
+# je.b8.nz will run out of memory with freshclam
+av_scanner = clamd:/var/run/clamav/clamd.ctl
+EOF
+
+ cat >> /etc/exim4/conf.d/data_local_acl <<'EOF'
+deny
+ malware = */defer_ok
+ !condition = ${if match {$malware_name}{\N^Heuristic\N}}
+ message = This message was detected as possible malware ($malware_name).
+
+warn
+ !hosts = +iank_trusted
+ !authenticated = *
+ condition = ${if def:malware_name}
+ remove_header = Subject:
+ add_header = Subject: [Clamav warning: $malware_name] $h_subject
+ log_message = heuristic malware warning: $malware_name
+
+warn
+ # fdate = future date. # tdate = temporary date.
+ condition = ${if def:h_fdate}
+ remove_header = fdate:
+ add_header = tdate:
+ control = freeze
+EOF
+
/a/exe/cedit nn /etc/hosts <<'EOF' || [[ $? == 1 ]]
10.173.8.2 nn.b8.nz
;;
# ** not MAIL_HOST|bk|je
*)
- # this one should be removed for all non mail hosts, but
+ echo|u /etc/exim4/conf.d/router/155_delay
+ # this one should be removed for all non mail_hosts. note
# bk and je never become mail_host
echo|u /etc/exim4/conf.d/router/195_dnslookup_vpn
echo|u /etc/exim4/conf.d/router/160_backup_redir
echo|u /etc/exim4/conf.d/router/161_backup_redir_nn
echo|u /etc/exim4/conf.d/router/185_sentarchive
echo|u /etc/exim4/conf.d/router/186_sentarchive_nn
+ # Note, in general we could submit to smarthosts on non MAIL_HOST.
+ # however, delayed mail makes this inconvenient, because I
+ # occasionally want to send an email from a non-MAIL_HOST and then
+ # turn off that computer or travel with it so it is disconnected.
+ # It is also probably easier to setup emacs to delay messages, but
+ # that would mean we need to keep emacs running, this is much
+ # nicer.
echo|u /etc/exim4/conf.d/router/188_exim4-config_smarthost
echo|u /etc/exim4/conf.d/router/190_exim4-config_fsfsmarthost
echo|u /etc/exim4/conf.d/rcpt_local_acl
echo|u /etc/exim4/conf.d/main/000_local-nn
- echo|u /etc/exim4/conf.d/clamav_data_acl
if $bhost_t; then
unseen
EOF
-
u /etc/myexim4/conf.d/router/160_backup_redir <<'EOF'
backup_redir:
driver = redirect
errors_to = alerts@iankelling.org
EOF
-
-
# for bk, we have a exim4in.service that will do this for us.
m update-exim4.conf -d /etc/myexim4
;;
m systemctl daemon-reload
fi
-# checking bhost_t is redundant, but could help us catch errors.
-if $bhost_t || [[ -e /etc/wireguard/wghole.conf ]]; then
- # todo: in mail-setup, we have a static list of backup hosts, not *y
- m systemctl --now enable wg-quick@wghole
+# optimization, this only needs to run once.
+if [[ ! -e /sys/class/net/wghole ]]; then
+ # checking bhost_t is redundant, but could help us catch errors.
+ if $bhost_t || [[ -e /etc/wireguard/wghole.conf ]]; then
+ # todo: in mail-setup, we have a static list of backup hosts, not *y
+ m systemctl --now enable wg-quick@wghole
+ fi
fi
-sysd-prom-fail-install epanicclean
-m systemctl --now enable epanicclean
+# optimization, this only needs to be run once
+if [[ ! -e /var/lib/prometheus/node-exporter/exim_paniclog.prom ]]; then
+ sysd-prom-fail-install epanicclean
+ m systemctl --now enable epanicclean
+fi
case $HOSTNAME in
je)
;;
esac
-m /a/bin/ds/mail-cert-cron -1
-sre mailcert.timer
+# optimization, this only needs to run once.
+if [[ ! -e /etc/exim4/fullchain.pem ]]; then
+ m /a/bin/ds/mail-cert-cron -1
+ m systemctl --now enable mailcert.timer
+fi
case $HOSTNAME in
$MAIL_HOST|bk)
else
m systemctl --now enable $vpnser
fi
+ ;;&
+ bk)
if ! systemctl is-active clamav-daemon >/dev/null; then
m systemctl --now enable clamav-daemon
out=$(rsync -aiSAX --chown=root:root --chmod=g-s /a/bin/ds/filesystem/etc/systemd/system/epanicclean.service /etc/systemd/system)
$MAIL_HOST|bk|je)
# start spamassassin/dovecot before exim.
sre dovecot spamassassin
- # need to wait a bit before restarting exim, else I
- # get a paniclog entry like: spam acl condition: all spamd servers failed
- sleep 3
+ # Wait a bit before restarting exim, else I get a paniclog entry
+ # like: spam acl condition: all spamd servers failed. But I'm tired
+ # of waiting. I'll deal with this some other way.
+ #
+ # sleep 3
m systemctl --now enable mailclean.timer
;;&
$MAIL_HOST)
test_to=${test_tos[0]}
for t in ${test_tos[@]:1}; do
+ if [[ $test_from == *@gnu.org && $t == *@gnu.org ]]; then
+ continue
+ fi
test_to+=", $t"
done
case $test_from in