<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let server =
        IrcServer::new("config.json").expect("Couldn't initialize server with config.json");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               None,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            let this_channel_data =
                                channel_data
                                    .entry(target.clone())
                                    .or_insert_with(|| ChannelData::new(&options));
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       Some(this_channel_data),
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      options: &HashMap<String, String>,
                      this_channel_data: Option<&mut ChannelData>,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = if response_is_action {
            format!("\x01ACTION {}\x01", line_with_nick)
        } else {
            line_with_nick
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"Topic github: <url>\", or if the \"Topic:\" line is a github URL.");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "bye" {
        match this_channel_data {
            None => {
                send_line(response_username, "'bye' only works in a channel");
            }
            Some(this_channel_data) => {
                this_channel_data.end_topic();
                server
                    .send(Command::PART(String::from(response_target),
                                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                                     response_username.unwrap()))))
                    .unwrap();
            }
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

/// A case-insensitive version of starts_with.
fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

/// Remove a case-insensitive start of the line, and if that prefix is
/// present return the rest of the line.
fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<commit_msg>Take the path to the configuration file on the command line.<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::env;
use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let config_file = {
        let mut config_file = None;
        let mut arg_count = 0;
        for arg in env::args_os() {
            arg_count = arg_count + 1;
            if arg_count == 1 {
                continue;
            }
            if arg_count > 2 {
                panic!("Expected only a single command-line argument, the JSON configuration file.");
            }
            config_file = Some(arg);
        }
        config_file.expect("Expected a single command-line argument, the JSON configuration file.")
    };

    let server = IrcServer::new(config_file).expect("Couldn't initialize server with given configuration file");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               None,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            let this_channel_data =
                                channel_data
                                    .entry(target.clone())
                                    .or_insert_with(|| ChannelData::new(&options));
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       Some(this_channel_data),
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      options: &HashMap<String, String>,
                      this_channel_data: Option<&mut ChannelData>,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = if response_is_action {
            format!("\x01ACTION {}\x01", line_with_nick)
        } else {
            line_with_nick
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"Topic github: <url>\", or if the \"Topic:\" line is a github URL.");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "bye" {
        match this_channel_data {
            None => {
                send_line(response_username, "'bye' only works in a channel");
            }
            Some(this_channel_data) => {
                this_channel_data.end_topic();
                server
                    .send(Command::PART(String::from(response_target),
                                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                                     response_username.unwrap()))))
                    .unwrap();
            }
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

/// A case-insensitive version of starts_with.
fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

/// Remove a case-insensitive start of the line, and if that prefix is
/// present return the rest of the line.
fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate vagment;

use std::env;
use vagment::logger;

fn main() {
    let version = env!("CARGO_PKG_VERSION");
    let command = env::args().nth(1);

    if command.is_none() {
        logger::error("Please provide a command");
    } else {
        if command.unwrap() == "version" {
            logger::info(version);
        }
    }
}
<commit_msg>Add vagrant global status output parsing<commit_after>extern crate vagment;

use std::env;
use std::process::Command;
use vagment::logger;
//
#[derive(Debug)]
struct Machine {
    id: String,
    name: String,
    provider: String,
    state: String,
    directory: String
}

// trait Const {
//     fn from_output_line(&self, &str: line) -> Machine;
// }
//
// impl Const for Machine {
//     fn from_output_line(&self, &str: line) -> Machine {
//         let mut words = line.to_string().split_whitespace();
//
//         let id = words.next().unwrap().to_string();
//         let name = words.next().unwrap().to_string();
//         let provider = words.next().unwrap().to_string();
//         let state = words.next().unwrap().to_string();
//         let directory = words.next().unwrap().to_string();
//
//         Machine{
//             id: id,
//             name: name,
//             provider: provider,
//             state: state,
//             directory: directory
//         }
//     }
// }

fn get_vm_info() {
    let output = Command::new("vagrant")
        .arg("global-status")
        .output()
        .expect("ls command failed to start");

    // println!("status: {}", output.status);
    // println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    // println!("stderr: {}", String::from_utf8_lossy(&output.stderr));

    let owned = String::from_utf8_lossy(&output.stdout).into_owned();
    let lines = owned.lines()
        .skip(2)
        .filter(|x| x.split_whitespace().count() == 5);

    lines.map(|line| {
        let mut words = line.split_whitespace();

        let id = words.next().unwrap().to_string();
        let name = words.next().unwrap().to_string();
        let provider = words.next().unwrap().to_string();
        let state = words.next().unwrap().to_string();
        let directory = words.next().unwrap().to_string();

        Machine{
            id: id,
            name: name,
            provider: provider,
            state: state,
            directory: directory
        }
    })
}

fn main() {
    let version = env!("CARGO_PKG_VERSION");
    let command = env::args().nth(1);

    let o = get_vm_info();

    // println!("{:?}", o);

    if command.is_none() {
        logger::error("Please provide a command");
    } else {
        if command.unwrap() == "version" {
            logger::info(version);
        }
    }
}
<|endoftext|>


<commit_before>#![allow(unstable)]

use parser::Parser;

mod token;
mod parser;
mod error;

fn main() {
    let mut parser = Parser::new("C3HeH4");
    println!("{:?}", parser.parse_molecule());
}<commit_msg>Add flag to not compile main when testing<commit_after>#![allow(unstable)]

use parser::Parser;

mod token;
mod parser;
mod error;

#[cfg(not(test))]
fn main() {
    let mut parser = Parser::new("C3HeH4");
    println!("{:?}", parser.parse_molecule());
}<|endoftext|>


<commit_before>// Spec found at https://tools.ietf.org/html/rfc959

/*
 * FIXME Filezilla says: Le serveur ne supporte pas les caractères non-ASCII.
 */

#![feature(proc_macro, conservative_impl_trait, generators)]

extern crate bytes;
#[macro_use]
extern crate cfg_if;
extern crate futures_await as futures;
extern crate time;
extern crate tokio_core;
extern crate tokio_io;

mod cmd; // FIXME: rename this module.
mod codec;
mod ftp;

use std::env;
use std::fs::{DirEntry, File, Metadata, read_dir};
use std::io::{self, Read};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;

use futures::{Sink, Stream};
use futures::prelude::{async, await};
use futures::stream::SplitSink;
use tokio_core::reactor::{Core, Handle};
use tokio_core::net::{TcpListener, TcpStream};
use tokio_io::AsyncRead;
use tokio_io::codec::Framed;

use cmd::{Command, TransferType};
use codec::{FtpCodec, StringCodec};
use ftp::{Answer, ResultCode};

const MONTHS: [&'static str; 12] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

type DataWriter = SplitSink<Framed<TcpStream, StringCodec>>;
type Writer = SplitSink<Framed<TcpStream, FtpCodec>>;

cfg_if! {
    if #[cfg(windows)] {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::windows::prelude::*;
            (time::at(time::Timespec::new(meta.last_write_time())), meta.file_size())
        }
    } else {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::unix::prelude::*;
            (time::at(time::Timespec::new(meta.mtime(), 0)), meta.size())
        }
    }
}

fn add_file_info(entry: DirEntry, out: &mut String) {
    let path = entry.path(); // TODO: handle error.
    let extra = if path.is_dir() { "/" } else { "" };
    let is_dir = if path.is_dir() { "d" } else { "-" };

    let meta = ::std::fs::metadata(&path).unwrap(); // TODO: handle error.
    let (time, file_size) = get_file_info(&meta);
    let path = path.to_str().unwrap().split("/").last().unwrap(); // TODO: handle error.
    let file_str = format!("{} {} {} {} {}:{} {}{}\r\n",
                           is_dir,
                           file_size,
                           MONTHS[time.tm_mon as usize],
                           time.tm_mday,
                           time.tm_hour,
                           time.tm_min,
                           path,
                           extra);
    out.push_str(&file_str);
    println!("==> {:?}", &file_str);
}

