summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2017-04-29 04:36:01 +0300
committerdefanor <defanor@uberspace.net>2017-04-29 04:36:01 +0300
commita06cc218bfa18943a46e051d5bbf463e1ddc0b6e (patch)
treed160d59dc14e0693ac8a304574dbf726b92850a0 /examples
Initial commit
Diffstat (limited to 'examples')
-rw-r--r--examples/chat/nc-chatroom.service10
-rwxr-xr-xexamples/chat/tls-chat.sh9
-rwxr-xr-xexamples/chat/tlsd-chat.sh15
-rw-r--r--examples/chat/tlsd-chatroom.service13
-rwxr-xr-xexamples/file-server/accept-files.sh12
-rwxr-xr-xexamples/file-server/browse-files.sh16
-rwxr-xr-xexamples/file-server/serve-files.sh13
-rw-r--r--examples/p2p-im/Makefile33
-rw-r--r--examples/p2p-im/approximate-setup.sh15
-rw-r--r--examples/p2p-im/libpurple-fifo-plugin.c400
-rw-r--r--examples/p2p-im/tlsd-im-cmd.sh13
-rw-r--r--examples/p2p-im/tlsd-im-reconnect.service13
-rw-r--r--examples/p2p-im/tlsd-im-reconnect.sh9
-rw-r--r--examples/p2p-im/tlsd-im-reconnect.timer8
-rw-r--r--examples/p2p-im/tlsd-im.service12
-rw-r--r--examples/p2p-im/tlsd-im.sh11
16 files changed, 602 insertions, 0 deletions
diff --git a/examples/chat/nc-chatroom.service b/examples/chat/nc-chatroom.service
new file mode 100644
index 0000000..8c3f359
--- /dev/null
+++ b/examples/chat/nc-chatroom.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Ncat chatroom
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/nc -vl --broker 127.0.0.1 7000
+User=nobody
+
+[Install]
+WantedBy=multi-user.target
diff --git a/examples/chat/tls-chat.sh b/examples/chat/tls-chat.sh
new file mode 100755
index 0000000..647331f
--- /dev/null
+++ b/examples/chat/tls-chat.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+rlwrap gnutls-cli --insecure --x509keyfile ~/.tls/key.pem \
+ --x509certfile ~/.tls/cert.pem "${1}" --port="${2}" \
+ | while read -r LINE
+do echo "${LINE}"
+ case "${LINE}" in
+ *"${USER}"*) printf '\a' ;;
+ esac
+done
diff --git a/examples/chat/tlsd-chat.sh b/examples/chat/tlsd-chat.sh
new file mode 100755
index 0000000..a1064a4
--- /dev/null
+++ b/examples/chat/tlsd-chat.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+NAME="${ALIAS}[$$]"
+
+quit () {
+ echo "$(date -u +%R) * ${NAME} quits" | nc localhost 7000
+}
+trap quit EXIT
+
+JOINMSG="$(date -u +%R) * ${NAME} joins"
+echo "${JOINMSG}"
+echo "${JOINMSG}" | nc localhost 7000
+while read -r LINE
+do echo "$(date -u +%R) ${NAME}: ${LINE}"
+done | stdbuf -oL tr -d '\000-\011\013-\037' | nc localhost 7000
diff --git a/examples/chat/tlsd-chatroom.service b/examples/chat/tlsd-chatroom.service
new file mode 100644
index 0000000..3b80c42
--- /dev/null
+++ b/examples/chat/tlsd-chatroom.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=TLSd chatroom
+Requires=nc-chatroom.service
+After=syslog.target
+
+[Service]
+Type=simple
+Environment="PATH=/usr/local/bin/:/usr/bin/"
+ExecStart=/usr/local/bin/tlsd -p 5600 -- fp2alias -a -- tlsd-chat.sh
+User=tlsd
+
+[Install]
+WantedBy=multi-user.target
diff --git a/examples/file-server/accept-files.sh b/examples/file-server/accept-files.sh
new file mode 100755
index 0000000..c3f472e
--- /dev/null
+++ b/examples/file-server/accept-files.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# Accept files from remote users.
+
+ROOT="/srv/tlsd/files"
+USERDIR="${ROOT}/${ALIAS}"
+
+if read -r LINE
+then if [ -d "${USERDIR}" ]
+ then cat > "${USERDIR}/$(echo "${LINE}" | sed -e 's/\.\.//g')"
+ else echo "No directory for you!"
+ fi
+fi
diff --git a/examples/file-server/browse-files.sh b/examples/file-server/browse-files.sh
new file mode 100755
index 0000000..c93716d
--- /dev/null
+++ b/examples/file-server/browse-files.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Serves files and directory listings.
+
+ROOT="/srv/tlsd/files"
+
+while read -r LINE
+do
+ echo "${SHA256}: ${LINE}" 1>&2
+ FILEPATH="${ROOT}/$(echo "${LINE}" | sed -e 's/\.\.//g')"
+ if [ -d "${FILEPATH}" ]
+ then ls -l "${FILEPATH}"
+ elif [ -f "${FILEPATH}" ]
+ then cat "${FILEPATH}"
+ else echo "${FILEPATH} is neither a file nor a directory";
+ fi
+done
diff --git a/examples/file-server/serve-files.sh b/examples/file-server/serve-files.sh
new file mode 100755
index 0000000..5728c10
--- /dev/null
+++ b/examples/file-server/serve-files.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+# Serve a single file and exits.
+
+ROOT="/srv/tlsd/files"
+
+if read -r LINE
+then echo "${SHA256}: ${LINE}" 1>&2
+ FILEPATH="${ROOT}/$(echo "${LINE}" | sed -e 's/\.\.//g')"
+ if [ -f "${FILEPATH}" ]
+ then cat "${FILEPATH}"
+ else echo "${FILEPATH} is not a file"
+ fi
+fi
diff --git a/examples/p2p-im/Makefile b/examples/p2p-im/Makefile
new file mode 100644
index 0000000..72ca0f5
--- /dev/null
+++ b/examples/p2p-im/Makefile
@@ -0,0 +1,33 @@
+# This Makefile builds and installs the libpurple plugin.
+
+# Using -Wno-unused-parameter here, since libpurple predefines those
+# parameters, and we don't always need them.
+
+# Not using -Werror, since on Debian 9 -pedantic points to an error in
+# libpurple/certificate.h.
+
+# C99 is needed to initialize large structures with less boilerplate,
+# and glib requires it anyway. GNU extensions are also handy, so using
+# gnu99.
+
+CC = gcc
+CFLAGS += -std=gnu99 -Wall -Wextra -Wno-unused-parameter -pedantic \
+ -g -DPURPLE_PLUGINS -fPIC -DPIC -shared \
+ `pkg-config --cflags purple glib-2.0`
+LDLIBS += `pkg-config --libs purple glib-2.0`
+PLUGIN_DIR = `pkg-config --variable=plugindir purple`
+SOURCES = libpurple-fifo-plugin.c
+PLUGIN_ID = prpl-defanor-fifo
+TARGET = ${PLUGIN_ID}.so
+
+all:
+ ${CC} ${CFLAGS} ${SOURCES} ${LDLIBS} -o ${TARGET}
+
+install:
+ install ${TARGET} ${PLUGIN_DIR}
+
+uninstall:
+ rm -f ${PLUGIN_DIR}/${TARGET}
+
+clean:
+ rm -f ${TARGET}
diff --git a/examples/p2p-im/approximate-setup.sh b/examples/p2p-im/approximate-setup.sh
new file mode 100644
index 0000000..a0fef4d
--- /dev/null
+++ b/examples/p2p-im/approximate-setup.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Assuming that tlsd and the plugin are installed, the key and
+# certificate are already generated, placed into /etc/tls/, and are
+# accessible to the tlsd group.
+install tlsd-im{,-cmd,-reconnect}.sh /usr/local/bin/
+useradd --system -G tlsd tlsd-im
+gpasswd -a bitlbee tlsd-im
+gpasswd -a $USER tlsd-im
+mkdir -p /var/lib/tlsd-im/
+chown tlsd-im:tlsd-im /var/lib/tlsd-im/
+chmod g+w /var/lib/tlsd-im
+install tlsd-im.service tlsd-im-reconnect.{service,timer} /etc/systemd/system/
+systemctl enable tlsd-im.service tlsd-im-reconnect.timer
+systemctl start tlsd-im.service tlsd-im-reconnect.timer
diff --git a/examples/p2p-im/libpurple-fifo-plugin.c b/examples/p2p-im/libpurple-fifo-plugin.c
new file mode 100644
index 0000000..15d34a5
--- /dev/null
+++ b/examples/p2p-im/libpurple-fifo-plugin.c
@@ -0,0 +1,400 @@
+/*
+ libpurple-fifo-plugin, an example plugin that interacts with FIFOs
+ created with std2fifo or similar programs.
+
+ In this whole example, there is plenty to improve, but it should
+ work for basic message transmission.
+
+ This is free and unencumbered software released into the public
+ domain.
+*/
+
+#include <glib.h>
+
+#include "prpl.h"
+#include "version.h"
+#include "debug.h"
+#include "cmds.h"
+#include "accountopt.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/inotify.h>
+#include <time.h>
+
+
+#define PLUGIN_ID "prpl-defanor-fifo"
+#define PLUGIN_NAME "FIFOs-based IM"
+#define PLUGIN_VERSION "0.1-dev"
+#define PLUGIN_SUMMARY "Reads from and writes into FIFOs"
+#define PLUGIN_DESCRIPTION "This is an example for the TLSd manual"
+
+#define MAX_PEERS 64
+#define MAX_DIRNAME_LEN 256
+#define MAX_BUF_SIZE 4096
+#define FIFO_OUT_MODE S_IRWXU | S_IRWXG | S_IWGRP
+
+
+typedef struct {
+ int fd;
+ int wd;
+ guint handle;
+} watch;
+
+typedef struct {
+ int input;
+ int output;
+ char dirname[MAX_DIRNAME_LEN];
+ char *path;
+ size_t path_len;
+ guint handle;
+ PurpleConnection *gc;
+} peer;
+
+typedef struct {
+ watch w;
+ peer p[MAX_PEERS];
+} conn_state;
+
+
+static void new_dir (gpointer data, gint source, PurpleInputCondition cond);
+static void incoming_msg (gpointer data, gint source, PurpleInputCondition cond);
+static void fifo_close(PurpleConnection *gc);
+
+void peer_reset (peer *cp)
+{
+ cp->output = -1;
+ cp->input = -1;
+ cp->path = NULL;
+ cp->path_len = 0;
+ cp->handle = 0;
+}
+
+void peer_terminate (peer *cp)
+{
+ if (cp->path == NULL)
+ return;
+ purple_debug_misc(PLUGIN_ID, "Terminating %s\n", cp->dirname);
+ purple_prpl_got_user_status(cp->gc->account, cp->dirname, "offline", NULL);
+ if (cp->handle)
+ purple_input_remove (cp->handle);
+ if (cp->path != NULL)
+ free (cp->path);
+ if (cp->output != -1)
+ close (cp->output);
+ if (cp->input != -1)
+ close (cp->input);
+ peer_reset(cp);
+}
+
+static int peer_suspend (peer *cp)
+{
+ purple_debug_misc(PLUGIN_ID, "Suspending %s\n", cp->dirname);
+ purple_prpl_got_user_status(cp->gc->account, cp->dirname, "offline", NULL);
+ if (cp->handle)
+ purple_input_remove (cp->handle);
+ if (cp->output != -1)
+ close (cp->output);
+ if (cp->input != -1) {
+ close (cp->input);
+ cp->input = -1;
+ }
+ strcpy(cp->path + cp->path_len - 4, "out");
+ cp->output = open(cp->path, O_RDONLY | O_NONBLOCK);
+ if (cp->output == -1) {
+ purple_debug_error(PLUGIN_ID, "Failed to open %s: %s\n",
+ cp->path, strerror(errno));
+ peer_terminate(cp);
+ return -1;
+ }
+ cp->handle = purple_input_add(cp->output, PURPLE_INPUT_READ,
+ incoming_msg, cp);
+ return 0;
+}
+
+
+static void incoming_msg (gpointer data,
+ gint source,
+ PurpleInputCondition cond)
+{
+ peer *cp = data;
+ ssize_t len;
+ static char buf[MAX_BUF_SIZE + 1];
+ len = read(cp->output, buf, MAX_BUF_SIZE);
+ if (len < 0) {
+ /* Error */
+ purple_debug_error(PLUGIN_ID, "Failed to read from %s: %s\n",
+ cp->dirname, strerror(errno));
+ peer_terminate(cp);
+ return;
+ } else if (len == 0) {
+ /* EOF */
+ purple_debug_misc(PLUGIN_ID, "EOF from %s\n", cp->dirname);
+ peer_suspend(cp);
+ return;
+ }
+ purple_prpl_got_user_status(cp->gc->account, cp->dirname, "available", NULL);
+ buf[len] = 0;
+ /* Messages would normally end with newline, but IM clients add
+ newlines on output as well, so we'll have to get rid of that. */
+ if (buf[len - 1] == '\n') {
+ if (len == 1)
+ /* Could be an automatically added newline, ignore that. */
+ return;
+ buf[len -1] = 0;
+ }
+ serv_got_im(purple_account_get_connection(cp->gc->account),
+ cp->dirname, buf, 0, time(NULL));
+}
+
+static int add_peer (PurpleConnection *gc,
+ const char *dname)
+{
+ conn_state *cs = gc->proto_data;
+ unsigned int i;
+ purple_debug_misc(PLUGIN_ID, "Adding peer %s\n", dname);
+ for (i = 0; (i < MAX_PEERS) && (cs->p[i].path != NULL); i++);
+ if (i == MAX_PEERS) {
+ purple_debug_error(PLUGIN_ID, "Too many peers (%d)\n", i);
+ return -1;
+ }
+ cs->p[i].gc = gc;
+ strncpy(cs->p[i].dirname, dname, MAX_DIRNAME_LEN - 1);
+ cs->p[i].path_len =
+ /* root, slash, sha256, slash, {in,out}, zero */
+ strlen(gc->account->username) + 1 + strlen(dname) + 1 + 3 + 1;
+ cs->p[i].path = malloc(cs->p[i].path_len);
+ if (cs->p[i].path == NULL) {
+ purple_debug_error(PLUGIN_ID, "Failed to allocate %d bytes of memory\n",
+ (int) cs->p[i].path_len);
+ peer_terminate(&cs->p[i]);
+ return -1;
+ }
+ snprintf(cs->p[i].path, cs->p[i].path_len,
+ "%s/%s/out", gc->account->username, dname);
+ /* Create a FIFO if it doesn't exist yet. Might be nicer to just set
+ a watch and wait, but that'd be more cumbersome, so this will do
+ for now. */
+ if (mkfifo(cs->p[i].path, FIFO_OUT_MODE) && errno != EEXIST) {
+ purple_debug_error(PLUGIN_ID, "Failed to create a FIFO at %s: %s\n",
+ cs->p[i].path, strerror(errno));
+ peer_terminate(&cs->p[i]);
+ return -1;
+ }
+ cs->p[i].output = open(cs->p[i].path, O_RDONLY | O_NONBLOCK);
+ if (cs->p[i].output == -1) {
+ purple_debug_error(PLUGIN_ID, "Failed to open %s: %s\n",
+ cs->p[i].path, strerror(errno));
+ peer_terminate(&cs->p[i]);
+ return -1;
+ }
+ strcpy(cs->p[i].path + cs->p[i].path_len - 4, "in");
+ cs->p[i].input = open(cs->p[i].path, O_WRONLY | O_NONBLOCK);
+ purple_prpl_got_user_status(gc->account, cs->p[i].dirname,
+ (cs->p[i].input == -1) ? "offline" : "available",
+ NULL);
+ /* Set a callback */
+ cs->p[i].handle = purple_input_add(cs->p[i].output, PURPLE_INPUT_READ,
+ incoming_msg, &cs->p[i]);
+ return i;
+}
+
+static void new_dir (gpointer data,
+ gint source,
+ PurpleInputCondition cond)
+{
+ static char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
+ struct inotify_event *ie;
+ ssize_t len;
+ PurpleConnection *gc = data;
+ conn_state *cs = gc->proto_data;
+ purple_debug_misc(PLUGIN_ID, "A 'new dir' watch for %s has fired\n",
+ gc->account->username);
+ len = read (cs->w.fd, buf, sizeof(struct inotify_event) + NAME_MAX + 1);
+ if (len == 0)
+ return;
+ else if (len < 0) {
+ /* error */
+ purple_debug_error(PLUGIN_ID, "A watch failure\n");
+ fifo_close (gc);
+ return;
+ }
+ ie = (struct inotify_event *) &buf;
+ purple_debug_misc(PLUGIN_ID, "A watch for %s has fired: %s, read %d bytes\n",
+ gc->account->username, ie->name, (int) len);
+ add_peer (gc, ie->name);
+}
+
+
+static int load_peers (PurpleConnection *gc)
+{
+ DIR *dp;
+ struct dirent *ep;
+
+ dp = opendir(gc->account->username);
+ if (dp == NULL)
+ return -1;
+ while ((ep = readdir(dp))) {
+ if ((ep->d_name[0] == '.') || (ep->d_type != DT_DIR))
+ continue;
+ purple_debug_misc(PLUGIN_ID, "Loading %s\n", ep->d_name);
+ add_peer (gc, ep->d_name);
+ }
+ closedir(dp);
+ return 0;
+}
+
+
+static int fifo_send_im(PurpleConnection *gc,
+ const char *who,
+ const char *message,
+ PurpleMessageFlags flags)
+{
+ size_t written = 0, total = strlen(message), ret;
+ unsigned int i;
+ conn_state *cs = gc->proto_data;
+ /* Find peer */
+ for (i = 0; i < MAX_PEERS; i++) {
+ if (! strncmp(who, cs->p[i].dirname, MAX_DIRNAME_LEN))
+ break;
+ }
+ if (i == MAX_PEERS)
+ return -ENOTCONN;
+ /* Write */
+ if (cs->p[i].input == -1) {
+ strcpy(cs->p[i].path + cs->p[i].path_len - 4, "in");
+ cs->p[i].input = open(cs->p[i].path, O_WRONLY | O_NONBLOCK);
+ if (cs->p[i].input == -1) {
+ purple_debug_misc(PLUGIN_ID, "Not connected to %s\n", cs->p[i].dirname);
+ peer_suspend(&cs->p[i]);
+ return -ENOTCONN;
+ }
+ }
+ while (written < total) {
+ ret = write(cs->p[i].input, message + written, total - written);
+ if (ret <= 0) {
+ purple_debug_misc(PLUGIN_ID, "Failed writing to %s\n", cs->p[i].dirname);
+ peer_suspend(&cs->p[i]);
+ return -ENOTCONN;
+ } else {
+ written += ret;
+ }
+ }
+ write(cs->p[i].input, "\n", 1);
+ purple_prpl_got_user_status(gc->account, cs->p[i].dirname, "available", NULL);
+ return 1;
+}
+
+
+/* Login: take dir name from username, get subdirs, set add input
+ handlers from "out", watch directory with inotify and add an input
+ handler for that too. */
+static void fifo_login(PurpleAccount *acct)
+{
+ PurpleConnection *gc = purple_account_get_connection(acct);
+ conn_state *cs;
+ unsigned int i;
+ purple_debug_misc(PLUGIN_ID, "Login: %s\n", acct->username);
+ purple_connection_update_progress(gc, "Connecting", 0, 2);
+ /* Allocate and prepare connection state */
+ cs = malloc(sizeof(conn_state));
+ gc->proto_data = cs;
+ for (i = 0; i < MAX_PEERS; i++)
+ peer_reset(&cs->p[i]);
+ /* Add a watch */
+ /* TODO: checks, error handling */
+ cs->w.fd = inotify_init();
+ cs->w.wd = inotify_add_watch(cs->w.fd, acct->username, IN_CREATE);
+ cs->w.handle = purple_input_add(cs->w.fd, PURPLE_INPUT_READ,
+ new_dir, gc);
+ /* Load peers */
+ purple_debug_misc(PLUGIN_ID, "Loading peers\n");
+ load_peers (gc);
+ purple_connection_update_progress(gc, "Connected", 1, 2);
+ purple_connection_set_state(gc, PURPLE_CONNECTED);
+}
+
+static void fifo_close(PurpleConnection *gc)
+{
+ unsigned int i;
+ conn_state *cs = gc->proto_data;
+ /* Terminate the watch */
+ purple_debug_misc(PLUGIN_ID, "Removing the watch\n");
+ if (cs->w.handle)
+ purple_input_remove (cs->w.handle);
+ if (cs->w.fd != -1 && cs->w.wd != -1)
+ inotify_rm_watch (cs->w.fd, cs->w.wd);
+ if (cs->w.fd != -1)
+ close (cs->w.fd);
+ /* Terminate the peers */
+ purple_debug_misc(PLUGIN_ID, "Terminating peers\n");
+ for (i = 0; i < MAX_PEERS; i++)
+ peer_terminate(&cs->p[i]);
+ /* Free */
+ free (gc->proto_data);
+ purple_debug_misc(PLUGIN_ID, "Closed: %s\n", gc->account->username);
+}
+
+
+static void fifo_init(PurplePlugin *plugin)
+{
+ purple_debug_misc(PLUGIN_ID, "Initializing\n");
+}
+
+static void fifo_destroy(PurplePlugin *plugin)
+{
+ purple_debug_misc(PLUGIN_ID, "Shutting down\n");
+}
+
+static const char *fifo_list_icon(PurpleAccount *acct,
+ PurpleBuddy *buddy)
+{
+ /* TODO: do something about it. Maybe draw an icon. */
+ return "irc";
+}
+
+static GList *fifo_status_types(PurpleAccount *acct)
+{
+ GList *types = NULL;
+ types = g_list_prepend(types, purple_status_type_new(PURPLE_STATUS_AVAILABLE,
+ NULL, NULL, TRUE));
+ types = g_list_prepend(types, purple_status_type_new(PURPLE_STATUS_OFFLINE,
+ NULL, NULL, TRUE));
+ return types;
+}
+
+static PurplePluginProtocolInfo fifo_info =
+ {
+ .options = OPT_PROTO_NO_PASSWORD,
+ .icon_spec = NO_BUDDY_ICONS,
+ .list_icon = fifo_list_icon,
+ .status_types = fifo_status_types,
+ .login = fifo_login,
+ .close = fifo_close,
+ .send_im = fifo_send_im,
+ .struct_size = sizeof(PurplePluginProtocolInfo)
+ };
+
+static PurplePluginInfo info =
+ {
+ .magic = PURPLE_PLUGIN_MAGIC,
+ .major_version = PURPLE_MAJOR_VERSION,
+ .minor_version = PURPLE_MINOR_VERSION,
+ .type = PURPLE_PLUGIN_PROTOCOL,
+ .priority = PURPLE_PRIORITY_DEFAULT,
+ .id = PLUGIN_ID,
+ .name = PLUGIN_NAME,
+ .version = PLUGIN_VERSION,
+ .summary = PLUGIN_SUMMARY,
+ .description = PLUGIN_DESCRIPTION,
+ .destroy = fifo_destroy,
+ .extra_info = &fifo_info
+ };
+
+PURPLE_INIT_PLUGIN(fifo, fifo_init, info)
diff --git a/examples/p2p-im/tlsd-im-cmd.sh b/examples/p2p-im/tlsd-im-cmd.sh
new file mode 100644
index 0000000..2888a66
--- /dev/null
+++ b/examples/p2p-im/tlsd-im-cmd.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+create_fifo () {
+ if [ ! -e "${CHATDIR}${SHA256}/$1" ]
+ then mkfifo -m 660 "${CHATDIR}${SHA256}/$1"
+ fi
+}
+
+CHATDIR="/var/lib/tlsd-im/"
+mkdir -m 770 -p "${CHATDIR}${SHA256}/"
+create_fifo "in"
+create_fifo "out"
+flock -n "${CHATDIR}${SHA256}/lock" std2fifo -c "${CHATDIR}"
diff --git a/examples/p2p-im/tlsd-im-reconnect.service b/examples/p2p-im/tlsd-im-reconnect.service
new file mode 100644
index 0000000..e118614
--- /dev/null
+++ b/examples/p2p-im/tlsd-im-reconnect.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=TLSd P2P IM reconnect
+Requisite=tlsd-im.service
+After=syslog.target
+
+[Service]
+Type=oneshot
+Environment="PATH=/usr/local/bin/:/usr/bin/"
+ExecStart=/usr/local/bin/tlsd-im-reconnect.sh
+User=tlsd-im
+
+[Install]
+WantedBy=multi-user.target
diff --git a/examples/p2p-im/tlsd-im-reconnect.sh b/examples/p2p-im/tlsd-im-reconnect.sh
new file mode 100644
index 0000000..3fc149f
--- /dev/null
+++ b/examples/p2p-im/tlsd-im-reconnect.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+CHATDIR="/var/lib/tlsd-im/"
+
+for DIR in $(find "${CHATDIR}" -maxdepth 1 -mindepth 1 -type d)
+do if [ -f "${DIR}/address" ]
+ then flock -n "${DIR}/lock" cat "${DIR}/address" > "${CHATDIR}connect"
+ fi
+done
diff --git a/examples/p2p-im/tlsd-im-reconnect.timer b/examples/p2p-im/tlsd-im-reconnect.timer
new file mode 100644
index 0000000..e55e206
--- /dev/null
+++ b/examples/p2p-im/tlsd-im-reconnect.timer
@@ -0,0 +1,8 @@
+[Unit]
+Description=TLSd P2P IM reconnect timer
+
+[Timer]
+OnUnitInactiveSec=600
+
+[Install]
+WantedBy=timers.target
diff --git a/examples/p2p-im/tlsd-im.service b/examples/p2p-im/tlsd-im.service
new file mode 100644
index 0000000..254fc6d
--- /dev/null
+++ b/examples/p2p-im/tlsd-im.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=TLSd P2P IM
+Requires=nc-chatroom.service
+After=syslog.target
+
+[Service]
+Environment="PATH=/usr/local/bin/:/usr/bin/"
+ExecStart=/usr/local/bin/tlsd-im.sh
+User=tlsd-im
+
+[Install]
+WantedBy=multi-user.target
diff --git a/examples/p2p-im/tlsd-im.sh b/examples/p2p-im/tlsd-im.sh
new file mode 100644
index 0000000..7c580ae
--- /dev/null
+++ b/examples/p2p-im/tlsd-im.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+CHATDIR="/var/lib/tlsd-im/"
+mkdir -p "${CHATDIR}"
+
+if [ ! -e "${CHATDIR}connect" ]
+then echo foo > /tmp/bar
+ mkfifo "${CHATDIR}connect"
+fi
+
+tail -f "${CHATDIR}connect" | torify tlsd -p 18765 -- tlsd-im-cmd.sh