/* Copyright (c) 2008, 2009 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) * Micah Cowan (micah@cowan.name) * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net) * Copyright (c) 1993-2002, 2003, 2005, 2006, 2007 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de) * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de) * Copyright (c) 1987 Oliver Laumann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING); if not, see * https://www.gnu.org/licenses/, or contact Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * **************************************************************** */ #include #include #include #include #ifndef SIGINT # include #endif #include "config.h" #include "screen.h" #include "extern.h" extern struct display *display, *displays; extern struct win *fore; extern struct layer *flayer; extern int ServerSocket; extern int real_uid, eff_uid; extern int real_gid, eff_gid; extern char *extra_incap, *extra_outcap; extern char *home, *RcFileName; extern char SockPath[], *SockName; #ifdef COPY_PASTE extern char *BufferFile; #endif extern int hardcopy_append; extern char *hardcopydir; static char *CatExtra __P((char *, char *)); static char *findrcfile __P((char *)); char *rc_name = ""; int rc_recursion = 0; static char * CatExtra(register char *str1, register char *str2) { register char *cp; register int len1, len2, add_colon; len1 = strlen(str1); if (len1 == 0) return str2; add_colon = (str1[len1 - 1] != ':'); if (str2) { len2 = strlen(str2); if ((cp = realloc(str2, (unsigned)len1 + len2 + add_colon + 1)) == NULL) Panic(0, "%s", strnomem); bcopy(cp, cp + len1 + add_colon, len2 + 1); } else { if ((cp = malloc((unsigned)len1 + add_colon + 1)) == NULL) Panic(0, "%s", strnomem); cp[len1 + add_colon] = '\0'; } bcopy(str1, cp, len1); if (add_colon) cp[len1] = ':'; return cp; } static char * findrcfile(char *rcfile) { char buf[256]; char *p; /* Tilde prefix support courtesy , * taken from a Debian patch. */ if (rcfile && *rcfile == '~') { static char rcfilename_tilde_exp[MAXPATHLEN + 1]; char *slash_position = strchr(rcfile, '/'); if (slash_position == rcfile + 1) { char *home = getenv("HOME"); if (!home) { Msg(0, "%s: source: tilde expansion failed", rc_name); return NULL; } snprintf(rcfilename_tilde_exp, MAXPATHLEN, "%s/%s", home, rcfile + 2); } else if (slash_position) { struct passwd *p; *slash_position = 0; p = getpwnam(rcfile + 1); if (!p) { Msg(0, "%s: source: tilde expansion failed for user %s", rc_name, rcfile + 1); return NULL; } snprintf(rcfilename_tilde_exp, MAXPATHLEN, "%s/%s", p->pw_dir, slash_position + 1); } else { Msg(0, "%s: source: illegal tilde expression.", rc_name); return NULL; } rcfile = rcfilename_tilde_exp; } if (rcfile) { char *rcend = rindex(rc_name, '/'); if (*rcfile != '/' && rcend && (rcend - rc_name) + strlen(rcfile) + 2 < sizeof(buf)) { strncpy(buf, rc_name, rcend - rc_name + 1); strcpy(buf + (rcend - rc_name) + 1, rcfile); if (access(buf, R_OK) == 0) return SaveStr(buf); } debug1("findrcfile: you specified '%s'\n", rcfile); return SaveStr(rcfile); } debug("findrcfile: you specified nothing...\n"); if ((p = getenv("SCREENRC")) != NULL && *p != '\0') { debug1(" $SCREENRC has: '%s'\n", p); return SaveStr(p); } else { debug(" ...nothing in $SCREENRC, defaulting $HOME/.screenrc\n"); if (strlen(home) > sizeof(buf) - 12) Panic(0, "Rc: home too large"); sprintf(buf, "%s/.screenrc", home); return SaveStr(buf); } } /* * this will be called twice: * 1) rcfilename = "/etc/screenrc" * 2) rcfilename = RcFileName */ int StartRc(char *rcfilename, int nopanic) { register int argc, len; register char *p, *cp; char buf[2048]; char *args[MAXARGS]; int argl[MAXARGS]; FILE *fp; char *oldrc_name = rc_name; /* always fix termcap/info capabilities */ extra_incap = CatExtra("TF", extra_incap); /* Special settings for vt100 and others */ if (display && (!strncmp(D_termname, "vt", 2) || !strncmp(D_termname, "xterm", 5))) extra_incap = CatExtra("xn:f0=\033Op:f1=\033Oq:f2=\033Or:f3=\033Os:f4=\033Ot:f5=\033Ou:f6=\033Ov:f7=\033Ow:f8=\033Ox:f9=\033Oy:f.=\033On:f,=\033Ol:fe=\033OM:f+=\033Ok:f-=\033Om:f*=\033Oj:f/=\033Oo:fq=\033OX", extra_incap); rc_name = findrcfile(rcfilename); if (rc_name == NULL || (fp = secfopen(rc_name, "r")) == NULL) { const char *rc_nonnull = rc_name ? rc_name : rcfilename; if (!rc_recursion && RcFileName && !strcmp(RcFileName, rc_nonnull)) { /* * User explicitly gave us that name, * this is the only case, where we get angry, if we can't read * the file. */ debug3("StartRc: '%s','%s', '%s'\n", RcFileName, rc_name ? rc_name : "(null)", rcfilename); if (!nopanic) Panic(0, "Unable to open \"%s\".", rc_nonnull); /* possibly NOTREACHED */ } debug1("StartRc: '%s' no good. ignored\n", rc_nonnull); if (rc_name) Free(rc_name); rc_name = oldrc_name; return 1; } while (fgets(buf, sizeof buf, fp) != NULL) { if ((p = rindex(buf, '\n')) != NULL) *p = '\0'; if ((argc = Parse(buf, sizeof buf, args, argl)) == 0) continue; if (strcmp(args[0], "echo") == 0) { if (!display) continue; if (argc < 2 || (argc == 3 && strcmp(args[1], "-n")) || argc > 3) { Msg(0, "%s: 'echo [-n] \"string\"' expected.", rc_name); continue; } AddStr(args[argc - 1]); if (argc != 3) { AddStr("\r\n"); Flush(0); } } else if (strcmp(args[0], "sleep") == 0) { if (!display) continue; debug("sleeeeeeep\n"); if (argc != 2) { Msg(0, "%s: sleep: one numeric argument expected.", rc_name); continue; } DisplaySleep1000(1000 * atoi(args[1]), 1); } #ifdef TERMINFO else if (!strcmp(args[0], "termcapinfo") || !strcmp(args[0], "terminfo")) { #else else if (!strcmp(args[0], "termcapinfo") || !strcmp(args[0], "termcap")) { #endif if (!display) continue; if (argc < 3 || argc > 4) { Msg(0, "%s: %s: incorrect number of arguments.", rc_name, args[0]); continue; } for (p = args[1]; p && *p; p = cp) { if ((cp = index(p, '|')) != 0) *cp++ = '\0'; len = strlen(p); if (p[len - 1] == '*') { if (!(len - 1) || !strncmp(p, D_termname, len - 1)) break; } else if (!strcmp(p, D_termname)) break; } if (!(p && *p)) continue; extra_incap = CatExtra(args[2], extra_incap); if (argc == 4) extra_outcap = CatExtra(args[3], extra_outcap); } else if (!strcmp(args[0], "source")) { if (rc_recursion <= 10) { rc_recursion++; (void)StartRc(args[1], 0); rc_recursion--; } } } fclose(fp); Free(rc_name); rc_name = oldrc_name; return 0; } void FinishRc(char *rcfilename) { char buf[2048]; FILE *fp; char *oldrc_name = rc_name; rc_name = findrcfile(rcfilename); if (rc_name == NULL || (fp = secfopen(rc_name, "r")) == NULL) { const char *rc_nonnull = rc_name ? rc_name : rcfilename; if (rc_recursion) Msg(errno, "%s: source %s", oldrc_name, rc_nonnull); else if (RcFileName && !strcmp(RcFileName, rc_nonnull)) { /* * User explicitly gave us that name, * this is the only case, where we get angry, if we can't read * the file. */ debug3("FinishRc:'%s','%s','%s'\n", RcFileName, rc_name ? rc_name : "(null)", rcfilename); Panic(0, "Unable to open \"%s\".", rc_nonnull); /* NOTREACHED */ } debug1("FinishRc: '%s' no good. ignored\n", rc_nonnull); if (rc_name) Free(rc_name); rc_name = oldrc_name; return; } debug("finishrc is going...\n"); while (fgets(buf, sizeof buf, fp) != NULL) RcLine(buf, sizeof buf); (void)fclose(fp); Free(rc_name); rc_name = oldrc_name; } void do_source(char *rcfilename) { if (rc_recursion > 10) { Msg(0, "%s: source: recursion limit reached", rc_name); return; } rc_recursion++; FinishRc(rcfilename); rc_recursion--; } /* * Running a Command Line in the environment determined by the display. * The fore window is taken from the display as well as the user. * This is bad when we run detached. */ void RcLine(char *ubuf, int ubufl) { char *args[MAXARGS]; int argl[MAXARGS]; #ifdef MULTIUSER extern struct acluser *EffectiveAclUser; /* acl.c */ extern struct acluser *users; /* acl.c */ #endif if (display) { fore = D_fore; flayer = D_forecv->c_layer; } else flayer = fore ? fore->w_savelayer : 0; if (Parse(ubuf, ubufl, args, argl) <= 0) return; #ifdef MULTIUSER if (!display) { /* the session owner does it, when there is no display here */ EffectiveAclUser = users; debug("RcLine: WARNING, no display no user! Session owner executes command\n"); } #endif DoCommand(args, argl); #ifdef MULTIUSER EffectiveAclUser = 0; #endif } /* needs display for copybuffer access and termcap dumping */ void WriteFile(struct acluser *user, char *fn, int dump) { /* dump==0: create .termcap, * dump==1: hardcopy, * #ifdef COPY_PASTE * dump==2: BUFFERFILE * #endif COPY_PASTE * dump==1: scrollback, */ register int i, j, k; register char *p; register FILE *f; char fnbuf[1024]; char *mode = "w"; #ifdef COPY_PASTE int public = 0; # ifdef HAVE_LSTAT struct stat stb, stb2; int fd, exists = 0; # endif #endif switch (dump) { case DUMP_TERMCAP: if (fn == 0) { i = SockName - SockPath; if (i > (int)sizeof(fnbuf) - 9) i = 0; strncpy(fnbuf, SockPath, i); strcpy(fnbuf + i, ".termcap"); fn = fnbuf; } break; case DUMP_HARDCOPY: case DUMP_SCROLLBACK: if (fn == 0) { if (fore == 0) return; if (hardcopydir && *hardcopydir && strlen(hardcopydir) < sizeof(fnbuf) - 21) sprintf(fnbuf, "%s/hardcopy.%d", hardcopydir, fore->w_number); else sprintf(fnbuf, "hardcopy.%d", fore->w_number); fn = fnbuf; } if (hardcopy_append && !access(fn, W_OK)) mode = "a"; break; #ifdef COPY_PASTE case DUMP_EXCHANGE: if (fn == 0) { strncpy(fnbuf, BufferFile, sizeof(fnbuf) - 1); fnbuf[sizeof(fnbuf) - 1] = 0; fn = fnbuf; } public = !strcmp(fn, DEFAULT_BUFFERFILE); # ifdef HAVE_LSTAT exists = !lstat(fn, &stb); if (public && exists && (S_ISLNK(stb.st_mode) || stb.st_nlink > 1)) { Msg(0, "No write to links, please."); return; } # endif break; #endif } debug2("WriteFile(%d) %s\n", dump, fn); if (UserContext() > 0) { debug("Writefile: usercontext\n"); #ifdef COPY_PASTE if (dump == DUMP_EXCHANGE && public) { # ifdef HAVE_LSTAT if (exists) { if ((fd = open(fn, O_WRONLY, 0666)) >= 0) { if (fstat(fd, &stb2) == 0 && stb.st_dev == stb2.st_dev && stb.st_ino == stb2.st_ino) ftruncate(fd, 0); else { close(fd); fd = -1; } } } else fd = open(fn, O_WRONLY|O_CREAT|O_EXCL, 0666); f = fd >= 0 ? fdopen(fd, mode) : 0; # else f = fopen(fn, mode); # endif } else #endif /* COPY_PASTE */ f = fopen(fn, mode); if (f == NULL) { debug2("WriteFile: fopen(%s,\"%s\") failed\n", fn, mode); UserReturn(0); } else { switch (dump) { case DUMP_HARDCOPY: case DUMP_SCROLLBACK: if (!fore) break; if (*mode == 'a') { putc('>', f); for (j = fore->w_width - 2; j > 0; j--) putc('=', f); fputs("<\n", f); } if (dump == DUMP_SCROLLBACK) { #ifdef COPY_PASTE for (i = fore->w_histheight - fore->w_scrollback_height; i < fore->w_histheight; i++) { p = (char *)(WIN(i)->image); for (k = fore->w_width - 1; k >= 0 && p[k] == ' '; k--) ; for (j = 0; j <= k; j++) putc(p[j], f); putc('\n', f); } #endif } for (i = 0; i < fore->w_height; i++) { p = (char *)fore->w_mlines[i].image; for (k = fore->w_width - 1; k >= 0 && p[k] == ' '; k--) ; for (j = 0; j <= k; j++) putc(p[j], f); putc('\n', f); } break; case DUMP_TERMCAP: DumpTermcap(fore->w_aflag, f); break; #ifdef COPY_PASTE case DUMP_EXCHANGE: p = user->u_plop.buf; for (i = user->u_plop.len; i-- > 0; p++) if (*p == '\r' && (i == 0 || p[1] != '\n')) putc('\n', f); else putc(*p, f); break; #endif } (void)fclose(f); UserReturn(1); } } if (UserStatus() <= 0) Msg(0, "Cannot open \"%s\"", fn); else if (display && !*rc_name) { switch (dump) { case DUMP_TERMCAP: Msg(0, "Termcap entry written to \"%s\".", fn); break; case DUMP_HARDCOPY: case DUMP_SCROLLBACK: Msg(0, "Screen image %s to \"%s\".", (*mode == 'a') ? "appended" : "written", fn); break; #ifdef COPY_PASTE case DUMP_EXCHANGE: Msg(0, "Copybuffer written to \"%s\".", fn); #endif } } } #ifdef COPY_PASTE /* * returns an allocated buffer which holds a copy of the file named fn. * lenp (if nonzero) points to a location, where the buffer size should be * stored. */ char * ReadFile(char *fn, int *lenp) { int i, l, size; char c, *bp, *buf; struct stat stb; ASSERT(lenp); debug1("ReadFile(%s)\n", fn); if ((i = secopen(fn, O_RDONLY, 0)) < 0) { Msg(errno, "no %s -- no slurp", fn); return NULL; } if (fstat(i, &stb)) { Msg(errno, "no good %s -- no slurp", fn); close(i); return NULL; } size = stb.st_size; if ((buf = malloc(size)) == NULL) { close(i); Msg(0, "%s", strnomem); return NULL; } errno = 0; if ((l = read(i, buf, size)) != size) { if (l < 0) l = 0; Msg(errno, "Got only %d bytes from %s", l, fn); } else { if (read(i, &c, 1) > 0) Msg(0, "Slurped only %d characters (of %d) into buffer - try again", l, size); else Msg(0, "Slurped %d characters into buffer", l); } close(i); *lenp = l; for (bp = buf; l-- > 0; bp++) if (*bp == '\n' && (bp == buf || bp[-1] != '\r')) *bp = '\r'; return buf; } void KillBuffers() { if (UserContext() > 0) UserReturn(unlink(BufferFile) ? errno : 0); errno = UserStatus(); Msg(errno, "%s %sremoved", BufferFile, errno ? "not " : ""); } #endif /* COPY_PASTE */ /* (Almost) secure open and fopen... */ FILE * secfopen(char *name, char *mode) { FILE *fi; #ifndef USE_SETEUID int flags, fd; #endif debug2("secfopen(%s, %s)\n", name, mode); #ifdef USE_SETEUID xseteuid(real_uid); xsetegid(real_gid); fi = fopen(name, mode); xseteuid(eff_uid); xsetegid(eff_gid); return fi; #else if (eff_uid == real_uid) return fopen(name, mode); if (mode[0] && mode[1] == '+') flags = O_RDWR; else flags = (mode[0] == 'r') ? O_RDONLY : O_WRONLY; if (mode[0] == 'w') flags |= O_CREAT | O_TRUNC; else if (mode[0] == 'a') flags |= O_CREAT | O_APPEND; else if (mode[0] != 'r') { errno = EINVAL; return 0; } if ((fd = secopen(name, flags, 0666)) < 0) return 0; if ((fi = fdopen(fd, mode)) == 0) { close(fd); return 0; } return fi; #endif } int secopen(char *name, int flags, int mode) { int fd; #ifndef USE_SETEUID int q; struct stat stb; #endif debug3("secopen(%s, 0x%x, 0%03o)\n", name, flags, mode); #ifdef USE_SETEUID xseteuid(real_uid); xsetegid(real_gid); fd = open(name, flags, mode); xseteuid(eff_uid); xsetegid(eff_gid); return fd; #else if (eff_uid == real_uid) return open(name, flags, mode); /* Truncation/creation is done in UserContext */ if ((flags & O_TRUNC) || ((flags & O_CREAT) && access(name, F_OK))) { if (UserContext() > 0) { if ((fd = open(name, flags, mode)) >= 0) { close(fd); UserReturn(0); } if (errno == 0) errno = EACCES; UserReturn(errno); } if ((q = UserStatus())) { if (q > 0) errno = q; return -1; } } if (access(name, F_OK)) return -1; if ((fd = open(name, flags & ~(O_TRUNC | O_CREAT), 0)) < 0) return -1; debug("open successful\n"); if (fstat(fd, &stb)) { close(fd); return -1; } debug("fstat successful\n"); if (stb.st_uid != real_uid) { switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) { case O_RDONLY: q = 0004; break; case O_WRONLY: q = 0002; break; default: q = 0006; break; } if ((stb.st_mode & q) != q) { debug1("secopen: permission denied (%03o)\n", stb.st_mode & 07777); close(fd); errno = EACCES; return -1; } } debug1("secopen ok - returning %d\n", fd); return fd; #endif } int printpipe(struct win *p, char *cmd) { int pi[2]; if (pipe(pi)) { WMsg(p, errno, "printing pipe"); return -1; } switch (fork()) { case -1: WMsg(p, errno, "printing fork"); return -1; case 0: display = p->w_pdisplay; displays = 0; ServerSocket = -1; #ifdef DEBUG if (dfp && dfp != stderr) fclose(dfp); #endif close(0); dup(pi[0]); closeallfiles(0); if (setgid(real_gid) || setuid(real_uid)) Panic(errno, "printpipe setuid"); eff_uid = real_uid; eff_gid = real_gid; #ifdef SIGPIPE signal(SIGPIPE, SIG_DFL); #endif execl("/bin/sh", "sh", "-c", cmd, (char *)0); Panic(errno, "/bin/sh"); default: break; } close(pi[0]); return pi[1]; } int readpipe(char **cmdv) { int pi[2]; if (pipe(pi)) { Msg(errno, "pipe"); return -1; } switch (fork()) { case -1: Msg(errno, "fork"); return -1; case 0: displays = 0; ServerSocket = -1; #ifdef DEBUG if (dfp && dfp != stderr) fclose(dfp); #endif close(1); if (dup(pi[1]) != 1) { close(pi[1]); Panic(0, "dup"); } closeallfiles(1); if (setgid(real_gid) || setuid(real_uid)) { close(1); Panic(errno, "setuid/setgid"); } eff_uid = real_uid; eff_gid = real_gid; #ifdef SIGPIPE signal(SIGPIPE, SIG_DFL); #endif execvp(*cmdv, cmdv); close(1); Panic(errno, "%s", *cmdv); default: break; } close(pi[1]); return pi[0]; }