Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added SMTP monitor #5489

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions db/knex_migrations/2025-01-01-0000-add-smtp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
exports.up = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.string("smtp_security").defaultTo(null);
});
};

exports.down = function (knex) {
return knex.schema.alterTable("monitor", function (table) {
table.dropColumn("smtp_security");
});
};
1 change: 1 addition & 0 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class Monitor extends BeanModel {
snmpOid: this.snmpOid,
jsonPathOperator: this.jsonPathOperator,
snmpVersion: this.snmpVersion,
smtpSecurity: this.smtpSecurity,
rabbitmqNodes: JSON.parse(this.rabbitmqNodes),
conditions: JSON.parse(this.conditions),
};
Expand Down
35 changes: 35 additions & 0 deletions server/monitor-types/smtp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { MonitorType } = require("./monitor-type");
const { UP } = require("../../src/util");
const nodemailer = require("nodemailer");

class SMTPMonitorType extends MonitorType {
name = "smtp";

/**
* @inheritdoc
*/
async check(monitor, heartbeat, _server) {
let options = {
port: monitor.port || 25,
host: monitor.hostname,
secure: monitor.smtpSecurity === "secure", // use SMTPS (not STARTTLS)
ignoreTLS: monitor.smtpSecurity === "nostarttls", // don't use STARTTLS even if it's available
requireTLS: monitor.smtpSecurity === "starttls", // use STARTTLS or fail
};
let transporter = nodemailer.createTransport(options);
try {
await transporter.verify();

heartbeat.status = UP;
heartbeat.msg = "SMTP connection verifies successfully";
} catch (e) {
throw new Error(`SMTP connection doesn't verify: ${e}`);
} finally {
transporter.close();
}
}
}

module.exports = {
SMTPMonitorType,
};
1 change: 1 addition & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ let needSetup = false;
monitor.kafkaProducerAllowAutoTopicCreation;
bean.gamedigGivenPortOnly = monitor.gamedigGivenPortOnly;
bean.remote_browser = monitor.remote_browser;
bean.smtpSecurity = monitor.smtpSecurity;
bean.snmpVersion = monitor.snmpVersion;
bean.snmpOid = monitor.snmpOid;
bean.jsonPathOperator = monitor.jsonPathOperator;
Expand Down
2 changes: 2 additions & 0 deletions server/uptime-kuma-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
UptimeKumaServer.monitorTypeList["smtp"] = new SMTPMonitorType();
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
Expand Down Expand Up @@ -551,6 +552,7 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
const { TailscalePing } = require("./monitor-types/tailscale-ping");
const { DnsMonitorType } = require("./monitor-types/dns");
const { MqttMonitorType } = require("./monitor-types/mqtt");
const { SMTPMonitorType } = require("./monitor-types/smtp");
const { SNMPMonitorType } = require("./monitor-types/snmp");
const { MongodbMonitorType } = require("./monitor-types/mongodb");
const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
Expand Down
3 changes: 2 additions & 1 deletion src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1051,5 +1051,6 @@
"RabbitMQ Password": "RabbitMQ Password",
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
"SendGrid API Key": "SendGrid API Key",
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
"Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
"smtpHelpText": "“SMTPS” tests that SMTP/TLS is working; “Ignore TLS” connects over plaintext; “STARTTLS” connects, issues a STARTTLS command and verifies the server certificate. None of these send an email."
}
21 changes: 18 additions & 3 deletions src/pages/EditMonitor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
<option value="ping">
Ping
</option>
<option value="smtp">
SMTP
</option>
<option value="snmp">
SNMP
</option>
Expand Down Expand Up @@ -281,8 +284,8 @@
</template>

<!-- Hostname -->
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping / SNMP only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping' || monitor.type === 'snmp'" class="my-3">
<!-- TCP Port / Ping / DNS / Steam / MQTT / Radius / Tailscale Ping / SNMP / SMTP only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'tailscale-ping' || monitor.type === 'smtp' || monitor.type === 'snmp'" class="my-3">
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
<input
id="hostname"
Expand All @@ -297,7 +300,7 @@

<!-- Port -->
<!-- For TCP Port / Steam / MQTT / Radius Type / SNMP -->
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'snmp'" class="my-3">
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'gamedig' || monitor.type === 'mqtt' || monitor.type === 'radius' || monitor.type === 'smtp' || monitor.type === 'snmp'" class="my-3">
<label for="port" class="form-label">{{ $t("Port") }}</label>
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
</div>
Expand Down Expand Up @@ -329,6 +332,18 @@
</select>
</div>

<div v-if="monitor.type === 'smtp'" class="my-3">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a helptext what this monitor actually does.
People will infer that this is sending mails otherwise..

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve pushed the changes based upon what I saw elsewhere in the file; let me know if they weren’t correct.

Copy link
Collaborator

@CommanderStorm CommanderStorm Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not what I meant.
I meant specifying as a helptext what kind of check the selected option does.

What does Ignore STARTTLS actually check?
It does not send a mail as I would expect => please specify in the helptext that it just tries to connect.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Thanks for clarifying.

I’ll describe them here, and also add them to the codebase:

  • Ignore STARTTLS simply connects, says HELO and looks for a correct response
  • Use STARTTLS connects, saysHELO, looks for STARTTLS in the response, issues the command, and verifies the certificate
  • SMTPS connects over TLS, verifies the certificate, says HELO and looks for a correct response

Details are here. Update to the code coming shortly.

<label for="smtp_security" class="form-label">{{ $t("SMTP Security") }}</label>
<select id="smtp_security" v-model="monitor.smtpSecurity" class="form-select">
<option value="secure">SMTPS</option>
<option value="nostarttls">Ignore STARTTLS</option>
<option value="starttls">Use STARTTLS</option>
</select>
<div class="form-text">
{{ $t("smtpHelpText") }}
</div>
</div>

<!-- Json Query -->
<!-- For Json Query / SNMP -->
<div v-if="monitor.type === 'json-query' || monitor.type === 'snmp'" class="my-3">
Expand Down
Loading