diff --git a/README.md b/README.md index 43e1c62..8351c73 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A simple command line application for sending mails. [![Java Development Kit 11](https://img.shields.io/badge/JDK-11-green.svg)](https://openjdk.java.net/projects/jdk/11/) ## Versions -- 0.2.x = **Java 11** with new 'jakarta' namespace +- > 0.2.x = **Java 11** with new 'jakarta' namespace - 0.1.0 = **Java 8** ## Why? @@ -25,19 +25,20 @@ You can download the latest JAR file here: https://github.com/fuinorg/sjsm/relea ## Command line arguments -| Argument | Value | Required | Example | -| -------- |--------------------------------------------------------| -------- |--------------------------------------------------------------------------------------------------| -| -host | SMTPS server name | yes | "smtp.no-where-no-no.com" | -| -port | SMTPS port number (SSL/TLS) | yes | 465 | -| -user | Your mailbox user | yes | "acc12345_from.not.exist" or "from.not.exist@no-where-no-no.com" (depends on your mail provider) | -| -pw | Your mailbox password | yes | "xxxxxxx" | -| -from | Sender's email address | yes | "from.not.exist@no-where-no-no.com" | -| -to | Receiver's email address (multiple separated with ";") | yes | "to.not.exist@no-where-no-no.com" or "jane.doe@no-where-no-no.com;john.doe@no-where-no-no.com" | -| -subject | Mail subject | yes | "My subject" | -| -message | Message body (TEXT or HTML) | yes | "<html><body><h1>This is a test mail</h1></body></html>" | -| -html | - | no | - | -| -charset | Mail encoding (defaults to "utf-8") | no | "utf-8" | -| -important | Send High Priority Email (X-Priority flag) | no | - | +| Argument | Value | Required | Example | +|------------|---------------------------------------------------------------------|----------|--------------------------------------------------------------------------------------------------| +| -host | SMTPS server name | yes | "smtp.no-where-no-no.com" | +| -port | SMTPS port number (SSL/TLS) | yes | 465 | +| -user | Your mailbox user | yes | "acc12345_from.not.exist" or "from.not.exist@no-where-no-no.com" (depends on your mail provider) | +| -pw | Your mailbox password (Either `pw`or `envPw` is mandatory) | no | - | +| -envPw | Name of an environment variable that contains your mailbox password | no | - | +| -from | Sender's email address | yes | "from.not.exist@no-where-no-no.com" | +| -to | Receiver's email address (multiple separated with ";") | yes | "to.not.exist@no-where-no-no.com" or "jane.doe@no-where-no-no.com;john.doe@no-where-no-no.com" | +| -subject | Mail subject | yes | "My subject" | +| -message | Message body (TEXT or HTML) | yes | "<html><body><h1>This is a test mail</h1></body></html>" | +| -html | - | no | - | +| -charset | Mail encoding (defaults to "utf-8") | no | "utf-8" | +| -important | Send High Priority Email (X-Priority flag) | no | - | ## TEXT example @@ -65,7 +66,8 @@ You can download the latest JAR file here: https://github.com/fuinorg/sjsm/relea -html \ ## CAUTION -Be aware that passing your password via the command line will most probably be visible in your command line history. +:warning: Be aware that passing your password via the command line (`-pw`) will most probably be visible in your command line history. +It's better to use an environment variable with `-envPw`. * * * diff --git a/pom.xml b/pom.xml index 0d37eec..90e1fb2 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,13 @@ test + + org.junit.jupiter + junit-jupiter-engine + 5.10.1 + test + + org.assertj assertj-core @@ -75,12 +82,28 @@ test + + com.github.stefanbirkner + system-lambda + 1.2.1 + test + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.3 + + -Djava.security.SecurityManager=allow --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED + + + org.apache.maven.plugins maven-shade-plugin diff --git a/src/main/java/org/fuin/sjsm/Config.java b/src/main/java/org/fuin/sjsm/Config.java index 424993f..20b03b7 100644 --- a/src/main/java/org/fuin/sjsm/Config.java +++ b/src/main/java/org/fuin/sjsm/Config.java @@ -40,9 +40,12 @@ public final class Config { @Option(name = "-user", usage = "User", metaVar = "USER", required = true) private String user; - @Option(name = "-pw", usage = "Password", metaVar = "PW", required = true) + @Option(name = "-pw", usage = "Password", metaVar = "PW") private String pw; + @Option(name = "-envPw", usage = "Name of an environment variable that contains the password", metaVar = "ENV_PW") + private String envPw; + @Option(name = "-from", usage = "Sender", metaVar = "SEND", required = true) private String from; @@ -149,6 +152,25 @@ public void setPw(final String pw) { this.pw = pw; } + /** + * Returns the name of an environment variable that contains the password. + * + * @return Environment variable with password. + */ + public String getEnvPw() { + return envPw; + } + + /** + * Sets the name of an environment variable that contains the password. + * + * @param envPw + * Environment variable to use. + */ + public void setEnvPw(final String envPw) { + this.envPw = envPw; + } + /** * Returns the important (X-Priority) flag. * @@ -440,7 +462,13 @@ public Properties createSessionProperties() { public Authenticator createAuthenticator() { return new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(user, pw); + final String p; + if (pw == null) { + p = System.getenv(envPw); + } else { + p = pw; + } + return new PasswordAuthentication(user, p); } }; } diff --git a/src/main/java/org/fuin/sjsm/Messages.java b/src/main/java/org/fuin/sjsm/Messages.java new file mode 100644 index 0000000..6584dc5 --- /dev/null +++ b/src/main/java/org/fuin/sjsm/Messages.java @@ -0,0 +1,31 @@ +package org.fuin.sjsm; + +import org.kohsuke.args4j.Localizable; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Message constants. + */ +public enum Messages implements Localizable { + + /** Neither 'pw' nor 'envPw' argument is set. */ + MISSING_PASSWORD_OPTION, + + /** The environment variable from 'envPw' argument is not set. */ + PASSWORD_ENV_VAR_NOT_SET; + + @Override + public String formatWithLocale(final Locale locale, final Object... args) { + final ResourceBundle localized = ResourceBundle.getBundle("sjsm-messages", locale); + return MessageFormat.format(localized.getString(name()), args); + } + + @Override + public String format(final Object... args) { + return formatWithLocale(Locale.getDefault(), args); + } + +} diff --git a/src/main/java/org/fuin/sjsm/SendMailApp.java b/src/main/java/org/fuin/sjsm/SendMailApp.java index 4dc5cd4..feb2a6c 100644 --- a/src/main/java/org/fuin/sjsm/SendMailApp.java +++ b/src/main/java/org/fuin/sjsm/SendMailApp.java @@ -66,6 +66,16 @@ public void send(final Config config) { } + private static void ensurePasswordIsSet(final CmdLineParser parser , final Config config) throws CmdLineException { + if (config.getPw() == null && config.getEnvPw() == null) { + throw new CmdLineException(parser, Messages.MISSING_PASSWORD_OPTION); + } + if (config.getEnvPw() != null && System.getenv(config.getEnvPw()) == null) { + throw new CmdLineException(parser, Messages.PASSWORD_ENV_VAR_NOT_SET, config.getEnvPw()); + } + } + + /** * Main application method. * @@ -78,6 +88,7 @@ public static void main(final String[] args) { final CmdLineParser parser = new CmdLineParser(config); try { parser.parseArgument(args); + ensurePasswordIsSet(parser, config); new SendMailApp().send(config); System.exit(0); } catch (final CmdLineException ex) { diff --git a/src/main/resources/sjsm-messages.properties b/src/main/resources/sjsm-messages.properties new file mode 100644 index 0000000..eb2146a --- /dev/null +++ b/src/main/resources/sjsm-messages.properties @@ -0,0 +1,2 @@ +MISSING_PASSWORD_OPTION=A password or an environment variable with the password is mandatory +PASSWORD_ENV_VAR_NOT_SET=The environment variable {0} is not set (has no value) \ No newline at end of file diff --git a/src/test/java/org/fuin/sjsm/SendMailAppTest.java b/src/test/java/org/fuin/sjsm/SendMailAppTest.java index a15b633..96871b1 100644 --- a/src/test/java/org/fuin/sjsm/SendMailAppTest.java +++ b/src/test/java/org/fuin/sjsm/SendMailAppTest.java @@ -1,26 +1,29 @@ /** - * Copyright (C) 2015 Michael Schnell. All rights reserved. + * Copyright (C) 2015 Michael Schnell. All rights reserved. * http://www.fuin.org/ - * + *

* This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) any * later version. - * + *

* This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. - * + *

* You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see http://www.gnu.org/licenses/. */ package org.fuin.sjsm; +import static com.github.stefanbirkner.systemlambda.SystemLambda.*; import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -122,4 +125,108 @@ void testSendMultipleReceiver() { } + @Test + void testMissingPasswordOption() throws Exception { + + final String[] args = new String[]{ + "-host", "localhost", + "-port", "" + dumbster.getPort(), + "-user", "myaccount", + "-from", "does-not@matter.com", + "-to", "not@used.com", + "-subject", "Whatever", + "-message", "None" + }; + + final AtomicLong exitCode = new AtomicLong(); + final String systemErr = tapSystemErr(() -> { + exitCode.set(catchSystemExit(() -> { + SendMailApp.main(args); + })); + }); + assertThat(systemErr).contains("A password or an environment variable with the password is mandatory"); + assertThat(exitCode.get()).isEqualTo(1); + + } + + @Test + void testPwOption() throws Exception { + + final String[] args = new String[]{ + "-host", "localhost", + "-port", "" + dumbster.getPort(), + "-user", "myaccount", + "-pw", "abc", + "-from", "does-not@matter.com", + "-to", "not@used.com", + "-subject", "Whatever", + "-message", "None", + "-smtp" + }; + + final AtomicLong exitCode = new AtomicLong(); + final String systemOut = tapSystemOut(() -> { + exitCode.set(catchSystemExit(() -> { + SendMailApp.main(args); + })); + }); + assertThat(systemOut).contains("Successfully sent message 'Whatever' to 'not@used.com"); + assertThat(exitCode.get()).isEqualTo(0); + + } + + @Test + void testEnvPwOption() throws Exception { + + final String[] args = new String[]{ + "-host", "localhost", + "-port", "" + dumbster.getPort(), + "-user", "myaccount", + "-envPw", "MAIL_PW_TEST", + "-from", "does-not@matter.com", + "-to", "not@used.com", + "-subject", "Whatever", + "-message", "None", + "-smtp" + }; + + final AtomicLong exitCode = new AtomicLong(); + final AtomicReference systemOut = new AtomicReference<>(); + withEnvironmentVariable("MAIL_PW_TEST", "abc").execute(() -> { + systemOut.set(tapSystemOut(() -> { + exitCode.set(catchSystemExit(() -> { + SendMailApp.main(args); + })); + })); + }); + assertThat(systemOut.get()).contains("Successfully sent message 'Whatever' to 'not@used.com"); + assertThat(exitCode.get()).isEqualTo(0); + + } + + @Test + void testMissingEnvPassword() throws Exception { + + final String[] args = new String[]{ + "-host", "localhost", + "-port", "" + dumbster.getPort(), + "-user", "myaccount", + "-envPw", "NOT_EXISTING_PW_ENV_VAR", + "-from", "does-not@matter.com", + "-to", "not@used.com", + "-subject", "Whatever", + "-message", "None" + }; + + final AtomicLong exitCode = new AtomicLong(); + final String systemErr = tapSystemErr(() -> { + exitCode.set(catchSystemExit(() -> { + SendMailApp.main(args); + })); + }); + assertThat(systemErr).contains("The environment variable NOT_EXISTING_PW_ENV_VAR is not set"); + assertThat(exitCode.get()).isEqualTo(1); + + } + }