====== RocketMSG ====== A Linux Commandline tool that can post a message into a room/team/channel/user, which is actually very useful especially in automations. We run a lot of automation in linux, powered by cron (or anacron) to handle data in, data out, report generation, backup's and the suchlike. We also use Rocket.chat for the company chat system, being privately hosted and secure. Having a commandline tool that can post a message, from the command line arguments, or even via stdin is a key part of that, keeping people informed of things that are happening, and providing visual verifications. This is written in c, very minimalist so it can compile on many systems. This is a very basic implementation. If you're interested in a fully featured commercial version with multiline quoting (fixed spacing), attachments, debug and more then hit us up on the gen.net.uk contact form. ===== Prerequisites ===== You will need two libraries, libcurl-devel, and json-c-devel, that's it. ===== The Code ===== #include #include #include #include #include const char *VERSION = "1.002"; struct MemoryStruct { char *memory; size_t size; }; static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct MemoryStruct *mem = (struct MemoryStruct *)userp; char *ptr = realloc(mem->memory, mem->size + realsize + 1); if (ptr == NULL) { printf("Not enough memory (realloc returned NULL)\n"); return 0; } mem->memory = ptr; memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; return realsize; } char *read_message_from_stdin() { char *message = NULL; size_t size = 0; size_t capacity = 0; char buffer[4096]; while (fgets(buffer, sizeof(buffer), stdin) != NULL) { size_t len = strlen(buffer); if (size + len + 1 > capacity) { capacity = (size + len + 1) * 2; char *new_message = realloc(message, capacity); if (new_message == NULL) { free(message); return NULL; } message = new_message; } memcpy(message + size, buffer, len); size += len; } if (message != NULL) { message[size] = '\0'; } return message; } char *json_escape_string(const char *str) { if (str == NULL) return NULL; size_t len = strlen(str); size_t escaped_len = 0; for (size_t i = 0; i < len; i++) { switch (str[i]) { case '\\': case '"': escaped_len += 2; break; default: escaped_len++; break; } } char *escaped_str = malloc(escaped_len + 1); if (escaped_str == NULL) return NULL; size_t j = 0; for (size_t i = 0; i < len; i++) { switch (str[i]) { case '\\': escaped_str[j++] = '\\'; escaped_str[j++] = '\\'; break; case '"': escaped_str[j++] = '\\'; escaped_str[j++] = '"'; break; default: escaped_str[j++] = str[i]; break; } } escaped_str[j] = '\0'; return escaped_str; } int main(int argc, char *argv[]) { fprintf(stderr, "RocketMSG Version %s\n", VERSION); if (argc < 2) { fprintf(stderr, "Usage: %s [message]\n", argv[0]); return 1; } const char *username = "some username"; const char *password = "usernames password"; const char *room = argv[1]; const char *message = NULL; if (argc < 3) { message = read_message_from_stdin(); if (message == NULL) { fprintf(stderr, "Failed to read message from stdin\n"); return 1; } } else { message = argv[2]; } const char *SERVER_URL = "http://10.1.2.3:3001"; CURL *curl; CURLcode res; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if (curl) { struct MemoryStruct chunk; chunk.memory = malloc(1); chunk.size = 0; char authUrl[256]; snprintf(authUrl, sizeof(authUrl), "%s/api/v1/login", SERVER_URL); char authPayload[256]; snprintf(authPayload, sizeof(authPayload), "{\"username\":\"%s\",\"password\":\"%s\"}", username, password); struct curl_slist *authHeaders = NULL; authHeaders = curl_slist_append(authHeaders, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_URL, authUrl); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, authPayload); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, authHeaders); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); res = curl_easy_perform(curl); if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { // Parse the response to extract the user ID and token json_object *jsonResponse = json_tokener_parse(chunk.memory); if (jsonResponse == NULL) { fprintf(stderr, "Failed to parse JSON response.\n"); } else { json_object *jsonData = json_object_object_get(jsonResponse, "data"); if (jsonData == NULL) { fprintf(stderr, "Failed to get 'data' object from JSON response.\n"); } else { const char *userId = json_object_get_string(json_object_object_get(jsonData, "userId")); const char *authToken = json_object_get_string(json_object_object_get(jsonData, "authToken")); if (userId && authToken) { free(chunk.memory); chunk.memory = malloc(1); chunk.size = 0; char url[256]; snprintf(url, sizeof(url), "%s/api/v1/chat.postMessage", SERVER_URL); char *escaped_message = json_escape_string(message); json_object *payload = json_object_new_object(); json_object_object_add(payload, "channel", json_object_new_string(room)); json_object_object_add(payload, "text", json_object_new_string(escaped_message)); const char *json_payload = json_object_to_json_string(payload); struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "Content-Type: application/json"); char authTokenHeader[256]; snprintf(authTokenHeader, sizeof(authTokenHeader), "X-Auth-Token: %s", authToken); headers = curl_slist_append(headers, authTokenHeader); char userIdHeader[256]; snprintf(userIdHeader, sizeof(userIdHeader), "X-User-Id: %s", userId); headers = curl_slist_append(headers, userIdHeader); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); res = curl_easy_perform(curl); if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { // Parse the response to check if the message was posted successfully json_object *postResponse = json_tokener_parse(chunk.memory); if (postResponse == NULL) { fprintf(stderr, "Failed to parse JSON response.\n"); } else { json_object *success = json_object_object_get(postResponse, "success"); if (success == NULL || !json_object_get_boolean(success)) { json_object *error = json_object_object_get(postResponse, "error"); if (error != NULL) { const char *errorMessage = json_object_get_string(error); fprintf(stderr, "Failed to post message. Error: %s\n", errorMessage); } else { fprintf(stderr, "Failed to post message. Unknown error.\n"); fprintf(stderr, "Response: %s\n", chunk.memory); } } else { printf("Message posted successfully.\n"); } json_object_put(postResponse); } } curl_slist_free_all(headers); json_object_put(payload); free(escaped_message); } else { fprintf(stderr, "Failed to extract user ID and auth token from the response.\n"); } } json_object_put(jsonResponse); } } free(chunk.memory); curl_slist_free_all(authHeaders); } curl_easy_cleanup(curl); curl_global_cleanup(); // free(message); return 0; } ===== Configuring ===== You need to pick a user who will be sending these messages, its best to create a new user, e.g. "automation" and give it a password and permissions with access to everywhere you want to be able to post messages. Then you need to code that user in *username, the user's password in *password, and finally set your rocket.chat server URL in, you guessed it, *SERVER_URL. ===== Compiling ===== We're going to use clang here instead of gcc simply because its, well, easier. If you don't have clang, just install it. This assumes you've called the source code rocketmsg.c clang -o rocketmsg rocketmsg.c -lcurl -ljson-c If you get any errors relating to either curl or json-c, then make sure the header files are in the PATH, and if not, make it so, OR, copy the headers locally to a subdirectory (e.g. if you're .c file is in /home/smartie, then stick the headers for json-c in /home/smartie/json-c and it'll find them. You must of course make sure you have the lib's installed (see prerequisites). Once compiled, and this is tested working on MacOS, RHEL and CentOS, simply run it like this.. ===== Usage ===== Send a message to Fred: rocketmsg "@Fred" "You are a muppet" Send a message to the Room #General: rocketmsg "#General" "I am rocketmsg, and I rule the world, or at least the automation server" Send the contents of auto.log to the Team SomeTeam: rocketmsg "SomeTeam" < auto.log