#[allow(dead_code)]
struct Client {
    address: String, // TODO: remove this?
    cwd: PathBuf,
    data_port: Option<u16>,
    data_writer: Option<DataWriter>,
    handle: Handle,
    name: Option<String>,
    server_root: PathBuf,
    transfer_type: TransferType,
    writer: Writer,
}

impl Client {
    fn new(address: String, handle: Handle, writer: Writer, server_root: PathBuf) -> Client {
        Client {
            address,
            cwd: PathBuf::from(""),
            data_port: None,
            data_writer: None,
            handle,
            name: None,
            server_root,
            transfer_type: TransferType::Ascii,
            writer,
        }
    }

    #[async]
    fn handle_cmd(mut self, cmd: Command) -> Result<Self, ()> {
        println!("Received command: {:?}", cmd);
        match cmd {
            Command::Auth =>
                self = await!(self.send(Answer::new(ResultCode::CommandNotImplemented, "Not implemented")))?
            ,
            Command::Cwd(directory) => self = await!(self.cwd(directory))?,
            Command::List(path) => self = await!(self.list(path))?,
            Command::Pasv => self = await!(self.pasv())?,
            Command::Port(port) => {
                self.data_port = Some(port);
                self = await!(self.send(Answer::new(ResultCode::Ok, &format!("Data port is now {}", port))))?;
            },
            Command::Pwd => {
                let message = format!("\"/{}\" ", self.cwd.to_str().unwrap());
                self = await!(self.send(Answer::new(ResultCode::PATHNAMECreated, &message)))?;
            }, // TODO: handle error.
            Command::Quit => self = await!(self.quit())?,
            Command::Retr(file) => self = await!(self.retr(file))?,
            Command::Syst => {
                self = await!(self.send(Answer::new(ResultCode::Ok, "I won't tell!")))?;
            },
            Command::Type(typ) => {
                self.transfer_type = typ;
                self = await!(self.send(Answer::new(ResultCode::Ok, "Transfer type changed successfully")))?;
            },
            Command::Unknown =>
                self = await!(self.send(Answer::new(ResultCode::UnknownCommand, "Not implemented")))?
            ,
            Command::User(content) => {
                if content.is_empty() {
                    self = await!(self.send(Answer::new(ResultCode::InvalidParameterOrArgument, "Invalid username")))?;
                } else {
                    self.name = Some(content.to_owned());
                    self = await!(self.send(Answer::new(ResultCode::UserloggedIn, &format!("Welcome {}!", content))))?;
                }
            }
        }
        Ok(self)
    }

    fn complete_path(self, path: PathBuf) -> Result<(PathBuf, Self), io::Error> {
        let directory = self.server_root.join(if path.has_root() {
            path.iter().skip(1).collect()
        } else {
            path
        });
        let dir = directory.canonicalize();
        println!("===> {:?} {:?}", dir, directory);
        if let Ok(ref dir) = dir {
            if !dir.starts_with(&self.server_root) {
                return Err(io::ErrorKind::PermissionDenied.into());
            }
        }
        dir.map(|dir| (dir, self))
    }

    #[async]
    fn cwd(mut self, directory: String) -> Result<Self, ()> {
        let mut directory = PathBuf::from(directory);
        let path = self.cwd.join(&directory);
        let (dir, new_self) = self.complete_path(path).unwrap(); // TODO: handle error.
        self = new_self;
        self.cwd = dir.strip_prefix(&self.server_root).unwrap().to_path_buf(); // TODO: handle error.
        println!("switched to {:?}", self.cwd);
        self = await!(self.send(Answer::new(ResultCode::Ok,
                                            &format!("Directory changed to \"{}\"", directory.display()))))?;
        Ok(self)
    }

    #[async]
    fn list(mut self, path: Option<PathBuf>) -> Result<Self, ()> {
        if self.data_writer.is_some() {
            let path = self.cwd.join(path.unwrap_or_default());
            let directory = PathBuf::from(&path);
            let (dir, new_self) = self.complete_path(directory).unwrap(); // TODO: handle error.
            self = new_self;
            self = await!(self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen,
                                                "Starting to list directory...")))?;
            let mut out = String::new();
            for entry in read_dir(dir).unwrap() { // TODO: handle error.
                add_file_info(entry.unwrap(), &mut out); // TODO: handle error.
            }
            self = await!(self.send_data(out))?;
            println!("-> and done!");
        } else {
            self = await!(self.send(Answer::new(ResultCode::ConnectionClosed, "No opened data connection")))?;
        }
        if self.data_writer.is_some() {
            self.data_writer = None;
            self = await!(self.send(Answer::new(ResultCode::ClosingDataConnection, "Transfer done")))?;
        }
        Ok(self)
    }

    #[async]
    fn pasv(mut self) -> Result<Self, ()> {
        let port =
            if let Some(port) = self.data_port {
                port
            } else {
                0
            };
        if self.data_writer.is_some() {
            self = await!(self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Already listening...")))?;
            return Ok(self);
        }

        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
        let listener = TcpListener::bind(&addr, &self.handle).unwrap(); // TODO: handle error
        let port = listener.local_addr().unwrap().port(); // TODO: handle error.

        self = await!(self.send(Answer::new(ResultCode::EnteringPassiveMode,
                              &format!("127,0,0,1,{},{}", port >> 8, port & 0xFF))))?;

        println!("Waiting clients on port {}...", port);
        // TODO: use into_future() instead of for loop?
        #[async]
        for (stream, _rest) in listener.incoming().map_err(|_| ()) {
            let (writer, _reader) = stream.framed(StringCodec).split();
            self.data_writer = Some(writer);
            break;
        }
        Ok(self)
    }

    #[async]
    fn quit(mut self) -> Result<Self, ()> {
        if self.data_writer.is_some() {
            unimplemented!();
        } else {
            self = await!(self.send(Answer::new(ResultCode::ServiceClosingControlConnection, "Closing connection...")))?;
            self.writer.close().unwrap(); // TODO: handle error.
        }
        Ok(self)
    }

    #[async]
    fn retr(mut self, path: PathBuf) -> Result<Self, ()> {
        // TODO: check if multiple data connection can be opened at the same time.
        if self.data_writer.is_some() {
            let path = self.cwd.join(path);
            let (dir, new_self) = self.complete_path(path).unwrap(); // TODO: handle error.
            self = new_self;
            if dir.is_file() {
                self = await!(self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Starting to send file...")))?;
                let mut file = File::open(dir).unwrap(); // TODO: handle error.
                let mut out = String::new();
                file.read_to_string(&mut out).unwrap(); // TODO: handle error.
                self = await!(self.send_data(out))?;
                println!("-> file transfer done!");
            } else {
                self = await!(self.send(Answer::new(ResultCode::LocalErrorInProcessing,
                                      &format!("\"{}\" doesn't exist", dir.to_str().unwrap()))))?;
            }
        } else {
            self = await!(self.send(Answer::new(ResultCode::ConnectionClosed, "No opened data connection")))?;
        }
        if self.data_writer.is_some() {
            self.data_writer = None;
            self = await!(self.send(Answer::new(ResultCode::ClosingDataConnection, "Transfer done")))?;
        }
        Ok(self)
    }

    #[async]
    fn send(mut self, answer: Answer) -> Result<Self, ()> {
        self.writer = await!(self.writer.send(answer)).map_err(|_| ())?;
        Ok(self)
    }

    #[async]
    fn send_data(mut self, data: String) -> Result<Self, ()> {
        if let Some(mut writer) = self.data_writer {
            self.data_writer = Some(await!(writer.send(data)).map_err(|_| ())?);
        }
        Ok(self)
    }
}

#[async]
fn handle_client(stream: TcpStream, handle: Handle, address: String, server_root: PathBuf) -> Result<(), ()> {
    let (writer, reader) = stream.framed(FtpCodec).split();
    let writer = await!(writer.send(Answer::new(ResultCode::ServiceReadyForNewUser, "Welcome to this FTP server!")))
        .map_err(|_| ())?;
    let mut client = Client::new(address, handle, writer, server_root);
    #[async]
    for cmd in reader.map_err(|_| ()) {
        client = await!(client.handle_cmd(cmd))?;
    }
    println!("Client closed");
    Ok(())
}

#[async]
fn server(handle: Handle, server_root: PathBuf) -> io::Result<()> {
    let port = 1234;
    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
    let listener = TcpListener::bind(&addr, &handle).unwrap();

    println!("Waiting clients on port {}...", port);
    #[async]
    for (stream, addr) in listener.incoming() {
        let address = format!("[address : {}]", addr);
        println!("New client: {}", address);
        handle.spawn(handle_client(stream, handle.clone(), address, server_root.clone()));
        println!("Waiting another client...");
    }
    Ok(())
}

fn main() {
    let mut core = Core::new()
        .expect("Cannot create tokio Core");
    let handle = core.handle();

    let server_root = env::current_dir().unwrap(); // TODO: handle error.

    core.run(server(handle, server_root))
        .expect("Run tokio server");
}
<commit_msg>Add FIXME<commit_after>// Spec found at https://tools.ietf.org/html/rfc959

/*
 * FIXME: Filezilla says: Le serveur ne supporte pas les caractères non-ASCII.
 * FIXME: ftp cli says "WARNING! 71 bare linefeeds received in ASCII mode" when retrieving a file.
 */

#![feature(proc_macro, conservative_impl_trait, generators)]

extern crate bytes;
#[macro_use]
extern crate cfg_if;
extern crate futures_await as futures;
extern crate time;
extern crate tokio_core;
extern crate tokio_io;

mod cmd; // FIXME: rename this module.
mod codec;
mod ftp;

use std::env;
use std::fs::{DirEntry, File, Metadata, read_dir};
use std::io::{self, Read};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;

use futures::{Sink, Stream};
use futures::prelude::{async, await};
use futures::stream::SplitSink;
use tokio_core::reactor::{Core, Handle};
use tokio_core::net::{TcpListener, TcpStream};
use tokio_io::AsyncRead;
use tokio_io::codec::Framed;

use cmd::{Command, TransferType};
use codec::{FtpCodec, StringCodec};
use ftp::{Answer, ResultCode};

const MONTHS: [&'static str; 12] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

type DataWriter = SplitSink<Framed<TcpStream, StringCodec>>;
type Writer = SplitSink<Framed<TcpStream, FtpCodec>>;

cfg_if! {
    if #[cfg(windows)] {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::windows::prelude::*;
            (time::at(time::Timespec::new(meta.last_write_time())), meta.file_size())
        }
    } else {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::unix::prelude::*;
            (time::at(time::Timespec::new(meta.mtime(), 0)), meta.size())
        }
    }
}

