From 27fb5c936dab7498e7b34a1c525c53328e8fc887 Mon Sep 17 00:00:00 2001
From: Armin Luntzer <armin.luntzer@univie.ac.at>
Date: Thu, 13 Jun 2019 15:06:02 +0200
Subject: [PATCH] * split out packet generation for network cmd and ack
 functions * add more privilege levels * add password input for priviledge
 level escalation to client gui * add MOTD * add welcome message

---
 src/client/gui.c                        |  41 +++-
 src/client/widgets/chatlog/chatlog.c    |   3 +
 src/include/ack.h                       |  26 +++
 src/include/cmd.h                       |  28 +++
 src/include/protocol.h                  |   2 +
 src/net/acks/ack_capabilities.c         |  15 +-
 src/net/acks/ack_fail.c                 |  19 +-
 src/net/acks/ack_getpos_azel.c          |  28 ++-
 src/net/acks/ack_invalid_pkt.c          |  16 +-
 src/net/acks/ack_moveto_azel.c          |   6 +
 src/net/acks/ack_nopriv.c               |  20 +-
 src/net/acks/ack_spec_acq_cfg.c         |  16 +-
 src/net/acks/ack_spec_acq_disable.c     |  23 ++-
 src/net/acks/ack_spec_acq_enable.c      |  24 ++-
 src/net/acks/ack_spec_data.c            |  29 ++-
 src/net/acks/ack_status_acq.c           |  25 ++-
 src/net/acks/ack_status_move.c          |  24 ++-
 src/net/acks/ack_status_rec.c           |  24 ++-
 src/net/acks/ack_status_slew.c          |  25 ++-
 src/net/acks/ack_success.c              |  20 +-
 src/net/acks/ack_userlist.c             |  20 +-
 src/net/cmds/cmd_capabilities.c         |  15 +-
 src/net/cmds/cmd_control.c              |  21 +-
 src/net/cmds/cmd_fail.c                 |  13 +-
 src/net/cmds/cmd_getpos_azel.c          |  14 +-
 src/net/cmds/cmd_invalid_pkt.c          |  15 +-
 src/net/cmds/cmd_message.c              |  21 +-
 src/net/cmds/cmd_moveto_azel.c          |  16 +-
 src/net/cmds/cmd_nick.c                 |  20 +-
 src/net/cmds/cmd_park_telescope.c       |  15 +-
 src/net/cmds/cmd_recalibrate_pointing.c |  15 +-
 src/net/cmds/cmd_spec_acq_cfg.c         |  26 ++-
 src/net/cmds/cmd_spec_acq_cfg_get.c     |  15 +-
 src/net/cmds/cmd_spec_acq_disable.c     |  16 +-
 src/net/cmds/cmd_spec_acq_enable.c      |  16 +-
 src/net/cmds/cmd_success.c              |  15 +-
 src/net/protocol.c                      |  17 +-
 src/server/cfg.c                        |  50 +++++
 src/server/config/server.cfg            |  22 ++-
 src/server/include/cfg.h                |   9 +-
 src/server/include/net.h                |   3 +
 src/server/net.c                        | 246 ++++++++++++++++++++----
 src/server/proc/proc_pr_control.c       |  25 ++-
 src/server/proc/proc_pr_message.c       |   2 +-
 src/server/proc/proc_pr_moveto_azel.c   |   2 -
 src/server/proc/proc_pr_nick.c          |   2 +-
 src/server/proc/proc_pr_spec_acq_cfg.c  |   2 -
 47 files changed, 890 insertions(+), 177 deletions(-)

