diff --git a/src/client/completion.c b/src/client/completion.c
index 1be4a1c284932531ecf3fffcbd45b7cbff18f42c..7d32ba712649d7b6aea52ebcf77a4daa7809b6db 100644
--- a/src/client/completion.c
+++ b/src/client/completion.c
@@ -4,6 +4,7 @@
 #include "talker_privs.h"
 #include "alias.h"
 #include "who.h"
+#include "gaglist.h"
 
 /*
   modes:	0 - commands
@@ -48,7 +49,7 @@ CompletionList tctable[]={
 {"emote's"	,1	,1	,-1	,part_who_talk},
 {"event"	,1	,1	,1	,list_script},
 {"freeze"	,1	,1	,1	,part_who_talk},
-{"gag"		,1	,2	,2	,part_gag_filter},
+{"gag"		,1	,2	,2	,gaglist_tc_filter},
 {"gag"		,1	,1	,2	,part_who_talk},
 {"kick"		,1	,1	,1	,part_who_talk},
 {"load"		,1	,1	,1	,list_mwsfile},
diff --git a/src/client/gaglist.c b/src/client/gaglist.c
new file mode 100644
index 0000000000000000000000000000000000000000..12c95577601bfb3d8193aa89a9b0391b0ff5ee31
--- /dev/null
+++ b/src/client/gaglist.c
@@ -0,0 +1,51 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "gaglist.h"
+
+char **gaglist;
+size_t gaglist_len;
+
+int gaglist_append(const char *gagname)
+{
+	char **tmp;
+
+	if (gaglist_len == GAGLIST_MAX_ENTRIES)
+		return 1;
+	tmp = realloc(gaglist, sizeof(*gaglist) * (gaglist_len + 1));
+	if (tmp == NULL) {
+		free(gaglist);
+		gaglist_len = 0;
+		return 1;
+	}
+	gaglist = tmp;
+	gaglist[gaglist_len++] = strdup(gagname);
+	return 0;
+}
+
+void gaglist_destroy(void)
+{
+	for (size_t i = 0; i < gaglist_len; i++)
+		free(gaglist[i]);
+	free(gaglist);
+}
+
+char *gaglist_tc_filter(const char *text, int state)
+{
+	static size_t len = 0;
+	static size_t i = 0;
+
+	if (state == 0) {
+		i = 0;
+		len = strlen(text);
+	}
+
+	for (; i < gaglist_len; i++) {
+		if (*gaglist[i] == '\0')
+			continue;
+		if (len == 0 || !strncasecmp(gaglist[i], text, len))
+			return strdup(gaglist[i++]);
+	}
+	return NULL;
+}
+
diff --git a/src/client/gaglist.h b/src/client/gaglist.h
new file mode 100644
index 0000000000000000000000000000000000000000..48aedd687beccf3af0493540ac4cd47237c2ae0f
--- /dev/null
+++ b/src/client/gaglist.h
@@ -0,0 +1,10 @@
+#ifndef GAGLIST_H
+#define GAGLIST_H
+
+#define GAGLIST_MAX_ENTRIES (128)
+
+extern int gaglist_append(const char *gagname);
+extern void gaglist_destroy(void);
+extern char *gaglist_tc_filter(const char *text, int state);
+
+#endif /* GAGLIST_H */
diff --git a/src/client/incoming.c b/src/client/incoming.c
index 514640d86a90bf54cedb52956a981f9b914501c7..9295ad511de2d3be60b5663d23680f9005d3ae3e 100644
--- a/src/client/incoming.c
+++ b/src/client/incoming.c
@@ -28,6 +28,7 @@
 #include "user.h"
 #include "util.h"
 #include "incoming.h"
+#include "gaglist.h"
 #include <jansson.h>
 #include <str_util.h>
 
@@ -667,6 +668,20 @@ static void display_content(ipc_message_t *msg)
 				force_vtext(msg, whom, "%s gives the reason \"%s\".", whom, reason);
 			}
 		}
+	} else
+	if (msg->head.type == IPC_GAGLIST) {
+		json_t *jgag;
+		size_t i = 0;
+
+		if (j== NULL || !json_is_array(j)) {
+			printf("Invalid GAGLIST message from %s.\n", whom);
+			json_decref(j);
+			return;
+		}
+		gaglist_destroy();
+		json_array_foreach(j, i, jgag) {
+			gaglist_append(json_string_value(jgag));
+		}
 	} else {
 		printf("Unknown message type %4.4s", (char *)&msg->head.type);
 	}