fn add_file_info(entry: DirEntry, out: &mut String) {
    let path = entry.path(); // TODO: handle error.
    let extra = if path.is_dir() { "/" } else { "" };
    let is_dir = if path.is_dir() { "d" } else { "-" };

    let meta = ::std::fs::metadata(&path).unwrap(); // TODO: handle error.
    let (time, file_size) = get_file_info(&meta);
    let path = path.to_str().unwrap().split("/").last().unwrap(); // TODO: handle error.
    let file_str = format!("{} {} {} {} {}:{} {}{}\r\n",
                           is_dir,
                           file_size,
                           MONTHS[time.tm_mon as usize],
                           time.tm_mday,
                           time.tm_hour,
                           time.tm_min,
                           path,
                           extra);
    out.push_str(&file_str);
    println!("==> {:?}", &file_str);
}

#[allow(dead_code)]
struct Client {
    address: String, // TODO: remove this?
    cwd: PathBuf,
    data_port: Option<u16>,
    data_writer: Option<DataWriter>,
    handle: Handle,
    name: Option<String>,
    server_root: PathBuf,
    transfer_type: TransferType,
    writer: Writer,
}

impl Client {
    fn new(address: String, handle: Handle, writer: Writer, server_root: PathBuf) -> Client {
        Client {
            address,
            cwd: PathBuf::from(""),
            data_port: None,
            data_writer: None,
            handle,
            name: None,
            server_root,
            transfer_type: TransferType::Ascii,
            writer,
        }
    }

    #[async]
    fn handle_cmd(mut self, cmd: Command) -> Result<Self, ()> {
        println!("Received command: {:?}", cmd);
        match cmd {
            Command::Auth =>
                self = await!(self.send(Answer::new(ResultCode::CommandNotImplemented, "Not implemented")))?
            ,
            Command::Cwd(directory) => self = await!(self.cwd(directory))?,
            Command::List(path) => self = await!(self.list(path))?,
            Command::Pasv => self = await!(self.pasv())?,
            Command::Port(port) => {
                self.data_port = Some(port);
                self = await!(self.send(Answer::new(ResultCode::Ok, &format!("Data port is now {}", port))))?;
            },
            Command::Pwd => {
                let message = format!("\"/{}\" ", self.cwd.to_str().unwrap());
                self = await!(self.send(Answer::new(ResultCode::PATHNAMECreated, &message)))?;
            }, // TODO: handle error.
            Command::Quit => self = await!(self.quit())?,
            Command::Retr(file) => self = await!(self.retr(file))?,
            Command::Syst => {
                self = await!(self.send(Answer::new(ResultCode::Ok, "I won't tell!")))?;
            },
            Command::Type(typ) => {
                self.transfer_type = typ;
                self = await!(self.send(Answer::new(ResultCode::Ok, "Transfer type changed successfully")))?;
            },
            Command::Unknown =>
                self = await!(self.send(Answer::new(ResultCode::UnknownCommand, "Not implemented")))?
            ,
            Command::User(content) => {
                if content.is_empty() {
                    self = await!(self.send(Answer::new(ResultCode::InvalidParameterOrArgument, "Invalid username")))?;
                } else {
                    self.name = Some(content.to_owned());
                    self = await!(self.send(Answer::new(ResultCode::UserloggedIn, &format!("Welcome {}!", content))))?;
                }
            }
        }
        Ok(self)
    }

    fn complete_path(self, path: PathBuf) -> Result<(PathBuf, Self), io::Error> {
        let directory = self.server_root.join(if path.has_root() {
            path.iter().skip(1).collect()
        } else {
            path
        });
        let dir = directory.canonicalize();
        println!("===> {:?} {:?}", dir, directory);
        if let Ok(ref dir) = dir {
            if !dir.starts_with(&self.server_root) {
                return Err(io::ErrorKind::PermissionDenied.into());
            }
        }
        dir.map(|dir| (dir, self))
    }

    #[async]
    fn cwd(mut self, directory: String) -> Result<Self, ()> {
        let mut directory = PathBuf::from(directory);
        let path = self.cwd.join(&directory);
        let (dir, new_self) = self.complete_path(path).unwrap(); // TODO: handle error.
        self = new_self;
        self.cwd = dir.strip_prefix(&self.server_root).unwrap().to_path_buf(); // TODO: handle error.
        println!("switched to {:?}", self.cwd);
        self = await!(self.send(Answer::new(ResultCode::Ok,
                                            &format!("Directory changed to \"{}\"", directory.display()))))?;
        Ok(self)
    }

