Skip to content

Instantly share code, notes, and snippets.

@nacx
Last active December 15, 2015 14:39
Show Gist options
  • Select an option

  • Save nacx/5276517 to your computer and use it in GitHub Desktop.

Select an option

Save nacx/5276517 to your computer and use it in GitHub Desktop.
IRC bot to expose the transmission-remote command
# Rasptrans Makefile
# Copyright (c) 2013 Ignasi Barrera
# This file is released under the MIT License, see LICENSE file.
TARGETS = rasptrans
TREMOTE = $(shell which transmission-remote)
CC = gcc
LN = $(CC)
PREFIX ?= /usr/local
CFLAGS = -pipe -O2 -Wall -ansi -pedantic -DTREMOTE=$(TREMOTE)
LDFLAGS = -lcircus -lpthread
all: $(TARGETS)
$(TARGETS):
$(CC) $(CFLAGS) -c $@.c
$(LN) -o $@ $@.o $(LDFLAGS)
install: all
install -d -m 0755 $(PREFIX)/bin
install -m 0555 rasptrans $(PREFIX)/bin
uninstall:
rm -f $(PREFIX)/bin/rasptrans
clean:
rm -f *.o
rm -f $(TARGETS)
/*
* Copyright (c) 2013 Ignasi Barrera
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
* IRC interface for transmission-remote
*/
#define _POSIX_C_SOURCE 200112L
#ifdef TREMOTE
#define STR(x) #x
#define STR_MACRO(x) STR(x)
#define TR_BIN STR_MACRO(TREMOTE)
#else
#error The absolute path to transmission-remote is not defined
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <circus/irc.h>
#include <circus/utils.h>
#include <circus/hashtable.h>
char* tr_host; /* The transmission-remote host and port */
char* tr_credentials; /* The password used to authenticate with rasptrans */
struct ht_table* auth_store; /* Authorization store */
/* Build the transmission-remote command for the given input */
char* build_command(char* input, char* tr_auth) {
char* cmd;
size_t lcmd = strlen(TR_BIN) + strlen(tr_host) + strlen(tr_auth) + strlen(input) + 12;
if ((cmd = malloc((lcmd * sizeof(char)))) == 0) {
perror("Out of memory (build_command)");
exit(EXIT_FAILURE);
}
memset(cmd, '\0', lcmd);
snprintf(cmd, lcmd, "%s %s -n %s %s 2>&1", TR_BIN, tr_host, tr_auth, input);
return cmd;
}
/* Disconnect if the nick is in use */
void on_nick_in_use(ErrorEvent* event) {
printf("Nick %s is already in use\n", event->params[1]);
irc_quit("Bye");
irc_disconnect();
ht_destroy(auth_store);
exit(EXIT_FAILURE);
}
/* Run the given transmission-remote command and reply with the output */
void transmission_remote(MessageEvent* event) {
struct ht_data* auth = ht_find(auth_store, event->user.nick);
/* Check if the user is authorized */
if (auth == NULL) {
irc_message(event->user.nick, "Error: You are not authorized");
} else {
char output[512];
char* cmd = build_command(event->message, auth->value);
FILE* fp = popen(cmd, "r");
if (fp == NULL) {
irc_message(event->user.nick, "Failed to run command");
} else {
while (fgets(output, sizeof(output) - 1, fp) != NULL) {
irc_message(event->user.nick, output);
poll(0, 0, 1000); /* Wait one second to avoid flooding */
}
pclose(fp);
}
free(cmd);
}
}
/* Authorizes the current user */
void login(MessageEvent* event) {
char msg[50];
if (event->message == NULL || s_ne(event->message, tr_credentials)) {
snprintf(msg, 50, "Error: Invalid credentials");
} else {
struct ht_data* auth = ht_find(auth_store, event->user.nick);
if (auth != NULL) {
snprintf(msg, 50, "Error: User %s is already authorized", event->user.nick);
} else {
ht_add_value(auth_store, event->user.nick, event->message);
snprintf(msg, 50, "User %s authorized!", event->user.nick);
}
}
irc_message(event->user.nick, msg);
}
/* Removes the authorization for the current user */
void logout(MessageEvent* event) {
char msg[50];
if (event->message == NULL || s_ne(event->message, tr_credentials)) {
snprintf(msg, 50, "Error: Invalid credentials");
} else {
struct ht_data* auth = ht_find(auth_store, event->user.nick);
if (auth == NULL) {
snprintf(msg, 50, "Error: No authorization found for user: %s", event->user.nick);
} else {
if (s_eq(auth->value, event->message)) {
ht_del(auth_store, event->user.nick);
snprintf(msg, 50, "Removed authorization for user: %s", event->user.nick);
} else {
snprintf(msg, 50, "Error: Invalid credentials for user: %s", event->user.nick);
}
}
}
irc_message(event->user.nick, msg);
}
/* Execute the given shell command */
void execute_shell(MessageEvent* event) {
struct ht_data* auth = ht_find(auth_store, event->user.nick);
/* Check if the user is authorized */
if (auth == NULL) {
irc_message(event->user.nick, "Error: You are not authorized");
} else {
char output[512];
FILE* fp = popen(event->message, "r");
if (fp == NULL) {
irc_message(event->user.nick, "Failed to run command");
} else {
while (fgets(output, sizeof(output) - 1, fp) != NULL) {
irc_message(event->user.nick, output);
poll(0, 0, 1000); /* Wait one second to avoid flooding */
}
pclose(fp);
}
}
}
int main(int argc, char **argv) {
char *irc_server, *irc_port, *irc_nick;
if (argc < 5) {
printf("Usage: %s <irc server> <irc port> <nickname> <transmission-host> <credentials>\n", argv[0]);
exit(EXIT_FAILURE);
}
irc_server = argv[1];
irc_port = argv[2];
irc_nick = argv[3];
tr_host = argv[4];
tr_credentials = argv[5];
irc_bind_event(ERR_NICKNAMEINUSE, (Callback) on_nick_in_use);
irc_bind_command("login", (Callback) login);
irc_bind_command("logout", (Callback) logout);
irc_bind_command("tr", (Callback) transmission_remote);
irc_bind_command("run", (Callback) execute_shell);
irc_connect(irc_server, irc_port);
irc_login(irc_nick, "Rasptrans", "IRC interface for transmission-remote");
auth_store = ht_create();
irc_listen();
irc_quit("Bye");
irc_disconnect();
ht_destroy(auth_store);
return 0;
}
# Upstart service for rasptrans
description "IRC interface for transmission-remote"
author "Ignasi Barrera <ignasi.barrera@gmail.com>"
# Star and stop configuration
start on (runlevel [2345] and net-device-up IFACE=eth0)
stop on runlevel [016]
# Automatically restart process if crashed
respawn
# Start the process
exec start-stop-daemon --start --chuid <user> --exec /usr/local/bin/rasptrans <irc-server> <irc-port> rasptrans <transmission-host> <credentials> >/dev/null 2>&1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment