Path: utzoo!utgpu!watmath!uunet!tektronix!reed!trost From: trost@reed.UUCP (Bill Trost) Newsgroups: gnu.emacs Subject: Re: Network transparent server? Message-ID: <11595@reed.UUCP> Date: 24 Jan 89 05:50:34 GMT References: <742@jupiter.iis.UUCP> Reply-To: trost@reed.UUCP (Bill Trost) Distribution: gnu Organization: Reed College, Portland OR Lines: 551 In article <742@jupiter.iis.UUCP> heiser@iis.ethz.ch (Gernot Heiser) writes: > >Does anybody know of a way to connect to a remote emacs server? > >When working under X windows I usually have emacs running on a (more powerful) >remote machine. However, the emacsclient does not seem to be able to connect >to the emacs process when running on a different computer (even if the home >directories on both machines are the same). Does anybody know of a solution? Ahh, I just ran into that exact same problem last week, so I hacked the 4.3 version of the server and client to do "the right thing." To fix the security problem mentioned by jr@bbn.com, I wrote the server information to a file readable only by the owner. The version of the client I have running has a few other "features" as well; the program is capable of reading from the standard input, and there's some other clever thing that I forgot right off hand. Also, the server will work both on the unix domain and tcp/ip protocols, so using the old server should be fine. I *think* I tested the unix domain code when I put it back in, so it should probably work. Anyhow, here's the code (not diffs, not worth the effort). ---------------- /* Communication subprocess for GNU Emacs acting as server. Copyright (C) 1986, 1987 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is distributed in the hope that it will be useful, but without any warranty. No author or distributor accepts responsibility to anyone for the consequences of using it or for whether it serves any particular purpose or works at all, unless he says so in writing. Everyone is granted permission to copy, modify and redistribute GNU Emacs, but only under the conditions described in the document "GNU Emacs copying permission notice". An exact copy of the document is supposed to have been given to you along with GNU Emacs so that you can know how you may redistribute it all. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. */ /* The GNU Emacs edit server process is run as a subprocess of Emacs under control of the file lisp/server.el. This program accepts communication from client (program emacsclient.c) and passes their commands (consisting of keyboard characters) up to the Emacs which then executes them. */ #define TCP_INFO ".emacs_sockinfo" #define NO_SHORTNAMES #include "config.h" #undef read #undef write #undef open #if !defined(BSD) && !defined(HAVE_SYSVIPC) #include main () { fprintf (stderr, "Sorry, the Emacs server is supported only on Berkeley Unix.\n"); exit (1); } #else /* BSD or IPC */ #ifdef BSD /* BSD code is very different from SYSV IPC code */ #include #include #include #include #include #include #include #include #include extern int errno; main () { int s, infd, fromlen, s2; int fd, len; struct hostent* hp; struct sockaddr_in sa; struct sockaddr_un server; char *homedir, hostname[256]; char *str, string[BUFSIZ], code[BUFSIZ]; FILE *infile; FILE *infofile; FILE **openfiles; int openfiles_size; char *getenv (); if ((homedir = getenv ("HOME")) == NULL) { fprintf (stderr,"No home directory\n"); exit (1); } /* begin TCP code */ if (chdir(homedir) < 0 || !(infofile = fopen(TCP_INFO, "w")) || fchmod(fileno(infofile), 0600) < 0 || gethostname(hostname, sizeof hostname) < 0 || !(hp = gethostbyname(hostname))) abort(); /* not terribly graceful, but... */ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) abort(); bcopy(hp->h_addr, &sa.sin_addr.s_addr, hp->h_length); sa.sin_family = AF_INET; sa.sin_port = htons(0); if (bind(s, (struct sockaddr *) &sa, sizeof (sa)) < 0) abort(); len = sizeof(sa); if (getsockname(s, &sa, &len) < 0) abort(); fprintf(infofile, "%s\n%d\n", hostname, ntohs(sa.sin_port)); fclose(infofile); openfiles_size = 20; openfiles = (FILE **) malloc (openfiles_size * sizeof (FILE *)); if (openfiles == 0) abort (); /* * Open up an AF_UNIX socket in this person's home directory */ if ((s2 = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) { perror ("socket"); exit (1); } server.sun_family = AF_UNIX; strcpy (server.sun_path, homedir); strcat (server.sun_path, "/.emacs_server"); if (bind (s2, &server, strlen (server.sun_path) + 2) < 0) { perror ("bind"); exit (1); } /* * Now, just wait for everything to come in.. */ if (listen(s, 5) < 0 || listen(s2, 5) < 0) { perror ("listen"); exit (1); } /* Disable sigpipes in case luser kills client... */ signal (SIGPIPE, SIG_IGN); for (;;) { int rmask = (1 << s) + (1 << s2) + 1; if (select (s + 1, &rmask, 0, 0, 0) < 0) perror ("select"); if (rmask & ((1 << s) | 1 << s2)) { /* client sends list of filenames */ infd = accept ((1 << s) & rmask ? s : s2, 0, 0); /* open socket fd */ if (infd < 0) { if (errno == EMFILE || errno == ENFILE) printf ("Too many clients.\n"); else perror ("accept"); continue; } if (infd >= openfiles_size) { openfiles_size *= 2; openfiles = (FILE **) realloc (openfiles, openfiles_size * sizeof (FILE *)); if (openfiles == 0) abort (); } infile = fdopen (infd, "r+"); /* open stream */ if (infile == NULL) { printf ("Too many clients.\n"); write (infd, "Too many clients.\n", 18); close (infd); /* Prevent descriptor leak.. */ continue; } str = fgets (string, BUFSIZ, infile); if (str == NULL) { perror ("fgets"); close (infd); /* Prevent descriptor leak.. */ continue; } openfiles[infd] = infile; printf ("Client: %d %s", infd, string); /* If what we read did not end in a newline, it means there is more. Keep reading from the socket and outputting to Emacs, until we get the newline. */ while (string[strlen (string) - 1] != '\n') { if (fgets (string, BUFSIZ, infile) == 0) break; printf ("%s", string); } fflush (stdout); fflush (infile); continue; } else if (rmask & 1) { /* emacs sends codeword, fd, and string message */ /* Read command codeword and fd */ scanf ("%s %d%*c", code, &infd); /* Transfer text from Emacs to the client, up to a newline. */ infile = openfiles[infd]; while (1) { if (fgets (string, BUFSIZ, stdin) == 0) break; fprintf (infile, "%s", string); if (string[strlen (string) - 1] == '\n') break; } fflush (infile); /* If command is close, close connection to client. */ if (strncmp (code, "Close:", 6) == 0) if (infd > 2) { fclose (infile); close (infd); } continue; } } } #else /* This is the SYSV IPC section */ #include #include #include #include #include jmp_buf msgenv; msgcatch () { longjmp (msgenv, 1); } /* "THIS has to be fixed. Remember, stderr may not exist...-rlk." Incorrect. This program runs as an inferior of Emacs. Its stderr always exists--rms. */ #include main () { int s, infd, fromlen; key_t key; struct msgbuf * msgp = (struct msgbuf *) malloc (sizeof *msgp + BUFSIZ); struct msqid_ds msg_st; int p; char *homedir, *getenv (); char string[BUFSIZ]; FILE *infile; /* * Create a message queue using ~/.emacs_server as the path for ftok */ if ((homedir = getenv ("HOME")) == NULL) { fprintf (stderr,"No home directory\n"); exit (1); } strcpy (string, homedir); strcat (string, "/.emacs_server"); creat (string, 0600); key = ftok (string, 1); /* unlikely to be anyone else using it */ s = msgget (key, 0600 | IPC_CREAT); if (s == -1) { perror ("msgget"); exit (1); } /* Fork so we can close connection even if parent dies */ p = fork (); if (setjmp (msgenv)) { msgctl (s, IPC_RMID, 0); kill (p, SIGKILL); exit (0); } signal (SIGTERM, msgcatch); signal (SIGINT, msgcatch); /* If parent goes away, remove message box and exit */ if (p == 0) { p = getppid (); setpgrp (); /* Gnu kills process group on exit */ while (1) { if (kill (p, 0) < 0) { msgctl (s, IPC_RMID, 0); exit (0); } sleep (10); } } while (1) { if ((fromlen = msgrcv (s, msgp, BUFSIZ - 1, 1, 0)) < 0) { perror ("msgrcv"); } else { msgctl (s, IPC_STAT, &msg_st); strncpy (string, msgp->mtext, fromlen); string[fromlen] = 0; /* make sure */ /* Newline is part of string.. */ printf ("Client: %d %s", s, string); fflush (stdout); /* Now, wait for a wakeup */ fgets (msgp->mtext, BUFSIZ, stdin); msgp->mtext[strlen (msgp->mtext)-1] = 0; /* strcpy (msgp->mtext, "done");*/ msgp->mtype = msg_st.msg_lspid; msgsnd (s, msgp, strlen (msgp->mtext)+1, 0); } } } #endif /* SYSV IPC */ #endif /* BSD && IPC */ ---------------- /* Client process that communicates with GNU Emacs acting as server. Copyright (C) 1986, 1987 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is distributed in the hope that it will be useful, but without any warranty. No author or distributor accepts responsibility to anyone for the consequences of using it or for whether it serves any particular purpose or works at all, unless he says so in writing. Everyone is granted permission to copy, modify and redistribute GNU Emacs, but only under the conditions described in the document "GNU Emacs copying permission notice". An exact copy of the document is supposed to have been given to you along with GNU Emacs so that you can know how you may redistribute it all. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. Modified by Bill Trost to do more and smarter things, such as: send it to another user's socket read from the standard input stop itself or not wait (with -q flag) usage is something along the lines of: emacsd [-u user] [-q] [file...] */ #define TCP_INFO ".emacs_sockinfo" #define NO_SHORTNAMES #include "config.h" #undef read #undef write #undef open #ifndef BSD #include main () { fprintf (stderr, "Sorry, the Emacs server is supported only on Berkeley Unix.\n"); exit (1); } #else /* BSD */ #include #include #include #include #include #include #include #define gethomedir(username) getpwnam(username)->pw_dir main (argc, argv) int argc; char **argv; { int s, i, wait=1, me=1; FILE *info; FILE *out; struct sockaddr_in server; struct hostent* hp; char *homedir, *cwd, *str; char string[BUFSIZ]; char *getenv(), *getwd(), *index(); /* parse any special arguments first */ if ((homedir = getenv ("HOME")) == NULL) { fprintf (stderr, "No home directory\n"); exit (1); } for (i=1; i < argc && *argv[i] == '-'; i++) switch (*(++argv[i])) { case 'q': wait = 0; break; case 'u': homedir = gethomedir(argv[++i]); me = 0; break; default: fprintf(stderr, "unrecognized switch: -%c\n", *argv[i]); fprintf(stderr, "usage: %s [-q] [-u user] [file]...\n", argv[0]); exit(1); } (void) sprintf(string, "%s/%s", getenv("HOME"), TCP_INFO); if (!(info = fopen(string, "r"))) { fprintf(stderr, "Can't open sockinfo file.\n"); exit(1); } if (!fgets(string, sizeof string, info)) { fprintf(stderr, "Can't read sockinfo file.\n"); exit(1); } /* * We *are* assuming that the string returned by fgets has the * newline on the end. -|trost 1/89 */ *index(string, '\n') = '\0'; if (!(hp = gethostbyname(string))) { fprintf(stderr, "gethostbyname of %s failed.\n", string); exit(1); } if (!fgets(string, sizeof string, info)) { fprintf(stderr, "Second read of sockinfo file failed!\n"); exit(1); } if (!(server.sin_port = htons(atoi(string)))) { fprintf(stderr, "Invalid port number in sockinfo file.\n"); exit(1); } bcopy(hp->h_addr, &server.sin_addr.s_addr, hp->h_length); server.sin_family = AF_INET; if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) { perror ("socket"); exit (1); } if (connect(s, &server, sizeof server) < 0) { perror("connect"); exit(1); } if ((out = fdopen (s, "r+")) == NULL) { perror ("fdopen"); exit (1); } cwd = getwd (string); if (cwd == 0) { fprintf(stderr, "Couldn't find .\n"); exit(1); } (void) strcat(cwd, "/"); if (i == argc) { /* handle the special case of reading from stdin */ char *tmpfile = "/tmp/emacsdXXXXXX"; FILE *fp; int c; if (me) umask(066); fp = fopen(mktemp(tmpfile), "w"); while ((c = getchar()) != EOF) putc(c, fp); fclose(fp); fprintf(out, "%s%s ", HOSTPREFIX, tmpfile); printf("[created %s]\n", tmpfile); } else for (; i < argc; i++) fprintf (out, "%s%s%s ", HOSTPREFIX, *argv[i] == '/' ? "" : cwd, argv[i]); fprintf (out, "\n"); fflush (out); if (!wait) exit(0); printf("Waiting for Emacs..."); fflush(stdout); /* I did have a kill(SIGTSTP, getppid()) right here, but there were at least two problems with this approach: 1) The parent process is usually a shell used for path resolution. 2) Such behavior is confusion and very hard to recover from when running under a windowing environment. -|trost 1/89 */ rewind(out); /* re-read the output */ str = fgets (string, BUFSIZ, out); /* Now, wait for an answer and print any messages. */ while (str = fgets (string, BUFSIZ, out)) printf ("%s", str); exit (0); } #endif /* BSD */ -- Whaddya mean, these are *my* opinions?? I never wrote that! Bill Trost trost@reed.bitnet, but probably ...!tektronix!reed!trost