    #[async]
    fn list(mut self, path: Option<PathBuf>) -> Result<Self, ()> {
        if self.data_writer.is_some() {
            let path = self.cwd.join(path.unwrap_or_default());
            let directory = PathBuf::from(&path);
            let (dir, new_self) = self.complete_path(directory).unwrap(); // TODO: handle error.
            self = new_self;
            self = await!(self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen,
                                                "Starting to list directory...")))?;
            let mut out = String::new();
            for entry in read_dir(dir).unwrap() { // TODO: handle error.
                add_file_info(entry.unwrap(), &mut out); // TODO: handle error.
            }
            self = await!(self.send_data(out))?;
            println!("-> and done!");
        } else {
            self = await!(self.send(Answer::new(ResultCode::ConnectionClosed, "No opened data connection")))?;
        }
        if self.data_writer.is_some() {
            self.data_writer = None;
            self = await!(self.send(Answer::new(ResultCode::ClosingDataConnection, "Transfer done")))?;
        }
        Ok(self)
    }

    #[async]
    fn pasv(mut self) -> Result<Self, ()> {
        let port =
            if let Some(port) = self.data_port {
                port
            } else {
                0
            };
        if self.data_writer.is_some() {
            self = await!(self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Already listening...")))?;
            return Ok(self);
        }

        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
        let listener = TcpListener::bind(&addr, &self.handle).unwrap(); // TODO: handle error
        let port = listener.local_addr().unwrap().port(); // TODO: handle error.

        self = await!(self.send(Answer::new(ResultCode::EnteringPassiveMode,
                              &format!("127,0,0,1,{},{}", port >> 8, port & 0xFF))))?;

        println!("Waiting clients on port {}...", port);
        // TODO: use into_future() instead of for loop?
        #[async]
        for (stream, _rest) in listener.incoming().map_err(|_| ()) {
            let (writer, _reader) = stream.framed(StringCodec).split();
            self.data_writer = Some(writer);
            break;
        }
        Ok(self)
    }

    #[async]
    fn quit(mut self) -> Result<Self, ()> {
        if self.data_writer.is_some() {
            unimplemented!();
        } else {
            self = await!(self.send(Answer::new(ResultCode::ServiceClosingControlConnection, "Closing connection...")))?;
            self.writer.close().unwrap(); // TODO: handle error.
        }
        Ok(self)
    }

    #[async]
    fn retr(mut self, path: PathBuf) -> Result<Self, ()> {
        // TODO: check if multiple data connection can be opened at the same time.
        if self.data_writer.is_some() {
            let path = self.cwd.join(path);
            let (dir, new_self) = self.complete_path(path).unwrap(); // TODO: handle error.
            self = new_self;
            if dir.is_file() {
                self = await!(self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Starting to send file...")))?;
                let mut file = File::open(dir).unwrap(); // TODO: handle error.
                let mut out = String::new();
                file.read_to_string(&mut out).unwrap(); // TODO: handle error.
                self = await!(self.send_data(out))?;
                println!("-> file transfer done!");
            } else {
                self = await!(self.send(Answer::new(ResultCode::LocalErrorInProcessing,
                                      &format!("\"{}\" doesn't exist", dir.to_str().unwrap()))))?;
            }
        } else {
            self = await!(self.send(Answer::new(ResultCode::ConnectionClosed, "No opened data connection")))?;
        }
        if self.data_writer.is_some() {
            self.data_writer = None;
            self = await!(self.send(Answer::new(ResultCode::ClosingDataConnection, "Transfer done")))?;
        }
        Ok(self)
    }

    #[async]
    fn send(mut self, answer: Answer) -> Result<Self, ()> {
        self.writer = await!(self.writer.send(answer)).map_err(|_| ())?;
        Ok(self)
    }

    #[async]
    fn send_data(mut self, data: String) -> Result<Self, ()> {
        if let Some(mut writer) = self.data_writer {
            self.data_writer = Some(await!(writer.send(data)).map_err(|_| ())?);
        }
        Ok(self)
    }
}

#[async]
fn handle_client(stream: TcpStream, handle: Handle, address: String, server_root: PathBuf) -> Result<(), ()> {
    let (writer, reader) = stream.framed(FtpCodec).split();
    let writer = await!(writer.send(Answer::new(ResultCode::ServiceReadyForNewUser, "Welcome to this FTP server!")))
        .map_err(|_| ())?;
    let mut client = Client::new(address, handle, writer, server_root);
    #[async]
    for cmd in reader.map_err(|_| ()) {
        client = await!(client.handle_cmd(cmd))?;
    }
    println!("Client closed");
    Ok(())
}

#[async]
fn server(handle: Handle, server_root: PathBuf) -> io::Result<()> {
    let port = 1234;
    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
    let listener = TcpListener::bind(&addr, &handle).unwrap();

    println!("Waiting clients on port {}...", port);
    #[async]
    for (stream, addr) in listener.incoming() {
        let address = format!("[address : {}]", addr);
        println!("New client: {}", address);
        handle.spawn(handle_client(stream, handle.clone(), address, server_root.clone()));
        println!("Waiting another client...");
    }
    Ok(())
}

fn main() {
    let mut core = Core::new()
        .expect("Cannot create tokio Core");
    let handle = core.handle();

    let server_root = env::current_dir().unwrap(); // TODO: handle error.

    core.run(server(handle, server_root))
        .expect("Run tokio server");
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate toml;

use std::io::{Read, Write};
use std::thread;

use hyper::Client;
use hyper::server::{Server, Request, Response};

fn main() {
    let address = "0.0.0.0:8080";

    println!("Listening on {}", address);
    Server::http(address).unwrap().handle(server).unwrap();
}

fn server(mut req: Request, res: Response) {
    let mut body = String::new();
    match req.method {
        hyper::Get => {
            let _ = req.read_to_string(&mut body);
            println!("debug1: {:?}", &body);
        },
        _ => { panic!("unhandled") },
    }
    let mut res = res.start().unwrap();
    let symbol_url = get_config("symbol_urls.public");
    let symbol = client(symbol_url);
    let _ = res.write_all(symbol.as_bytes());
    res.end().unwrap();
}

fn client(url: String) -> String {
    let mut handles = vec![];
    // TODO decide smart min/max possible threads
    for i in 0..5 {
        let this_url = url.clone();
        handles.push(thread::spawn(move || {
            let c = Client::new();
            let mut res = c.get(&this_url).send().unwrap();
            let mut body = String::new();
            let _ = res.read_to_string(&mut body);

            (i, body)
        }));
    }

    let mut result = String::new();

    for handle in handles {
        let (i, body) = handle.join().unwrap();
        for c in format!("{:?} {:?}\n", i, body).chars() {
            result.push(c);
        }
    }

    result
}

fn get_config(value_name: &str) -> String {
    let toml = r#"
        [symbol_urls]
        public = "https://s3-us-west-2.amazonaws.com/org.mozilla.crash-stats.symbols-public/v1/"
    "#;
    // TODO support multiple URLs
    let value: toml::Value = toml.parse().unwrap();

    value.lookup(value_name).unwrap().as_str().unwrap().to_string()
}
<commit_msg>handle POST body<commit_after>extern crate hyper;
extern crate toml;

use std::io::{Read, Write};
use std::thread;

use hyper::Client;
use hyper::server::{Server, Request, Response};
use hyper::status::StatusCode;

fn main() {
    let address = "0.0.0.0:8080";

    println!("Listening on {}", address);
    Server::http(address).unwrap().handle(server).unwrap();
}

fn server(mut req: Request, mut res: Response) {
    match req.method {
        hyper::Post => {
            let mut buffer = String::new();
            let _ = req.read_to_string(&mut buffer);
            println!("debug1: {:?}", buffer);
        },
        _ => { *res.status_mut() = StatusCode::MethodNotAllowed },
    }
    let mut res = res.start().unwrap();
    let symbol_url = get_config("symbol_urls.public");
    let symbol = client(symbol_url);
    let _ = res.write_all(symbol.as_bytes());
    res.end().unwrap();
}

fn client(url: String) -> String {
    let mut handles = vec![];
    // TODO decide smart min/max possible threads
    for i in 0..5 {
        let this_url = url.clone();
        handles.push(thread::spawn(move || {
            let c = Client::new();
            let mut res = c.get(&this_url).send().unwrap();
            let mut body = String::new();
            let _ = res.read_to_string(&mut body);

            (i, body)
        }));
    }

    let mut result = String::new();

    for handle in handles {
        let (i, body) = handle.join().unwrap();
        for c in format!("{:?} {:?}\n", i, body).chars() {
            result.push(c);
        }
    }

    result
}

fn get_config(value_name: &str) -> String {
    let toml = r#"
        [symbol_urls]
        public = "https://s3-us-west-2.amazonaws.com/org.mozilla.crash-stats.symbols-public/v1/"
    "#;
    // TODO support multiple URLs
    let value: toml::Value = toml.parse().unwrap();

    value.lookup(value_name).unwrap().as_str().unwrap().to_string()
}
<|endoftext|>


<commit_before>extern crate emailaddress;
extern crate iron;
extern crate lettre;
extern crate openssl;
extern crate rand;
extern crate redis;
extern crate router;
extern crate rustc_serialize;
extern crate serde_json;
extern crate time;
extern crate url;
extern crate urlencoded;

use emailaddress::EmailAddress;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::prelude::*;
use iron::status;
use lettre::email::EmailBuilder;
use lettre::transport::smtp::SmtpTransportBuilder;
use lettre::transport::EmailTransport;
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
use openssl::bn::BigNum;
use openssl::crypto::pkey::PKey;
use openssl::crypto::hash;
use serde_json::builder::ObjectBuilder;
use serde_json::de::from_reader;
use serde_json::value::Value;
use redis::{Client, Commands, RedisResult};
use router::Router;
use rustc_serialize::base64::{self, ToBase64};
use std::collections::{BTreeMap, HashMap};
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::{self, BufReader, Write};
use std::iter::Iterator;
use time::now_utc;
use url::percent_encoding::{utf8_percent_encode, QUERY_ENCODE_SET};

fn json_response(obj: &Value) -> IronResult<Response> {
    let content = serde_json::to_string(&obj).unwrap();
    let mut rsp = Response::with((status::Ok, content));
    rsp.headers.set(ContentType::json());
    Ok(rsp)
}

struct WelcomeHandler { app: AppConfig }
impl Handler for WelcomeHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        json_response(&ObjectBuilder::new()
            .insert("ladaemon", "Welcome")
            .insert("version", &self.app.version)
            .unwrap())
    }
}

