From: Jules Maselbas <jmaselbas@kalray.eu>
To: barebox@lists.infradead.org
Cc: Jules Maselbas <jmaselbas@kalray.eu>
Subject: [RFC PATCH 4/5] Add irc command
Date: Tue, 9 Aug 2022 15:20:20 +0200 [thread overview]
Message-ID: <20220809132021.7110-4-jmaselbas@kalray.eu> (raw)
In-Reply-To: <20220809132021.7110-1-jmaselbas@kalray.eu>
The irc command implement an IRC client, right here in barebox.
This could be used to ask help in case a user cannot boot linux...
More realistically this is an example of using the TCP support.
Signed-off-by: Jules Maselbas <jmaselbas@kalray.eu>
---
commands/Kconfig | 9 +
commands/Makefile | 1 +
commands/irc.c | 572 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 582 insertions(+)
create mode 100644 commands/irc.c
diff --git a/commands/Kconfig b/commands/Kconfig
index 86e4714849..5faf8eccc1 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -1244,6 +1244,15 @@ config CMD_PING
Usage: ping DESTINATION
+config CMD_IRC
+ tristate
+ prompt "irc"
+ default n
+ help
+ Simple IRC client
+
+ Usage: irc [-n] DESTINATION [PORT]
+
config CMD_TFTP
depends on FS_TFTP
tristate
diff --git a/commands/Makefile b/commands/Makefile
index b3b7bafe6b..ecdcfe9619 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_USB_GADGET_SERIAL) += usbserial.o
obj-$(CONFIG_CMD_GPIO) += gpio.o
obj-$(CONFIG_CMD_UNCOMPRESS) += uncompress.o
obj-$(CONFIG_CMD_I2C) += i2c.o
+obj-$(CONFIG_CMD_IRC) += irc.o
obj-$(CONFIG_CMD_SPI) += spi.o
obj-$(CONFIG_CMD_MIPI_DBI) += mipi_dbi.o
obj-$(CONFIG_CMD_UBI) += ubi.o
diff --git a/commands/irc.c b/commands/irc.c
new file mode 100644
index 0000000000..b2b154b1dc
--- /dev/null
+++ b/commands/irc.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2022 Jules Maselbas <jmaselbas@kalray.eu>
+
+/* irc.c - IRC client */
+
+#include <common.h>
+#include <command.h>
+#include <net.h>
+#include <errno.h>
+#include <getopt.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <readkey.h>
+#include <sched.h>
+#include <crc.h>
+#include <globalvar.h>
+
+static IPaddr_t net_ip;
+
+static struct net_connection *con;
+
+static char chan[32];
+static char nick[32];
+static char input_line[128];
+
+static int redraw;
+LIST_HEAD(irc_log);
+static int irc_log_nb_msgs;
+
+struct msg_entry {
+ struct list_head list;
+ unsigned char type;
+ unsigned char flag;
+ char *msg;
+ char *nick;
+};
+
+enum type {
+ NONE = 0,
+ INFO = 1,
+ PART,
+ QUIT,
+ JOIN,
+ PRIV,
+ EMOT,
+};
+
+enum flag {
+ SELF = 1,
+ HIGH = 2,
+};
+
+const char *nickcolor[] = {
+ "\033[0m", /* normal */
+ "\033[31m", /* red */
+ "\033[32m", /* green */
+ "\033[33m", /* yellow */
+ "\033[34m", /* blue */
+ "\033[35m", /* purple */
+ "\033[36m", /* cyan */
+ "\033[1;31m",
+ "\033[1;32m",
+ "\033[1;33m",
+ "\033[1;34m",
+ "\033[1;35m",
+ "\033[1;36m",
+};
+
+const char *color[] = {
+ [NONE] = "",
+ [INFO] = "\033[1;34m", /* blue */
+ [PART] = "\033[1;31m", /* red */
+ [QUIT] = "\033[1;31m", /* red */
+ [JOIN] = "\033[1;32m", /* green */
+ [PRIV] = "",
+ [EMOT] = "",
+};
+
+const char *prefix[] = {
+ [NONE] = "",
+ [INFO] = "-!-",
+ [PART] = "<--",
+ [QUIT] = "<--",
+ [JOIN] = "-->",
+ [PRIV] = "",
+ [EMOT] = "",
+};
+
+static int irc_send(const char *msg, int len);
+static int irc_pong(const char *txt);
+static int irc_ctcp(const char *dst, const char *act, const char *msg);
+
+static void irc_print_msg(struct msg_entry *msg)
+{
+ const char *inv = "";
+ const char *col = "";
+ const char *off = "";
+ const char *pre = prefix[msg->type];
+ uint32_t crc;
+
+ if (console_allow_color()) {
+ col = color[msg->type];
+ off = "\033[0m";
+
+ if (msg->type == PRIV || msg->type == EMOT) {
+ crc = crc32(0, msg->nick, strlen(msg->nick));
+ if (msg->flag & SELF)
+ col = "\033[1m";
+ else
+ col = nickcolor[crc % ARRAY_SIZE(nickcolor)];
+ }
+
+ if (msg->flag & HIGH)
+ inv = "\033[7m";
+ }
+
+ if (msg->type == PRIV)
+ printf("<%s%s%s%s> %s\n", inv, col, msg->nick, off, msg->msg);
+ else if (msg->type == EMOT)
+ printf(" * %s%s%s%s %s\n", inv, col, msg->nick, off, msg->msg);
+ else
+ printf("%s%s%s %s\n", col, pre, off, msg->msg);
+}
+
+static void irc_add_line(enum type type, enum flag flag, char *name, char *line)
+{
+ struct msg_entry *msg;
+
+ msg = malloc(sizeof(*msg));
+ if (!msg)
+ return;
+ msg->msg = line;
+ msg->type = type;
+ msg->flag = flag;
+ msg->nick = basprintf("%s", name ? name : "");
+
+ if (!(flag & SELF)) {
+ if (strstr(line, nick) != NULL)
+ msg->flag |= HIGH;
+ }
+
+ list_add_tail(&msg->list, &irc_log);
+ irc_log_nb_msgs++;
+ printf("\r\x1b[K");
+ irc_print_msg(msg);
+ printf("\r[%s] %s\x1b[K", nick, input_line);
+}
+
+static void irc_draw(void)
+{
+ struct msg_entry *msg;
+
+ clear();
+ printf("\r\x1b[K");
+ list_for_each_entry(msg, &irc_log, list) {
+ irc_print_msg(msg);
+ }
+ printf("\r[%s] %s\x1b[K", nick, input_line);
+}
+
+static void irc_recv(char *msg)
+{
+ char *nick = NULL, *host = NULL;
+ char *cmd = NULL, *chan = NULL, *arg = NULL;
+ char *text = NULL;
+ char **argp[] = { &cmd, &chan, &arg };
+ int argc = 0;
+ char *p = NULL, *l = NULL;
+ char t = NONE;
+
+ /* :<nick>!<user>@<host> */
+ if (msg[0] == ':') {
+ nick = ++msg;
+ if (!(p = strchr(msg, ' ')))
+ return;
+ *p = '\0';
+ msg = skip_spaces(p + 1);
+ if ((p = strchr(nick, '!'))) {
+ *p = '\0';
+ host = ++p;
+ }
+ }
+
+ if ((p = strchr(msg, ':'))) {
+ *p = '\0';
+ text = ++p;
+ if ((p = strchr(text, '\r')))
+ *p = '\0';
+ }
+
+ /* <cmd> [<chan> [<arg> ]]\0 */
+ while (argc < (ARRAY_SIZE(argp) - 1) && (p = strchr(msg, ' '))) {
+ *p = '\0';
+ *argp[argc++] = msg;
+ msg = ++p;
+ }
+ if (argc == (ARRAY_SIZE(argp) - 1))
+ *argp[argc] = msg;
+
+ if (!cmd || !strcmp("PONG", cmd)) {
+ return;
+ } else if (!strcmp("PING", cmd)) {
+ irc_pong(text);
+ return;
+ } else if (!nick || !host) {
+ t = INFO;
+ l = basprintf("%s%s", arg ? arg : "", text ? text : "");
+ } else if (!strcmp("ERROR", cmd)) {
+ t = INFO;
+ l = basprintf("error %s", text ? text : "unknown");
+ } else if (!strcmp("JOIN", cmd) && (chan || text)) {
+ if (text)
+ chan = text;
+ t = JOIN;
+ l = basprintf("%s(%s) has joined %s", nick, host, chan);
+ } else if (!strcmp("PART", cmd) && chan) {
+ t = PART;
+ l = basprintf("%s(%s) has left %s", nick, host, chan);
+ } else if (!strcmp("QUIT", cmd)) {
+ t = QUIT;
+ l = basprintf("%s(%s) has quit (%s)", nick, host, text ? text : "");
+ } else if (!strcmp("NICK", cmd) && text) {
+ t = INFO;
+ l = basprintf("%s changed nick to %s", nick, text);
+ } else if (!strcmp("NOTICE", cmd)) {
+ t = INFO;
+ l = basprintf("%s: %s", nick, text ? text : "");
+ } else if (!strcmp("PRIVMSG", cmd)) {
+ if (!text)
+ text = "";
+ if (!strncmp("\001ACTION", text, strlen("\001ACTION"))) {
+ text += strlen("\001ACTION");
+ if (text[0] == ' ')
+ text++;
+ if ((p = strchr(text, '\001')))
+ *p = '\0';
+ t = EMOT;
+ l = basprintf("%s", text);
+ } else if (!strncmp("\001VERSION", text, strlen("\001VERSION"))) {
+ irc_ctcp(nick, "VERSION", version_string);
+ } else if (!strncmp("\001CLIENTINFO", text, strlen("\001CLIENTINFO"))) {
+ irc_ctcp(nick, "CLIENTINFO", "ACTION VERSION");
+ } else {
+ t = PRIV;
+ l = basprintf("%s", text);
+ }
+ } else {
+ t = INFO;
+ l = basprintf("%s", cmd);
+ }
+
+ irc_add_line(t, 0, nick, l);
+}
+
+static int rem;
+
+static void tcp_handler(char *buf, int len)
+{
+ static char msg[512];
+ char *end, *eol;
+
+ if (!len)
+ return;
+
+ end = buf + len;
+
+ /* messages ends with CR LF "\r\n" */
+ while ((eol = memchr(buf, '\n', len))) {
+ *eol = '\0';
+ if (rem) {
+ strlcpy(msg + rem, buf, sizeof(msg) - rem);
+ irc_recv(msg);
+ rem = 0;
+ } else {
+ irc_recv(buf);
+ }
+ if (eol == end)
+ break;
+ len -= (eol - buf) + 1;
+ buf += (eol - buf) + 1;
+ }
+ if (buf < end) {
+ /* keep unterminated line (rem bytes) into the msg buffer */
+ size_t n = min_t(size_t, end - buf + 1, sizeof(msg) - rem);
+ rem += strlcpy(msg + rem, buf, n);
+ }
+
+ printf("\r[%s] %s", nick, input_line);
+}
+
+static void net_handler(void *ctx, char *pkt, unsigned len)
+{
+ struct iphdr *ip = net_eth_to_iphdr(pkt);
+
+ if (net_read_ip((void *)&ip->saddr) != net_ip) {
+ printf("bad ip !\n");
+ return;
+ }
+
+ tcp_handler(net_eth_to_tcp_payload(pkt), net_eth_to_tcplen(pkt));
+}
+
+static int irc_send(const char *msg, int len)
+{
+ char *buf = net_tcp_get_payload(con);
+
+ if (len > 0) {
+ memcpy(buf, msg, len);
+ return net_tcp_send(con, len);
+ }
+
+ return 0; /* nothing to do */
+}
+
+static int irc_priv_msg(char *dst, char *str)
+{
+ char *buf = net_tcp_get_payload(con);
+ int len;
+
+ len = snprintf(buf, 256, "PRIVMSG %s :%s\r\n", dst, str);
+ if (len <= 0)
+ return -1;
+ irc_add_line(PRIV, SELF, nick, basprintf("%s", str));
+ return net_tcp_send(con, len);
+}
+
+static int irc_pong(const char *txt)
+{
+ char *buf = net_tcp_get_payload(con);
+ int len;
+
+ len = snprintf(buf, 256, "PONG %s\r\n", txt);
+ if (len <= 0)
+ return -1;
+ return net_tcp_send(con, len);
+}
+
+static int irc_ctcp(const char *dst, const char *act, const char *msg)
+{
+ char *buf = net_tcp_get_payload(con);
+ int len;
+
+ len = snprintf(buf, 256, "NOTICE %s :\001%s %s \001\r\n", dst, act, msg);
+ if (len <= 0)
+ return -1;
+ return net_tcp_send(con, len);
+}
+
+static char msg[512];
+
+static int irc_input(char *buf)
+{
+ int len;
+ char *p;
+ char *k;
+
+ if (buf[0] == '\0')
+ return 0;
+ if (buf[0] != '/')
+ return irc_priv_msg(chan, buf);
+
+ len = 0;
+ p = strchr(buf, ' ');
+ p = (p != NULL) ? p + 1 : NULL;
+ switch (buf[1]) {
+ case 'j': /* join */
+ if (!p)
+ break;
+ len = snprintf(msg, sizeof(msg), "JOIN %s\r\n", p);
+ k = strchr(p, ' ');
+ if (k)
+ *k = '\0';
+ strlcpy(chan, p, sizeof(chan));
+ break;
+ case 'p': /* part */
+ case 'l': /* leave */
+ if (!p)
+ p = "leaving";
+ len = snprintf(msg, sizeof(msg), "PART %s :%s\r\n", chan, p);;
+ break;
+ case 'n': /* nick */
+ if (!p)
+ break;
+ len = snprintf(msg, sizeof(msg), "NICK %s\r\n", p);
+ strlcpy(nick, p, sizeof(nick));
+ break;
+ case 'm': /* me */
+ if (!p)
+ break;
+ len = snprintf(msg, sizeof(msg),
+ "PRIVMSG %s :\001ACTION %s\001\r\n", chan, p);
+ irc_add_line(EMOT, SELF, nick, basprintf("%s", p));
+ break;
+ case 'w': /* wisper */
+ if (!p)
+ break;
+ k = strchr(p, ' ');
+ if (!k)
+ break;
+ *k = '\0';
+ return irc_priv_msg(p, k + 1);
+ case 'q': /* quit */
+ if (!p)
+ p = "quiting";
+ len = snprintf(msg, sizeof(msg), "QUIT :%s\r\n", p);
+ break;
+ default:
+ len = snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
+ break;
+ }
+
+ return irc_send(msg, len);
+}
+
+static int irc_login(char *host, char *real)
+{
+ int len;
+
+ len = snprintf(msg, sizeof(msg),
+ "NICK %s\r\n"
+ "USER %s localhost %s :%s\r\n", nick, nick, host, real);
+
+ return irc_send(msg, len);
+}
+
+static int irc_readline(char *buf, int len)
+{
+ int n, c;
+
+ memset(buf, 0, len);
+ printf("\r[%s] \x1b[K", nick);
+
+ for (n = 0; n < len; ) {
+ while (!tstc()) {
+ if (redraw) {
+ redraw = 0;
+ irc_draw();
+ }
+ net_poll();
+ resched();
+ }
+ c = getchar();
+ if (c < 0)
+ return (-1);
+ switch (c) {
+ case '\b':
+ case BB_KEY_DEL7:
+ case BB_KEY_DEL:
+ if (n > 0) {
+ buf[--n] = '\0';
+ printf("\b \b");
+ }
+ break;
+ default:
+ if (isascii(c) && isprint(c)) {
+ buf[n++] = c;
+ printf("%c", c);
+ }
+ break;
+ case BB_KEY_CLEAR_SCREEN:
+ redraw = 1;
+ break;
+ case '\r':
+ case '\n':
+ buf[n] = '\0';
+ return n;
+ case CTL_CH('c'):
+ buf[0] = '\0';
+ if (n == 0) {
+ printf("QUIT");
+ return -1;
+ }
+ return 0;
+ }
+ }
+ return n;
+}
+
+static int do_irc(int argc, char *argv[])
+{
+ int ret;
+ char *host, *p;
+ char *command = NULL;
+ uint16_t port = 6667;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "c:n:")) > 0) {
+ switch (opt) {
+ case 'c':
+ command = optarg;
+ break;
+ case 'n':
+ strlcpy(nick, optarg, sizeof(nick));
+ break;
+ }
+ }
+ argv += optind;
+ argc -= optind;
+ if (argc < 1)
+ return COMMAND_ERROR_USAGE;
+ host = argv[0];
+ if ((p = strchr(host, '/'))) {
+ *p = '\0';
+ port = simple_strtoul(p + 1, NULL, 10);
+ }
+ if (argc > 1)
+ port = simple_strtoul(argv[1], NULL, 10);
+
+ ret = resolv(host, &net_ip);
+ if (ret) {
+ printf("Cannot resolve \"%s\": %s\n", host, strerror(-ret));
+ return ret;
+ }
+
+ con = net_tcp_new(net_ip, port, net_handler, NULL);
+ if (IS_ERR(con)) {
+ printf("net tcp new fail\n");
+ ret = PTR_ERR(con);
+ goto out;
+ }
+
+ ret = net_tcp_open(con);
+ if (ret) {
+ printf("net_tcp_open: %d\n", ret);
+ goto out;
+ }
+
+ redraw = 1;
+ rem = 0;
+ chan[0] = '\0';
+ if (nick[0] == '\0')
+ strlcpy(nick, "barebox", sizeof(nick));
+ irc_login(host, "barebox");
+
+ if (command)
+ irc_input(command);
+
+ while (con->state == TCP_ESTABLISHED) {
+ int len;
+ len = irc_readline(input_line, sizeof(input_line) - 1);
+ if (len < 0)
+ break;
+ if (irc_input(input_line) < 0)
+ break;
+ if (ctrlc()) {
+ ret = -EINTR;
+ break;
+ }
+ }
+ net_tcp_close(con);
+ net_poll();
+
+ ret = con->ret;
+out:
+ if (!IS_ERR(con))
+ net_unregister(con);
+
+ return ret;
+}
+BAREBOX_CMD_HELP_START(irc)
+BAREBOX_CMD_HELP_TEXT("Options:")
+BAREBOX_CMD_HELP_OPT ("-n NICK\t", "nick to use")
+BAREBOX_CMD_HELP_OPT ("-c COMMAND\t", "command to run after login")
+BAREBOX_CMD_HELP_END
+
+BAREBOX_CMD_START(irc)
+ .cmd = do_irc,
+ BAREBOX_CMD_DESC("IRC client")
+ BAREBOX_CMD_OPTS("[-nc] DESTINATION[[/]PORT]")
+ BAREBOX_CMD_GROUP(CMD_GRP_NET)
+BAREBOX_CMD_END
--
2.17.1
next prev parent reply other threads:[~2022-08-09 13:22 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-08-09 13:20 [RFC PATCH 1/5] net: Add a function to retrieve UDP connection Jules Maselbas
2022-08-09 13:20 ` [RFC PATCH 2/5] net: Implement source port randomization Jules Maselbas
2022-08-09 13:20 ` [RFC PATCH 3/5] net: Add simple TCP support Jules Maselbas
2022-08-12 7:04 ` Sascha Hauer
2022-08-12 16:17 ` Jules Maselbas
2022-08-09 13:20 ` Jules Maselbas [this message]
2022-08-09 13:20 ` [RFC PATCH 5/5] Add tcpdump command Jules Maselbas
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220809132021.7110-4-jmaselbas@kalray.eu \
--to=jmaselbas@kalray.eu \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox