this repo has no description

irc: implement portions of MODE

Just so we can set chanops

rockorager.dev eaacb7eb 9269e68d

verified
+132
+132
src/main.zig
··· 950 950 .PASS => {}, 951 951 .PING => try self.handlePing(conn, msg), 952 952 .QUIT => try self.handleQuit(conn, msg), 953 + .MODE => try self.handleMode(conn, msg), 953 954 954 955 .JOIN => try self.handleJoin(conn, msg), 955 956 .PART => try self.handlePart(conn, msg), ··· 1227 1228 return error.ClientQuit; 1228 1229 } 1229 1230 1231 + fn handleMode(self: *Server, conn: *Connection, msg: Message) Allocator.Error!void { 1232 + var iter = msg.paramIterator(); 1233 + const target = iter.next() orelse return self.errNeedMoreParams(conn, "MODE"); 1234 + if (target.len == 0) return self.errNeedMoreParams(conn, "MODE"); 1235 + 1236 + if (target[0] != '#') { 1237 + // User MODE 1238 + const source = conn.user orelse 1239 + return self.errUnknownError(conn, "MODE", "must be authenticated for MODE"); 1240 + _ = source; 1241 + // TODO: implement this 1242 + return; 1243 + } 1244 + 1245 + // Target is a channel 1246 + const channel = self.channels.get(target) orelse { 1247 + return self.errNoSuchChannel(conn, target); 1248 + }; 1249 + 1250 + const modestring = iter.next() orelse { 1251 + // TODO: send the channel mode. We don't have any right now 1252 + return; 1253 + }; 1254 + 1255 + // If we have a modestring, we also have to be authenticated 1256 + const user = conn.user orelse { 1257 + return self.errChanOpPrivsNeeded(conn, channel.name); 1258 + }; 1259 + 1260 + const privs = channel.getPrivileges(user); 1261 + 1262 + if (!user.modes.operator and !privs.operator) { 1263 + // User either needs to be global ops or chanops 1264 + return self.errChanOpPrivsNeeded(conn, channel.name); 1265 + } 1266 + 1267 + // We have the right privileges. Get the arguments (we should have one, a nickname) 1268 + const arg = iter.next() orelse return self.errNeedMoreParams(conn, "MODE"); 1269 + 1270 + // Validate the argument 1271 + if (arg.len == 0) return self.errNeedMoreParams(conn, "MODE"); 1272 + if (arg[0] == '#') return self.errUnknownError(conn, "MODE", "argument cannot be a channel"); 1273 + 1274 + // Get the target user we are modifying the mode of 1275 + const target_user = self.nick_map.get(arg) orelse { 1276 + return self.errNoSuchNick(conn, arg); 1277 + }; 1278 + 1279 + // Get the target_users current privileges 1280 + var target_privs = channel.getPrivileges(target_user); 1281 + 1282 + // Parse and apply the privileges 1283 + const State = enum { none, add, remove }; 1284 + var state: State = .none; 1285 + for (modestring) |b| { 1286 + switch (b) { 1287 + '+' => state = .add, 1288 + '-' => state = .remove, 1289 + 'o' => { 1290 + switch (state) { 1291 + .add => target_privs.operator = true, 1292 + .remove => target_privs.operator = false, 1293 + .none => {}, 1294 + } 1295 + }, 1296 + else => log.warn("unsupported mode byte: {c}", .{b}), 1297 + } 1298 + } 1299 + 1300 + // Update the state in mem and db 1301 + return channel.storePrivileges(self, target_user, target_privs); 1302 + } 1303 + 1230 1304 fn handlePrivMsg(self: *Server, conn: *Connection, msg: Message) Allocator.Error!void { 1231 1305 const source = conn.user orelse 1232 1306 return self.errUnknownError(conn, "PRIVMSG", "cannot PRIVMSG before authentication"); ··· 1888 1962 ); 1889 1963 } 1890 1964 1965 + fn errChanOpPrivsNeeded(self: *Server, conn: *Connection, channel: []const u8) Allocator.Error!void { 1966 + try conn.print( 1967 + self.gpa, 1968 + ":{s} 482 {s} {s} :You must be a channel operator to perform that command\r\n", 1969 + .{ self.hostname, conn.nickname(), channel }, 1970 + ); 1971 + } 1972 + 1891 1973 fn errUnknownError( 1892 1974 self: *Server, 1893 1975 conn: *Connection, ··· 2000 2082 // Add the channel to the user 2001 2083 try user.channels.append(server.gpa, channel); 2002 2084 } 2085 + } 2086 + 2087 + fn updatePrivileges( 2088 + server: *Server, 2089 + user: *User, 2090 + privs: ChannelPrivileges, 2091 + channel: []const u8, 2092 + ) void { 2093 + const conn = server.db_pool.acquire(); 2094 + defer server.db_pool.release(conn); 2095 + 2096 + const sql = 2097 + \\UPDATE channel_membership 2098 + \\SET privileges = ? 2099 + \\WHERE channel_id = ( 2100 + \\ SELECT id FROM channels WHERE name = ? 2101 + \\) 2102 + \\AND user_id = ( 2103 + \\ SELECT id FROM users WHERE nick = ? 2104 + \\); 2105 + ; 2106 + const privs_as_int: u1 = @bitCast(privs); 2107 + conn.exec(sql, .{ privs_as_int, channel, user.nick }) catch |err| { 2108 + log.err("updating privileges: {}: {s}", .{ err, conn.lastError() }); 2109 + return; 2110 + }; 2003 2111 } 2004 2112 2005 2113 /// Checks if a user is already in the db. If they are, checks their nick is the same. Updates ··· 3010 3118 .{ server.hostname, client, self.name }, 3011 3119 ); 3012 3120 try server.queueWrite(conn.client, conn); 3121 + } 3122 + 3123 + fn getPrivileges(self: *Channel, user: *User) ChannelPrivileges { 3124 + for (self.members.items) |m| { 3125 + if (m.user == user) { 3126 + return m.privileges; 3127 + } 3128 + } 3129 + return .none; 3130 + } 3131 + 3132 + /// Updates the privileges of user. Saves to the db 3133 + fn storePrivileges(self: *Channel, server: *Server, user: *User, privs: ChannelPrivileges) !void { 3134 + for (self.members.items) |*m| { 3135 + if (m.user == user) { 3136 + m.privileges = privs; 3137 + // Save to the db 3138 + try server.thread_pool.spawn( 3139 + db.updatePrivileges, 3140 + .{ server, user, privs, self.name }, 3141 + ); 3142 + return; 3143 + } 3144 + } 3013 3145 } 3014 3146 }; 3015 3147