2023-01-30 16:27:06 +03:00
package main
import (
"bufio"
"context"
"fmt"
"io"
"log"
"net"
"os"
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
"path/filepath"
2023-01-30 16:27:06 +03:00
"runtime/debug"
"sort"
"strconv"
"strings"
"time"
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
"github.com/mjl-/bstore"
2023-01-30 16:27:06 +03:00
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/message"
"github.com/mjl-/mox/metrics"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/queue"
"github.com/mjl-/mox/smtp"
"github.com/mjl-/mox/store"
)
// ctl represents a connection to the ctl unix domain socket of a running mox instance.
// ctl provides functions to read/write commands/responses/data streams.
type ctl struct {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
cmd string // Set for server-side of commands.
2023-01-30 16:27:06 +03:00
conn net . Conn
r * bufio . Reader // Set for first reader.
x any // If set, errors are handled by calling panic(x) instead of log.Fatal.
log * mlog . Log // If set, along with x, logging is done here.
}
// xctl opens a ctl connection.
func xctl ( ) * ctl {
p := mox . DataDirPath ( "ctl" )
conn , err := net . Dial ( "unix" , p )
if err != nil {
log . Fatalf ( "connecting to control socket at %q: %v" , p , err )
}
ctl := & ctl { conn : conn }
version := ctl . xread ( )
if version != "ctlv0" {
log . Fatalf ( "ctl protocol mismatch, got %q, expected ctlv0" , version )
}
return ctl
}
// Interpret msg as an error.
// If ctl.x is set, the string is also written to the ctl to be interpreted as error by the other party.
func ( c * ctl ) xerror ( msg string ) {
if c . x == nil {
log . Fatalln ( msg )
}
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
c . log . Debugx ( "ctl error" , fmt . Errorf ( "%s" , msg ) , mlog . Field ( "cmd" , c . cmd ) )
2023-01-30 16:27:06 +03:00
c . xwrite ( msg )
panic ( c . x )
}
// Check if err is not nil. If so, handle error through ctl.x or log.Fatal. If
// ctl.x is set, the error string is written to ctl, to be interpreted as an error
// by the command reading from ctl.
func ( c * ctl ) xcheck ( err error , msg string ) {
if err == nil {
return
}
if c . x == nil {
log . Fatalf ( "%s: %s" , msg , err )
}
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
c . log . Debugx ( msg , err , mlog . Field ( "cmd" , c . cmd ) )
2023-01-30 16:27:06 +03:00
fmt . Fprintf ( c . conn , "%s: %s\n" , msg , err )
panic ( c . x )
}
// Read a line and return it without trailing newline.
func ( c * ctl ) xread ( ) string {
if c . r == nil {
c . r = bufio . NewReader ( c . conn )
}
line , err := c . r . ReadString ( '\n' )
c . xcheck ( err , "read from ctl" )
return strings . TrimSuffix ( line , "\n" )
}
// Read a line. If not "ok", the string is interpreted as an error.
func ( c * ctl ) xreadok ( ) {
line := c . xread ( )
if line != "ok" {
c . xerror ( line )
}
}
// Write a string, typically a command or parameter.
func ( c * ctl ) xwrite ( text string ) {
_ , err := fmt . Fprintln ( c . conn , text )
c . xcheck ( err , "write" )
}
// Write "ok" to indicate success.
func ( c * ctl ) xwriteok ( ) {
c . xwrite ( "ok" )
}
// Copy data from a stream from ctl to dst.
func ( c * ctl ) xstreamto ( dst io . Writer ) {
_ , err := io . Copy ( dst , c . reader ( ) )
c . xcheck ( err , "reading message" )
}
// Copy data from src to a stream to ctl.
func ( c * ctl ) xstreamfrom ( src io . Reader ) {
w := c . writer ( )
_ , err := io . Copy ( w , src )
c . xcheck ( err , "copying" )
w . xclose ( )
}
// Writer returns an io.Writer for a data stream to ctl.
// When done writing, caller must call xclose to signal the end of the stream.
// Behaviour of "x" is copied from ctl.
func ( c * ctl ) writer ( ) * ctlwriter {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
return & ctlwriter { cmd : c . cmd , conn : c . conn , x : c . x , log : c . log }
2023-01-30 16:27:06 +03:00
}
// Reader returns an io.Reader for a data stream from ctl.
// Behaviour of "x" is copied from ctl.
func ( c * ctl ) reader ( ) * ctlreader {
if c . r == nil {
c . r = bufio . NewReader ( c . conn )
}
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
return & ctlreader { cmd : c . cmd , conn : c . conn , r : c . r , x : c . x , log : c . log }
2023-01-30 16:27:06 +03:00
}
/ *
Ctlwriter and ctlreader implement the writing and reading a data stream . They
implement the io . Writer and io . Reader interface . In the protocol below each
non - data message ends with a newline that is typically stripped when
interpreting .
Zero or more data transactions :
> "123" ( for data size ) or an error message
> data , 123 bytes
< "ok" or an error message
Followed by a end of stream indicated by zero data bytes message :
> "0"
* /
type ctlwriter struct {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
cmd string // Set for server-side of commands.
2023-01-30 16:27:06 +03:00
conn net . Conn // Ctl socket from which messages are read.
buf [ ] byte // Scratch buffer, for reading response.
x any // If not nil, errors in Write and xcheckf are handled with panic(x), otherwise with a log.Fatal.
log * mlog . Log
}
func ( s * ctlwriter ) Write ( buf [ ] byte ) ( int , error ) {
_ , err := fmt . Fprintf ( s . conn , "%d\n" , len ( buf ) )
s . xcheck ( err , "write count" )
_ , err = s . conn . Write ( buf )
s . xcheck ( err , "write data" )
if s . buf == nil {
s . buf = make ( [ ] byte , 512 )
}
n , err := s . conn . Read ( s . buf )
s . xcheck ( err , "reading response to write" )
line := strings . TrimSuffix ( string ( s . buf [ : n ] ) , "\n" )
if line != "ok" {
s . xerror ( line )
}
return len ( buf ) , nil
}
func ( s * ctlwriter ) xerror ( msg string ) {
if s . x == nil {
log . Fatalln ( msg )
} else {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
s . log . Debugx ( "error" , fmt . Errorf ( "%s" , msg ) , mlog . Field ( "cmd" , s . cmd ) )
2023-01-30 16:27:06 +03:00
panic ( s . x )
}
}
func ( s * ctlwriter ) xcheck ( err error , msg string ) {
if err == nil {
return
}
if s . x == nil {
log . Fatalf ( "%s: %s" , msg , err )
} else {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
s . log . Debugx ( msg , err , mlog . Field ( "cmd" , s . cmd ) )
2023-01-30 16:27:06 +03:00
panic ( s . x )
}
}
func ( s * ctlwriter ) xclose ( ) {
_ , err := fmt . Fprintf ( s . conn , "0\n" )
s . xcheck ( err , "write eof" )
}
type ctlreader struct {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
cmd string // Set for server-side of command.
2023-01-30 16:27:06 +03:00
conn net . Conn // For writing "ok" after reading.
r * bufio . Reader // Buffered ctl socket.
err error // If set, returned for each read. can also be io.EOF.
npending int // Number of bytes that can still be read until a new count line must be read.
x any // If set, errors are handled with panic(x) instead of log.Fatal.
log * mlog . Log // If x is set, logging goes to log.
}
func ( s * ctlreader ) Read ( buf [ ] byte ) ( N int , Err error ) {
if s . err != nil {
return 0 , s . err
}
if s . npending == 0 {
line , err := s . r . ReadString ( '\n' )
s . xcheck ( err , "reading count" )
line = strings . TrimSuffix ( line , "\n" )
n , err := strconv . ParseInt ( line , 10 , 32 )
if err != nil {
s . xerror ( line )
}
if n == 0 {
s . err = io . EOF
return 0 , s . err
}
s . npending = int ( n )
_ , err = fmt . Fprintln ( s . conn , "ok" )
s . xcheck ( err , "writing ok after reading" )
}
rn := len ( buf )
if rn > s . npending {
rn = s . npending
}
n , err := s . r . Read ( buf [ : rn ] )
s . xcheck ( err , "read from ctl" )
s . npending -= n
return n , err
}
func ( s * ctlreader ) xerror ( msg string ) {
if s . x == nil {
log . Fatalln ( msg )
} else {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
s . log . Debugx ( "error" , fmt . Errorf ( "%s" , msg ) , mlog . Field ( "cmd" , s . cmd ) )
2023-01-30 16:27:06 +03:00
panic ( s . x )
}
}
func ( s * ctlreader ) xcheck ( err error , msg string ) {
if err == nil {
return
}
if s . x == nil {
log . Fatalf ( "%s: %s" , msg , err )
} else {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
s . log . Debugx ( msg , err , mlog . Field ( "cmd" , s . cmd ) )
2023-01-30 16:27:06 +03:00
panic ( s . x )
}
}
// servectl handles requests on the unix domain socket "ctl", e.g. for graceful shutdown, local mail delivery.
func servectl ( ctx context . Context , log * mlog . Log , conn net . Conn , shutdown func ( ) ) {
log . Debug ( "ctl connection" )
var stop = struct { } { } // Sentinel value for panic and recover.
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
ctl := & ctl { conn : conn , x : stop , log : log }
2023-01-30 16:27:06 +03:00
defer func ( ) {
x := recover ( )
if x == nil || x == stop {
return
}
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
log . Error ( "servectl panic" , mlog . Field ( "err" , x ) , mlog . Field ( "cmd" , ctl . cmd ) )
2023-01-30 16:27:06 +03:00
debug . PrintStack ( )
metrics . PanicInc ( "ctl" )
} ( )
defer conn . Close ( )
ctl . xwrite ( "ctlv0" )
for {
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
servectlcmd ( ctx , log , ctl , shutdown )
2023-01-30 16:27:06 +03:00
}
}
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
func servectlcmd ( ctx context . Context , log * mlog . Log , ctl * ctl , shutdown func ( ) ) {
2023-01-30 16:27:06 +03:00
cmd := ctl . xread ( )
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
ctl . cmd = cmd
2023-01-30 16:27:06 +03:00
log . Info ( "ctl command" , mlog . Field ( "cmd" , cmd ) )
switch cmd {
case "stop" :
shutdown ( )
os . Exit ( 0 )
case "deliver" :
/ * The protocol , double quoted are literals .
> "deliver"
> address
< "ok"
> stream
< "ok"
* /
to := ctl . xread ( )
a , addr , err := store . OpenEmail ( to )
ctl . xcheck ( err , "lookup destination address" )
msgFile , err := store . CreateMessageTemp ( "ctl-deliver" )
ctl . xcheck ( err , "creating temporary message file" )
defer func ( ) {
if msgFile != nil {
2023-02-16 15:22:00 +03:00
err := os . Remove ( msgFile . Name ( ) )
log . Check ( err , "removing temporary message file" , mlog . Field ( "path" , msgFile . Name ( ) ) )
err = msgFile . Close ( )
log . Check ( err , "closing temporary message file" )
2023-01-30 16:27:06 +03:00
}
} ( )
mw := & message . Writer { Writer : msgFile }
ctl . xwriteok ( )
ctl . xstreamto ( mw )
err = msgFile . Sync ( )
ctl . xcheck ( err , "syncing message to storage" )
msgPrefix := [ ] byte { }
if ! mw . HaveHeaders {
msgPrefix = [ ] byte ( "\r\n\r\n" )
}
m := & store . Message {
Received : time . Now ( ) ,
Size : int64 ( len ( msgPrefix ) ) + mw . Size ,
MsgPrefix : msgPrefix ,
}
a . WithWLock ( func ( ) {
err := a . Deliver ( log , addr , m , msgFile , true )
ctl . xcheck ( err , "delivering message" )
log . Info ( "message delivered through ctl" , mlog . Field ( "to" , to ) )
} )
2023-02-16 15:22:00 +03:00
err = msgFile . Close ( )
log . Check ( err , "closing delivered message file" )
2023-01-30 16:27:06 +03:00
msgFile = nil
err = a . Close ( )
ctl . xcheck ( err , "closing account" )
ctl . xwriteok ( )
2023-02-06 13:31:46 +03:00
case "setaccountpassword" :
/ * protocol :
> "setaccountpassword"
> address
> password
< "ok" or error
* /
addr := ctl . xread ( )
pw := ctl . xread ( )
acc , _ , err := store . OpenEmail ( addr )
ctl . xcheck ( err , "open account" )
defer func ( ) {
if acc != nil {
2023-02-16 15:22:00 +03:00
err := acc . Close ( )
log . Check ( err , "closing account after setting password" )
2023-02-06 13:31:46 +03:00
}
} ( )
err = acc . SetPassword ( pw )
ctl . xcheck ( err , "setting password" )
err = acc . Close ( )
ctl . xcheck ( err , "closing account" )
acc = nil
ctl . xwriteok ( )
2023-01-30 16:27:06 +03:00
case "queue" :
/ * protocol :
> "queue"
< "ok"
< stream
* /
2023-05-22 15:40:36 +03:00
qmsgs , err := queue . List ( ctx )
2023-01-30 16:27:06 +03:00
ctl . xcheck ( err , "listing queue" )
ctl . xwriteok ( )
xw := ctl . writer ( )
fmt . Fprintln ( xw , "queue:" )
for _ , qm := range qmsgs {
var lastAttempt string
if qm . LastAttempt != nil {
lastAttempt = time . Since ( * qm . LastAttempt ) . Round ( time . Second ) . String ( )
}
2023-03-09 22:18:34 +03:00
fmt . Fprintf ( xw , "%5d %s from:%s to:%s next %s last %s error %q\n" , qm . ID , qm . Queued . Format ( time . RFC3339 ) , qm . Sender ( ) . LogString ( ) , qm . Recipient ( ) . LogString ( ) , - time . Since ( qm . NextAttempt ) . Round ( time . Second ) , lastAttempt , qm . LastError )
2023-01-30 16:27:06 +03:00
}
if len ( qmsgs ) == 0 {
fmt . Fprint ( xw , "(empty)\n" )
}
xw . xclose ( )
new feature: when delivering messages from the queue, make it possible to use a "transport"
the default transport is still just "direct delivery", where we connect to the
destination domain's MX servers.
other transports are:
- regular smtp without authentication, this is relaying to a smarthost.
- submission with authentication, e.g. to a third party email sending service.
- direct delivery, but with with connections going through a socks proxy. this
can be helpful if your ip is blocked, you need to get email out, and you have
another IP that isn't blocked.
keep in mind that for all of the above, appropriate SPF/DKIM settings have to
be configured. the "dnscheck" for a domain does a check for any SOCKS IP in the
SPF record. SPF for smtp/submission (ranges? includes?) and any DKIM
requirements cannot really be checked.
which transport is used can be configured through routes. routes can be set on
an account, a domain, or globally. the routes are evaluated in that order, with
the first match selecting the transport. these routes are evaluated for each
delivery attempt. common selection criteria are recipient domain and sender
domain, but also which delivery attempt this is. you could configured mox to
attempt sending through a 3rd party from the 4th attempt onwards.
routes and transports are optional. if no route matches, or an empty/zero
transport is selected, normal direct delivery is done.
we could already "submit" emails with 3rd party accounts with "sendmail". but
we now support more SASL authentication mechanisms with SMTP (not only PLAIN,
but also SCRAM-SHA-256, SCRAM-SHA-1 and CRAM-MD5), which sendmail now also
supports. sendmail will use the most secure mechanism supported by the server,
or the explicitly configured mechanism.
for issue #36 by dmikushin. also based on earlier discussion on hackernews.
2023-06-16 19:38:28 +03:00
case "queuekick" :
2023-01-30 16:27:06 +03:00
/ * protocol :
new feature: when delivering messages from the queue, make it possible to use a "transport"
the default transport is still just "direct delivery", where we connect to the
destination domain's MX servers.
other transports are:
- regular smtp without authentication, this is relaying to a smarthost.
- submission with authentication, e.g. to a third party email sending service.
- direct delivery, but with with connections going through a socks proxy. this
can be helpful if your ip is blocked, you need to get email out, and you have
another IP that isn't blocked.
keep in mind that for all of the above, appropriate SPF/DKIM settings have to
be configured. the "dnscheck" for a domain does a check for any SOCKS IP in the
SPF record. SPF for smtp/submission (ranges? includes?) and any DKIM
requirements cannot really be checked.
which transport is used can be configured through routes. routes can be set on
an account, a domain, or globally. the routes are evaluated in that order, with
the first match selecting the transport. these routes are evaluated for each
delivery attempt. common selection criteria are recipient domain and sender
domain, but also which delivery attempt this is. you could configured mox to
attempt sending through a 3rd party from the 4th attempt onwards.
routes and transports are optional. if no route matches, or an empty/zero
transport is selected, normal direct delivery is done.
we could already "submit" emails with 3rd party accounts with "sendmail". but
we now support more SASL authentication mechanisms with SMTP (not only PLAIN,
but also SCRAM-SHA-256, SCRAM-SHA-1 and CRAM-MD5), which sendmail now also
supports. sendmail will use the most secure mechanism supported by the server,
or the explicitly configured mechanism.
for issue #36 by dmikushin. also based on earlier discussion on hackernews.
2023-06-16 19:38:28 +03:00
> "queuekick"
2023-01-30 16:27:06 +03:00
> id
> todomain
> recipient
new feature: when delivering messages from the queue, make it possible to use a "transport"
the default transport is still just "direct delivery", where we connect to the
destination domain's MX servers.
other transports are:
- regular smtp without authentication, this is relaying to a smarthost.
- submission with authentication, e.g. to a third party email sending service.
- direct delivery, but with with connections going through a socks proxy. this
can be helpful if your ip is blocked, you need to get email out, and you have
another IP that isn't blocked.
keep in mind that for all of the above, appropriate SPF/DKIM settings have to
be configured. the "dnscheck" for a domain does a check for any SOCKS IP in the
SPF record. SPF for smtp/submission (ranges? includes?) and any DKIM
requirements cannot really be checked.
which transport is used can be configured through routes. routes can be set on
an account, a domain, or globally. the routes are evaluated in that order, with
the first match selecting the transport. these routes are evaluated for each
delivery attempt. common selection criteria are recipient domain and sender
domain, but also which delivery attempt this is. you could configured mox to
attempt sending through a 3rd party from the 4th attempt onwards.
routes and transports are optional. if no route matches, or an empty/zero
transport is selected, normal direct delivery is done.
we could already "submit" emails with 3rd party accounts with "sendmail". but
we now support more SASL authentication mechanisms with SMTP (not only PLAIN,
but also SCRAM-SHA-256, SCRAM-SHA-1 and CRAM-MD5), which sendmail now also
supports. sendmail will use the most secure mechanism supported by the server,
or the explicitly configured mechanism.
for issue #36 by dmikushin. also based on earlier discussion on hackernews.
2023-06-16 19:38:28 +03:00
> transport // if empty, transport is left unchanged; in future, we may want to differtiate between "leave unchanged" and "set to empty string".
2023-01-30 16:27:06 +03:00
< count
< "ok" or error
* /
idstr := ctl . xread ( )
todomain := ctl . xread ( )
recipient := ctl . xread ( )
new feature: when delivering messages from the queue, make it possible to use a "transport"
the default transport is still just "direct delivery", where we connect to the
destination domain's MX servers.
other transports are:
- regular smtp without authentication, this is relaying to a smarthost.
- submission with authentication, e.g. to a third party email sending service.
- direct delivery, but with with connections going through a socks proxy. this
can be helpful if your ip is blocked, you need to get email out, and you have
another IP that isn't blocked.
keep in mind that for all of the above, appropriate SPF/DKIM settings have to
be configured. the "dnscheck" for a domain does a check for any SOCKS IP in the
SPF record. SPF for smtp/submission (ranges? includes?) and any DKIM
requirements cannot really be checked.
which transport is used can be configured through routes. routes can be set on
an account, a domain, or globally. the routes are evaluated in that order, with
the first match selecting the transport. these routes are evaluated for each
delivery attempt. common selection criteria are recipient domain and sender
domain, but also which delivery attempt this is. you could configured mox to
attempt sending through a 3rd party from the 4th attempt onwards.
routes and transports are optional. if no route matches, or an empty/zero
transport is selected, normal direct delivery is done.
we could already "submit" emails with 3rd party accounts with "sendmail". but
we now support more SASL authentication mechanisms with SMTP (not only PLAIN,
but also SCRAM-SHA-256, SCRAM-SHA-1 and CRAM-MD5), which sendmail now also
supports. sendmail will use the most secure mechanism supported by the server,
or the explicitly configured mechanism.
for issue #36 by dmikushin. also based on earlier discussion on hackernews.
2023-06-16 19:38:28 +03:00
transport := ctl . xread ( )
2023-01-30 16:27:06 +03:00
id , err := strconv . ParseInt ( idstr , 10 , 64 )
if err != nil {
ctl . xwrite ( "0" )
ctl . xcheck ( err , "parsing id" )
}
new feature: when delivering messages from the queue, make it possible to use a "transport"
the default transport is still just "direct delivery", where we connect to the
destination domain's MX servers.
other transports are:
- regular smtp without authentication, this is relaying to a smarthost.
- submission with authentication, e.g. to a third party email sending service.
- direct delivery, but with with connections going through a socks proxy. this
can be helpful if your ip is blocked, you need to get email out, and you have
another IP that isn't blocked.
keep in mind that for all of the above, appropriate SPF/DKIM settings have to
be configured. the "dnscheck" for a domain does a check for any SOCKS IP in the
SPF record. SPF for smtp/submission (ranges? includes?) and any DKIM
requirements cannot really be checked.
which transport is used can be configured through routes. routes can be set on
an account, a domain, or globally. the routes are evaluated in that order, with
the first match selecting the transport. these routes are evaluated for each
delivery attempt. common selection criteria are recipient domain and sender
domain, but also which delivery attempt this is. you could configured mox to
attempt sending through a 3rd party from the 4th attempt onwards.
routes and transports are optional. if no route matches, or an empty/zero
transport is selected, normal direct delivery is done.
we could already "submit" emails with 3rd party accounts with "sendmail". but
we now support more SASL authentication mechanisms with SMTP (not only PLAIN,
but also SCRAM-SHA-256, SCRAM-SHA-1 and CRAM-MD5), which sendmail now also
supports. sendmail will use the most secure mechanism supported by the server,
or the explicitly configured mechanism.
for issue #36 by dmikushin. also based on earlier discussion on hackernews.
2023-06-16 19:38:28 +03:00
var xtransport * string
if transport != "" {
xtransport = & transport
2023-01-30 16:27:06 +03:00
}
new feature: when delivering messages from the queue, make it possible to use a "transport"
the default transport is still just "direct delivery", where we connect to the
destination domain's MX servers.
other transports are:
- regular smtp without authentication, this is relaying to a smarthost.
- submission with authentication, e.g. to a third party email sending service.
- direct delivery, but with with connections going through a socks proxy. this
can be helpful if your ip is blocked, you need to get email out, and you have
another IP that isn't blocked.
keep in mind that for all of the above, appropriate SPF/DKIM settings have to
be configured. the "dnscheck" for a domain does a check for any SOCKS IP in the
SPF record. SPF for smtp/submission (ranges? includes?) and any DKIM
requirements cannot really be checked.
which transport is used can be configured through routes. routes can be set on
an account, a domain, or globally. the routes are evaluated in that order, with
the first match selecting the transport. these routes are evaluated for each
delivery attempt. common selection criteria are recipient domain and sender
domain, but also which delivery attempt this is. you could configured mox to
attempt sending through a 3rd party from the 4th attempt onwards.
routes and transports are optional. if no route matches, or an empty/zero
transport is selected, normal direct delivery is done.
we could already "submit" emails with 3rd party accounts with "sendmail". but
we now support more SASL authentication mechanisms with SMTP (not only PLAIN,
but also SCRAM-SHA-256, SCRAM-SHA-1 and CRAM-MD5), which sendmail now also
supports. sendmail will use the most secure mechanism supported by the server,
or the explicitly configured mechanism.
for issue #36 by dmikushin. also based on earlier discussion on hackernews.
2023-06-16 19:38:28 +03:00
count , err := queue . Kick ( ctx , id , todomain , recipient , xtransport )
ctl . xcheck ( err , "kicking queue" )
ctl . xwrite ( fmt . Sprintf ( "%d" , count ) )
ctl . xwriteok ( )
case "queuedrop" :
/ * protocol :
> "queuedrop"
> id
> todomain
> recipient
< count
< "ok" or error
* /
idstr := ctl . xread ( )
todomain := ctl . xread ( )
recipient := ctl . xread ( )
id , err := strconv . ParseInt ( idstr , 10 , 64 )
if err != nil {
ctl . xwrite ( "0" )
ctl . xcheck ( err , "parsing id" )
}
count , err := queue . Drop ( ctx , id , todomain , recipient )
ctl . xcheck ( err , "dropping messages from queue" )
2023-01-30 16:27:06 +03:00
ctl . xwrite ( fmt . Sprintf ( "%d" , count ) )
ctl . xwriteok ( )
case "queuedump" :
/ * protocol :
> "queuedump"
> id
< "ok" or error
< stream
* /
idstr := ctl . xread ( )
id , err := strconv . ParseInt ( idstr , 10 , 64 )
if err != nil {
ctl . xcheck ( err , "parsing id" )
}
2023-05-22 15:40:36 +03:00
mr , err := queue . OpenMessage ( ctx , id )
2023-01-30 16:27:06 +03:00
ctl . xcheck ( err , "opening message" )
2023-02-16 15:22:00 +03:00
defer func ( ) {
err := mr . Close ( )
log . Check ( err , "closing message from queue" )
} ( )
2023-01-30 16:27:06 +03:00
ctl . xwriteok ( )
ctl . xstreamfrom ( mr )
case "importmaildir" , "importmbox" :
mbox := cmd == "importmbox"
2023-05-22 15:40:36 +03:00
importctl ( ctx , ctl , mbox )
2023-01-30 16:27:06 +03:00
case "domainadd" :
/ * protocol :
> "domainadd"
> domain
> account
> localpart
< "ok" or error
* /
domain := ctl . xread ( )
account := ctl . xread ( )
localpart := ctl . xread ( )
d , err := dns . ParseDomain ( domain )
ctl . xcheck ( err , "parsing domain" )
err = mox . DomainAdd ( ctx , d , account , smtp . Localpart ( localpart ) )
ctl . xcheck ( err , "adding domain" )
ctl . xwriteok ( )
case "domainrm" :
/ * protocol :
> "domainrm"
> domain
< "ok" or error
* /
domain := ctl . xread ( )
d , err := dns . ParseDomain ( domain )
ctl . xcheck ( err , "parsing domain" )
err = mox . DomainRemove ( ctx , d )
ctl . xcheck ( err , "removing domain" )
ctl . xwriteok ( )
case "accountadd" :
/ * protocol :
> "accountadd"
> account
> address
< "ok" or error
* /
account := ctl . xread ( )
address := ctl . xread ( )
err := mox . AccountAdd ( ctx , account , address )
ctl . xcheck ( err , "adding account" )
ctl . xwriteok ( )
case "accountrm" :
/ * protocol :
> "accountrm"
> account
< "ok" or error
* /
account := ctl . xread ( )
err := mox . AccountRemove ( ctx , account )
ctl . xcheck ( err , "removing account" )
ctl . xwriteok ( )
case "addressadd" :
/ * protocol :
> "addressadd"
> address
> account
< "ok" or error
* /
address := ctl . xread ( )
account := ctl . xread ( )
err := mox . AddressAdd ( ctx , address , account )
ctl . xcheck ( err , "adding address" )
ctl . xwriteok ( )
case "addressrm" :
/ * protocol :
> "addressrm"
> address
< "ok" or error
* /
address := ctl . xread ( )
err := mox . AddressRemove ( ctx , address )
ctl . xcheck ( err , "removing address" )
ctl . xwriteok ( )
case "loglevels" :
/ * protocol :
> "loglevels"
< "ok"
< stream
* /
ctl . xwriteok ( )
l := mox . Conf . LogLevels ( )
keys := [ ] string { }
for k := range l {
keys = append ( keys , k )
}
sort . Slice ( keys , func ( i , j int ) bool {
return keys [ i ] < keys [ j ]
} )
s := ""
for _ , k := range keys {
ks := k
if ks == "" {
ks = "(default)"
}
s += ks + ": " + mlog . LevelStrings [ l [ k ] ] + "\n"
}
ctl . xstreamfrom ( strings . NewReader ( s ) )
case "setloglevels" :
/ * protocol :
> "setloglevels"
> pkg
2023-02-06 17:17:46 +03:00
> level ( if empty , log level for pkg will be unset )
2023-01-30 16:27:06 +03:00
< "ok" or error
* /
pkg := ctl . xread ( )
levelstr := ctl . xread ( )
2023-02-06 17:17:46 +03:00
if levelstr == "" {
mox . Conf . LogLevelRemove ( pkg )
} else {
level , ok := mlog . Levels [ levelstr ]
if ! ok {
ctl . xerror ( "bad level" )
}
mox . Conf . LogLevelSet ( pkg , level )
2023-01-30 16:27:06 +03:00
}
ctl . xwriteok ( )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
case "retrain" :
/ * protocol :
> "retrain"
> account
< "ok" or error
* /
account := ctl . xread ( )
acc , err := store . OpenAccount ( account )
ctl . xcheck ( err , "open account" )
acc . WithWLock ( func ( ) {
conf , _ := acc . Conf ( )
if conf . JunkFilter == nil {
ctl . xcheck ( store . ErrNoJunkFilter , "looking for junk filter" )
}
// Remove existing junk filter files.
basePath := mox . DataDirPath ( "accounts" )
dbPath := filepath . Join ( basePath , acc . Name , "junkfilter.db" )
bloomPath := filepath . Join ( basePath , acc . Name , "junkfilter.bloom" )
2023-02-16 15:22:00 +03:00
err := os . Remove ( dbPath )
log . Check ( err , "removing old junkfilter database file" , mlog . Field ( "path" , dbPath ) )
err = os . Remove ( bloomPath )
log . Check ( err , "removing old junkfilter bloom filter file" , mlog . Field ( "path" , bloomPath ) )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
// Open junk filter, this creates new files.
2023-05-22 15:40:36 +03:00
jf , _ , err := acc . OpenJunkFilter ( ctx , ctl . log )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
ctl . xcheck ( err , "open new junk filter" )
defer func ( ) {
if jf == nil {
return
}
2023-02-16 15:22:00 +03:00
err := jf . Close ( )
log . Check ( err , "closing junk filter during cleanup" )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
} ( )
// Read through messages with junk or nonjunk flag set, and train them.
var total , trained int
2023-05-22 15:40:36 +03:00
q := bstore . QueryDB [ store . Message ] ( ctx , acc . DB )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
err = q . ForEach ( func ( m store . Message ) error {
total ++
2023-05-22 15:40:36 +03:00
ok , err := acc . TrainMessage ( ctx , ctl . log , jf , m )
improve training of junk filter
before, we used heuristics to decide when to train/untrain a message as junk or
nonjunk: the message had to be seen, be in certain mailboxes. then if a message
was marked as junk, it was junk. and otherwise it was nonjunk. this wasn't good
enough: you may want to keep some messages around as neither junk or nonjunk.
and that wasn't possible.
ideally, we would just look at the imap $Junk and $NotJunk flags. the problem
is that mail clients don't set these flags, or don't make it easy. thunderbird
can set the flags based on its own bayesian filter. it has a shortcut for
marking Junk and moving it to the junk folder (good), but the counterpart of
notjunk only marks a message as notjunk without showing in the UI that it was
marked as notjunk. there is also no "move and mark as notjunk" mechanism. e.g.
"archive" does not mark a message as notjunk. ios mail and mutt don't appear to
have any way to see or change the $Junk and $NotJunk flags.
what email clients do have is the ability to move messages to other
mailboxes/folders. so mox now has a mechanism that allows you to configure
mailboxes that automatically set $Junk or $NotJunk (or clear both) when a
message is moved/copied/delivered to that folder. e.g. a mailbox called junk or
spam or rejects marks its messags as junk. inbox, postmaster, dmarc, tlsrpt,
neutral* mark their messages as neither junk or notjunk. other folders mark
their messages as notjunk. e.g. list/*, archive. this functionality is
optional, but enabled with the quickstart and for new accounts.
also, mox now keeps track of the previous training of a message and will only
untrain/train if needed. before, there probably have been duplicate or missing
(un)trainings.
this also includes a new subcommand "retrain" to recreate the junkfilter for an
account. you should run it after updating to this version. and you should
probably also modify your account config to include the AutomaticJunkFlags.
2023-02-12 01:00:12 +03:00
if ok {
trained ++
}
return err
} )
ctl . xcheck ( err , "training messages" )
ctl . log . Info ( "retrained messages" , mlog . Field ( "total" , total ) , mlog . Field ( "trained" , trained ) )
// Close junk filter, marking success.
err = jf . Close ( )
jf = nil
ctl . xcheck ( err , "closing junk filter" )
} )
ctl . xwriteok ( )
add a "backup" subcommand to make consistent backups, and a "verifydata" subcommand to verify a backup before restoring, and add tests for future upgrades
the backup command will make consistent snapshots of all the database files. i
had been copying the db files before, and it usually works. but if the file is
modified during the backup, it is inconsistent and is likely to generate errors
when reading (can be at any moment in the future, when reading some db page).
"mox backup" opens the database file and writes out a copy in a transaction.
it also duplicates the message files.
before doing a restore, you could run "mox verifydata" on the to-be-restored
"data" directory. it check the database files, and compares the message files
with the database.
the new "gentestdata" subcommand generates a basic "data" directory, with a
queue and a few accounts. we will use it in the future along with "verifydata"
to test upgrades from old version to the latest version. both when going to the
next version, and when skipping several versions. the script test-upgrades.sh
executes these tests and doesn't do anything at the moment, because no releases
have this subcommand yet.
inspired by a failed upgrade attempt of a pre-release version.
2023-05-26 20:26:51 +03:00
case "backup" :
backupctl ( ctx , ctl )
2023-01-30 16:27:06 +03:00
default :
log . Info ( "unrecognized command" , mlog . Field ( "cmd" , cmd ) )
ctl . xwrite ( "unrecognized command" )
return
}
}