Public Access
- With NickServ, auto-join waits for the logged-in reply (900) or 2 s
at most, so registered-only IRC channels let us in
- A failed SASL login falls back to NickServ (its own password, or the
SASL account and password)
- IDENTIFY names the account explicitly, and once logged in on a
fallback nick, REGAIN takes ours back from a stale session
- Auto-join entries take keys ("#private key, #public"); keys from
/join are reused when rejoining; keyed channels go first in JOIN
- Serial irc dump: whole Buffers, and which login is configured
(never the secrets)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
85 lines
3.0 KiB
C++
85 lines
3.0 KiB
C++
#include "irc_config.h"
|
|
|
|
#include <cctype>
|
|
#include <cstring>
|
|
|
|
namespace roro {
|
|
|
|
namespace {
|
|
bool validNick(const std::string& n) {
|
|
if (n.empty() || n.size() > 30) return false;
|
|
if (std::isdigit(static_cast<unsigned char>(n[0])) || n[0] == '-') return false;
|
|
for (char c : n)
|
|
if (!std::isalnum(static_cast<unsigned char>(c)) && !std::strchr("[]\\`_^{|}-", c)) return false;
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
void IrcConfig::load(const std::string& defaultNick) {
|
|
nick = defaultNick;
|
|
std::string s;
|
|
int32_t i;
|
|
if (store_.getString("irc_host", s)) host = s;
|
|
if (store_.getInt("irc_port", i)) port = i;
|
|
if (store_.getInt("irc_tls", i)) tls = i != 0;
|
|
if (store_.getInt("irc_selfsign", i)) allowSelfSigned = i != 0;
|
|
store_.getString("irc_pin", pinnedSha256);
|
|
if (store_.getString("irc_nick", s)) nick = s;
|
|
store_.getString("irc_sasl_user", saslUser);
|
|
store_.getString("irc_sasl_pass", saslPassword);
|
|
store_.getString("irc_ns_pass", nickservPassword);
|
|
if (store_.getString("irc_join", s)) autojoin = parseChannels(s);
|
|
}
|
|
|
|
std::string IrcConfig::validate() const {
|
|
if (host.empty() || host.size() > 63) return "Server must be 1 to 63 characters";
|
|
if (port < 1 || port > 65535) return "Port must be 1 to 65535";
|
|
if (!validNick(nick)) return "Nick: letters, digits and []\\`_^{|}- only, not starting with a digit";
|
|
for (auto& c : autojoin)
|
|
if (c.channel.size() < 2 || (c.channel[0] != '#' && c.channel[0] != '&'))
|
|
return "Auto-join: IRC channels start with # or &, each followed by its key if it has one";
|
|
return "";
|
|
}
|
|
|
|
std::string IrcConfig::save() {
|
|
std::string error = validate();
|
|
if (!error.empty()) return error;
|
|
store_.putString("irc_host", host);
|
|
store_.putInt("irc_port", port);
|
|
store_.putInt("irc_tls", tls ? 1 : 0);
|
|
store_.putInt("irc_selfsign", allowSelfSigned ? 1 : 0);
|
|
store_.putString("irc_pin", pinnedSha256);
|
|
store_.putString("irc_nick", nick);
|
|
store_.putString("irc_sasl_user", saslUser);
|
|
store_.putString("irc_sasl_pass", saslPassword);
|
|
store_.putString("irc_ns_pass", nickservPassword);
|
|
store_.putString("irc_join", formatChannels(autojoin));
|
|
return "";
|
|
}
|
|
|
|
std::vector<IrcChannel> IrcConfig::parseChannels(const std::string& text) {
|
|
std::vector<IrcChannel> out;
|
|
std::string word;
|
|
auto take = [&]() {
|
|
if (word.empty()) return;
|
|
bool channel = word[0] == '#' || word[0] == '&';
|
|
if (!channel && !out.empty() && out.back().key.empty()) out.back().key = word;
|
|
else out.push_back({word, ""}); // a stray word fails validation as a channel
|
|
word.clear();
|
|
};
|
|
for (char c : text) {
|
|
if (c == ' ' || c == ',') take();
|
|
else word += c;
|
|
}
|
|
take();
|
|
return out;
|
|
}
|
|
|
|
std::string IrcConfig::formatChannels(const std::vector<IrcChannel>& channels) {
|
|
std::string out;
|
|
for (auto& c : channels) out += (out.empty() ? "" : ", ") + c.channel + (c.key.empty() ? "" : " " + c.key);
|
|
return out;
|
|
}
|
|
|
|
} // namespace roro
|