It only applied to pre-ICS devices.
ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED is no longer
broadcast.
ConnectivityManager.getBackgroundDataSetting() always returns true.
This was dead code. The exception message will always start with either
"SMTP response is 0 length" from checkLine() or else "Negative SMTP reply"
from NegativeSmtpReplyException().
The problem originated from way back before 4.904.
The constructor now saves the certificate chain, so the code to retrieve
it again or to perform any additional error checking in
getCertificateChain() is no longer needed.
The constructor now retrieves and saves the private key so that any
resulting errors are detected sooner.
Methods that retrieve the alias perform checks to assure that the client
cert. satisfies the requested issuers and key type. It's known that
Sendmail may provide a list of issuers in its certificate request, but
then may authenticate against a much larger set of CAs, but then later
reject the mail because the client certificate was not acceptable.
Vetting against the issuer list helps detect such certificate problems
sooner (upon connection) rather than later (upon transmission of mail).
Earlier error detection is necessary so that errors may be presented to
the user during account setup.
Portions of these modifications are based on code from KeyManagerImpl:
https://android.googlesource.com/platform/external/conscrypt/+/master/src/main/java/org/conscrypt/KeyManagerImpl.java
Move KeyChainKeyManager to com.fsck.k9.net.ssl because it is used by
SslHelper and because the class extends X509ExtendedKeyManager, which is
in javax.net.ssl.
The problem can be observed if, when modifying the outgoing server
settings, you change the state of the mRequireLoginView check box,
then change the screen orientation.
This is necessary because the OnCheckChanged listener (which
normally updates the view visibility) is not yet set. (The listeners
are set up after view initialization so that they only fire on
user input.)
It should not be triggered when the instance state is restored
with an AuthType spinner selection of EXTERNAL.
The logic here for the AuthType spinner is similar to that of
the parent commit for the SecurityType spinner.
The problem: begin modifying the server settings by changing the security
type (which will change the port to a default value), then change the port
to a custom value, then change screen orientation. The default port value
is restored, wiping out the custom value.
When onRestoreInstanceState() is called, the custom port value is
restored. But the spinner doesn't actually restore its state at that
time. Instead, it waits until View.layout(), at which time it posts a
runnable to call onItemSelected() if the restored state doesn't match the
state initialized in onCreate(). When onItemSelected() is eventually run
sometime later, it wipes out the custom port value that was restored.
The solution is to keep track of the spinner state ourselves and only
revert the port to a default when we see the spinner state changed by the
user.
This problem goes back to 4.904 and before.
For convenience. Implemented in onCheckChanged().
As a consequence, onCheckChanged() must not be triggered when the instance
state is restored (would occur if the check box state was checked when
saved), otherwise the certificate chooser would pop up once the state was
restored. Therefore, all listeners have been moved into
initializeViewListeners() which is invoked after the state has been
restored.
Because onCheckChanged() is no longer triggered in
onRestoreInstanceState(), updateViewVisibility() was implemented to
restore the view visibility.
This is done when the SASL EXTERNAL mechanism isn't advertised (indicating
the possibility that the server did not accept the client certificate) or
when the command for authenticating with SASL EXTERNAL fails.
The CertificateValidationException will trigger a notification to the user
that there's an authentication problem that needs addressing.
Also, there were instances where CertificateValidationException was being
thrown with a new CertificateException as the cause for the purpose of
notifying the user when STARTTLS is not available. This has been slightly
simplified by eliminating the need to include a new CertificateException
as a cause.
If the alias is empty or null, don't bother using KeyChainKeyManager.
If the alias is not empty, confirm that it is associated with a
certificate, otherwise throw a CertificateValidationException
which will notify the user of the problem and ask the user to
check the server settings.
Likewise, the user is notified if the client certificate was
not accepted by the server.
If the user chooses client certificate authentication,
immediately pop up the certificate chooser.
If the user chooses password authentication, move the focus to the
password View.
With this commit, KeyChainKeyManager no longer throws the exception and
AccountSetupCheckSettings no longer catches it.
It was being thrown when the server requested a client certificate but no
client certificate alias had been configured for the server.
The code was making the incorrect assumption that the server would only
request a client certificate when such a certificate was *required*.
However, servers can be configured to accept multiple forms of
authentication, including both password authentication and client
certificate authentication. So a server may request a certificate without
requiring it. If a user has not configured a client certificate, then
that should not be treated as an error because the configuration may be
valid and the server may accept it.
The only indication that a certificate is *required* is when a
SSLProtocolException is thrown, caused by a SSLHandshakeException
resulting from a fatal handshake alert message received from the server.
Unfortunately, such a message is fairly generic and only "indicates that
the sender was unable to negotiate an acceptable set of security
parameters given the options available." So there is no definitive way to
know that a client certificate is required.
Also, KeyChainKeyManager.getCertificateChain() and getPrivateKey() no
longer throw IllegalStateException(). These methods are permitted to
return null, and such a response is appropriate if the user has deleted
client certificates from the device. Again, this may or may not cause the
server to abort the connection, depending on whether the server *requires*
a client certificate.
The app's minSdkVersion = 15 (Android 4.0.3, Ice Cream Sandwich MR1),
so there's no need to test the API level.
This also removes '@SuppressLint("TrulyRandom")'. I find no
documentation for it, nor do I find any additional lint errors
with its removal.
The user may toggle the checkbox, and then decide to toggle it again.
This also fixes a problem when restoring the activity state. When the
checkbox was restored as checked, the listener was firing and wiping the
the restored alias.
Previously, with settings of Security=SSL and authentication=certificate,
attempting to change Security=None would (of course) be blocked. So
Security would remain SSL. But the authentication options would then
include "Password, transmitted insecurely", whereas the option should
have remained as "Normal password" (because the security remained SSL).
The problem could have been fixed with a simple shuffling in
updatePortFromSecurityType() so that updateAuthPlainTextFromSecurityType()
was invoked before mPortView.setText(), but the logic for requiring that
ordering was not plain to see. (Although no longer necessary, the
shuffling was done as well.)
Add test for username == "".
Without it, the mRequireLoginView remains checked, and the empty username
and password boxes are not hidden.
The problem occurs if importing an SMTP server that has an
authenticationType, but no username or password (i.e., no authentication
required).
That's the way settings were exported in 4.904 and before.
Occurs when the SMTP server doesn't require authentication
(authenticationType == null).
The javadoc for ServerSettings says that both authenticationType and
connectionSecurity may be null.
The referenced issue states that it is only applicable to Android < 4.2
(testing confirms the problem on 4.1.2, but not on 4.2.2).
A test was added for the version code, primarily as a finder's aid for a
day when K-9 Mail no longer supports Android < 4.2 and the work-around can
be removed.
The referenced issue also states that it is only necessary to hold a
reference to the first PrivateKey retrieved. (Testing indicates that the
problem is avoided so long at least one reference is always maintained to
a PrivateKey -- it doesn't actually need to be a continuous reference to
the first PrivateKey.)
From my understanding, a normal class loader never unloads a class, so the
static reference can be safely kept privately in KeyChainKeyManager.
In response to Issue 1794,
- add a configuration option in the account preferences to show
notifications only for 1st/2nd/etc class folders
- add an option in the folder preferences to set the notification class
as 1st, 2nd or inherited from the folder's push class
The default behaviour remains unchanged.
E/AndroidRuntime(25655): FATAL EXCEPTION: main
E/AndroidRuntime(25655): Process: com.fsck.k9, PID: 25655
E/AndroidRuntime(25655): java.lang.NullPointerException
E/AndroidRuntime(25655): at com.fsck.k9.view.MessageOpenPgpView.handleError(MessageOpenPgpView.java:385)
E/AndroidRuntime(25655): at com.fsck.k9.view.MessageOpenPgpView.access$3(MessageOpenPgpView.java:384)
E/AndroidRuntime(25655): at com.fsck.k9.view.MessageOpenPgpView$DecryptVerifyCallback.onReturn(MessageOpenPgpView.java:357)
E/AndroidRuntime(25655): at org.openintents.openpgp.util.OpenPgpApi$OpenPgpAsyncTask.onPostExecute(OpenPgpApi.java:195)
E/AndroidRuntime(25655): at org.openintents.openpgp.util.OpenPgpApi$OpenPgpAsyncTask.onPostExecute(OpenPgpApi.java:1)
E/AndroidRuntime(25655): at android.os.AsyncTask.finish(AsyncTask.java:632)
E/AndroidRuntime(25655): at android.os.AsyncTask.access$600(AsyncTask.java:177)
E/AndroidRuntime(25655): at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
E/AndroidRuntime(25655): at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime(25655): at android.os.Looper.loop(Looper.java:136)
E/AndroidRuntime(25655): at android.app.ActivityThread.main(ActivityThread.java:5128)
E/AndroidRuntime(25655): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(25655): at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(25655): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:795)
E/AndroidRuntime(25655): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:611)
E/AndroidRuntime(25655): at dalvik.system.NativeStart.main(Native Method)
Specifically, warn and block them when attempting to configure Client
Certificate Authentication in combination with Connection Security = None.
If this were not made obvious to the user, they might not understand why
they are not permitted to tap "Next".
Also, move the initialization of all view listeners out of onCreate() into
initializeViewListeners() which is then called near the end of onCreate(),
helping to assure that the listeners won't be triggered during the
initialization of views inside onCreate().
Previously, it was possible to have "Require sign-in" unchecked and a
"Security = None" setting for the outgoing server and still not be able to
tap "Next" because of a hidden (and irrelevant) "Authentication = Client
certificate" setting.
Check that the user has actually chosen a client certificate in
AccountSetupOutgoing.validateFields().
Also, there's no need to clear the password and certificate fields when
hiding them. The user may accidentally change settings and want to change
them back without wiping out the existing settings.
Leave the hostname == null checks so we can fall back if a hostname is not
found. Also convert message-id to upper case to match Apple Mail (for
privacy).
I wrote this fix to avoid obviously specifying that I am using a mobile device
to reply to an email.
Others want this for ease of filtering messages from their host by Message-ID.
E/AndroidRuntime(24914): FATAL EXCEPTION: main
E/AndroidRuntime(24914): Process: com.fsck.k9, PID: 24914
E/AndroidRuntime(24914): java.lang.NullPointerException
E/AndroidRuntime(24914): at org.openintents.openpgp.util.OpenPgpServiceConnection.<init>(OpenPgpServiceConnection.java:35)
E/AndroidRuntime(24914): at com.fsck.k9.view.MessageOpenPgpView.updateLayout(MessageOpenPgpView.java:115)
E/AndroidRuntime(24914): at com.fsck.k9.view.SingleMessageView.setMessage(SingleMessageView.java:623)
E/AndroidRuntime(24914): at com.fsck.k9.fragment.MessageViewFragment$Listener$2.run(MessageViewFragment.java:602)
E/AndroidRuntime(24914): at android.os.Handler.handleCallback(Handler.java:733)
E/AndroidRuntime(24914): at android.os.Handler.dispatchMessage(Handler.java:95)
E/AndroidRuntime(24914): at android.os.Looper.loop(Looper.java:136)
E/AndroidRuntime(24914): at android.app.ActivityThread.main(ActivityThread.java:5081)
E/AndroidRuntime(24914): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(24914): at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(24914): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
E/AndroidRuntime(24914): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
E/AndroidRuntime(24914): at dalvik.system.NativeStart.main(Native Method)
Instead of interpreting a "-" at the beginning of a line as
an error response, consider the absence of a "+" at the
beginning of a line as an error response.
This is what Thunderbird does.
http://hg.mozilla.org/releases/comm-esr24/file/55e96a433bd1/mailnews/local/src/nsPop3Protocol.cpp#l1177
The problem arises with godaddy servers spewing additional
lines of data upon login failure. The login was being
interpreted as successful, and a STAT commanded was subsequently
being sent, resulting in a dialog saying 'Cannot connect to
server. (Invalid int: "auth_error:")'.
$ openssl s_client -quiet -crlf -connect pop.secureserver.net:995
...
+OK <24984.1394317012@pop.secureserver.net>
user testuser
+OK
pass testpass
testuser not found in the auth database
warning: auth_error: authorization failed (no such object)
-ERR authorization failed Check your server settings.
Some IMAP servers are broken and don't correctly handle string
literals with the LOGIN command.
This switches to using quoted strings instead.
This is what Thunderbird does.
Error messages were not being put in the folder because of a problem with
how loopCatch was being handled.
It looks like this problem goes back to commit 5aea9e7.
Additional device info has been added to the error messages.
Also, now the feature is only enabled in debug mode.
getName() shows the user's name from the first identity for the account.
What we really want is getDescription(), which is the account name that
shows in the account list.
If an error occurs while connecting for push (for example, if the server's
certificate fails authentication), several more attempts will be made to
connect, then finally all attempts will cease.
This commit makes it so that pushers are restarted if the user goes in and
edits the server settings (presumably because the user was notified of a
problem and is attempting to fix it). Without this, the user could fix
the problem and would still not receive email via push.
These options originated in the AOSP email client from which K-9 Mail was
forked. They provide an odd combination of 2 features:
1. Don't bother to authenticate the server's certificate (applies to both
SSL/TLS and STARTTLS); i.e., blindly accept all certificates. This is
generally a bad security policy which is susceptible to MITM attacks.
2. If STARTTLS is selected but the server doesn't claim to support
STARTTLS, then proceed without using encryption. This, too, is a bad
security policy which is susceptible to MITM attacks.
Since the time that K-9 Mail was forked, a couple things have changed:
> K-9 Mail has implemented the ability for users to review and permanently
accept individual certificates that would otherwise fail authentication.
With this ability, there is no need for a user to subject themselves to
the ongoing risks of feature 1. above. Hence, this commit removes feature
1.
> The AOSP email client has changed its behavior and no longer permits a
security downgrade to an unencrypted connection if the server doesn't
claim to support STARTTLS (i.e., they eliminated feature 2. above). K-9
Mail should do the same. It's unlikely that a server is going to provide
STARTTLS on an intermittent basis, so providing a contingency for such
unusual behavior is an unnecessary risk. Hence, this commit removes that
feature as well.
Effect on existing users:
If the old connection security setting was "SSL/TLS (if available)" (which
now gets remapped to "SSL/TLS"), and the server does not provide a
certificate that can be authenticated, then a "Certificate error for
<account name>" notification is generated telling the user to check their
server settings. Tapping the notification takes the user to the relevant
server settings, where the user can tap "Next" to review the certificate
and choose to permanently accept it. This process would occur during the
first syncing of folders after application upgrade or (in the case of
SMTP) during the first attempt to send a message.
If the connection security setting was "STARTTLS (if available)" (which
now gets remapped to "STARTTLS"), and the server does not provide a
certificate that can be authenticated, then the same process as above
would occur.
If the old connection security setting was "STARTTLS (if available)", and
the server doesn't claim to support STARTTLS, then the user would get a
certificate error notification which would lead them to the server's
settings. There they would need to choose a different connection security
-- most likely "NONE". If they didn't change anything but instead just
tapped "Next", the server settings would be checked again and a dialog
would pop up saying, "Cannot connect to server. (STARTTLS connection
security not available)". (The implementation of notifications when
STARTTLS is not available is not actually included here -- it's in the
commit that follows.)
Regarding the changes to providers.xml: in cases where the scheme ended
with "+ssl", the schemes were simply updated by appending "+". In cases
where the scheme ended with "+tls", a check of the server was made to
assure that STARTTLS was available before appending "+" to the scheme.
Domains paran.com and nate.com failed the check and were removed because
no current information could be found. Domains me.com and mac.com also
failed and were updated based on http://support.apple.com/kb/ht4864.
Previously, if a server's certificate failed authentication while
connecting for push (if, for example, the certificate had expired), then
the attempt to connect would fail, and another attempt would be tried
later. After a certain number of failed attempts, no further attempts
would be made. Meanwhile, the user is oblivious to the failures, and it
could be quite some time before the user realizes that they are not
getting email. Even when they do realize it, they would not know the
cause.
With this commit, users receive a notification when such failures occur
while connecting for push. (These notifications are already generated
with failures while polling.) Tapping the notification will take the user
to the relevant server settings where they can choose to accept the
certificate.
Applicable for strings not intended for user consumption.
A %d string format code can generate eastern-arabic numerals
for users with an arabic locale.
V/k9 (20763): conn1103774136>>> 5 UID SEARCH ٦٤٦:٦٧٠ NOT DELETED
V/k9 (20763): conn1103774136<<<#5# [BAD, Invalid Search criteria]
E/k9 (20763): synchronizeMailbox
E/k9 (20763): com.fsck.k9.mail.store.ImapStore$ImapException: Command: UID SEARCH ٦٤٦:٦٧٠ NOT DELETED; response: #5# [BAD, Invalid Search criteria]
When sending a command it would be sent like this:
PKG1: 1 STARTTLS
PKG2: \r\n
Some imap proxys (maybe from Fortinet?) don't accept commands across packets:
PKG1: 1 STARTTLS\r\n
The naturalize tool detected that using "identity" is more consistent
with the current codebase state:
* "identity" in SettingsImporter is 78.46% probable ("identitiy" 21.54%)
The naturalize tool detected that using "tokenizer" is more consistent
with the current codebase state:
* "tokenizer" in MessageCompose is 60.40% probable ("tokens" 39.60%)
The naturalize tool detected that using "localFolder" is more consistent
with the current codebase state:
* "localFolder" in MessageListFragment is 76.31% probable ("local_folder" 23.69%)
The naturalize tool detected that using "sizeParam" is more consistent
with the current codebase state:
* "sizeParam" in LocalStore is 22.89% probable ("s" 10.52%)
The naturalize tool detected that using "uee" is more consistent with
the current codebase state:
* "uee" in LocalStore is 28.47% probable ("usee" 5.01%)
* "uee" in TextBody is 45.02% probable ("usee" 9.10%)
If the user chooses a connection security option which assures the use of
encryption, then the PLAIN auth. option is labeled "Normal password",
otherwise it is labeled "Password, transmitted insecurely".
This is similar to Thunderbird's behavior.
The server settings for IMAP and POP3 have no such AUTOMATIC setting.
(Nor does Thunderbird have any such setting.)
The AUTOMATIC option is no longer offered to users as a choice. A
pre-existing setting will continue to be honored, but only to the extent
that it doesn't result in insecure password transmission. Users in such a
situation will get a "Failed to send some messages" notification
containing the exception text that says to update their outgoing server
authentication setting.
One of the problems with "AUTOMATIC" is that users may not fully
understand its security implications. For example, a MITM attack could
mask a server's support for STARTTLS and CRAM-MD5, resulting in password
disclosure in certain configurations.
This commit also makes changes to the SMTP authentication process. No
attempt is made to authenticate using methods that the server does not
profess to support in its EHLO response. This is the same behavior as
found in Thunderbird.
The LOGIN option is no longer offered to users as a choice. This does
*not* eliminate the SASL LOGIN authentication mechanism. Any pre-existing
LOGIN setting or any imported LOGIN setting will still be recognized. In
all cases, a user setting of either "Normal password" or "LOGIN" will
result in the SASL PLAIN mechanism being tried first if available,
otherwise SASL LOGIN will be tried if available.
This mirrors similar behavior that exists for IMAP.
AUTOMATIC = "Automatic"
PLAIN = "Normal password"
CRAM_MD5 = "Encrypted password"
SMTP also uses LOGIN. No localized text was associated with that because
a future commit will remove that option.
(The text is similar to that of Thunderbird's)
Changes:
Extract code and create login() and authCramMD5() methods.
Implement the SASL PLAIN authentication mechanism. Its primary benefit is
the explicit support for UTF-8. If the user has configured "PLAIN"
authentication, then SASL PLAIN will be used, if available, otherwise
login() will be used.
Implement POP3 APOP authentication (issue 3218). If the user has
configured "CRAM_MD5" authentication (a future commit will change this
user option to a localized string "Encrypted password"), then SASL
CRAM-MD5 will be used, if available, otherwise the availability of POP3
APOP will be checked and used (per RFC 2449, there is no APOP
"capability").
Extend getCapabilities() to check for available authentication methods by
sending the "AUTH" command with no arguments
(http://tools.ietf.org/html/draft-myers-sasl-pop3-05). This never became
a standard, but there are servers that support it, and Thunderbird
includes this check.
The SASL PLAIN and CRAM-MD5 authentication methods are not attempted
unless the server professes to have the appropriate capability.
(Previously, CRAM-MD5 was tried regardless of capability.) No check is
made for the USER capability prior to use of that method. All this is the
same behavior as in Thunderbird.
Eliminate the testing for capabilities in cases where the test results are
never used (PIPELINING, USER).
Change when getCapabilities() is called. It is called once upon
connection. If STARTTLS is negotiated (POP3 STLS), then
getCapabilities() is called again after the connection is encrypted (and
the server is authenticated), but before user authentication is attempted.
In AccountSetupCheckSettings.onCreate(Bundle), the account settings are
checked.
If an AuthenticationFailedException occurs, A dialog saying "Username or
password incorrect." pops up. We don't want to say this if the cause is
not related to an incorrect user name or password. Instead we want to say
the more generic "Cannot connect to server" which pops up for other
exception types.
This commit attempts to eliminate the use of AuthenticationFailedException
in instances where it could not be due to "Username or password
incorrect."
Previously, the user name and password were being transmitted as IMAP (RFC
3501) quoted strings.
The problem is that quoted strings are only permitted to carry 7-bit
(ASCII) data, whereas user names and passwords entered in K-9 Mail may not
be ASCII, so K-9 was violating the RFC by sending them as quoted strings.
The solution is to transmit the credentials as IMAP literal strings, which
are permitted for user names and passwords, and which permit the
transmission of 8-bit data.
This is only a partial attempt for fixing the LOGIN command for users with
non-ASCII credentials. The problem is that IMAP permits 8-bit data for
user names and passwords (if transmitted as literals), but the RFC says
nothing about the character encoding for 8-bit data. This commit encodes
them as UTF-8.
The RFC author's comments on the subject:
http://mailman2.u.washington.edu/pipermail/imap-protocol/2008-February/000822.html
Ideally, users should avoid the LOGIN command and use the SASL PLAIN
mechanism (within TLS) which explicitly permits UTF-8. (K-9 Mail always
chooses PLAIN over LOGIN, when PLAIN is available.)
CRAM-MD5 (RFC 2195) permits 8-bit data but does not identify its encoding.
Since ASCII does not permit 8-bit data, this commit changes the encoding
to UTF-8.
There is an expired Internet-Draft that proposed that the RFC be changed
to explicitly require UTF-8 encoding of user names and shared secrets.
(But then there's also an expired draft proposing that CRAM-MD5 be retired
to historic status.)
Instead of CRAM-MD5, a better option for users is the SASL PLAIN mechanism
(within TLS) which explicitly permits UTF-8.
See Issue 4492
This method made way too many assumptions about server responses and
should not have been attempting to read and parse them. That should be
left to ImapResponseParser.
Changes:
Implement the PLAIN SASL mechanism. IMAPv4rev1 assures its availability
so long as the connection is encrypted. The big advantage of PLAIN over
IMAP "LOGIN" is that PLAIN uses UTF-8 encoding for the user name and
password, whereas "LOGIN" is only safe for 7-bit US-ASCII -- the encoding
of 8-bit data is undefined.
(Note that RFC 6855 says that IMAP "LOGIN" does not support UTF-8, and
clients must use IMAP "AUTHENTICATE" to pass UTF-8 user names and
passwords.)
Honor the "LOGINDISABLED" CAPABILITY (RFC 2595) when the server declares
it. There's no sense transmitting a password in the clear when it is
known that it will be rejected.
No attempt is made to try CRAM-MD5 if the server doesn't profess to
support it in its CAPABILITY response. (This is the same behavior as
Thunderbird.)
Extract code from ImapConnection.open into new method
ImapConnection.login.
Extract code from ImapConnection.executeSimpleCommand into new method
ImapConnection.readStatusResponse.
Related issues: 6015, 6016
All \r and \n codes have been replaced with <br />, so the patterns in
these replacements don't match anything.
This problem has existed for some time -- since commits 1ea27d7 and
e12dd32.
No attempt is made here to reimplement the replacements because users are
now used to the current behavior without much apparent complaint, and such
replacements are never perfect and can occasionally fail to work as
desired without additional tweaking for special cases.