Skip to content
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
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// $Id: ChangeLog,v 1.173 2010/04/10 18:56:06 danielaustin Exp $ //

2026-03-16 MrIron
* gnuworld/mod.cservice:
- Respect RFC1459 casemapping.

2026-01-09 MrIron
* Major update: TLS support (based on Compy's initial implementation), SASL/SCRAM authentication, fingerprint features, and expanded user/channel modes.
* configure.ac:
Expand Down
2 changes: 2 additions & 0 deletions doc/cservice.sql
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ CREATE INDEX help_language_id_idx ON help (language_id);
CREATE TABLE channels (
id SERIAL,
name TEXT NOT NULL UNIQUE,
canon_name TEXT NOT NULL UNIQUE,
flags INT4 NOT NULL DEFAULT '0',
-- 0x0000 0001 - No Purge
-- 0x0000 0002 - Special Channel
Expand Down Expand Up @@ -188,6 +189,7 @@ CREATE TABLE channels (

-- A channel is inactive if the manager hasn't logged in for 21 days
CREATE UNIQUE INDEX channels_name_idx ON channels(LOWER(name));
CREATE UNIQUE INDEX channels_canon_name_idx ON channels(canon_name);

-- Table for bans; channel_id references the channel entry this ban belongs to.
CREATE TABLE bans (
Expand Down
19 changes: 18 additions & 1 deletion libgnuworld/misc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace gnuworld {
/**
* Converts a character to its RFC1459 lowercase equivalent.
* In addition to standard ASCII A-Z -> a-z, RFC1459 defines:
* [ -> {, ] -> }, \\ -> |, ^ -> ~
* [ -> {, ] -> }, \ -> |, ~ -> ^
* @param c The character to convert.
* @return The RFC1459-lowercased character.
*/
Expand All @@ -62,11 +62,28 @@ unsigned char rfc1459_tolower(unsigned char c) {
return '}';
case '\\':
return '|';
case '~':
return '^';

default:
return c;
}
}

/**
* Converts a string to its RFC1459 lowercase equivalent.
* Applies rfc1459_tolower to each character.
* @param s The string to convert.
* @return The RFC1459-lowercased string.
*/
std::string rfc1459_tolower(const std::string& s) {
std::string result(s);
for (size_t i = 0; i < result.size(); ++i) {
result[i] = rfc1459_tolower(static_cast<unsigned char>(result[i]));
}
return result;
}

/**
* Compares two strings using RFC1459 case-insensitive rules.
* Returns -1 if a < b, 1 if a > b, 0 if equal (RFC1459-insensitive).
Expand Down
10 changes: 9 additions & 1 deletion libgnuworld/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,20 @@ using std::string;
/**
* Converts a character to its RFC1459 lowercase equivalent.
* In addition to standard ASCII A-Z -> a-z, RFC1459 defines:
* [ -> {, ] -> }, \\ -> |, ^ -> ~
* [ -> {, ] -> }, \\ -> |, ~ -> ^
* @param c The character to convert.
* @return The RFC1459-lowercased character.
*/
unsigned char rfc1459_tolower(unsigned char c);

/**
* Converts a string to its RFC1459 lowercase equivalent.
* Applies rfc1459_tolower to each character.
* @param s The string to convert.
* @return The RFC1459-lowercased string.
*/
std::string rfc1459_tolower(const std::string& s);

/**
* Compares two strings using RFC1459 case-insensitive rules.
* Returns -1 if a < b, 1 if a > b, 0 if equal (RFC1459-insensitive).
Expand Down
2 changes: 1 addition & 1 deletion mod.cservice/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ namespace sql {
* articles of data.
*/
const std::string channel_fields =
"id,name,flags,mass_deop_pro,flood_pro,url,channels.description,comment,keywords,registered_ts,"
"id,name,canon_name,flags,mass_deop_pro,flood_pro,url,channels.description,comment,keywords,registered_ts,"
"channel_ts,channel_mode,userflags,channels.last_updated,limit_offset,limit_period,limit_grace,"
"limit_max,max_bans,no_take,welcome,limit_joinmax,limit_joinsecs,limit_joinperiod,"
"limit_joinmode";
Expand Down
4 changes: 2 additions & 2 deletions mod.cservice/cservice.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3569,7 +3569,7 @@ bool cservice::sqlRegisterChannel(iClient* theClient, sqlUser* mngrUsr, const st
} else
newChan->commit();

sqlChannelCache.insert(cservice::sqlChannelHashType::value_type(newChan->getName(), newChan));
sqlChannelCache.insert(cservice::sqlChannelHashType::value_type(newChan->getCanonicalName(), newChan));
sqlChannelIDCache.insert(cservice::sqlChannelIDHashType::value_type(newChan->getID(), newChan));

// First delete previous levels
Expand Down Expand Up @@ -6671,7 +6671,7 @@ void cservice::preloadChannelCache() {
newChan->setAllMembers(i);
newChan->setLastUsed(currentTime());

sqlChannelCache.insert(sqlChannelHashType::value_type(newChan->getName(), newChan));
sqlChannelCache.insert(sqlChannelHashType::value_type(newChan->getCanonicalName(), newChan));
sqlChannelIDCache.insert(sqlChannelIDHashType::value_type(newChan->getID(), newChan));

} // for()
Expand Down
6 changes: 3 additions & 3 deletions mod.cservice/cservice.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class cservice : public xClient {
typedef map<string, sqlUser*, noCaseCompare> sqlUserHashType;

// Channel hash, Key is channelname.
typedef map<string, sqlChannel*, noCaseCompare> sqlChannelHashType;
typedef map<string, sqlChannel*, rfc1459Compare> sqlChannelHashType;
typedef map<int, sqlChannel*> sqlChannelIDHashType;

// Accesslevel cache, key is pair(userid, chanid).
Expand Down Expand Up @@ -622,7 +622,7 @@ class cservice : public xClient {
/* Remove a sqlChannel* from cache. */
inline void removeChannelCache(sqlChannel* theChan) {
if (theChan) {
sqlChannelCache.erase(theChan->getName());
sqlChannelCache.erase(theChan->getCanonicalName());
sqlChannelIDCache.erase(theChan->getID());
}
}
Expand Down Expand Up @@ -804,7 +804,7 @@ class cservice : public xClient {
typedef vector<ValidUserData> ValidUserDataListType;

/* List of channels in 'pending' registration state. */
typedef map<string, sqlPendingChannel*, noCaseCompare> pendingChannelListType;
typedef map<string, sqlPendingChannel*, rfc1459Compare> pendingChannelListType;
pendingChannelListType pendingChannelList;

struct AppData {
Expand Down
8 changes: 8 additions & 0 deletions mod.cservice/migrations/001_canon_names.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- 2026-03-16: MrIron
-- Added canonicalized channel name column
ALTER TABLE channels ADD COLUMN canon_name TEXT UNIQUE;
UPDATE channels SET canon_name = lower(
replace(replace(replace(replace(name, '[', '{'), ']', '}'), '\\', '|'), '~', '^')
);
ALTER TABLE channels ALTER COLUMN canon_name SET NOT NULL;
CREATE UNIQUE INDEX channels_canon_name_idx ON channels(canon_name);
63 changes: 33 additions & 30 deletions mod.cservice/sqlChannel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const int sqlChannel::EV_SUSPEND = 19;
const int sqlChannel::EV_UNSUSPEND = 20;

sqlChannel::sqlChannel(cservice* _bot)
: id(0), name(), flags(0), mass_deop_pro(3), flood_pro(0), msg_period(0), notice_period(0),
: id(0), name(), canon_name(), flags(0), mass_deop_pro(3), flood_pro(0), msg_period(0), notice_period(0),
ctcp_period(0), flood_period(0), repeat_count(0), floodlevel(FLOODPRO_KICK),
man_floodlevel(FLOODPRO_KICK), url(), description(), comment(), keywords(), welcome(),
registered_ts(0), channel_ts(0), channel_mode(), userflags(0), last_topic(0), inChan(false),
Expand Down Expand Up @@ -132,7 +132,7 @@ bool sqlChannel::loadData(const string& channelName) {
#ifdef THERETURN_ENABLED
queryString << "LEFT JOIN channels_w cw on channels.id = cw.channel_id ";
#endif
queryString << "WHERE lower(channels.name) = '" << escapeSQLChars(string_lower(channelName))
queryString << "WHERE lower(channels.canon_name) = '" << escapeSQLChars(rfc1459_tolower(channelName))
<< "'";

if (SQLDb->Exec(queryString, true))
Expand Down Expand Up @@ -197,32 +197,33 @@ void sqlChannel::setAllMembers(int row) {

id = atoi(SQLDb->GetValue(row, 0).c_str());
name = SQLDb->GetValue(row, 1);
flags = atoi(SQLDb->GetValue(row, 2).c_str());
mass_deop_pro = atoi(SQLDb->GetValue(row, 3).c_str());
flood_pro = atoi(SQLDb->GetValue(row, 4).c_str());
url = SQLDb->GetValue(row, 5);
description = SQLDb->GetValue(row, 6);
comment = SQLDb->GetValue(row, 7);
keywords = SQLDb->GetValue(row, 8);
registered_ts = atoi(SQLDb->GetValue(row, 9).c_str());
channel_ts = atoi(SQLDb->GetValue(row, 10).c_str());
channel_mode = SQLDb->GetValue(row, 11);
userflags = atoi(SQLDb->GetValue(row, 12));
last_updated = atoi(SQLDb->GetValue(row, 13));
limit_offset = atoi(SQLDb->GetValue(row, 14));
limit_period = atoi(SQLDb->GetValue(row, 15));
limit_grace = atoi(SQLDb->GetValue(row, 16));
limit_max = atoi(SQLDb->GetValue(row, 17));
max_bans = atoi(SQLDb->GetValue(row, 18));
no_take = atoi(SQLDb->GetValue(row, 19));
welcome = SQLDb->GetValue(row, 20);
limit_joinmax = atoi(SQLDb->GetValue(row, 21));
limit_joinsecs = atoi(SQLDb->GetValue(row, 22));
limit_joinperiod = atoi(SQLDb->GetValue(row, 23));
limit_joinmode = SQLDb->GetValue(row, 24);
canon_name = SQLDb->GetValue(row, 2);
flags = atoi(SQLDb->GetValue(row, 3).c_str());
mass_deop_pro = atoi(SQLDb->GetValue(row, 4).c_str());
flood_pro = atoi(SQLDb->GetValue(row, 5).c_str());
url = SQLDb->GetValue(row, 6);
description = SQLDb->GetValue(row, 7);
comment = SQLDb->GetValue(row, 8);
keywords = SQLDb->GetValue(row, 9);
registered_ts = atoi(SQLDb->GetValue(row, 10).c_str());
channel_ts = atoi(SQLDb->GetValue(row, 11).c_str());
channel_mode = SQLDb->GetValue(row, 12);
userflags = atoi(SQLDb->GetValue(row, 13));
last_updated = atoi(SQLDb->GetValue(row, 14));
limit_offset = atoi(SQLDb->GetValue(row, 15));
limit_period = atoi(SQLDb->GetValue(row, 16));
limit_grace = atoi(SQLDb->GetValue(row, 17));
limit_max = atoi(SQLDb->GetValue(row, 18));
max_bans = atoi(SQLDb->GetValue(row, 19));
no_take = atoi(SQLDb->GetValue(row, 20));
welcome = SQLDb->GetValue(row, 21);
limit_joinmax = atoi(SQLDb->GetValue(row, 22));
limit_joinsecs = atoi(SQLDb->GetValue(row, 23));
limit_joinperiod = atoi(SQLDb->GetValue(row, 24));
limit_joinmode = SQLDb->GetValue(row, 25);
#ifdef THERETURN_ENABLED
hasw = atoi(SQLDb->GetValue(row, 25)); // This must always be the last column.
w_ts = atoi(SQLDb->GetValue(row, 26));
hasw = atoi(SQLDb->GetValue(row, 26)); // This must always be the last column.
w_ts = atoi(SQLDb->GetValue(row, 27));
#endif

setAllFlood();
Expand Down Expand Up @@ -260,7 +261,9 @@ bool sqlChannel::commit() {
static const char* queryCondition = "WHERE id = ";

stringstream queryString;
queryString << queryHeader << "SET flags = " << flags << ", "
queryString << queryHeader << "SET "
<< "canon_name = '" << escapeSQLChars(canon_name) << "', "
<< "flags = " << flags << ", "
<< "mass_deop_pro = " << mass_deop_pro << ", "
<< "flood_pro = " << flood_pro << ", "
<< "url = '" << escapeSQLChars(url) << "', "
Expand Down Expand Up @@ -294,11 +297,11 @@ bool sqlChannel::commit() {
}

bool sqlChannel::insertRecord() {
static const char* queryHeader = "INSERT INTO channels (name, flags, registered_ts, "
static const char* queryHeader = "INSERT INTO channels (name, canon_name, flags, registered_ts, "
"channel_ts, channel_mode, last_updated, no_take) VALUES (";

stringstream queryString;
queryString << queryHeader << "'" << escapeSQLChars(name) << "', " << flags << ", "
queryString << queryHeader << "'" << escapeSQLChars(name) << "', '" << escapeSQLChars(canon_name) << "', " << flags << ", "
<< registered_ts << ", " << channel_ts << ", '" << escapeSQLChars(channel_mode)
<< "', "
<< "date_part('epoch', CURRENT_TIMESTAMP)::int," << no_take << ")" << ends;
Expand Down
8 changes: 7 additions & 1 deletion mod.cservice/sqlChannel.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ class sqlChannel {

inline const string& getName() const { return name; }

inline const string& getCanonicalName() const { return canon_name; }

inline const flagType& getFlags() const { return flags; }

#ifdef THERETURN_ENABLED
Expand Down Expand Up @@ -296,7 +298,10 @@ class sqlChannel {

inline void setID(const unsigned int& _id) { id = _id; }

inline void setName(const string& _name) { name = _name; }
inline void setName(const string& _name) {
name = _name;
canon_name = rfc1459_tolower(_name);
}

inline void setFlag(const flagType& whichFlag) { flags |= whichFlag; }

Expand Down Expand Up @@ -468,6 +473,7 @@ class sqlChannel {
protected:
unsigned int id;
string name;
string canon_name;
flagType flags;
unsigned short mass_deop_pro;
unsigned int flood_pro;
Expand Down