Summoning.
[ircbot] / summon.c
diff --git a/summon.c b/summon.c
new file mode 100644 (file)
index 0000000..d5e7989
--- /dev/null
+++ b/summon.c
@@ -0,0 +1,101 @@
+/*
+ * usage:
+ *  .../summon <real-summoner> <calling-nick> <calling-path> <channel>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+
+#include <utmp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+static void check(const char *string, const char *what, int maxlen) {
+  int c;
+  
+  if (strlen(string) > maxlen) { fprintf(stderr,"%s too long\n",what); exit(8); }
+  while ((c= *string++)) {
+    if (isspace((unsigned char)c) || !isprint((unsigned char)c))
+      { fprintf(stderr,"bad char in %s\n",what); exit(8); }
+  }
+}
+
+static void die(const char *msg) {
+  fprintf(stderr,"%s\n",msg); exit(8);
+}
+
+static void problem(const char *msg) {
+  printf("problem %s\n",msg);
+  exit(0);
+}
+
+enum howbad { hb_notlogon, hb_noterminal, hb_nomessages, hb_ok };
+static enum howbad closest= hb_notlogon;
+
+static time_t best_idle;
+static char best_line[UT_LINESIZE+1];
+
+static void thisis(enum howbad hb) {
+  if (hb > closest) closest= hb;
+}
+
+int main(int argc, const char *const *argv) {
+  struct utmp *ue;
+  const char *myself;
+  struct stat stab;
+  int fd;
+  char idlebuf[20];
+  
+  if (argc != 5) die("bad usage");
+
+  myself= getenv("USER");
+  if (!myself) die("USER not set");
+  if (strlen(myself) > UT_NAMESIZE) die("own username too long");
+
+  check(argv[2],"nick",20);
+  check(argv[3],"path",60);
+  check(argv[4],"channel",20);
+
+  if (chdir("/dev")) { perror("chdir /dev"); exit(8); }
+  
+  while ((errno=0, ue= getutent())) {
+    if (ue->ut_type != USER_PROCESS) continue;
+    if (strncmp(ue->ut_user,myself,UT_NAMESIZE)) continue;
+    if (!ue->ut_line[0]) { thisis(hb_noterminal); continue; }
+    ue->ut_line[UT_LINESIZE]= 0; /* overflows into next field :-/ */
+    if (stat(ue->ut_line,&stab)) {
+      printf("warning could not stat %s: %s\n",ue->ut_line,strerror(errno));
+      thisis(hb_noterminal); continue;
+    }
+    if (!(stab.st_mode & S_IWGRP)) { thisis(hb_nomessages); continue; }
+    closest= hb_ok;
+    if (closest == hb_ok && stab.st_atime <= best_idle) continue;
+    strcpy(best_line,ue->ut_line);
+    best_idle= stab.st_atime;
+  }
+  if (errno) { perror("getutent set errno"); exit(8); }
+
+  switch (closest) {
+  case hb_notlogon:   problem("is not logged on");
+  case hb_noterminal: problem("does not have any terminal/shell sessions");
+  case hb_nomessages: problem("is refusing messages");
+  case hb_ok: break;
+  default: abort();
+  }
+
+  sprintf(idlebuf,"%lu",(unsigned long)best_idle);
+
+  fd= open(best_line, O_NOCTTY|O_NONBLOCK|O_WRONLY);
+  if (fd < 0) {
+    fprintf(stderr,"unable to open terminal (%s): %s",best_line,strerror(errno));
+    exit(8);
+  }
+  if (dup2(fd,0)) { perror("dup2"); exit(8); }
+  execlp(argv[1],argv[1], best_line,idlebuf,argv[2],argv[3],argv[4], (const char*)0);
+  perror(argv[1]); exit(8);
+}