/* * 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[] = { [ADMIN] = "ADMIN", [AWAY] = "AWAY", [CONNECT] = "CONNECT", [DIE] = "DIE", [ERROR] = "ERROR", [INFO] = "INFO", [INVITE] = "INVITE", [ISON] = "ISON", [JOIN] = "JOIN", [KICK] = "KICK", [KILL] = "KILL", [LINKS] = "LINKS", [LIST] = "LIST", [LUSERS] = "LUSERS", [MODE] = "MODE", [MOTD] = "MOTD", [NAMES] = "NAMES", [NICK] = "NICK", [NOTICE] = "NOTICE", [OPER] = "OPER", [PART] = "PART", [PASS] = "PASS", [PING] = "PING", [PONG] = "PONG", [PRIVMSG] = "PRIVMSG", [QUIT] = "QUIT", [REHASH] = "REHASH", [RESTART] = "RESTART", [SERVER] = "SERVER", [SERVICE] = "SERVICE", [SERVLIST] = "SERVLIST", [SQUERY] = "SQUERY", [SQUIT] = "SQUIT", [STATS] = "STATS", [SUMMON] = "SUMMON", [TIME] = "TIME", [TOPIC] = "TOPIC", [TRACE] = "TRACE", [USERHOST] = "USERHOST", [USERS] = "USERS", [USER] = "USER", [VERSION] = "VERSION", [WALLOPS] = "WALLOPS", [WHOIS] = "WHOIS", [WHOWAS] = "WHOWAS", [WHO] = "WHO", #ifdef UIRC_IRCV3 [ACCOUNT] = "ACCOUNT", [ACC] = "ACC", [ACK] = "ACK", [AUTHENTICATE] = "AUTHENTICATE", [BATCH] = "BATCH", [CAP] = "CAP", [CHGHOST] = "CHGHOST", [FAIL] = "FAIL", [MONITOR] = "MONITOR", [NOTE] = "NOTE", [RENAME] = "RENAME", [RESUME] = "RESUME", [SETNAME] = "SETNAME", [WARN] = "WARN", [WEBIRC] = "WEBIRC" #endif }; #ifdef UIRC_IRCV3 struct tagmapping { const char* const name; IRC_Tag* assg; }; #endif signed int Ircmd_stoi(char* str) { if (str == NULL) return ERR_UIRC_NULL_ARGS; for (unsigned short i = UIRC_FCMD; i <= UIRC_LCMD; 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; #ifdef UIRC_IRCV3 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; } #endif 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; i++) { if ((out->args[i] = strtok_r(NULL, " ", &progr)) == NULL) return ERR_UIRC_INVALID_FORMAT; } out->args[i] = NULL; if (*progr) out->trailing = (*progr == ':') ? progr + 1 : 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; #ifdef UIRC_IRCV3 if ((ret = Assm_tags(pos, &in->tags)) < 0) return ret; else pos += ret; #endif 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 Val_mesg(IRC_Message* mesg) { if (mesg == NULL) return ERR_UIRC_NULL_ARGS; for (unsigned int i = 0; i < 14 && mesg->args[i] != NULL; i++) { if (Val_type_nocrlf(mesg->args[i]) != 1) return ERR_UIRC_VAL_FAILED; if (Val_type_nospcl(mesg->args[i]) != 1) return ERR_UIRC_VAL_FAILED; } if (mesg->trailing != NULL) { if (Val_type_nocrlf(mesg->trailing) != 1) return ERR_UIRC_VAL_FAILED; } return 1; } signed int Val_type_nocrlf(char* str) { if (str == NULL) return ERR_UIRC_NULL_ARGS; for (; *str; str++) { if (*str == '\r' || *str == '\n') return 0; } return 1; } signed int Val_type_nospcl(char* str) { if (str == NULL) return ERR_UIRC_NULL_ARGS; for (; *str; str++) { if (*str == ' ' || *str == ':') return 0; } return 1; } signed int Val_type_noblcm(char* str) { if (str == NULL) return ERR_UIRC_NULL_ARGS; for (; *str; str++) { if (*str == '\a' || *str == ',') return 0; } return 1; } signed int Val_channame(char* chan) { if (chan == NULL) return ERR_UIRC_NULL_ARGS; if (*chan != '#' || *chan != '+' || *chan != '!' || *chan != '&') return 0; char* clps = ++chan; if ((clps = strchr(chan, ':')) != NULL) { *clps = '\0'; if (Val_type_nospcl(chan) != 1 || Val_type_nocrlf(chan) != 1 || Val_type_noblcm(chan) != 1) { *clps = ':'; return 0; } *clps = ':'; chan = ++clps; } if (Val_type_nospcl(chan) != 1 || Val_type_nocrlf(chan) != 1 || Val_type_noblcm(chan) != 1) return 0; return 1; } #ifdef UIRC_IRCV3 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).value != NULL) { 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; } cnt = (*(*tagmps[i].assg).value) ? sprintf(pos, "%s=%s", tagmps[i].name, (*tagmps[i].assg).value) : 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 the tag is present we point it to the value if given, or to the delimiter ('\0') * This is done for a few reasons. First, we have a non-null address so we show that * the tag is present and second, we have no value that way */ (*tagmps[i].assg).value = (cval != NULL) ? cval : ctag + strlen(ctag); (*tagmps[i].assg).clientbound = clientbound; } } } return 1; } #endif 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) *(out->host++) = '\0'; if (useorig && (out->orig = strchr(pos, '%')) != NULL) *(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)) /* 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; }