diff --git a/src/client/gui.c b/src/client/gui.c
index 54563bf..0b79118 100644
--- a/src/client/gui.c
+++ b/src/client/gui.c
@@ -197,11 +197,13 @@ static void gui_update_nick(GtkEditable *ed, ChatLog *p)
 
 static void gui_request_control(GtkWidget *w, gpointer data)
 {
-
+	const gchar *pwd;
 	gchar *digest;
 
+	pwd = gtk_entry_get_text(GTK_ENTRY(data));
+
 	digest = g_compute_hmac_for_string(G_CHECKSUM_SHA256,
-					   (guint8*)"radtel", 6,
+					   (guint8*) pwd, 6,
 					   "thisishardcoed", 13);
 
 
@@ -223,6 +225,7 @@ static GtkWidget *gui_create_popover_menu(GtkWidget *widget)
 
 	GtkWidget *w;
 	GtkWidget *pop;
+	GtkWidget *pwd;
 
 	GSettings *s;
 
@@ -321,7 +324,8 @@ static GtkWidget *gui_create_popover_menu(GtkWidget *widget)
 	gtk_grid_attach(GTK_GRID(grid), w, 1, 3, 1, 1);
 
 
-	w = gtk_label_new("Request Control");
+
+	w = gtk_label_new("Control Password");
 	gtk_widget_set_halign(w, GTK_ALIGN_END);
 	gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
 	gtk_style_context_add_class(gtk_widget_get_style_context(w),
@@ -330,6 +334,27 @@ static GtkWidget *gui_create_popover_menu(GtkWidget *widget)
 
 
 
+	pwd = gtk_entry_new();
+	gtk_entry_set_text(GTK_ENTRY(pwd), "radtel");
+
+	gtk_entry_set_alignment(GTK_ENTRY(pwd), 1.0);
+
+	gtk_widget_set_tooltip_text(pwd, "Control Password");
+	gtk_entry_set_visibility(GTK_ENTRY(pwd), FALSE);
+
+	gtk_grid_attach(GTK_GRID(grid), pwd, 1, 4, 1, 1);
+
+
+
+	w = gtk_label_new("Request Control");
+	gtk_widget_set_halign(w, GTK_ALIGN_END);
+	gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
+	gtk_style_context_add_class(gtk_widget_get_style_context(w),
+				    "dim-label");
+	gtk_grid_attach(GTK_GRID(grid), w, 0, 5, 1, 1);
+
+
+
 	w = gtk_button_new();
 	gtk_button_set_always_show_image(GTK_BUTTON(w), TRUE);
 	gtk_button_set_image(GTK_BUTTON(w),
@@ -338,9 +363,9 @@ static GtkWidget *gui_create_popover_menu(GtkWidget *widget)
 	gtk_widget_set_tooltip_text(w, "Request Control");
 
 	g_signal_connect(G_OBJECT(w), "clicked",
-			 G_CALLBACK(gui_request_control), NULL);
+			 G_CALLBACK(gui_request_control), pwd);
 
-	gtk_grid_attach(GTK_GRID(grid), w, 1, 4, 1, 1);
+	gtk_grid_attach(GTK_GRID(grid), w, 1, 5, 1, 1);
 
 
 
@@ -349,7 +374,7 @@ static GtkWidget *gui_create_popover_menu(GtkWidget *widget)
 	gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
 	gtk_style_context_add_class(gtk_widget_get_style_context(w),
 				    "dim-label");
-	gtk_grid_attach(GTK_GRID(grid), w, 0, 5, 1, 1);
+	gtk_grid_attach(GTK_GRID(grid), w, 0, 6, 1, 1);
 
 	w = gtk_button_new();
 	gtk_button_set_always_show_image(GTK_BUTTON(w), TRUE);
@@ -361,9 +386,7 @@ static GtkWidget *gui_create_popover_menu(GtkWidget *widget)
 	g_signal_connect(G_OBJECT(w), "clicked",
 			 G_CALLBACK(gui_reconnect_net), NULL);
 
-	gtk_grid_attach(GTK_GRID(grid), w, 1, 5, 1, 1);
-
-
+	gtk_grid_attach(GTK_GRID(grid), w, 1, 6, 1, 1);
 
 
 
diff --git a/src/client/widgets/chatlog/chatlog.c b/src/client/widgets/chatlog/chatlog.c
index 2b4b5de..9f48f52 100644
--- a/src/client/widgets/chatlog/chatlog.c
+++ b/src/client/widgets/chatlog/chatlog.c
@@ -223,6 +223,7 @@ static GtkWidget *chatlog_create_chat(ChatLog *p)
 	gtk_container_add(GTK_CONTAINER(w), textview);
 
 
+	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD);
 	b = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
 	gtk_text_buffer_get_end_iter(b, &iter);
 	/* create right gravity mark on empty buffer, will always stay
@@ -310,6 +311,8 @@ static GtkWidget *chatlog_create_log(ChatLog *p)
 
 	textview = gtk_text_view_new();
 	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 3);
+	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD);
+
 	gtk_container_add(GTK_CONTAINER(w), textview);
 
 	b = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
diff --git a/src/include/ack.h b/src/include/ack.h
index c903651..fdde730 100644
--- a/src/include/ack.h
+++ b/src/include/ack.h
@@ -22,6 +22,32 @@
 #include <protocol.h>
 #include <net_common.h>
 
+/* ack packet generation functions */
+
+struct packet *ack_invalid_pkt_gen(uint16_t trans_id);
+struct packet *ack_capabilities_gen(uint16_t trans_id, struct capabilities *c);
+struct packet *ack_getpos_azel_gen(uint16_t trans_id, struct getpos *pos);
+struct packet *ack_spec_data_gen(uint16_t trans_id, struct spec_data *s);
+struct packet *ack_spec_acq_enable_gen(uint16_t trans_id);
+struct packet *ack_spec_acq_disable_gen(uint16_t trans_id);
+struct packet *ack_fail_gen(uint16_t trans_id);
+struct packet *ack_success_gen(uint16_t trans_id);
+struct packet *ack_spec_acq_cfg_gen(uint16_t trans_id,
+				    struct spec_acq_cfg *acq);
+struct packet *ack_status_acq_gen(uint16_t trans_id, struct status *c);
+struct packet *ack_status_slew_get(uint16_t trans_id, struct status *c);
+struct packet *ack_status_move_gen(uint16_t trans_id, struct status *c);
+struct packet *ack_status_rec_gen(uint16_t trans_id, struct status *c);
+struct packet *ack_moveto_azel_gen(uint16_t trans_id, double az, double el);
+struct packet *ack_nopriv_gen(uint16_t trans_id);
+struct packet *ack_userlist_gen(uint16_t trans_id, const uint8_t *userlist,
+				uint16_t len);
+
+
+
+
+/* command generation and sending functions */
+
 void ack_invalid_pkt(uint16_t trans_id);
 void ack_capabilities(uint16_t trans_id, struct capabilities *c);
 void ack_getpos_azel(uint16_t trans_id, struct getpos *pos);
diff --git a/src/include/cmd.h b/src/include/cmd.h
index a7034be..2808495 100644
--- a/src/include/cmd.h
+++ b/src/include/cmd.h
@@ -22,6 +22,34 @@
 #include <protocol.h>
 #include <net_common.h>
 
+
+/* command packet generation functions */
+
+struct packet *cmd_invalid_pkt_gen(uint16_t trans_id);
+struct packet *cmd_capabilities_gen(uint16_t trans_id);
+struct packet *cmd_success_gen(uint16_t trans_id);
+struct packet *cmd_fail_gen(uint16_t trans_id);
+struct packet *cmd_moveto_azel_gen(uint16_t trans_id, double az, double el);
+struct packet *cmd_recalibrate_pointing_gen(uint16_t trans_id);
+struct packet *cmd_park_telescope_gen(uint16_t trans_id);
+struct packet *cmd_spec_acq_cfg_gen(uint16_t trans_id,
+				    uint64_t f0, uint64_t f1, uint32_t bw_div,
+				    uint32_t bin_div, uint32_t n_stack,
+				    uint32_t acq_max);
+struct packet *cmd_getpos_azel_gen(uint16_t trans_id);
+struct packet *cmd_spec_acq_enable_gen(uint16_t trans_id);
+struct packet *cmd_spec_acq_disable_gen(uint16_t trans_id);
+struct packet *cmd_spec_acq_cfg_get_gen(uint16_t trans_id);
+struct packet *cmd_control_gen(uint16_t trans_id, const uint8_t *digest,
+			       uint16_t len);
+struct packet *cmd_message_gen(uint16_t trans_id, const uint8_t *message,
+			       uint16_t len);
+struct packet *cmd_nick_gen(uint16_t trans_id, const uint8_t *nick,
+			    uint16_t len);
+
+
+/* command generation and sending functions */
+
 void cmd_invalid_pkt(uint16_t trans_id);
 void cmd_capabilities(uint16_t trans_id);
 void cmd_success(uint16_t trans_id);
diff --git a/src/include/protocol.h b/src/include/protocol.h
index b439c74..1644295 100644
--- a/src/include/protocol.h
+++ b/src/include/protocol.h
@@ -104,6 +104,8 @@ struct packet {
 #define MAX_PACKET_SIZE (sizeof(struct packet) + MAX_PAYLOAD_SIZE)
 
 
+size_t pkt_size_get(struct packet *pkt);
+
 void pkt_hdr_to_net_order(struct packet *pkt);
 void pkt_hdr_to_host_order(struct packet *pkt);
 
diff --git a/src/net/acks/ack_capabilities.c b/src/net/acks/ack_capabilities.c
index 71471bf..61e688a 100644
--- a/src/net/acks/ack_capabilities.c
+++ b/src/net/acks/ack_capabilities.c
@@ -25,7 +25,7 @@
  * @brief acknowledge backend capabilities command
  */
 
-void ack_capabilities(uint16_t trans_id, struct capabilities *c)
+struct packet *ack_capabilities_gen(uint16_t trans_id, struct capabilities *c)
 {
 	gsize pkt_size;
 	gsize data_size;
@@ -54,8 +54,19 @@ void ack_capabilities(uint16_t trans_id, struct capabilities *c)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void ack_capabilities(uint16_t trans_id, struct capabilities *c)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_capabilities_gen(trans_id, c);
+
 	g_debug("Sending capabilities");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_fail.c b/src/net/acks/ack_fail.c
index 200cc8c..b55ec13 100644
--- a/src/net/acks/ack_fail.c
+++ b/src/net/acks/ack_fail.c
@@ -19,7 +19,7 @@
 #include <ack.h>
 
 
-void ack_fail(uint16_t trans_id, gpointer ref)
+struct packet *ack_fail_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,23 @@ void ack_fail(uint16_t trans_id, gpointer ref)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+/**
+ * @note this ack is always directed to a single client
+ */
+
+void ack_fail(uint16_t trans_id, gpointer ref)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_fail_gen(trans_id);
+
 	g_debug("Signalling failed operation");
-	net_send_single(ref, (void *) pkt, pkt_size);
+	net_send_single(ref, (void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_getpos_azel.c b/src/net/acks/ack_getpos_azel.c
index d823fa9..10682b0 100644
--- a/src/net/acks/ack_getpos_azel.c
+++ b/src/net/acks/ack_getpos_azel.c
@@ -20,14 +20,7 @@
 #include <net_common.h>
 
 
-
-/**
- * @brief acknowledge backend getpos_azel command
- *
- * @note the caller is responsible for freeing pos
- */
-
-void ack_getpos_azel(uint16_t trans_id, struct getpos *pos)
+struct packet *ack_getpos_azel_gen(uint16_t trans_id, struct getpos *pos)
 {
 	gsize pkt_size;
 
@@ -49,8 +42,25 @@ void ack_getpos_azel(uint16_t trans_id, struct getpos *pos)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+/**
+ * @brief acknowledge backend getpos_azel command
+ *
+ * @note the caller is responsible for freeing pos
+ */
+
+void ack_getpos_azel(uint16_t trans_id, struct getpos *pos)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_getpos_azel_gen(trans_id, pos);
+
 	g_debug("Sending AZEL");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	g_free(pkt);
 }
diff --git a/src/net/acks/ack_invalid_pkt.c b/src/net/acks/ack_invalid_pkt.c
index 9217bd9..39990d8 100644
--- a/src/net/acks/ack_invalid_pkt.c
+++ b/src/net/acks/ack_invalid_pkt.c
@@ -19,7 +19,7 @@
 #include <ack.h>
 
 
-void ack_invalid_pkt(uint16_t trans_id)
+struct packet *ack_invalid_pkt_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,20 @@ void ack_invalid_pkt(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+void ack_invalid_pkt(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_invalid_pkt_gen(trans_id);
+
 	g_debug("Signalling invalid packet");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_moveto_azel.c b/src/net/acks/ack_moveto_azel.c
index de4e14b..ce1d779 100644
--- a/src/net/acks/ack_moveto_azel.c
+++ b/src/net/acks/ack_moveto_azel.c
@@ -21,6 +21,12 @@
 #include <net_common.h>
 
 
+struct packet *ack_moveto_azel_gen(uint16_t trans_id, double az, double el)
+{
+	return cmd_moveto_azel_gen(trans_id, az, el);
+}
+
+
 
 /**
  * @brief acknowledge moveto_azel command
diff --git a/src/net/acks/ack_nopriv.c b/src/net/acks/ack_nopriv.c
index 0d012ec..758f64a 100644
--- a/src/net/acks/ack_nopriv.c
+++ b/src/net/acks/ack_nopriv.c
@@ -19,7 +19,7 @@
 #include <ack.h>
 
 
-void ack_nopriv(uint16_t trans_id, gpointer ref)
+struct packet *ack_nopriv_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,24 @@ void ack_nopriv(uint16_t trans_id, gpointer ref)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+/**
+ * @note this ack is always directed to a single client
+ */
+
+void ack_nopriv(uint16_t trans_id, gpointer ref)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_nopriv_gen(trans_id);
+
+
 	g_debug("Signalling lack of priviledge");
-	net_send_single(ref, (void *) pkt, pkt_size);
+	net_send_single(ref, (void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_spec_acq_cfg.c b/src/net/acks/ack_spec_acq_cfg.c
index d58aa17..2567cff 100644
--- a/src/net/acks/ack_spec_acq_cfg.c
+++ b/src/net/acks/ack_spec_acq_cfg.c
@@ -20,7 +20,7 @@
 #include <ack.h>
 
 
-void ack_spec_acq_cfg(uint16_t trans_id, struct spec_acq_cfg *acq)
+struct packet *ack_spec_acq_cfg_gen(uint16_t trans_id, struct spec_acq_cfg *acq)
 {
 	gsize pkt_size;
 
@@ -43,6 +43,17 @@ void ack_spec_acq_cfg(uint16_t trans_id, struct spec_acq_cfg *acq)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void ack_spec_acq_cfg(uint16_t trans_id, struct spec_acq_cfg *acq)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_spec_acq_cfg_gen(trans_id, acq);
+
 	g_debug("Sending current spectrometer configuration "
 		  "FREQ range: %g - %g MHz, BW div: %d, BIN div %d,"
 		  "STACK: %d, ACQ %d",
@@ -54,8 +65,7 @@ void ack_spec_acq_cfg(uint16_t trans_id, struct spec_acq_cfg *acq)
 		  acq->acq_max);
 
 
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	g_free(pkt);
 }
-
diff --git a/src/net/acks/ack_spec_acq_disable.c b/src/net/acks/ack_spec_acq_disable.c
index ec5b307..0c7cf70 100644
--- a/src/net/acks/ack_spec_acq_disable.c
+++ b/src/net/acks/ack_spec_acq_disable.c
@@ -21,11 +21,7 @@
 
 
 
-/**
- * @brief acknowledge backend spec_acq_disable command
- */
-
-void ack_spec_acq_disable(uint16_t trans_id)
+struct packet *ack_spec_acq_disable_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -45,8 +41,23 @@ void ack_spec_acq_disable(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+/**
+ * @brief acknowledge backend spec_acq_disable command
+ */
+
+void ack_spec_acq_disable(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_spec_acq_disable_gen(trans_id);
+
 	g_debug("Sending SPEC ACQ DISABLE");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	g_free(pkt);
 }
diff --git a/src/net/acks/ack_spec_acq_enable.c b/src/net/acks/ack_spec_acq_enable.c
index 251a7f5..8c9039e 100644
--- a/src/net/acks/ack_spec_acq_enable.c
+++ b/src/net/acks/ack_spec_acq_enable.c
@@ -20,12 +20,7 @@
 #include <net_common.h>
 
 
-
-/**
- * @brief acknowledge backend spec_acq_enable command
- */
-
-void ack_spec_acq_enable(uint16_t trans_id)
+struct packet *ack_spec_acq_enable_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -45,8 +40,23 @@ void ack_spec_acq_enable(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+/**
+ * @brief acknowledge backend spec_acq_enable command
+ */
+
+void ack_spec_acq_enable(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_spec_acq_enable_gen(trans_id);
+
 	g_debug("Sending SPEC ACQ ENABLE");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	g_free(pkt);
 }
diff --git a/src/net/acks/ack_spec_data.c b/src/net/acks/ack_spec_data.c
index 5e0fda4..3a5a74c 100644
--- a/src/net/acks/ack_spec_data.c
+++ b/src/net/acks/ack_spec_data.c
@@ -20,13 +20,8 @@
 #include <ack.h>
 
 
-/**
- * @brief send spectral data
- *
- * @note the caller must take care to clean the spectral data supplied
- */
 
-void ack_spec_data(uint16_t trans_id, struct spec_data *s)
+struct packet *ack_spec_data_gen(uint16_t trans_id, struct spec_data *s)
 {
 	gsize pkt_size;
 	gsize data_size;
@@ -51,9 +46,27 @@ void ack_spec_data(uint16_t trans_id, struct spec_data *s)
 
 	pkt_hdr_to_net_order(pkt);
 
-	g_debug("Transmitting spectral data");
 
-	net_send((void *) pkt, pkt_size);
+	return pkt;
+}
+
+
+/**
+ * @brief send spectral data
+ *
+ * @note the caller must take care to clean the spectral data supplied
+ */
+
+void ack_spec_data(uint16_t trans_id, struct spec_data *s)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_spec_data_gen(trans_id, s);
+
+
+	g_debug("Transmitting spectral data");
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up packet */
 	g_free(pkt);
diff --git a/src/net/acks/ack_status_acq.c b/src/net/acks/ack_status_acq.c
index 06e48d1..07d85ad 100644
--- a/src/net/acks/ack_status_acq.c
+++ b/src/net/acks/ack_status_acq.c
@@ -21,11 +21,7 @@
 
 
 
-/**
- * @brief acknowledge acquisition status
- */
-
-void ack_status_acq(uint16_t trans_id, struct status *c)
+struct packet *ack_status_acq_gen(uint16_t trans_id, struct status *c)
 {
 	gsize pkt_size;
 
@@ -48,8 +44,25 @@ void ack_status_acq(uint16_t trans_id, struct status *c)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+
+/**
+ * @brief acknowledge acquisition status
+ */
+
+void ack_status_acq(uint16_t trans_id, struct status *c)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_status_acq_gen(trans_id, c);
+
 	g_debug("Sending ACQ status");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_status_move.c b/src/net/acks/ack_status_move.c
index 58fb4f6..235371f 100644
--- a/src/net/acks/ack_status_move.c
+++ b/src/net/acks/ack_status_move.c
@@ -21,11 +21,7 @@
 
 
 
-/**
- * @brief acknowledge move-to-target status
- */
-
-void ack_status_move(uint16_t trans_id, struct status *c)
+struct packet *ack_status_move_gen(uint16_t trans_id, struct status *c)
 {
 	gsize pkt_size;
 
@@ -48,8 +44,24 @@ void ack_status_move(uint16_t trans_id, struct status *c)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+/**
+ * @brief acknowledge move-to-target status
+ */
+
+void ack_status_move(uint16_t trans_id, struct status *c)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_status_move_gen(trans_id, c);
+
 	g_debug("Sending MOVE status");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_status_rec.c b/src/net/acks/ack_status_rec.c
index 774f366..88235dd 100644
--- a/src/net/acks/ack_status_rec.c
+++ b/src/net/acks/ack_status_rec.c
@@ -21,11 +21,7 @@
 
 
 
-/**
- * @brief acknowledge recording status
- */
-
-void ack_status_rec(uint16_t trans_id, struct status *c)
+struct packet *ack_status_rec_gen(uint16_t trans_id, struct status *c)
 {
 	gsize pkt_size;
 
@@ -48,8 +44,24 @@ void ack_status_rec(uint16_t trans_id, struct status *c)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+/**
+ * @brief acknowledge recording status
+ */
+
+void ack_status_rec(uint16_t trans_id, struct status *c)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_status_rec_gen(trans_id, c);
+
 	g_debug("Sending REC status");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_status_slew.c b/src/net/acks/ack_status_slew.c
index ad9b497..cfe87ea 100644
--- a/src/net/acks/ack_status_slew.c
+++ b/src/net/acks/ack_status_slew.c
@@ -20,12 +20,7 @@
 #include <net_common.h>
 
 
-
-/**
- * @brief acknowledge slew status
- */
-
-void ack_status_slew(uint16_t trans_id, struct status *c)
+struct packet *ack_status_slew_get(uint16_t trans_id, struct status *c)
 {
 	gsize pkt_size;
 
@@ -48,8 +43,24 @@ void ack_status_slew(uint16_t trans_id, struct status *c)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+/**
+ * @brief acknowledge slew status
+ */
+
+void ack_status_slew(uint16_t trans_id, struct status *c)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_status_slew_get(trans_id, c);
+
 	g_debug("Sending SLEW status");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_success.c b/src/net/acks/ack_success.c
index 6bc237c..7cf8740 100644
--- a/src/net/acks/ack_success.c
+++ b/src/net/acks/ack_success.c
@@ -19,7 +19,7 @@
 #include <ack.h>
 
 
-void ack_success(uint16_t trans_id, gpointer ref)
+struct packet *ack_success_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,24 @@ void ack_success(uint16_t trans_id, gpointer ref)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+/**
+ * @note this ack is always directed to a single client
+ */
+
+void ack_success(uint16_t trans_id, gpointer ref)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_success_gen(trans_id);
+
 	g_debug("Signalling successful operation");
-	net_send((void *) pkt, pkt_size);
+	net_send_single(ref, (void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/acks/ack_userlist.c b/src/net/acks/ack_userlist.c
index ff54950..4faca4e 100644
--- a/src/net/acks/ack_userlist.c
+++ b/src/net/acks/ack_userlist.c
@@ -20,7 +20,8 @@
 #include <protocol.h>
 
 
-void ack_userlist(uint16_t trans_id, const uint8_t *userlist, uint16_t len)
+struct packet *ack_userlist_gen(uint16_t trans_id, const uint8_t *userlist,
+				uint16_t len)
 {
 	gsize pkt_size;
 	gsize data_size;
@@ -30,7 +31,7 @@ void ack_userlist(uint16_t trans_id, const uint8_t *userlist, uint16_t len)
 
 
 	if (len != strlen(userlist))
-	       	return;
+	       	return NULL;
 
 	/* all strings terminate with \0 char, we transport that as well! */
 	data_size = sizeof(struct userlist) + (len + 1) * sizeof(uint8_t);
@@ -52,9 +53,22 @@ void ack_userlist(uint16_t trans_id, const uint8_t *userlist, uint16_t len)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void ack_userlist(uint16_t trans_id, const uint8_t *userlist, uint16_t len)
+{
+	struct packet *pkt;
+
+
+	pkt = ack_userlist_gen(trans_id, userlist, len);
+	if (!pkt)
+		return;
+
 	g_debug("Sending userlist: %s", userlist);
 
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_capabilities.c b/src/net/cmds/cmd_capabilities.c
index 945f6bb..b3f3735 100644
--- a/src/net/cmds/cmd_capabilities.c
+++ b/src/net/cmds/cmd_capabilities.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_capabilities(uint16_t trans_id)
+struct packet *cmd_capabilities_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,19 @@ void cmd_capabilities(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_capabilities(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_capabilities_gen(trans_id);
+
 	g_debug("Requesting capabilities");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_control.c b/src/net/cmds/cmd_control.c
index 5cb473f..4f58f48 100644
--- a/src/net/cmds/cmd_control.c
+++ b/src/net/cmds/cmd_control.c
@@ -19,7 +19,8 @@
 #include <cmd.h>
 
 
-void cmd_control(uint16_t trans_id, const uint8_t *digest, uint16_t len)
+struct packet *cmd_control_gen(uint16_t trans_id, const uint8_t *digest,
+			       uint16_t len)
 {
 	gsize pkt_size;
 	gsize data_size;
@@ -29,7 +30,7 @@ void cmd_control(uint16_t trans_id, const uint8_t *digest, uint16_t len)
 
 
 	if (len != strlen(digest))
-	       	return;
+	       	return NULL;
 
 	/* all strings terminate with \0 char, we transport that as well! */
 	data_size = sizeof(struct control) + (len + 1) * sizeof(uint8_t);
@@ -51,9 +52,23 @@ void cmd_control(uint16_t trans_id, const uint8_t *digest, uint16_t len)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+void cmd_control(uint16_t trans_id, const uint8_t *digest, uint16_t len)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_control_gen(trans_id, digest, len);
+	if (!pkt)
+		return;
+
 	g_debug("Requesting telescope control");
 
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_fail.c b/src/net/cmds/cmd_fail.c
index 3d2f807..3eed38d 100644
--- a/src/net/cmds/cmd_fail.c
+++ b/src/net/cmds/cmd_fail.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_fail(uint16_t trans_id)
+struct packet *cmd_fail_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,17 @@ void cmd_fail(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_fail(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
 	g_debug("Signalling failed command");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_getpos_azel.c b/src/net/cmds/cmd_getpos_azel.c
index 524d863..85f5640 100644
--- a/src/net/cmds/cmd_getpos_azel.c
+++ b/src/net/cmds/cmd_getpos_azel.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_getpos_azel(uint16_t trans_id)
+struct packet *cmd_getpos_azel_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,18 @@ void cmd_getpos_azel(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_getpos_azel(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+	pkt = cmd_getpos_azel_gen(trans_id);
+
 	g_debug("Requesting telescope AZEL");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_invalid_pkt.c b/src/net/cmds/cmd_invalid_pkt.c
index 1d7a5f8..4b412c2 100644
--- a/src/net/cmds/cmd_invalid_pkt.c
+++ b/src/net/cmds/cmd_invalid_pkt.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_invalid_pkt(uint16_t trans_id)
+struct packet *cmd_invalid_pkt_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,19 @@ void cmd_invalid_pkt(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_invalid_pkt(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_invalid_pkt_gen(trans_id);
+
 	g_debug("Signalling invalid packet");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_message.c b/src/net/cmds/cmd_message.c
index 60f4aa2..1c2041b 100644
--- a/src/net/cmds/cmd_message.c
+++ b/src/net/cmds/cmd_message.c
@@ -20,7 +20,8 @@
 #include <protocol.h>
 
 
-void cmd_message(uint16_t trans_id, const uint8_t *message, uint16_t len)
+struct packet *cmd_message_gen(uint16_t trans_id, const uint8_t *message,
+			       uint16_t len)
 {
 	gsize pkt_size;
 	gsize data_size;
@@ -30,7 +31,7 @@ void cmd_message(uint16_t trans_id, const uint8_t *message, uint16_t len)
 
 
 	if (len != strlen(message))
-	       	return;
+	       	return NULL;
 
 	/* all strings terminate with \0 char, we transport that as well! */
 	data_size = sizeof(struct message) + (len + 1) * sizeof(uint8_t);
@@ -52,9 +53,23 @@ void cmd_message(uint16_t trans_id, const uint8_t *message, uint16_t len)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+void cmd_message(uint16_t trans_id, const uint8_t *message, uint16_t len)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_message_gen(trans_id, message, len);
+	if (!pkt)
+		return;
+
 	g_debug("Sending text message: %s", message);
 
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_moveto_azel.c b/src/net/cmds/cmd_moveto_azel.c
index 8d25ab8..7af9007 100644
--- a/src/net/cmds/cmd_moveto_azel.c
+++ b/src/net/cmds/cmd_moveto_azel.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_moveto_azel(uint16_t trans_id, double az, double el)
+struct packet *cmd_moveto_azel_gen(uint16_t trans_id, double az, double el)
 {
 	gsize pkt_size;
 
@@ -49,10 +49,20 @@ void cmd_moveto_azel(uint16_t trans_id, double az, double el)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_moveto_azel(uint16_t trans_id, double az, double el)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_moveto_azel_gen(trans_id, az, el);
+
 	g_debug("Sending command moveto AZ/EL %g/%g", az, el);
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
 }
-
diff --git a/src/net/cmds/cmd_nick.c b/src/net/cmds/cmd_nick.c
index 93a13cf..9ebd1ac 100644
--- a/src/net/cmds/cmd_nick.c
+++ b/src/net/cmds/cmd_nick.c
@@ -20,7 +20,8 @@
 #include <protocol.h>
 
 
-void cmd_nick(uint16_t trans_id, const uint8_t *nick, uint16_t len)
+struct packet *cmd_nick_gen(uint16_t trans_id, const uint8_t *nick,
+			    uint16_t len)
 {
 	gsize pkt_size;
 	gsize data_size;
@@ -30,7 +31,7 @@ void cmd_nick(uint16_t trans_id, const uint8_t *nick, uint16_t len)
 
 
 	if (len != strlen(nick))
-	       	return;
+	       	return NULL;
 
 	/* all strings terminate with \0 char, we transport that as well! */
 	data_size = sizeof(struct nick) + (len + 1) * sizeof(uint8_t);
@@ -52,9 +53,22 @@ void cmd_nick(uint16_t trans_id, const uint8_t *nick, uint16_t len)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_nick(uint16_t trans_id, const uint8_t *nick, uint16_t len)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_nick_gen(trans_id, nick, len);
+	if (!pkt)
+		return;
+
 	g_debug("Sending new nick: %s", nick);
 
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_park_telescope.c b/src/net/cmds/cmd_park_telescope.c
index f29bfbb..eee398a 100644
--- a/src/net/cmds/cmd_park_telescope.c
+++ b/src/net/cmds/cmd_park_telescope.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_park_telescope(uint16_t trans_id)
+struct packet *cmd_park_telescope_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,19 @@ void cmd_park_telescope(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_park_telescope(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_park_telescope_gen(trans_id);
+
 	g_debug("Requesting park_telescope");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_recalibrate_pointing.c b/src/net/cmds/cmd_recalibrate_pointing.c
index 5f2fc13..5cb01a2 100644
--- a/src/net/cmds/cmd_recalibrate_pointing.c
+++ b/src/net/cmds/cmd_recalibrate_pointing.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_recalibrate_pointing(uint16_t trans_id)
+struct packet *cmd_recalibrate_pointing_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,19 @@ void cmd_recalibrate_pointing(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_recalibrate_pointing(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_recalibrate_pointing_gen(trans_id);
+
 	g_debug("Requesting recalibrate_pointing");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_spec_acq_cfg.c b/src/net/cmds/cmd_spec_acq_cfg.c
index 57ae9a5..63b42ca 100644
--- a/src/net/cmds/cmd_spec_acq_cfg.c
+++ b/src/net/cmds/cmd_spec_acq_cfg.c
@@ -31,9 +31,10 @@
  *
  */
 
-void cmd_spec_acq_cfg(uint16_t trans_id,
-		      uint64_t f0, uint64_t f1, uint32_t bw_div,
-		      uint32_t bin_div, uint32_t n_stack, uint32_t acq_max)
+struct packet *cmd_spec_acq_cfg_gen(uint16_t trans_id,
+				    uint64_t f0, uint64_t f1, uint32_t bw_div,
+				    uint32_t bin_div, uint32_t n_stack,
+				    uint32_t acq_max)
 {
 	gsize pkt_size;
 
@@ -66,6 +67,22 @@ void cmd_spec_acq_cfg(uint16_t trans_id,
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_spec_acq_cfg(uint16_t trans_id,
+		      uint64_t f0, uint64_t f1, uint32_t bw_div,
+		      uint32_t bin_div, uint32_t n_stack, uint32_t acq_max)
+{
+	struct packet *pkt;
+	struct spec_acq_cfg *acq;
+
+	pkt = cmd_spec_acq_cfg_gen(trans_id, f0, f1, bw_div,
+				   bin_div, n_stack, acq_max);
+
+	acq = (struct spec_acq_cfg *) pkt->data;
+
 	g_debug("Sending command acquire spectrum "
 		  "FREQ range: %g - %g MHz, BW div: %d, BIN div %d,"
 		  "STACK: %d, ACQ %d",
@@ -77,9 +94,8 @@ void cmd_spec_acq_cfg(uint16_t trans_id,
 		  acq->acq_max);
 
 
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
 }
-
diff --git a/src/net/cmds/cmd_spec_acq_cfg_get.c b/src/net/cmds/cmd_spec_acq_cfg_get.c
index 3088670..c697eeb 100644
--- a/src/net/cmds/cmd_spec_acq_cfg_get.c
+++ b/src/net/cmds/cmd_spec_acq_cfg_get.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_spec_acq_cfg_get(uint16_t trans_id)
+struct packet *cmd_spec_acq_cfg_get_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,19 @@ void cmd_spec_acq_cfg_get(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_spec_acq_cfg_get(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_spec_acq_cfg_get_gen(trans_id);
+
 	g_debug("Requesting spectral acquisition configuration");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_spec_acq_disable.c b/src/net/cmds/cmd_spec_acq_disable.c
index 684b7a2..036ded2 100644
--- a/src/net/cmds/cmd_spec_acq_disable.c
+++ b/src/net/cmds/cmd_spec_acq_disable.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_spec_acq_disable(uint16_t trans_id)
+struct packet *cmd_spec_acq_disable_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,20 @@ void cmd_spec_acq_disable(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+void cmd_spec_acq_disable(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_spec_acq_disable_gen(trans_id);
+
 	g_debug("Requesting disable of spectral acquisition");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_spec_acq_enable.c b/src/net/cmds/cmd_spec_acq_enable.c
index 5371cbc..8b65baf 100644
--- a/src/net/cmds/cmd_spec_acq_enable.c
+++ b/src/net/cmds/cmd_spec_acq_enable.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_spec_acq_enable(uint16_t trans_id)
+struct packet *cmd_spec_acq_enable_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,20 @@ void cmd_spec_acq_enable(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+
+	return pkt;
+}
+
+
+void cmd_spec_acq_enable(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_spec_acq_enable_gen(trans_id);
+
 	g_debug("Requesting enable of spectral acquisition");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/cmds/cmd_success.c b/src/net/cmds/cmd_success.c
index f5d5d40..e6aac0f 100644
--- a/src/net/cmds/cmd_success.c
+++ b/src/net/cmds/cmd_success.c
@@ -19,7 +19,7 @@
 #include <cmd.h>
 
 
-void cmd_success(uint16_t trans_id)
+struct packet *cmd_success_gen(uint16_t trans_id)
 {
 	gsize pkt_size;
 
@@ -38,8 +38,19 @@ void cmd_success(uint16_t trans_id)
 
 	pkt_hdr_to_net_order(pkt);
 
+	return pkt;
+}
+
+
+void cmd_success(uint16_t trans_id)
+{
+	struct packet *pkt;
+
+
+	pkt = cmd_success_gen(trans_id);
+
 	g_debug("Signalling successful command");
-	net_send((void *) pkt, pkt_size);
+	net_send((void *) pkt, pkt_size_get(pkt));
 
 	/* clean up */
 	g_free(pkt);
diff --git a/src/net/protocol.c b/src/net/protocol.c
index bf1e241..dcfdb0f 100644
--- a/src/net/protocol.c
+++ b/src/net/protocol.c
@@ -19,6 +19,21 @@
 #include <glib.h>
 #include <protocol.h>
 
+
+
+/**
+ * @brief get the total size of a packet in bytes
+ */
+
+size_t pkt_size_get(struct packet *pkt)
+{
+	if (!pkt)
+		return 0;
+
+	return sizeof(struct packet) + g_htonl(pkt->data_size);
+}
+
+
 /**
  * @brief convert packet header to network byte order
  */
@@ -78,7 +93,7 @@ uint16_t CRC16(unsigned char *buf, size_t size)
 
 			if ((b & 0x80) ^ ((S & 0x8000) >> 8))
 				S = ((S << 1) ^ 0x1021) & 0xFFFF;
-			else 
+			else
 				S = (S << 1) & 0xFFFF;
 
 			b = b << 1;
diff --git a/src/server/cfg.c b/src/server/cfg.c
index c457a83..4fa6cf1 100644
--- a/src/server/cfg.c
+++ b/src/server/cfg.c
@@ -32,6 +32,8 @@ static void server_cfg_load_network(GKeyFile *kf, struct server_settings *s)
 
 
 	s->port = g_key_file_get_integer(kf, grp, "port", NULL);
+
+	s->masterkey = g_key_file_get_string(kf, grp, "masterkey", NULL);
 }
 
 
@@ -79,6 +81,17 @@ static void server_cfg_load_location(GKeyFile *kf, struct server_settings *s)
 	s->n_hor = len_az;
 }
 
+/**
+ * @brief load configuration keys in the message of the day group
+ */
+
+static void server_cfg_load_motd(GKeyFile *kf, struct server_settings *s)
+{
+	const char *grp = "MOTD";
+
+
+	s->motd = g_key_file_get_string(kf, grp, "motd", NULL);
+}
 
 
 /**
@@ -170,6 +183,42 @@ gsize server_cfg_get_hor_limits(gint32 **hor_az, gint32 **hor_el)
 }
 
 
+/**
+ * @brief get the message of the day
+ *
+ * @returns a copy of the motd, cleanup using g_free()
+ *
+ */
+
+gchar *server_cfg_get_motd(void)
+{
+	return g_strdup(server_cfg->motd);
+}
+
+/**
+ * @brief update the message of the day at run time
+ *
+ * @note does not update the configuration file
+ */
+
+void server_cfg_set_motd(const gchar *motd)
+{
+	g_free(server_cfg->motd);
+
+	server_cfg->motd = g_strdup(motd);
+}
+
+
+/**
+ * @brief get the server masterkey
+ */
+
+const gchar *server_cfg_get_masterkey(void)
+{
+	return server_cfg->masterkey;
+}
+
+
 /**
  * @brief load the server configuration file from a given prefix
  *
@@ -211,6 +260,7 @@ static int server_load_config_from_prefix(const gchar *prefix, GError **err)
 	server_cfg_load_network(kf, server_cfg);
 	server_cfg_load_backend(kf, server_cfg);
 	server_cfg_load_location(kf, server_cfg);
+	server_cfg_load_motd(kf, server_cfg);
 
 	g_key_file_free(kf);
 	g_free(cfg);
diff --git a/src/server/config/server.cfg b/src/server/config/server.cfg
index fc358c8..c04c708 100644
--- a/src/server/config/server.cfg
+++ b/src/server/config/server.cfg
@@ -1,16 +1,22 @@
+[MOTD]
+motd = \t\thi / good luck and have fun
+ 
 [Network]
-port=1420
-#masterpasswd= unimplemented sha512hash (or somesuch)
+port = 1420
+# a SHA256 hash digest for maximum privilege level
+masterkey = b2e17e7c7599dd9e6c4517b294c3dbe3aef0dc5ac8193eb59e33f53f15facdd0
 
 [Backend]
-#uncommecnt for Vienna SRT hardware
+# uncomment for Vienna SRT hardware
 #plugins=SRT/srt_comlink;SRT/srt_drive;SRT/srt_spectrometer;
-plugins=SIM/rt_sim
+plugins = SIM/rt_sim
+
 [Location]
-station=Vienna SRT
-lat=48.23
-# XXX: east = positive
-lon=-16.34
+station = RT Simulator
+#station = Vienna University Observatory SRT
+lat = 48.23
+# east = negative
+lon = -16.34
 # XXX: implement, check
 alt=245.
 
diff --git a/src/server/include/cfg.h b/src/server/include/cfg.h
index 8a237c3..9d3449f 100644
--- a/src/server/include/cfg.h
+++ b/src/server/include/cfg.h
@@ -27,7 +27,9 @@ struct server_settings {
 	gdouble   lon;		/* station longitude */
 	gint32   *hor_az;	/* horizon profile azimuth values */
 	gint32   *hor_el;	/* horizon profile elevaltion values */
-	gsize     n_hor;		/* number of profile values */
+	gsize     n_hor;	/* number of profile values */
+	gchar    *motd;		/* a message of the day */
+	gchar    *masterkey;	/* guess */
 };
 
 
@@ -44,6 +46,11 @@ gsize server_cfg_get_hor_limits(int **hor_az, int **hor_el);
 
 int server_cfg_load(void);
 
+gchar *server_cfg_get_motd(void);
+void server_cfg_set_motd(const gchar *motd);
+
+const gchar *server_cfg_get_masterkey(void);
+
 
 
 #endif /* _SERVER_INCLUDE_CFG_H_ */
diff --git a/src/server/include/net.h b/src/server/include/net.h
index 3c8d0df..afce07d 100644
--- a/src/server/include/net.h
+++ b/src/server/include/net.h
@@ -23,7 +23,10 @@
 
 int net_server(void);
 void net_server_reassign_control(gpointer ref);
+void net_server_drop_priv(gpointer ref);
+void net_server_iddqd(gpointer ref);
 void net_server_broadcast_message(const gchar *msg, gpointer ref);
+void net_server_direct_message(const gchar *msg, gpointer ref);
 void net_server_set_nickname(const gchar *nick, gpointer ref);
 
 
diff --git a/src/server/net.c b/src/server/net.c
index e215d79..6d28d43 100644
--- a/src/server/net.c
+++ b/src/server/net.c
@@ -38,13 +38,19 @@
 /* max allowed client */
 #define SERVER_CON_MAX 64
 
+/* privilege range */
+#define PRIV_DEFAULT	0
+#define PRIV_CONTROL	1
+#define PRIV_FULL	2
+
+
 
 /* client connection data */
 struct con_data {
 	GSocketConnection *con;
 	GInputStream *istream;
 	gsize nbytes;
-	gboolean priv;
+	gint priv;
 	gchar *nick;
 	gboolean new;
 	gboolean kick;
@@ -97,6 +103,78 @@ static gchar *net_get_host_string(GSocketConnection *con)
 }
 
 
+/**
+ * @brief generate a chat message string
+ *
+ * @param msg the message
+ * @param c the connection (may be NULL)
+ *
+ * @returns an allocated buffer holding the string, clean using g_free()
+ *
+ * @note if c is NULL, it is assumed the server generated the message
+ */
+
+static gchar *net_server_msg_nick(const gchar *msg, struct con_data *c)
+{
+	gchar *buf;
+	gchar *nick;
+	gchar *col;
+
+
+	col  = "#FF0000";
+	nick = "A hollow voice says";
+
+	if (c) {
+		col  = "#7F9F7F";
+		nick = c->nick;
+	}
+
+	buf = g_strdup_printf("<tt><span foreground='%s'>"
+			      "%s:</span></tt> %s\n",
+			      col, nick, msg);
+
+
+	return buf;
+}
+
+static gboolean net_push_station_single(gpointer data)
+{
+	gchar *buf;
+
+	buf = g_strdup_printf("Your are connected to %s\n",
+			      server_cfg_get_station());
+
+	net_server_direct_message(buf, data);
+
+	g_free(buf);
+
+	return G_SOURCE_REMOVE;
+}
+
+
+static gboolean net_push_motd_single(gpointer data)
+{
+	gchar *buf;
+	gchar *motd;
+
+
+	motd = server_cfg_get_motd();
+	if (!motd)
+		goto exit;
+
+
+	buf = g_strdup_printf("The MOTD is: \n\n%s\n\n", motd);
+
+	net_server_direct_message(buf, data);
+
+	g_free(buf);
+	g_free(motd);
+
+exit:
+	return G_SOURCE_REMOVE;
+}
+
+
 /**
  * @brief distribute a list of users to all clients
  */
@@ -127,13 +205,21 @@ static gboolean net_push_userlist_cb(gpointer data)
 
 		tmp = msg;
 
-		if (c->priv)
+		g_message("%s priv is %d", c->nick, c->priv);
+		switch (c->priv) {
+		case PRIV_FULL:
 			buf = g_strdup_printf("<tt><span foreground='#FF0000'>"
 					      "%s</span></tt>\n", c->nick);
-		else
+			break;
+		case PRIV_CONTROL:
+			buf = g_strdup_printf("<tt><span foreground='#FFFF00'>"
+					      "%s</span></tt>\n", c->nick);
+			break;
+		default:
 			buf = g_strdup_printf("<tt><span foreground='#7F9F7F'>"
 					      "%s</span></tt>\n", c->nick);
-
+			break;
+		}
 		msg = g_strconcat(buf, tmp, NULL);
 
 		g_free(buf);
@@ -143,8 +229,8 @@ static gboolean net_push_userlist_cb(gpointer data)
 			c->new = FALSE;
 
 			msgs[msgcnt++] = g_strdup_printf("<tt><span foreground='#F1C40F'>"
-					"%s</span></tt> joined",
-					c->nick);
+							 "%s</span></tt> joined",
+							 c->nick);
 
 			g_print("%s joined\n", c->nick);
 
@@ -170,6 +256,7 @@ static gboolean net_push_userlist_cb(gpointer data)
 }
 
 
+
 /**
  * @brief if connected, disconnect the socket
  */
@@ -377,6 +464,10 @@ static gboolean net_send_internal(struct con_data *c, const char *pkt, gsize nby
 	if (c->kick)
 		return FALSE;
 
+	if (!G_IS_SOCKET_CONNECTION(c->con)) {
+		g_warning("%s:%d: supplied argument is not a socket connection",
+			  __func__, __LINE__);
+	}
 
 	if (g_thread_pool_get_num_threads(c->pool) >= SERVER_CON_POOL_SIZE) {
 
@@ -526,7 +617,7 @@ pending:
 
 	/* verify packet payload */
 	if (CRC16((guchar *) pkt->data, pkt->data_size) == pkt->data_crc16)  {
-		if (process_pkt(pkt, c->priv, c))
+		if (process_pkt(pkt, (gboolean) c->priv, c))
 			goto drop_pkt;
 
 		/* valid packets were free'd */
@@ -607,7 +698,7 @@ error:
 
 static void assign_default_priv(struct con_data *c)
 {
-	gboolean priv = FALSE;
+	gint priv = PRIV_DEFAULT;
 
 	GList *elem;
 
@@ -621,11 +712,11 @@ static void assign_default_priv(struct con_data *c)
 		item = (struct con_data *) elem->data;
 
 		if (item->priv)
-			priv = TRUE;
+			priv = PRIV_CONTROL;
 	}
 
-	if (!priv)
-		c->priv = TRUE;
+	if (priv == PRIV_DEFAULT)
+		c->priv = PRIV_CONTROL;
 
 	g_mutex_unlock(&listlock);
 }
@@ -717,11 +808,12 @@ static gboolean net_incoming(GSocketService    *service,
 	con_list = g_list_append(con_list, c);
 	g_mutex_unlock(&listlock);
 
-	/* push new username after 1 seconds, so they have time to configure
-	 * theirs
+	/* push usernames and messages after 1 seconds, so the incoming
+	 * connections have time to configure theirs
 	 */
 	g_timeout_add_seconds(1, net_push_userlist_cb, NULL);
-
+	g_timeout_add_seconds(1, net_push_station_single, c);
+	g_timeout_add_seconds(1, net_push_motd_single, c);
 
 	str = net_get_host_string(c->con);
 	g_message("Received connection from %s", str);
@@ -803,35 +895,64 @@ gint net_send(const char *pkt, gsize nbytes)
 	return ret;
 }
 
-
 /**
  * @brief assign control privilege level to connection
  */
 
-void net_server_reassign_control(gpointer ref)
+
+static void net_server_reassign_control_internal(gpointer ref, gint lvl)
 {
 	GList *elem;
 
 	struct con_data *c;
 	struct con_data *item;
 
+	struct con_data *p = NULL;
+
 	gchar *msg;
 	gchar *str;
 
+
 	c = (struct con_data *) ref;
 
 	g_mutex_lock(&listlock);
 	for (elem = con_list; elem; elem = elem->next) {
 		item = (struct con_data *) elem->data;
-		item->priv = FALSE;
+
+		if (item->priv <= lvl) {
+			item->priv = PRIV_DEFAULT;
+		} else {
+			p = item;
+			break;
+		}
 	}
 	g_mutex_unlock(&listlock);
 
-	c->priv = TRUE;
-
 	str = net_get_host_string(c->con);
-	msg = g_strdup_printf("Reassigned control to %s (connected from %s)",
-			      c->nick, str);
+
+	if (!p) {
+		c->priv = lvl;
+		msg = g_strdup_printf("Reassigned control to %s "
+				      "(connected from %s)",
+				      c->nick, str);
+	} else if (p == c) {
+		c->priv = lvl;
+		msg = g_strdup_printf("%s (connected from %s) changed their "
+				      "own privilege level",
+				      c->nick, str);
+	} else {
+		gchar *h;
+
+		h = net_get_host_string(c->con);
+
+		msg = g_strdup_printf("Failed to reassign control to %s "
+				      "(connected from %s), as "
+				      "%s (connected from %s) holds a higher "
+				      "level of privilege",
+				      c->nick, str, p->nick, h);
+		g_free(h);
+	}
+
 
 	net_server_broadcast_message(msg, NULL);
 	net_push_userlist_cb(NULL);
@@ -841,6 +962,35 @@ void net_server_reassign_control(gpointer ref)
 }
 
 
+
+/**
+ * @brief escalate to maximum privilege level
+ */
+
+void net_server_iddqd(gpointer ref)
+{
+	net_server_reassign_control_internal(ref, PRIV_FULL);
+}
+
+
+/**
+ * @brief assign control privilege level to connection
+ */
+
+void net_server_reassign_control(gpointer ref)
+{
+	net_server_reassign_control_internal(ref, PRIV_CONTROL);
+}
+
+/**
+ * @brief drop to lowest priviledge on connection
+ */
+
+void net_server_drop_priv(gpointer ref)
+{
+	net_server_reassign_control_internal(ref, PRIV_DEFAULT);
+}
+
 /**
  * @brief set the nickname for a connection
  */
@@ -856,15 +1006,21 @@ void net_server_set_nickname(const gchar *nick, gpointer ref)
 	c = (struct con_data *) ref;
 
 
+	if (!strlen(nick)) {
+		g_message("Rejected nickname of zero length");
+		return;
+	}
+
+
 	old = c->nick;
 
 	c->nick = g_strdup(nick);
 
 	if (!c->new) {
-		buf = g_strdup_printf("<tt><span foreground='#F1C40F'>%s</span></tt> "
-				      "is now known as "
-				      "<tt><span foreground='#F1C40F'>%s</span></tt> ",
-				      old, c->nick);
+		buf = g_strdup_printf("<tt><span foreground='#F1C40F'>%s</span>"
+				      "</tt> is now known as "
+				      "<tt><span foreground='#F1C40F'>%s</span>"
+				      "</tt> ", old, c->nick);
 
 		net_server_broadcast_message(buf, NULL);
 
@@ -877,6 +1033,7 @@ void net_server_set_nickname(const gchar *nick, gpointer ref)
 }
 
 
+
 /**
  * @brief broadcast a text message to all clients
  */
@@ -885,33 +1042,46 @@ void net_server_broadcast_message(const gchar *msg, gpointer ref)
 {
 	gchar *buf;
 
-
 	struct con_data *c;
 
 
 	c = (struct con_data *) ref;
 
+	buf = net_server_msg_nick(msg, c);
 
-	if (!c) {
-		buf = g_strdup_printf("<tt><span foreground='#FF0000'>"
-				      "A hollow voice says:</span></tt> %s\n",
-				      msg);
-	} else {
-		buf = g_strdup_printf("<tt><span foreground='#7F9F7F'>"
-				      "%s:</span></tt> %s\n",
-				      c->nick, msg);
+	cmd_message(PKT_TRANS_ID_UNDEF, (guchar *) buf, strlen(buf));
 
-		g_print("%s %s\n", c->nick, msg);
-	}
+	g_free(buf);
+}
 
 
-	cmd_message(PKT_TRANS_ID_UNDEF, (guchar *) buf, strlen(buf));
+/**
+ * @brief send a text message to a client
+ *
+ */
+
+void net_server_direct_message(const gchar *msg, gpointer ref)
+{
+	gchar *buf;
+
+	struct con_data *c;
+	struct packet *pkt;
+
 
+	c = (struct con_data *) ref;
+
+	buf = net_server_msg_nick(msg, NULL);
+
+	pkt = cmd_message_gen(PKT_TRANS_ID_UNDEF, (uint8_t *) buf, strlen(buf));
+
+	net_send_single(c, (void *) pkt, pkt_size_get(pkt));
+
+	/* clean up */
+	g_free(pkt);
 	g_free(buf);
 }
 
 
-
 /**
  * initialise server networking
  */
diff --git a/src/server/proc/proc_pr_control.c b/src/server/proc/proc_pr_control.c
index 6b16046..0ba4b4e 100644
--- a/src/server/proc/proc_pr_control.c
+++ b/src/server/proc/proc_pr_control.c
@@ -22,7 +22,6 @@
 #include <net.h>
 
 
-
 void proc_pr_control(struct packet *pkt, gpointer ref)
 {
 	gchar *digest;
@@ -34,21 +33,35 @@ void proc_pr_control(struct packet *pkt, gpointer ref)
 	c = (struct control *) pkt->data;
 
 
-	if (strlen(c->digest) != c->len)
+	if (strlen((gchar *) c->digest) != c->len)
 		return;
 
+	/* haha, typo'ed :D */
 	digest = g_compute_hmac_for_string(G_CHECKSUM_SHA256,
 					   (guint8*)"radtel", 6,
 					   "thisishardcoed", 13);
 
 
 
-	if (!strcmp(digest, c->digest)) {
+
+	if (!strcmp(digest, (gchar *) c->digest)) {
+
 		g_message("Client telescope control reassigned");
 		net_server_reassign_control(ref);
-	} else {
-		g_message("Client telescope control NOT reassigned, digest "
-			  "mismatch %s %s", digest, c->digest);
+
+	} else if (strlen(server_cfg_get_masterkey())) {
+
+		if(!strcmp(server_cfg_get_masterkey(), (gchar *) c->digest)) {
+			g_message("Escalating privilege");
+			net_server_iddqd(ref);
+		} else {
+			g_message("Client telescope control NOT reassigned, "
+				  "digest mismatch %s %s, dropping client "
+				  "priviledge.",
+				  digest, c->digest);
+
+			net_server_drop_priv(ref);
+		}
 	}
 
 	g_free(digest);
diff --git a/src/server/proc/proc_pr_message.c b/src/server/proc/proc_pr_message.c
index dca0ca8..2bc91b2 100644
--- a/src/server/proc/proc_pr_message.c
+++ b/src/server/proc/proc_pr_message.c
@@ -32,7 +32,7 @@ void proc_pr_message(struct packet *pkt, gpointer ref)
 	c = (struct message *) pkt->data;
 
 
-	if (strlen(c->message) != c->len)
+	if (strlen((gchar *) c->message) != c->len)
 		return;
 
 	net_server_broadcast_message((const gchar *) c->message, ref);
diff --git a/src/server/proc/proc_pr_moveto_azel.c b/src/server/proc/proc_pr_moveto_azel.c
index a40c5b0..5617f50 100644
--- a/src/server/proc/proc_pr_moveto_azel.c
+++ b/src/server/proc/proc_pr_moveto_azel.c
@@ -29,8 +29,6 @@ void proc_pr_moveto_azel(struct packet *pkt, gpointer ref)
 	double az;
 	double el;
 
-	gsize pkt_size;
-
 	struct moveto *m;
 
 
diff --git a/src/server/proc/proc_pr_nick.c b/src/server/proc/proc_pr_nick.c
index 8023a4d..b8bdb84 100644
--- a/src/server/proc/proc_pr_nick.c
+++ b/src/server/proc/proc_pr_nick.c
@@ -32,7 +32,7 @@ void proc_pr_nick(struct packet *pkt, gpointer ref)
 	c = (struct nick *) pkt->data;
 
 
-	if (strlen(c->nick) != c->len)
+	if (strlen((gchar *) c->nick) != c->len)
 		return;
 
 	net_server_set_nickname((const gchar *) c->nick, ref);
diff --git a/src/server/proc/proc_pr_spec_acq_cfg.c b/src/server/proc/proc_pr_spec_acq_cfg.c
index 8c23c5d..20cd5fd 100644
--- a/src/server/proc/proc_pr_spec_acq_cfg.c
+++ b/src/server/proc/proc_pr_spec_acq_cfg.c
@@ -21,8 +21,6 @@
 
 void proc_pr_spec_acq_cfg(struct packet *pkt, gpointer ref)
 {
-	gsize pkt_size;
-
 	struct spec_acq_cfg *acq;
 
 
-- 
GitLab