You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

414 lines
16 KiB

//! Acting as an IRC server (IRC server-to-client protocol)
//!
//! Based heavily off https://modern.ircdocs.horse/
use tokio_core::net::{TcpListener, Incoming, TcpStream};
use tokio_codec::Framed;
use irc::proto::IrcCodec;
use irc::proto::message::Message;
use irc::proto::command::Command;
use futures::sync::mpsc::{UnboundedSender, UnboundedReceiver};
use futures::{Future, Async, Poll, Stream, Sink, self};
use failure::{Error, format_err};
use std::net::{SocketAddr, ToSocketAddrs};
use std::collections::VecDeque;
use std::collections::HashMap;
use huawei_modem::pdu::DeliverPdu;
use crate::util::Result;
use crate::sender_common::Sender;
use crate::irc_s2c_registration::{PendingIrcConnectionWrapper, RegistrationInformation};
use crate::config::IrcServerConfig;
use crate::comm::InitParameters;
use crate::models::Group;
use crate::comm::*;
use crate::control_common::ControlCommon;
use crate::store::Store;
pub static SERVER_NAME: &str = "sms-irc.";
pub static USER_MODES: &str = "i";
pub static CHANNEL_MODES: &str = "nt";
pub static MOTD: &str = r#"Welcome to sms-irc!
This is the experimental IRC server backend.
Please refer to https://git.theta.eu.org/sms-irc.git/about/
for more information about sms-irc.
Alternatively, come and chat to us in #sms-irc on chat.freenode.net
if you have comments or want help using the software!"#;
pub struct IrcConnection {
sock: Framed<TcpStream, IrcCodec>,
addr: SocketAddr,
reginfo: RegistrationInformation,
outbox: Vec<Message>,
store: Store,
/// map from channel name to group info
joined_groups: HashMap<String, Group>,
wa_outbox: VecDeque<WhatsappCommand>,
m_outbox: VecDeque<ModemCommand>,
cf_outbox: VecDeque<ContactFactoryCommand>,
new: bool
}
pub struct IrcServer {
cf_rx: UnboundedReceiver<ContactFactoryCommand>,
cb_rx: UnboundedReceiver<ControlBotCommand>,
wa_tx: UnboundedSender<WhatsappCommand>,
m_tx: UnboundedSender<ModemCommand>,
_cfg: IrcServerConfig,
store: Store,
incoming: Incoming,
connections: Vec<IrcConnection>,
pending: Vec<PendingIrcConnectionWrapper>
}
impl Future for IrcConnection {
type Item = ();
type Error = Error;
fn poll(&mut self) -> Poll<(), Error> {
if self.new {
self.new = false;
self.on_new()?;
}
while let Async::Ready(msg) = self.sock.poll()? {
let msg = msg.ok_or(format_err!("Socket disconnected"))?;
trace!("<-- [{}] {}", self.addr, msg);
self.handle_remote_message(msg)?;
}
sink_outbox!(self, outbox, sock, self.addr);
Ok(Async::NotReady)
}
}
impl Future for IrcServer {
type Item = ();
type Error = Error;
fn poll(&mut self) -> Poll<(), Error> {
while let Async::Ready(inc) = self.incoming.poll()? {
let (ts, sa) = inc.ok_or(format_err!("TCP listener stopped"))?;
info!("New connection from {}", sa);
let pending = PendingIrcConnectionWrapper::from_incoming(ts, sa, self.store.clone())?;
self.pending.push(pending);
}
let mut to_remove = vec![];
for (i, p) in self.pending.iter_mut().enumerate() {
match p.poll() {
Ok(Async::Ready(c)) => {
info!("Connection on {} completed registration", c.addr);
self.connections.push(c);
to_remove.push(i);
},
Ok(Async::NotReady) => {},
Err(e) => {
to_remove.push(i);
info!("Pending connection closed: {}", e);
}
}
}
while let Some(i) = to_remove.pop() {
self.pending.remove(i);
}
while let Async::Ready(cbc) = self.cb_rx.poll().unwrap() {
let cbc = cbc.ok_or(format_err!("cb_rx stopped"))?;
self.handle_control(cbc)?;
}
while let Async::Ready(cfc) = self.cf_rx.poll().unwrap() {
let cfc = cfc.ok_or(format_err!("cf_rx stopped"))?;
self.handle_contact(cfc)?;
}
for (i, c) in self.connections.iter_mut().enumerate() {
if let Err(e) = c.poll() {
info!("Connection on {} closed: {}", c.addr, e);
to_remove.push(i);
}
while let Some(wac) = c.wa_outbox.pop_front() {
self.wa_tx.unbounded_send(wac).unwrap();
}
while let Some(mc) = c.m_outbox.pop_front() {
self.m_tx.unbounded_send(mc).unwrap();
}
}
while let Some(i) = to_remove.pop() {
self.connections.remove(i);
}
Ok(Async::NotReady)
}
}
impl IrcServer {
pub fn new(p: InitParameters<IrcServerConfig>) -> Result<Self> {
let store = p.store;
let cfg = p.cfg2.clone();
let addr = cfg.listen.to_socket_addrs()?
.nth(0)
.ok_or(format_err!("no listen addresses found"))?;
let listener = TcpListener::bind(&addr, &p.hdl)?;
info!("Listening on {} for connections", addr);
let incoming = listener.incoming();
Ok(Self {
store, _cfg: cfg, incoming,
cb_rx: p.cm.cb_rx.take().unwrap(),
cf_rx: p.cm.cf_rx.take().unwrap(),
wa_tx: p.cm.wa_tx.clone(),
m_tx: p.cm.modem_tx.clone(),
connections: vec![],
pending: vec![]
})
}
pub fn handle_control(&mut self, cmd: ControlBotCommand) -> Result<()> {
for c in self.connections.iter_mut() {
if let Err(e) = c.handle_control(cmd.clone()) {
warn!("Connection on {} failed to handle control: {}", c.addr, e);
}
}
Ok(())
}
pub fn handle_contact(&mut self, cfc: ContactFactoryCommand) -> Result<()> {
if let ContactFactoryCommand::ProcessMessages = cfc {
for c in self.connections.iter_mut() {
if let Err(e) = c.process_messages() {
warn!("Connection on {} failed to process messages: {}", c.addr, e);
}
}
}
Ok(())
}
}
impl IrcConnection {
pub fn from_pending(
sock: Framed<TcpStream, IrcCodec>,
addr: SocketAddr,
store: Store,
reginfo: RegistrationInformation
) -> Self {
Self {
sock, addr, reginfo, store,
outbox: vec![],
joined_groups: HashMap::new(),
wa_outbox: VecDeque::new(),
m_outbox: VecDeque::new(),
cf_outbox: VecDeque::new(),
new: true
}
}
pub fn handle_control(&mut self, cmd: ControlBotCommand) -> Result<()> {
use self::ControlBotCommand::*;
match cmd {
Log(thing) => {
self.outbox.push(Message::new(Some("root"), "PRIVMSG", vec!["&smsirc"], Some(&thing))?);
},
ReportFailure(thing) => {
// FIXME: make shoutier
self.reply_s2c("PRIVMSG", vec![], Some(&thing as &str))?;
},
CommandResponse(thing) => {
self.outbox.push(Message::new(Some("root"), "PRIVMSG", vec!["&smsirc"], Some(&thing))?);
},
ProcessGroups => {}
}
Ok(())
}
fn reply_s2c<'a, T: Into<Option<&'a str>>>(&mut self, cmd: &str, args: Vec<&str>, suffix: T) -> Result<()> {
let mut new_args = vec![&self.reginfo.nick as &str];
new_args.extend(args.into_iter());
self.outbox.push(Message::new(Some(&SERVER_NAME), cmd, new_args, suffix.into())?);
Ok(())
}
fn reply_from_user<'a, T: Into<Option<&'a str>>>(&mut self, cmd: &str, args: Vec<&str>, suffix: T) -> Result<()> {
let host = format!("{}!{}@{}", self.reginfo.nick, self.reginfo.user, self.addr.ip());
self.outbox.push(Message::new(Some(&host), cmd, args, suffix.into())?);
Ok(())
}
fn reply_from_nick<'a, T: Into<Option<&'a str>>>(&mut self, from: &str, cmd: &str, args: Vec<&str>, suffix: T) -> Result<()> {
// FIXME: make hostmask not suck
let host = format!("{}!{}@sms-irc.", from, from);
self.outbox.push(Message::new(Some(&host), cmd, args, suffix.into())?);
Ok(())
}
fn on_new(&mut self) -> Result<()> {
self.reply_s2c("001", vec![], "Welcome to sms-irc, a SMS/WhatsApp to IRC bridge!")?;
self.reply_s2c("002", vec![], &format!("This is sms-irc version {}, running in IRC server mode.", env!("CARGO_PKG_VERSION")) as &str)?;
self.reply_s2c("003", vec![], "(This server doesn't keep creation timestamp information at present.)")?;
let server_version = format!("sms-irc-{}", env!("CARGO_PKG_VERSION"));
self.reply_s2c("004", vec![SERVER_NAME, &server_version, USER_MODES, CHANNEL_MODES], None)?;
self.reply_s2c("005",
vec!["AWAYLEN=200", "CASEMAPPING=ascii", "NETWORK=sms-irc", "NICKLEN=100", "PREFIX=(qaohv)~&@%+"],
"are supported by this server")?;
self.send_motd()?;
self.setup_control_channel()?;
for grp in self.store.get_all_groups()? {
self.setup_group(grp)?;
}
Ok(())
}
pub fn process_messages(&mut self) -> Result<()> {
use std::convert::TryFrom;
for msg in self.store.get_all_messages()? {
debug!("Processing message #{}", msg.id);
let addr = msg.get_addr()?;
let recip = match self.store.get_recipient_by_addr_opt(&addr)? {
Some(r) => r,
None => {
warn!("stub impl doesn't make new recipients yet");
continue;
},
};
if msg.pdu.is_some() {
let pdu = DeliverPdu::try_from(msg.pdu.as_ref().unwrap() as &[u8])?;
self.process_msg_pdu(&recip.nick, msg, pdu)?;
}
else {
self.process_msg_plain(&recip.nick, msg)?;
}
}
Ok(())
}
fn setup_group(&mut self, grp: Group) -> Result<()> {
self.reply_from_user("JOIN", vec![&grp.channel], None)?;
self.reply_s2c("332", vec![&grp.channel], Some(&grp.topic as &str))?;
self.reply_s2c("353", vec!["=", &grp.channel], Some(&format!("&{}", self.reginfo.nick) as &str))?;
let mut recips = Vec::with_capacity(grp.participants.len());
for id in grp.participants.iter() {
recips.push(self.store.get_recipient_by_id_opt(*id)?
.ok_or(format_err!("recipient group wat"))?);
}
for recips in recips.chunks(5) {
let nicks = recips
.iter()
.map(|x| {
let op = if grp.admins.contains(&x.id) {
"@"
}
else {
""
};
format!("{}{} ", op, x.nick)
})
.collect::<String>();
self.reply_s2c("353", vec!["@", &grp.channel], Some(&nicks as &str))?;
}
self.reply_s2c("366", vec![&grp.channel], Some("End of /NAMES list."))?;
self.joined_groups.insert(grp.channel.clone(), grp);
Ok(())
}
fn setup_control_channel(&mut self) -> Result<()> {
self.reply_from_user("JOIN", vec!["&smsirc"], None)?;
self.reply_s2c("332", vec!["&smsirc"], Some("sms-irc admin channel"))?;
self.reply_s2c("353", vec!["@", "&smsirc"], Some(&format!("&{} ~root", self.reginfo.nick) as &str))?;
for recips in self.store.get_all_recipients()?.chunks(5) {
let nicks = recips
.iter()
.map(|x| format!("{} ", x.nick))
.collect::<String>();
self.reply_s2c("353", vec!["@", "&smsirc"], Some(&nicks as &str))?;
}
self.reply_s2c("366", vec!["&smsirc"], Some("End of /NAMES list."))?;
self.reply_s2c("324", vec!["&smsirc"], None)?;
Ok(())
}
fn send_motd(&mut self) -> Result<()> {
self.reply_s2c("375", vec![], "- Message of the day -")?;
for line in MOTD.lines() {
self.reply_s2c("372", vec![], Some(line))?;
}
self.reply_s2c("376", vec![], "End of /MOTD command.")?;
Ok(())
}
fn handle_remote_message(&mut self, msg: Message) -> Result<()> {
match msg.command {
Command::PING(tok, _) => {
self.reply_s2c("PONG", vec![], Some(&tok as &str))?;
},
Command::QUIT(_) => {
Err(format_err!("Client quit"))?;
},
Command::NICK(new) => {
self.reply_from_user("NICK", vec![&new], None)?;
self.reginfo.nick = new;
},
Command::JOIN(_, _, _) => {
// Just ignore /JOIN requests at present, since we autojoin.
},
Command::PART(chan, _) => {
// This is ERR_NOTONCHANNEL, which isn't amazing.
self.reply_s2c("442", vec![&chan], Some("You may not part."))?;
},
Command::ChannelMODE(target, modes) => {
if modes.len() > 0 {
self.reply_s2c("482", vec![&target], Some("You may not alter channel modes."))?;
}
else {
self.reply_s2c("324", vec![&target, "+nt"], None)?;
}
},
Command::PRIVMSG(target, msg) => {
if target == "&smsirc" {
self.process_admin_command(msg)?;
}
else if target.starts_with("#") {
// FIXME: check the channel actually exists
self.wa_outbox.push_back(WhatsappCommand::SendGroupMessage(target, msg));
}
else {
if let Some(recip) = self.store.get_recipient_by_nick_opt(&target)? {
let addr = recip.get_addr()?;
if recip.whatsapp {
self.wa_outbox.push_back(WhatsappCommand::SendDirectMessage(addr, msg));
}
else {
self.m_outbox.push_back(ModemCommand::SendMessage(addr, msg));
}
}
else {
self.reply_s2c("401", vec![&target], "Unknown nickname.")?;
}
}
},
u => {
// FIXME: the irc crate is hacky, and requires hacky workarounds
let st: String = (&u).into();
warn!("Got unknown command: {}", st.trim());
let verb = st.split(" ").next().unwrap();
self.reply_s2c("421", vec![verb], "Unknown or unimplemented command.")?;
}
}
Ok(())
}
}
impl ControlCommon for IrcConnection {
fn cf_send(&mut self, c: ContactFactoryCommand) {
self.cf_outbox.push_back(c);
}
fn wa_send(&mut self, c: WhatsappCommand) {
self.wa_outbox.push_back(c);
}
fn m_send(&mut self, c: ModemCommand) {
self.m_outbox.push_back(c);
}
fn control_response(&mut self, msg: &str) -> Result<()> {
self.outbox.push(Message::new(Some("root"), "PRIVMSG", vec!["&smsirc"], Some(msg))?);
Ok(())
}
}
impl Sender for IrcConnection {
fn report_error(&mut self, from_nick: &str, err: String) -> Result<()> {
self.reply_from_nick(from_nick, "NOTICE", vec![&self.reginfo.nick.clone()], Some(&err as &str))?;
Ok(())
}
fn store(&mut self) -> &mut Store {
&mut self.store
}
fn private_target(&mut self) -> String {
self.reginfo.nick.clone()
}
fn send_irc_message(&mut self, from_nick: &str, to: &str, msg: &str) -> Result<()> {
self.reply_from_nick(from_nick, "PRIVMSG", vec![to], Some(&msg as &str))?;
Ok(())
}
}