struct OIDConfigHandler { app: AppConfig }
impl Handler for OIDConfigHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        json_response(&ObjectBuilder::new()
            .insert("issuer", &self.app.base_url)
            .insert("authorization_endpoint",
                    format!("{}/auth", self.app.base_url))
            .insert("jwks_uri", format!("{}/keys.json", self.app.base_url))
            .insert("scopes_supported", vec!["openid", "email"])
            .insert("claims_supported",
                    vec!["aud", "email", "email_verified", "exp", "iat", "iss", "sub"])
            .insert("response_types_supported", vec!["id_token"])
            .insert("response_modes_supported", vec!["form_post"])
            .insert("grant_types_supported", vec!["implicit"])
            .insert("subject_types_supported", vec!["public"])
            .insert("id_token_signing_alg_values_supported", vec!["RS256"])
            .unwrap())
    }
}

#[derive(Clone)]
struct ProviderConfig {
    discovery: String,
    client_id: String,
    secret: String,
}

#[derive(Clone)]
struct AppConfig {
    version: String,
    base_url: String,
    priv_key: PKey,
    store: Client,
    expire_keys: usize,
    sender: String,
    token_validity: i64,
    providers: BTreeMap<String, ProviderConfig>,
}

fn json_big_num(n: &BigNum) -> String {
    n.to_vec().to_base64(base64::URL_SAFE)
}

struct KeysHandler { app: AppConfig }
impl Handler for KeysHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        let rsa = self.app.priv_key.get_rsa();
        json_response(&ObjectBuilder::new()
            .insert_array("keys", |builder| {
                builder.push_object(|builder| {
                    builder.insert("kty", "RSA")
                        .insert("alg", "RS256")
                        .insert("use", "sig")
                        .insert("kid", "base")
                        .insert("n", json_big_num(&rsa.n().unwrap()))
                        .insert("e", json_big_num(&rsa.e().unwrap()))
                })
            })
            .unwrap())
    }
}

const CODE_CHARS: &'static [char] = &[
    '2', '3', '4', '6', '7', '9', 'a', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k',
    'm', 'n', 'p', 'q', 'r', 't', 'v', 'w', 'x', 'y', 'z',
];

struct AuthHandler { app: AppConfig }
impl Handler for AuthHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let chars: String = (0..6).map(|_| CODE_CHARS[rand::random::<usize>() % CODE_CHARS.len()]).collect();
        let params = req.get_ref::<UrlEncodedBody>().unwrap();
        let email_addr = EmailAddress::new(&params.get("login_hint").unwrap()[0]);
        let client_id = &params.get("client_id").unwrap()[0];
        let key = format!("{}:{}", email_addr, client_id);
        let set_res: RedisResult<String> = self.app.store.hset_multiple(key.clone(), &[
            ("code", chars.clone()),
            ("redirect", params.get("redirect_uri").unwrap()[0].clone()),
        ]);
        let exp_res: RedisResult<bool> = self.app.store.expire(key.clone(), self.app.expire_keys);

        let href = format!("{}/confirm?email={}&origin={}&code={}",
                           self.app.base_url,
                           utf8_percent_encode(&email_addr.to_string(), QUERY_ENCODE_SET),
                           utf8_percent_encode(client_id, QUERY_ENCODE_SET),
                           utf8_percent_encode(&chars, QUERY_ENCODE_SET));
        let email = EmailBuilder::new()
            .to(email_addr.to_string().as_str())
            .from(self.app.sender.as_str())
            .body(&format!("Enter your login code:\n\n{}\n\nOr click this link:\n\n{}",
                           chars, href))
            .subject(&format!("Code: {} - Finish logging in to {}", chars, client_id))
            .build().unwrap();
        let mut mailer = SmtpTransportBuilder::localhost().unwrap().build();
        let result = mailer.send(email);
        mailer.close();

        let mut obj = ObjectBuilder::new()
            .insert("href", href);
        if !result.is_ok() {
            let error = result.unwrap_err();
            obj = obj.insert("error", error.to_string());
            obj = match error {
                lettre::transport::error::Error::IoError(inner) => {
                    obj.insert("cause", inner.description())
                }
                _ => obj,
            }
        }
        if !set_res.is_ok() {
            obj = obj.insert("hset_multiple", set_res.unwrap_err().to_string());
        }
        if !exp_res.is_ok() {
            obj = obj.insert("expire", exp_res.unwrap_err().to_string());
        }
        json_response(&obj.unwrap())

    }
}