@@ -716,6 +731,9 @@ static void accept_pipe_cmd(ipc_message_t *msg, struct user *mesg_user)
 		case IPC_GAG:
 			force_gag(newbuff, mesg_user->record.chatprivs, mesg_user->record.name);
 			break;
+		case IPC_GAGLIST:
+			display_content(msg);
+			break;
 		case IPC_PROTLEVEL:
 			force_protlevel(newbuff, mesg_user->record.chatprivs, mesg_user->record.name);
 			break;
diff --git a/src/client/script.c b/src/client/script.c
index 056ae3facfdc17f59bb5e137f2d518e2805e7e16..5f94919d7037b61decd5ff2ba05d9f1dc1ed6d5f 100644
--- a/src/client/script.c
+++ b/src/client/script.c
@@ -23,6 +23,7 @@
 #include "user.h"
 #include "userio.h"
 #include "who.h"
+#include "gaglist.h"
 #include <util.h>
 
 #define MAX_ARGC 128
@@ -138,7 +139,7 @@ FuncNames scrtc[]={
 {"talkname"	,part_who_talk},
 {"whoname"	,part_who},
 {"username"	,part_user},
-{"gag"		,part_gag_filter},
+{"gag"		,gaglist_tc_filter},
 {"bind"		,list_bind},
 {"boardexec"	,list_commands},
 {"exec"		,list_chat_commands},
diff --git a/src/client/talker_privs.c b/src/client/talker_privs.c
index 3d19e45171cd8b40f69773b0f71f992a2015cbf6..79eee7525ac27c565502f9906330ee32290025cc 100644
--- a/src/client/talker_privs.c
+++ b/src/client/talker_privs.c
@@ -5,7 +5,6 @@
 #include "talker.h"
 #include "bb.h"
 #include "rooms.h"
-#include <gags.h>
 #include "main.h"
 #include "user.h"
 #include <util.h>
@@ -17,22 +16,16 @@ unsigned long chatmode_describe(unsigned long old, unsigned long new, int ourapl
 {
 	static char	message[81];
 	unsigned long	add, sub, dif;
-	unsigned long	oldg, newg, difg;
 	/* oldp, newp are permanent protection levels.  oldep, newep are
 	 * effective levels, which include temporarary protection.
 	 */
 	int		oldp, oldep, newp, newep, difp;
 
-	/* get new and old gag flags */
-	newg = (new & CM_GAGMASK) >> CM_GAGSHIFT;
-	oldg = (old & CM_GAGMASK) >> CM_GAGSHIFT;
-
 	/* get new and old protection flags */
 	newp = (new & CM_PROTMASK) >> CM_PROTSHIFT;
 	oldp = (old & CM_PROTMASK) >> CM_PROTSHIFT;
 
 	dif  = old^new;
-	difg = oldg^newg;
 	difp = newp-oldp;
 	/* Protection level changes don't count as flags added/removed. */
 	add = new & dif & ~CM_PROTMASK;
@@ -120,31 +113,6 @@ unsigned long chatmode_describe(unsigned long old, unsigned long new, int ourapl
 		return(old);
 	}
 
-	/* check for a new gag */
-	if (difg!=0)
-	{
-		GagInfo *gi = gag_find(newg);
-
-		if (gi!=NULL)
-		{
-			/* gag was enabled */
-			_autofree char *buff=NULL;
-			asprintf(&buff, "*** %s", gi->gag);
-			display_message(buff, 1, 1);
-		}
-		else
-		{
-			/* gag was disabled, what -used- to be set */
-			gi = gag_find(oldg);
-			if (gi!=NULL)
-			{
-				_autofree char *buff=NULL;
-				asprintf(&buff, "*** %s", gi->ungag);
-				display_message(buff, 1, 1);
-			}
-		}
-	}
-
 	if (add & CM_VERBOSE)
 		display_message("*** You have just been verbosed!", 1, 1);
 	if (sub & CM_VERBOSE)
@@ -196,33 +164,3 @@ unsigned long chatmode_describe(unsigned long old, unsigned long new, int ourapl
 
 	return(new);
 }
-
-
-/***************************/
-/* user->chatprivs options */
-
-char *part_gag_filter(const char *text, int state)
-{
- 	static int ptr=0;
- 	static int len=0;
- 	char *c;
-
-	if (state==0)
-	{
-	 	ptr=0;
-	 	len=strlen(text);
-	}
-
-	while (gaglist[ptr].name!=NULL)
-	{
-	 	if ((len==0 || !strncasecmp(gaglist[ptr].name, text, len)) && strcmp(gaglist[ptr].name, ""))
-	 	{
-	 	 	c=dupstr(gaglist[ptr].name,"");
-	 	 	ptr++;
-	 	 	return(c);
-	 	}
-	 	ptr++;
-	}
-	return(NULL);
-}
-
diff --git a/src/client/talker_privs.h b/src/client/talker_privs.h
index 95440bc0da07a09657940c9bce665e0c210ed282..c00a9b9ba484a3e36c65f86afa1bdbefc19d9be4 100644
--- a/src/client/talker_privs.h
+++ b/src/client/talker_privs.h
@@ -3,7 +3,6 @@
 
 #include <talker_privs.h>
 
-char *part_gag_filter(const char *text, int state);
 unsigned long chatmode_describe(unsigned long old, unsigned long new, int ourapl, int theirapl, const char *from);
 
 #endif /* CLIENT_TALKER_PRIVS_H */
diff --git a/src/ipc.c b/src/ipc.c
index 1c461c2a6b58b364294be0e3374f1bf333b687e9..a76ce5d604b19666f1dde5a287bf883c711edb27 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -193,6 +193,7 @@ const char * ipc_nametype(enum ipc_types msgtype)
 		case IPC_CHANNEL: return "IPC_CHANNEL"; break;
 		case IPC_WIZ: return "IPC_WIZ"; break;
 		case IPC_GAG: return "IPC_GAG"; break;
+		case IPC_GAGLIST: return "IPC_GAGLIST"; break;
 		case IPC_SCRIPTIPC: return "IPC_SCRIPTIPC"; break;
 		case IPC_SCRIPTRPC: return "IPC_SCRIPTRPC"; break;
 		case IPC_CHECKONOFF: return "IPC_CHECKONOFF"; break;
diff --git a/src/ipc.h b/src/ipc.h
index 835f300170566bf0c63752200f6be1fc2e19ad43..7ccc425eab0fcdc06bdd3237c2c1a3f3c49a572a 100644
--- a/src/ipc.h
+++ b/src/ipc.h
@@ -35,6 +35,7 @@ enum ipc_types {
 	IPC_CHANNEL	= 16,
 	IPC_WIZ		= 17,
 	IPC_GAG		= 18,
+	IPC_GAGLIST	= 19,
 	IPC_SCRIPTIPC	= 21,
 	IPC_SCRIPTRPC	= 22,
 	IPC_CHECKONOFF	= 23,
diff --git a/src/server/actions.c b/src/server/actions.c
index 6b6572858ab99cfbf91bd554461875ace9518410..8f37054d1f280b19414f261ce4ea5de5261a5645 100644
--- a/src/server/actions.c
+++ b/src/server/actions.c
@@ -21,8 +21,8 @@
 #include <talker_privs.h>
 #include <ipc.h>
 #include <perms.h>
-#include <gags.h>
 
+#include "gags.h"
 #include "servsock.h"
 #include "replay.h"
 #include "actions.h"
@@ -194,6 +194,16 @@ void accept_action(ipc_connection_t *conn, ipc_message_t *msg)
 			ipcmsg_append(update, buff, strlen(buff));
 			msg_attach_to_username(update, victim_name);
 			ipcmsg_destroy(update);
+
+			ipc_message_t *gm = ipcmsg_create(IPC_EVENT, msg->head.src);
+			json_t * gj = json_init(NULL);
+			json_addstring(gj, "target", victim_name);
+			json_addint(gj, "success", success);
+			json_addstring(gj, "type", "gag");
+			json_vaddstring(gj, "text", "%s", gag_gag_msg(gtnum));
+			ipcmsg_json_encode(gm, gj);
+			msg_attach_to_username(gm, victim_name);
+			ipcmsg_destroy(gm);
 		} else {
 			json_vaddstring(ej, "text", "%s just tried to gag %s with %s and failed",
 			               attacker_name, victim_name, gag_type(gtnum));
@@ -215,7 +225,11 @@ void accept_action(ipc_connection_t *conn, ipc_message_t *msg)
 		json_addstring(ej, "type", "ungag");
 
 		if (success) {
-			json_vaddstring(ej, "text", "%s has just ungagged %s", attacker_name, victim_name);
+			unsigned long old = victim.record.chatmode;
+			unsigned long oldg = (old & CM_GAGMASK) >> CM_GAGSHIFT;
+
+			json_vaddstring(ej, "text", "%s has just ungagged %s",
+			                attacker_name, victim_name);
 
 			// change users mode
 			_autofree char *buff=NULL;
@@ -225,6 +239,16 @@ void accept_action(ipc_connection_t *conn, ipc_message_t *msg)
 			ipcmsg_append(update, buff, strlen(buff));
 			msg_attach_to_username(update, victim_name);
 			ipcmsg_destroy(update);
+
+			ipc_message_t *gm = ipcmsg_create(IPC_EVENT, msg->head.src);
+			json_t * gj = json_init(NULL);
+			json_addstring(gj, "target", victim_name);
+			json_addint(gj, "success", success);
+			json_addstring(gj, "type", "ungag");
+			json_vaddstring(gj, "text", "%s", gag_ungag_msg(oldg));
+			ipcmsg_json_encode(gm, gj);
+			msg_attach_to_username(gm, victim_name);
+			ipcmsg_destroy(gm);
 		} else {
 			json_vaddstring(ej, "text", "%s just tried to ungag %s and failed",
 			               attacker_name, victim_name);
diff --git a/src/gags.c b/src/server/gags.c
similarity index 99%
rename from src/gags.c
rename to src/server/gags.c
index 1b5fd5e0b474bab091c9b7127c4e35aad4972069..6570b1621601fda0595b2a19d4e9b02850e49057 100644
--- a/src/gags.c
+++ b/src/server/gags.c
@@ -433,6 +433,20 @@ GagInfo * gag_find(int num)
 	return NULL;
 }
 
+const char *gag_gag_msg(int type)
+{
+	GagInfo *gi = gag_find(type);
+
+	return gi ? gi->gag : "Unknown";
+}
+
+const char *gag_ungag_msg(int type)
+{
+	GagInfo *gi = gag_find(type);
+
+	return gi ? gi->ungag : "Unknown";
+}
+
 int gag_code(const char *str)
 {
 	GagInfo *gi = gaglist;
diff --git a/src/gags.h b/src/server/gags.h
similarity index 90%
rename from src/gags.h
rename to src/server/gags.h
index a74fb9b645ccccc2cfedfc4b32a4a0a6edec390f..479d4a015949b2e1f0f9d7a311e9f7d0a3896c5d 100644
--- a/src/gags.h
+++ b/src/server/gags.h
@@ -22,5 +22,7 @@ char * apply_gag(struct user *speaker, const char *text);
 GagInfo *gag_find(int num);
 int gag_code(const char *str);
 const char *gag_type(int type);
+const char *gag_gag_msg(int type);
+const char *gag_ungag_msg(int type);
 
 #endif /* GAGS_H */
diff --git a/src/gagtable.c b/src/server/gagtable.c
similarity index 100%
rename from src/gagtable.c
rename to src/server/gagtable.c
diff --git a/src/gagtable.h b/src/server/gagtable.h
similarity index 100%
rename from src/gagtable.h
rename to src/server/gagtable.h
diff --git a/src/server/servsock.c b/src/server/servsock.c
index 9faa4866b06a11f3108284321c0257db73f79461..4774337b00005932a8031a88de9c6c5f7d2ee3ad 100644
--- a/src/server/servsock.c
+++ b/src/server/servsock.c
@@ -25,8 +25,8 @@
 #include "servsock.h"
 #include "replay.h"
 #include "actions.h"
+#include "gags.h"
 #include <folders.h>
-#include <gags.h>
 #include <perms.h>
 #include <special.h>
 
@@ -267,6 +267,18 @@ void send_error(ipc_connection_t *conn, ipc_message_t *orig, const char *format,
 	json_decref(j);
 }
 
+static ipc_message_t *msg_gaglist(void)
+{
+	ipc_message_t * msg = ipcmsg_create(IPC_GAGLIST, SYSTEM_USER);
+	json_t *arr = json_array();
+
+	for (GagInfo *gi = gaglist; gi->name != NULL; gi++)
+		json_array_append_new(arr, json_string(gi->name));
+
+	ipcmsg_json_encode(msg, arr);
+	json_decref(arr);
+	return msg;
+}
 
 void process_msg(ipc_connection_t *conn, ipc_message_t *msg)
 {
@@ -308,6 +320,10 @@ void process_msg(ipc_connection_t *conn, ipc_message_t *msg)
 		ipc_message_t * whoinfo = msg_wholist();
 		msg_attach_to_all(whoinfo);
 		ipcmsg_destroy(whoinfo);
+
+		ipc_message_t *gaginfo = msg_gaglist();
+		msg_attach(gaginfo, conn);
+		ipcmsg_destroy(gaginfo);
 		return;
 	}