/* * This file is part of uIRC. (https://git.redxen.eu/caskd/uIRC) * Copyright (c) 2019, 2020 Alex-David Denes * * uIRC is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * uIRC is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with uIRC. If not, see . */ #include "uirc.h" const char* const uirc_ircmd[] = { [PASS] = "PASS", [NICK] = "NICK", [USER] = "USER", [SERVER] = "SERVER", [OPER] = "OPER", [SERVICE] = "SERVICE", [QUIT] = "QUIT", [SQUIT] = "SQUIT", [JOIN] = "JOIN", [PART] = "PART", [MODE] = "MODE", [TOPIC] = "TOPIC", [NAMES] = "NAMES", [LIST] = "LIST", [INVITE] = "INVITE", [KICK] = "KICK", [VERSION] = "VERSION", [STATS] = "STATS", [LINKS] = "LINKS", [TIME] = "TIME", [CONNECT] = "CONNECT", [TRACE] = "TRACE", [ADMIN] = "ADMIN", [INFO] = "INFO", [SERVLIST] = "SERVLIST", [SQUERY] = "SQUERY", [PRIVMSG] = "PRIVMSG", [NOTICE] = "NOTICE", [MOTD] = "MOTD", [LUSERS] = "LUSERS", [WHO] = "WHO", [WHOIS] = "WHOIS", [WHOWAS] = "WHOWAS", [KILL] = "KILL", [PING] = "PING", [PONG] = "PONG", [ERROR] = "ERROR", [AWAY] = "AWAY", [REHASH] = "REHASH", [DIE] = "DIE", [RESTART] = "RESTART", [SUMMON] = "SUMMON", [USERS] = "USERS", [WALLOPS] = "WALLOPS", [USERHOST] = "USERHOST", [ISON] = "ISON"}; struct tagmapping { const char* name; IRC_Tag* assg; }; signed int Ircmd_stoi(char* str) { if (str == NULL) return ERR_UIRC_NULL_ARGS; for (unsigned short i = PASS; i <= ISON; i++) { if ((uirc_ircmd[i] != NULL) && (strcmp(uirc_ircmd[i], str) == 0)) return i; } return ERR_UIRC_UNKNOWN_TOKEN; } signed int Tok_mesg(char* str, IRC_Message* out) { if (str == NULL || out == NULL) return ERR_UIRC_NULL_ARGS; int ret; char *progr = str, *command; if (*progr == '@') { char* tags; if ((tags = strtok_r(progr, " ", &progr)) != NULL) { if ((ret = Tok_tags(tags, &out->tags)) < 0) return ret; } else return ERR_UIRC_INVALID_FORMAT; } if (*progr == ':') { char* prefix; if ((prefix = strtok_r(progr, " ", &progr)) != NULL) { if ((ret = Tok_user(prefix, &out->name, false)) < 0) return ret; } else return ERR_UIRC_INVALID_FORMAT; } if ((command = strtok_r(NULL, " ", &progr)) != NULL) { if (!(out->cmd = (isalpha(*command)) ? Ircmd_stoi(command) : atoi(command))) return ERR_UIRC_UNKNOWN_TOKEN; } else { return ERR_UIRC_INVALID_FORMAT; } int i; for (i = 0; *progr != ':' && i < 14 && *progr != '\0'; i++) { if ((out->args[i] = strtok_r(NULL, " ", &progr)) == NULL) return ERR_UIRC_INVALID_FORMAT; } out->args[i] = NULL; if (*progr == ':') ++progr; if (*progr != '\0') out->trailing = progr; return 1; } signed int Assm_mesg(char* buf, IRC_Message* in) { if (buf == NULL || in == NULL) return ERR_UIRC_BUFFER_ERR; char* pos = buf; int cnt, ret; if ((ret = Assm_tags(pos, &in->tags)) < 0) return ret; else pos += ret; if (in->name.nick != NULL || in->name.host != NULL) { *(pos++) = ':'; if ((ret = Assm_user(pos, &in->name, false)) <= 0) return ret; else pos += ret; *(pos++) = ' '; } if ((uirc_ircmd[in->cmd] != NULL) && ((cnt = sprintf(pos, "%s", uirc_ircmd[in->cmd])) > 0)) pos += cnt; else return ERR_UIRC_GENERIC; for (unsigned int i = 0; i < 14 && in->args[i] != NULL; i++) { if ((cnt = sprintf(pos, " %s", in->args[i])) > 0) pos += cnt; else return ERR_UIRC_BUFFER_ERR; } if (in->trailing != NULL) { if ((cnt = sprintf(pos, " :%s", in->trailing)) > 0) pos += cnt; else return ERR_UIRC_GENERIC; } if ((cnt = sprintf(pos, "\r\n")) == 2) pos += cnt; else return ERR_UIRC_BUFFER_ERR; return pos - buf; } signed int Assm_tags(char* buf, IRC_Tags* in) { if (buf == NULL || in == NULL) return ERR_UIRC_NULL_ARGS; char* pos = buf; int cnt; struct tagmapping tagmps[] = { {.name = "time", .assg = &in->time}, {.name = "account", .assg = &in->account}, {.name = "batch", .assg = &in->batch}, {.name = "label", .assg = &in->label}, {.name = "msgid", .assg = &in->msgid}, {.name = "multiline-concat", .assg = &in->multiline_concat}, {.name = "typing", .assg = &in->typing}, {.name = "react", .assg = &in->react}, {.name = "reply", .assg = &in->reply}}; for (unsigned int i = 0; i < sizeof(tagmps) / sizeof(struct tagmapping); i++) { if ((*tagmps[i].assg).present) { if (pos == buf) { if (sprintf(pos++, "@") != 1) return ERR_UIRC_BUFFER_ERR; } else { if (sprintf(pos++, ";") != 1) return ERR_UIRC_BUFFER_ERR; } if ((*tagmps[i].assg).clientbound) { if (sprintf(pos++, "+") != 1) return ERR_UIRC_BUFFER_ERR; } if ((*tagmps[i].assg).value != NULL) cnt = sprintf(pos, "%s=%s", tagmps[i].name, (*tagmps[i].assg).value); else cnt = sprintf(pos, "%s", tagmps[i].name); if (cnt > 0) pos += cnt; else return ERR_UIRC_BUFFER_ERR; } } return pos - buf; } signed int Tok_tags(char* str, IRC_Tags* out) { if (str == NULL || out == NULL) return ERR_UIRC_NULL_ARGS; char *cval, *cpos = str, *ctag = NULL; bool clientbound; const struct tagmapping tagmps[] = { {.name = "time", .assg = &out->time}, {.name = "account", .assg = &out->account}, {.name = "batch", .assg = &out->batch}, {.name = "label", .assg = &out->label}, {.name = "msgid", .assg = &out->msgid}, {.name = "multiline-concat", .assg = &out->multiline_concat}, {.name = "typing", .assg = &out->typing}, {.name = "react", .assg = &out->react}, {.name = "reply", .assg = &out->reply}}; if (*cpos == '@') cpos++; while ((ctag = strtok_r(NULL, "; ", &cpos)) != NULL) { clientbound = false; if (*ctag == '+') { ctag++; clientbound = true; } if ((cval = strchr(ctag, '=')) != NULL) *(cval++) = '\0'; for (unsigned int i = 0; i < sizeof(tagmps) / sizeof(struct tagmapping); i++) { if (!strcmp(ctag, tagmps[i].name)) { if (cval != NULL) (*tagmps[i].assg).value = cval; (*tagmps[i].assg).clientbound = clientbound; (*tagmps[i].assg).present = true; } } } return 1; } signed int Assm_user(char* buf, IRC_User* in, bool useorig) { if (buf == NULL || in == NULL) return ERR_UIRC_NULL_ARGS; int cnt; char* pos = buf; if (in->nick == NULL && in->host != NULL) { if ((cnt = sprintf(pos, "%s", in->host)) > 0) pos += cnt; else return ERR_UIRC_BUFFER_ERR; } else if (in->nick != NULL) { if ((cnt = sprintf(pos, "%s", in->nick)) > 0) pos += cnt; else return ERR_UIRC_BUFFER_ERR; if (in->user != NULL) { if ((cnt = sprintf(pos, "!%s", in->user)) > 0) pos += cnt; else return ERR_UIRC_BUFFER_ERR; } if (useorig && in->orig != NULL) { if ((cnt = sprintf(pos, "%%%s", in->orig)) > 0) pos += cnt; else return ERR_UIRC_BUFFER_ERR; } if (in->host != NULL) { if ((cnt = sprintf(pos, "@%s", in->host)) > 0) pos += cnt; else return ERR_UIRC_BUFFER_ERR; } } else return ERR_UIRC_NULL_ARGS; return pos - buf; } signed int Tok_user(char* str, IRC_User* out, bool useorig) { char* pos = (*str == ':') ? str + 1 : str; if ((out->host = strchr(pos, '@')) != NULL) *((char*)out->host++) = '\0'; if (useorig && (out->orig = strchr(pos, '%')) != NULL) *((char*)out->orig++) = '\0'; if ((out->user = strchr(pos, '!')) != NULL) /* RFC2812 says this cannot be here without the host but RFC1459 says it can, we accept both options */ *((char*)out->user++) = '\0'; if (*(out->nick = pos) == '\0') /* NOTE: This may be the server instead of a nick according to RFC2812, it is left to the library out to decide how to interpret this based on the context. */ return ERR_UIRC_INVALID_FORMAT; return 1; }