fn create_jwt(key: &PKey, header: &str, payload: &str) -> String {
    let mut input = Vec::<u8>::new();
    input.extend(header.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    input.push(b'.');
    input.extend(payload.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    let sha256 = hash::hash(hash::Type::SHA256, &input);
    let sig = key.sign(&sha256);
    input.push(b'.');
    input.extend(sig.to_base64(base64::URL_SAFE).into_bytes());
    String::from_utf8(input).unwrap()
}

const FORWARD_TEMPLATE: &'static str = r#"<!DOCTYPE html>
<html>
  <head>
    <title>Let's Auth</title>
    <script>
      document.addEventListener('DOMContentLoaded', function() {
        document.getElementById('form').submit();
      });
    </script>
  </head>
  <body>
    <form id="form" action="{{ return_url }}" method="post">
      <input type="hidden" name="id_token" value="{{ jwt }}">
    </form>
  </body>
</html>"#;

fn send_jwt_response(email: &str, origin: &str, redirect: &str, app: &AppConfig) -> IronResult<Response> {

    let now = now_utc().to_timespec().sec;
    let payload = ObjectBuilder::new()
        .insert("aud", origin)
        .insert("email", email)
        .insert("email_verified", email)
        .insert("exp", now + app.token_validity)
        .insert("iat", now)
        .insert("iss", &app.base_url)
        .insert("sub", email)
        .unwrap();
    let headers = ObjectBuilder::new()
        .insert("kid", "base")
        .insert("alg", "RS256")
        .unwrap();
    let jwt = create_jwt(&app.priv_key,
                         &serde_json::to_string(&headers).unwrap(),
                         &serde_json::to_string(&payload).unwrap());

    let html = FORWARD_TEMPLATE.replace("{{ return_url }}", redirect)
        .replace("{{ jwt }}", &jwt);
    let mut rsp = Response::with((status::Ok, html));
    rsp.headers.set(ContentType::html());
    Ok(rsp)

}

struct ConfirmHandler { app: AppConfig }
impl Handler for ConfirmHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let params = req.get_ref::<UrlEncodedQuery>().unwrap();
        let email = &params.get("email").unwrap()[0];
        let origin = &params.get("origin").unwrap()[0];
        let key = format!("{}:{}", email, origin);
        let stored: HashMap<String, String> = self.app.store.hgetall(key.clone()).unwrap();

        let req_code = &params.get("code").unwrap()[0];
        if stored.get("code").is_none() || req_code != stored.get("code").unwrap() {
            let mut obj = ObjectBuilder::new().insert("error", "code fail");
            obj = obj.insert("stored", stored);
            return json_response(&obj.unwrap());
        }

        let redirect = stored.get("redirect").unwrap();
        send_jwt_response(email, origin, redirect, &self.app)

    }
}

fn main() {

    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        io::stderr().write(b"no configuration file specified\n").unwrap();
        return;
    }

    let config_file = File::open(&args[1]).unwrap();
    let config_reader = BufReader::new(config_file);
    let config: Value = from_reader(config_reader).unwrap();

    let key_file_name = config.find("private_key_file").unwrap().as_string().unwrap();
    let priv_key_file = File::open(key_file_name).unwrap();
    let mut reader = BufReader::new(priv_key_file);
    let app = AppConfig {
        version: env!("CARGO_PKG_VERSION").to_string(),
        base_url: config.find("base_url").unwrap().as_string().unwrap().to_string(),
        priv_key: PKey::private_key_from_pem(&mut reader).unwrap(),
        store: Client::open(config.find("redis").unwrap().as_string().unwrap()).unwrap(),
        expire_keys: config.find("expire_keys").unwrap().as_u64().unwrap() as usize,
        sender: "Let's Auth <letsauth@xavamedia.nl>".to_string(),
        token_validity: config.find("token_validity").unwrap().as_i64().unwrap(),
        providers: config.find("providers").unwrap().as_object().unwrap().iter()
            .map(|(host, params)| {
                let pobj = params.as_object().unwrap();
                (host.clone(), ProviderConfig {
                    discovery: pobj["discovery"].as_string().unwrap().to_string(),
                    client_id: pobj["client_id"].as_string().unwrap().to_string(),
                    secret: pobj["secret"].as_string().unwrap().to_string(),
                })
            })
            .collect::<BTreeMap<String, ProviderConfig>>(),
    };

    let mut router = Router::new();
    router.get("/", WelcomeHandler { app: app.clone() });
    router.get("/.well-known/openid-configuration",
               OIDConfigHandler { app: app.clone() });
    router.get("/keys.json", KeysHandler { app: app.clone() });
    router.post("/auth", AuthHandler { app: app.clone() });
    router.get("/confirm", ConfirmHandler { app: app.clone() });
    Iron::new(router).http("0.0.0.0:3333").unwrap();

}
<commit_msg>Add issuer field to provider config<commit_after>extern crate emailaddress;
extern crate iron;
extern crate lettre;
extern crate openssl;
extern crate rand;
extern crate redis;
extern crate router;
extern crate rustc_serialize;
extern crate serde_json;
extern crate time;
extern crate url;
extern crate urlencoded;

use emailaddress::EmailAddress;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::prelude::*;
use iron::status;
use lettre::email::EmailBuilder;
use lettre::transport::smtp::SmtpTransportBuilder;
use lettre::transport::EmailTransport;
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
use openssl::bn::BigNum;
use openssl::crypto::pkey::PKey;
use openssl::crypto::hash;
use serde_json::builder::ObjectBuilder;
use serde_json::de::from_reader;
use serde_json::value::Value;
use redis::{Client, Commands, RedisResult};
use router::Router;
use rustc_serialize::base64::{self, ToBase64};
use std::collections::{BTreeMap, HashMap};
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::{self, BufReader, Write};
use std::iter::Iterator;
use time::now_utc;
use url::percent_encoding::{utf8_percent_encode, QUERY_ENCODE_SET};

fn json_response(obj: &Value) -> IronResult<Response> {
    let content = serde_json::to_string(&obj).unwrap();
    let mut rsp = Response::with((status::Ok, content));
    rsp.headers.set(ContentType::json());
    Ok(rsp)
}

struct WelcomeHandler { app: AppConfig }
impl Handler for WelcomeHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        json_response(&ObjectBuilder::new()
            .insert("ladaemon", "Welcome")
            .insert("version", &self.app.version)
            .unwrap())
    }
}

struct OIDConfigHandler { app: AppConfig }
impl Handler for OIDConfigHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        json_response(&ObjectBuilder::new()
            .insert("issuer", &self.app.base_url)
            .insert("authorization_endpoint",
                    format!("{}/auth", self.app.base_url))
            .insert("jwks_uri", format!("{}/keys.json", self.app.base_url))
            .insert("scopes_supported", vec!["openid", "email"])
            .insert("claims_supported",
                    vec!["aud", "email", "email_verified", "exp", "iat", "iss", "sub"])
            .insert("response_types_supported", vec!["id_token"])
            .insert("response_modes_supported", vec!["form_post"])
            .insert("grant_types_supported", vec!["implicit"])
            .insert("subject_types_supported", vec!["public"])
            .insert("id_token_signing_alg_values_supported", vec!["RS256"])
            .unwrap())
    }
}

#[derive(Clone)]
struct ProviderConfig {
    discovery: String,
    client_id: String,
    secret: String,
    issuer: String,
}

#[derive(Clone)]
struct AppConfig {
    version: String,
    base_url: String,
    priv_key: PKey,
    store: Client,
    expire_keys: usize,
    sender: String,
    token_validity: i64,
    providers: BTreeMap<String, ProviderConfig>,
}

fn json_big_num(n: &BigNum) -> String {
    n.to_vec().to_base64(base64::URL_SAFE)
}

struct KeysHandler { app: AppConfig }
impl Handler for KeysHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        let rsa = self.app.priv_key.get_rsa();
        json_response(&ObjectBuilder::new()
            .insert_array("keys", |builder| {
                builder.push_object(|builder| {
                    builder.insert("kty", "RSA")
                        .insert("alg", "RS256")
                        .insert("use", "sig")
                        .insert("kid", "base")
                        .insert("n", json_big_num(&rsa.n().unwrap()))
                        .insert("e", json_big_num(&rsa.e().unwrap()))
                })
            })
            .unwrap())
    }
}

const CODE_CHARS: &'static [char] = &[
    '2', '3', '4', '6', '7', '9', 'a', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k',
    'm', 'n', 'p', 'q', 'r', 't', 'v', 'w', 'x', 'y', 'z',
];

struct AuthHandler { app: AppConfig }
impl Handler for AuthHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let chars: String = (0..6).map(|_| CODE_CHARS[rand::random::<usize>() % CODE_CHARS.len()]).collect();
        let params = req.get_ref::<UrlEncodedBody>().unwrap();
        let email_addr = EmailAddress::new(&params.get("login_hint").unwrap()[0]);
        let client_id = &params.get("client_id").unwrap()[0];
        let key = format!("{}:{}", email_addr, client_id);
        let set_res: RedisResult<String> = self.app.store.hset_multiple(key.clone(), &[
            ("code", chars.clone()),
            ("redirect", params.get("redirect_uri").unwrap()[0].clone()),
        ]);
        let exp_res: RedisResult<bool> = self.app.store.expire(key.clone(), self.app.expire_keys);

        let href = format!("{}/confirm?email={}&origin={}&code={}",
                           self.app.base_url,
                           utf8_percent_encode(&email_addr.to_string(), QUERY_ENCODE_SET),
                           utf8_percent_encode(client_id, QUERY_ENCODE_SET),
                           utf8_percent_encode(&chars, QUERY_ENCODE_SET));
        let email = EmailBuilder::new()
            .to(email_addr.to_string().as_str())
            .from(self.app.sender.as_str())
            .body(&format!("Enter your login code:\n\n{}\n\nOr click this link:\n\n{}",
                           chars, href))
            .subject(&format!("Code: {} - Finish logging in to {}", chars, client_id))
            .build().unwrap();
        let mut mailer = SmtpTransportBuilder::localhost().unwrap().build();
        let result = mailer.send(email);
        mailer.close();

        let mut obj = ObjectBuilder::new()
            .insert("href", href);
        if !result.is_ok() {
            let error = result.unwrap_err();
            obj = obj.insert("error", error.to_string());
            obj = match error {
                lettre::transport::error::Error::IoError(inner) => {
                    obj.insert("cause", inner.description())
                }
                _ => obj,
            }
        }
        if !set_res.is_ok() {
            obj = obj.insert("hset_multiple", set_res.unwrap_err().to_string());
        }
        if !exp_res.is_ok() {
            obj = obj.insert("expire", exp_res.unwrap_err().to_string());
        }
        json_response(&obj.unwrap())

    }
}

fn create_jwt(key: &PKey, header: &str, payload: &str) -> String {
    let mut input = Vec::<u8>::new();
    input.extend(header.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    input.push(b'.');
    input.extend(payload.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    let sha256 = hash::hash(hash::Type::SHA256, &input);
    let sig = key.sign(&sha256);
    input.push(b'.');
    input.extend(sig.to_base64(base64::URL_SAFE).into_bytes());
    String::from_utf8(input).unwrap()
}

const FORWARD_TEMPLATE: &'static str = r#"<!DOCTYPE html>
<html>
  <head>
    <title>Let's Auth</title>
    <script>
      document.addEventListener('DOMContentLoaded', function() {
        document.getElementById('form').submit();
      });
    </script>
  </head>
  <body>
    <form id="form" action="{{ return_url }}" method="post">
      <input type="hidden" name="id_token" value="{{ jwt }}">
    </form>
  </body>
</html>"#;

fn send_jwt_response(email: &str, origin: &str, redirect: &str, app: &AppConfig) -> IronResult<Response> {

    let now = now_utc().to_timespec().sec;
    let payload = ObjectBuilder::new()
        .insert("aud", origin)
        .insert("email", email)
        .insert("email_verified", email)
        .insert("exp", now + app.token_validity)
        .insert("iat", now)
        .insert("iss", &app.base_url)
        .insert("sub", email)
        .unwrap();
    let headers = ObjectBuilder::new()
        .insert("kid", "base")
        .insert("alg", "RS256")
        .unwrap();
    let jwt = create_jwt(&app.priv_key,
                         &serde_json::to_string(&headers).unwrap(),
                         &serde_json::to_string(&payload).unwrap());

    let html = FORWARD_TEMPLATE.replace("{{ return_url }}", redirect)
        .replace("{{ jwt }}", &jwt);
    let mut rsp = Response::with((status::Ok, html));
    rsp.headers.set(ContentType::html());
    Ok(rsp)

}

struct ConfirmHandler { app: AppConfig }
impl Handler for ConfirmHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let params = req.get_ref::<UrlEncodedQuery>().unwrap();
        let email = &params.get("email").unwrap()[0];
        let origin = &params.get("origin").unwrap()[0];
        let key = format!("{}:{}", email, origin);
        let stored: HashMap<String, String> = self.app.store.hgetall(key.clone()).unwrap();

        let req_code = &params.get("code").unwrap()[0];
        if stored.get("code").is_none() || req_code != stored.get("code").unwrap() {
            let mut obj = ObjectBuilder::new().insert("error", "code fail");
            obj = obj.insert("stored", stored);
            return json_response(&obj.unwrap());
        }

        let redirect = stored.get("redirect").unwrap();
        send_jwt_response(email, origin, redirect, &self.app)

    }
}

fn main() {

    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        io::stderr().write(b"no configuration file specified\n").unwrap();
        return;
    }

    let config_file = File::open(&args[1]).unwrap();
    let config_reader = BufReader::new(config_file);
    let config: Value = from_reader(config_reader).unwrap();

    let key_file_name = config.find("private_key_file").unwrap().as_string().unwrap();
    let priv_key_file = File::open(key_file_name).unwrap();
    let mut reader = BufReader::new(priv_key_file);
    let app = AppConfig {
        version: env!("CARGO_PKG_VERSION").to_string(),
        base_url: config.find("base_url").unwrap().as_string().unwrap().to_string(),
        priv_key: PKey::private_key_from_pem(&mut reader).unwrap(),
        store: Client::open(config.find("redis").unwrap().as_string().unwrap()).unwrap(),
        expire_keys: config.find("expire_keys").unwrap().as_u64().unwrap() as usize,
        sender: "Let's Auth <letsauth@xavamedia.nl>".to_string(),
        token_validity: config.find("token_validity").unwrap().as_i64().unwrap(),
        providers: config.find("providers").unwrap().as_object().unwrap().iter()
            .map(|(host, params)| {
                let pobj = params.as_object().unwrap();
                (host.clone(), ProviderConfig {
                    discovery: pobj["discovery"].as_string().unwrap().to_string(),
                    client_id: pobj["client_id"].as_string().unwrap().to_string(),
                    secret: pobj["secret"].as_string().unwrap().to_string(),
                    issuer: pobj["issuer"].as_string().unwrap().to_string(),
                })
            })
            .collect::<BTreeMap<String, ProviderConfig>>(),
    };

    let mut router = Router::new();
    router.get("/", WelcomeHandler { app: app.clone() });
    router.get("/.well-known/openid-configuration",
               OIDConfigHandler { app: app.clone() });
    router.get("/keys.json", KeysHandler { app: app.clone() });
    router.post("/auth", AuthHandler { app: app.clone() });
    router.get("/confirm", ConfirmHandler { app: app.clone() });
    Iron::new(router).http("0.0.0.0:3333").unwrap();

}
<|endoftext|>


<commit_before>extern crate pnet;

use std::time::Duration;
use std::thread;

use pnet::datalink::{self, NetworkInterface};
use pnet::datalink::Channel;
use pnet::packet::ethernet::EthernetPacket;

fn send_arp_request_packet() {
    let interfaces = datalink::interfaces();

    // Get en1 interface in osx.
    let interfaces_name_match = |iface: &NetworkInterface| iface.name == "en1";
    let interface = interfaces.into_iter().filter(interfaces_name_match).next().unwrap();

    let(mut tx, mut rx) = match datalink::channel(&interface, Default::default()) {
        Ok(Channel::Ethernet(tx, rx)) => (tx, rx),
        Ok(_) => panic!("Unknown channel type"),
        Err(e) => panic!("Error happened {}", e),
    };

    /// ethernet_packet = Ethernet {
    ///     destination: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
    ///     source: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     ethertype: [0x08, 0x06], // Arp(0x0806)
    ///     payload: arp_packet
    /// }
    ///
    /// arp_packet = Arp {
    ///     hardware_type: [0x00, 0x01],
    ///     protocol_type: [0x08, 0x00], // Ipv4(0x0800)
    ///     hw_addr_len: [0x06],
    ///     proto_addr_len: [0x04],
    ///     operation: [0x00, 0x01], // Request(0x0001)
    ///     sender_hw_addr: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     sender_proto_addr: [0xc0, 0xa8, 0x00, 0x66], // Ipv4(192.168.0.102)
    ///     target_hw_addr: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff], // Broadcast
    ///     target_proto_addr: [0xc0, 0xa8, 0x00, 0x65], // // Ipv4(192.168.0.101)
    ///     payload: [],
    /// }

    let buffer: &[u8] = &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0x08, 0x06,
                         0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0xc0, 0xa8, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01];
    let ethernet_packet = EthernetPacket::new(&buffer);
    tx.send_to(&ethernet_packet.unwrap(), Some(interface));
}

fn send_arp_reply_packet() {
    let interfaces = datalink::interfaces();

    // Get en1 interface in osx.
    let interfaces_name_match = |iface: &NetworkInterface| iface.name == "en1";
    let interface = interfaces.into_iter().filter(interfaces_name_match).next().unwrap();

    let(mut tx, mut rx) = match datalink::channel(&interface, Default::default()) {
        Ok(Channel::Ethernet(tx, rx)) => (tx, rx),
        Ok(_) => panic!("Unknown channel type"),
        Err(e) => panic!("Error happened {}", e),
    };

    /// ethernet_packet = Ethernet {
    ///     destination: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
    ///     source: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     ethertype: [0x08, 0x06], // Arp(0x0806)
    ///     payload: arp_packet
    /// }
    ///
    /// arp_packet = Arp {
    ///     hardware_type: [0x00, 0x01],
    ///     protocol_type: [0x08, 0x00], // Ipv4(0x0800)
    ///     hw_addr_len: [0x06],
    ///     proto_addr_len: [0x04],
    ///     operation: [0x00, 0x01], // Reply(0x0002)
    ///     sender_hw_addr: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     sender_proto_addr: [0xc0, 0xa8, 0x00, 0x66], // Ipv4(192.168.0.102)
    ///     target_hw_addr: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff], // Broadcast
    ///     target_proto_addr: [0xc0, 0xa8, 0x00, 0x65], // // Ipv4(192.168.0.101)
    ///     payload: [],
    /// }

    // Arp spoofing.
    //   Send an arp request with the source mac as the target mac
    //   the target ip with a fake ip addr.
    // F8-CF-C5-80-C9-94 (mac) 192.168.0.191 (0xC0A800BF)

    let buffer: &[u8] = &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0x08, 0x06,
                         0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0xc0, 0xa8, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01];
    let ethernet_packet = EthernetPacket::new(&buffer);
    tx.send_to(&ethernet_packet.unwrap(), Some(interface));
}

fn send_gratitious_arp_request() {

}

fn main() {
    loop {
        send_arp_reply_packet();
        thread::sleep(Duration::new(2, 0));
    }
}
<commit_msg>Get the mac and ip address from the user<commit_after>extern crate pnet;

use std::io;
use std::thread;
use std::time::Duration;
use std::net::Ipv4Addr;
use std::net::AddrParseError;

use pnet::datalink::{self, NetworkInterface};
use pnet::datalink::Channel;
use pnet::packet::ethernet::EthernetPacket;
use pnet::util::{MacAddr, ParseMacAddrErr};

fn send_arp_request_packet() {
    let interfaces = datalink::interfaces();

    // Get en1 interface in osx.
    let interfaces_name_match = |iface: &NetworkInterface| iface.name == "en1";
    let interface = interfaces.into_iter().filter(interfaces_name_match).next().unwrap();

    let(mut tx, mut rx) = match datalink::channel(&interface, Default::default()) {
        Ok(Channel::Ethernet(tx, rx)) => (tx, rx),
        Ok(_) => panic!("Unknown channel type"),
        Err(e) => panic!("Error happened {}", e),
    };

    /// ethernet_packet = Ethernet {
    ///     destination: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
    ///     source: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     ethertype: [0x08, 0x06], // Arp(0x0806)
    ///     payload: arp_packet
    /// }
    ///
    /// arp_packet = Arp {
    ///     hardware_type: [0x00, 0x01],
    ///     protocol_type: [0x08, 0x00], // Ipv4(0x0800)
    ///     hw_addr_len: [0x06],
    ///     proto_addr_len: [0x04],
    ///     operation: [0x00, 0x01], // Request(0x0001)
    ///     sender_hw_addr: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     sender_proto_addr: [0xc0, 0xa8, 0x00, 0x66], // Ipv4(192.168.0.102)
    ///     target_hw_addr: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff], // Broadcast
    ///     target_proto_addr: [0xc0, 0xa8, 0x00, 0x65], // // Ipv4(192.168.0.101)
    ///     payload: [],
    /// }

    let buffer: &[u8] = &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0x08, 0x06,
                         0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0xc0, 0xa8, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01];
    let ethernet_packet = EthernetPacket::new(&buffer);
    tx.send_to(&ethernet_packet.unwrap(), Some(interface));
}

fn send_arp_reply_packet() {
    let interfaces = datalink::interfaces();

    // Get en1 interface in osx.
    let interfaces_name_match = |iface: &NetworkInterface| iface.name == "en1";
    let interface = interfaces.into_iter().filter(interfaces_name_match).next().unwrap();

    let(mut tx, mut rx) = match datalink::channel(&interface, Default::default()) {
        Ok(Channel::Ethernet(tx, rx)) => (tx, rx),
        Ok(_) => panic!("Unknown channel type"),
        Err(e) => panic!("Error happened {}", e),
    };

    /// ethernet_packet = Ethernet {
    ///     destination: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
    ///     source: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     ethertype: [0x08, 0x06], // Arp(0x0806)
    ///     payload: arp_packet
    /// }
    ///
    /// arp_packet = Arp {
    ///     hardware_type: [0x00, 0x01],
    ///     protocol_type: [0x08, 0x00], // Ipv4(0x0800)
    ///     hw_addr_len: [0x06],
    ///     proto_addr_len: [0x04],
    ///     operation: [0x00, 0x01], // Reply(0x0002)
    ///     sender_hw_addr: [0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b],
    ///     sender_proto_addr: [0xc0, 0xa8, 0x00, 0x66], // Ipv4(192.168.0.102)
    ///     target_hw_addr: [0xff, 0xff, 0xff, 0xff, 0xff, 0xff], // Broadcast
    ///     target_proto_addr: [0xc0, 0xa8, 0x00, 0x65], // // Ipv4(192.168.0.101)
    ///     payload: [],
    /// }

    let buffer: &[u8] = &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0x08, 0x06,
                         0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x02, 0x28, 0xef, 0xf9, 0x5f, 0x8e, 0x2b, 0xc0, 0xa8, 0x00, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01];
    let ethernet_packet = EthernetPacket::new(&buffer);
    tx.send_to(&ethernet_packet.unwrap(), Some(interface));
}

fn send_gratitious_arp_request() {

}

fn main() {
    let mut mac_addr = String::new();
    io::stdin().read_line(&mut mac_addr).expect("Failed to read input");

    let mac_addr: Result<MacAddr, ParseMacAddrErr> = mac_addr.trim().parse();
    println!("Mac address is {:?}", mac_addr);

    let mut ip_addr = String::new();
    io::stdin().read_line(&mut ip_addr).expect("Failed to read input");

    let ip_addr: Result<Ipv4Addr, AddrParseError> = ip_addr.trim().parse();
    println!("Ip address is {:?}", ip_addr);

    // // loop {
    // //     //send_arp_reply_packet();
    // //     thread::sleep(Duration::new(2, 0));
    // // }
}
<|endoftext|>