/* @(#)rmt.c 1.22 03/02/02 Copyright 1994,2000-2002 J. Schilling */ #ifndef lint static char sccsid[] = "@(#)rmt.c 1.22 03/02/02 Copyright 1994,2000-2002 J. Schilling"; #endif /* * Remote tape server * Supports both the old BSD format and the new abstract Sun format * which is called RMT V1 protocol. * * A client that likes to use the enhanced features of the RMT V1 protocol * needs to send a "I-1\n0\n" request directly after opening the remote * file using the 'O' rmt command. * If the client requests the new protocol, MTIOCTOP ioctl opcodes * in the range 0..7 are mapped to the BSD values to prevent problems * from Linux opcode incompatibility. * * The open modes support an abstract string notation found in rmt.c from * GNU. This makes it possible to use more than O_RDONLY|O_WRONLY|O_RDWR * with open(). MTIOCTOP tape ops could be enhanced the same way, but it * seems that the current interface supports all what we need over the * wire. * * Copyright (c) 1994,2000-2002 J. Schilling */ /* * 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 2, 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, write to the Free Software * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*#define FORCE_DEBUG*/ #include #include #include #include /* includes sys/types.h */ #include #include #include #include #ifdef HAVE_SYS_PARAM_H #include /* BSD-4.2 & Linux need this for MAXHOSTNAMELEN */ #endif #include #ifdef HAVE_SYS_MTIO_H #include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_ARPA_INET_H #include /* BeOS does not have */ #endif /* but inet_ntaoa() is in */ #include EXPORT int main __PR((int argc, char **argv)); LOCAL void checkuser __PR((void)); LOCAL char *getpeer __PR((void)); LOCAL BOOL checkaccess __PR((char *device)); LOCAL BOOL strmatch __PR((char *str, char *pat)); LOCAL void dormt __PR((void)); LOCAL void opentape __PR((void)); LOCAL int rmtoflags __PR((char *omode)); LOCAL void closetape __PR((void)); LOCAL void readtape __PR((void)); LOCAL void writetape __PR((void)); LOCAL void ioctape __PR((int cmd)); #ifdef HAVE_SYS_MTIO_H LOCAL int rmtmapold __PR((int cmd)); LOCAL int rmtmapnew __PR((int cmd)); #endif LOCAL void statustape __PR((int cmd)); LOCAL void seektape __PR((void)); LOCAL void doversion __PR((void)); LOCAL int fillrdbuf __PR((void)); LOCAL int readchar __PR((char *cp)); LOCAL void readbuf __PR((char *buf, int n)); LOCAL void readarg __PR((char *buf, int n)); LOCAL char * preparebuffer __PR((int size)); LOCAL int checktape __PR((char *device)); LOCAL void rmtrespond __PR((long ret, int err)); LOCAL void rmterror __PR((char *str)); #define CMD_SIZE 80 LOCAL char *username; LOCAL char *peername; LOCAL int tape_fd = -1; LOCAL char *debug_name; LOCAL FILE *debug_file; LOCAL BOOL found_dfltfile; #define DEBUG(fmt) if (debug_file) js_fprintf(debug_file, fmt) #define DEBUG1(fmt,a) if (debug_file) js_fprintf(debug_file, fmt, a) #define DEBUG2(fmt,a1,a2) if (debug_file) js_fprintf(debug_file, fmt, a1, a2) #define DEBUG3(fmt,a1,a2,a3) if (debug_file) js_fprintf(debug_file, fmt, a1, a2, a3) #define DEBUG4(fmt,a1,a2,a3,a4) if (debug_file) js_fprintf(debug_file, fmt, a1, a2, a3, a4) EXPORT int main(argc, argv) int argc; char **argv; { save_args(argc, argv); argc--, argv++; if (argc > 0 && strcmp(*argv, "-c") == 0) { /* * Skip params in case we have been installed/called as shell. */ argc--, argv++; argc--, argv++; } /* * If we are running as root (uid 0), the existence of /usr/local/etc/srmt.conf * is required. If our uid is != 0 and there is no /usr/local/etc/srmt.conf * we will only allow to access files in /dev (see below). * * WARNING you are only allowed to change the defaults configuration * filename if you also change the documentation and add a statement * that makes clear where the official location of the file is, why you * did choose a nonstandard location and that the nonstandard location * only refers to inofficial rmt versions. * * I was forced to add this because some people change cdrecord without * rational reason and then publish the result. As those people * don't contribute work and don't give support, they are causing extra * work for me and this way slow down the development. */ if (defltopen("/usr/local/etc/srmt.conf") < 0) { if (geteuid() == 0) { rmterror("Remote configuration error: Cannot open /usr/local/etc/srmt.conf"); exit(EX_BAD); } } else { found_dfltfile = TRUE; } debug_name = defltread("DEBUG="); /* Get debug file name */ #ifdef FORCE_DEBUG if (debug_name == NULL && argc <= 0) debug_name = "/tmp/RMT"; #endif if (argc > 0) debug_name = *argv; if (debug_name != NULL) debug_file = fopen(debug_name, "w"); if (argc > 0) { if (debug_file == 0) { rmtrespond((long)-1, geterrno()); exit(EX_BAD); } (void) setbuf(debug_file, (char *)0); } checkuser(); /* Check if we are called by a bad guy */ peername = getpeer(); /* Get host name of caller */ dormt(); return (0); } LOCAL void checkuser() { uid_t uid = getuid(); char *uname; struct passwd *pw; pw = getpwuid(uid); if (pw == NULL) goto notfound; username = pw->pw_name; /* * If no /usr/local/etc/srmt.conf could be found allow general access. */ if (!found_dfltfile) return; defltfirst(); while ((uname = defltnext("USER=")) != NULL) { if (strmatch(username, uname)) return; } notfound: rmterror("Illegal user id for RMT server"); exit(EX_BAD); } #ifndef NI_MAXHOST #ifdef MAXHOSTNAMELEN /* XXX remove this and sys/param.h */ #define NI_MAXHOST MAXHOSTNAMELEN #else #define NI_MAXHOST 64 #endif #endif LOCAL char * getpeer() { #ifdef HAVE_GETNAMEINFO struct sockaddr_storage sa; #else struct sockaddr sa; struct hostent *he; #endif struct sockaddr *sap; struct sockaddr_in *s; socklen_t sasize = sizeof (sa); static char buffer[NI_MAXHOST]; sap = (struct sockaddr *)&sa; if (getpeername(STDIN_FILENO, sap, &sasize) < 0) { int errsav = geterrno(); struct stat sb; if (fstat(STDIN_FILENO, &sb) >= 0) { if (S_ISFIFO(sb.st_mode)) { DEBUG("rmt: stdin is a PIPE\n"); return ("PIPE"); } DEBUG1("rmt: stdin st_mode %0llo\n", (Llong)sb.st_mode); } DEBUG1("rmt: peername %s\n", errmsgstr(errsav)); return ("ILLEGAL_SOCKET"); } else { s = (struct sockaddr_in *)&sa; #ifdef AF_INET6 if (s->sin_family != AF_INET && s->sin_family != AF_INET6) { #else if (s->sin_family != AF_INET) { #endif #ifdef AF_UNIX /* * AF_UNIX is not defined on BeOS */ if (s->sin_family == AF_UNIX) { DEBUG("rmt: stdin is a PIPE (UNIX domain socket)\n"); return ("PIPE"); } #endif DEBUG1("rmt: stdin NOT_IP socket (sin_family: %d)\n", s->sin_family); return ("NOT_IP"); } #ifdef HAVE_GETNAMEINFO buffer[0] = '\0'; if (debug_file && getnameinfo(sap, sasize, buffer, sizeof (buffer), NULL, 0, NI_NUMERICHOST) == 0) { DEBUG1("rmt: peername %s\n", buffer); } buffer[0] = '\0'; if (getnameinfo(sap, sasize, buffer, sizeof (buffer), NULL, 0, 0) == 0) { DEBUG1("rmt: peername %s\n", buffer); return (buffer); } return ("CANNOT_MAP_ADDRESS"); #else /* HAVE_GETNAMEINFO */ #ifdef HAVE_INET_NTOA (void) js_snprintf(buffer, sizeof (buffer), "%s", inet_ntoa(s->sin_addr)); #else (void) js_snprintf(buffer, sizeof (buffer), "%x", s->sin_addr.s_addr); #endif DEBUG1("rmt: peername %s\n", buffer); he = gethostbyaddr((char *)&s->sin_addr.s_addr, 4, AF_INET); DEBUG1("rmt: peername %s\n", he != NULL ? he->h_name:buffer); if (he != NULL) return (he->h_name); return (buffer); #endif /* HAVE_GETNAMEINFO */ } } LOCAL BOOL checkaccess(device) char *device; { char *target; char *user; char *host; char *fname; char *p; if (peername == NULL) return (FALSE); defltfirst(); while ((target = defltnext("ACCESS=")) != NULL) { p = target; while (*p == '\t') p++; user = p; if ((p = strchr(p, '\t')) != NULL) *p++ = '\0'; else continue; if (!strmatch(username, user)) continue; while (*p == '\t') p++; host = p; if ((p = strchr(p, '\t')) != NULL) *p++ = '\0'; else continue; if (!strmatch(peername, host)) continue; fname = p; if ((p = strchr(p, '\t')) != NULL) *p++ = '\0'; DEBUG3("ACCESS %s %s %s\n", user, host, fname); if (!strmatch(device, fname)) continue; return (TRUE); } return (FALSE); } LOCAL BOOL strmatch(str, pat) char *str; char *pat; { int *aux; int *state; int alt; int plen; char *p; plen = strlen(pat); aux = malloc(plen*sizeof (int)); state = malloc((plen+1)*sizeof (int)); if (aux == NULL || state == NULL) { if (aux) free(aux); if (state) free(state); return (FALSE); } if ((alt = patcompile((const unsigned char *)pat, plen, aux)) == 0) { /* Bad pattern */ free(aux); free(state); return (FALSE); } p = (char *)patmatch((const unsigned char *)pat, aux, (const unsigned char *)str, 0, strlen(str), alt, state); free(aux); free(state); if (p != NULL && *p == '\0') return (TRUE); return (FALSE); } LOCAL void dormt() { char c; while (readchar(&c) == 1) { seterrno(0); switch (c) { case 'O': opentape(); break; case 'C': closetape(); break; case 'R': readtape(); break; case 'W': writetape(); break; case 'I': case 'i': ioctape(c); break; case 'S': case 's': statustape(c); break; case 'L': seektape(); break; /* * It would be nice to have something like 'V' for retrieving * Version information. But unfortunately newer BSD rmt version * implement this command in a way that is not useful at all. */ case 'v': doversion(); break; default: DEBUG1("rmtd: garbage command %c\n", c); rmterror("Garbage command"); exit(EX_BAD); } } exit(0); } LOCAL void opentape() { char device[CMD_SIZE]; char omode[CMD_SIZE]; int omodes; if (tape_fd >= 0) (void) close(tape_fd); readarg(device, sizeof (device)); readarg(omode, sizeof (omode)); omodes = rmtoflags(omode); if (omodes == -1) { /* * Mask off all bits that differ between operating systems. */ omodes = atoi(omode); omodes &= (O_RDONLY|O_WRONLY|O_RDWR); } #ifdef O_TEXT /* * Default to O_BINARY the client may not know that we need it. */ if ((omodes & O_TEXT) == 0) omodes |= O_BINARY; #endif DEBUG2("rmtd: O %s %s\n", device, omode); if (!checktape(device)) { tape_fd = -1; seterrno(EACCES); } else { tape_fd = open(device, omodes, 0666); } rmtrespond((long)tape_fd, geterrno()); } LOCAL struct oflags { char *fname; int fval; } oflags[] = { { "O_RDONLY", O_RDONLY }, { "O_RDWR", O_RDWR }, { "O_WRONLY", O_WRONLY }, #ifdef O_TEXT { "O_TEXT", O_TEXT }, #endif #ifdef O_NDELAY { "O_NDELAY", O_NDELAY }, #endif #ifdef O_APPEND { "O_APPEND", O_APPEND }, #endif #ifdef O_SYNC { "O_SYNC", O_SYNC }, #endif #ifdef O_DSYNC { "O_DSYNC", O_DSYNC }, #endif #ifdef O_RSYNC { "O_RSYNC", O_RSYNC }, #endif #ifdef O_NONBLOCK { "O_NONBLOCK", O_NONBLOCK }, #endif #ifdef O_PRIV { "O_PRIV", O_PRIV }, #endif #ifdef O_LARGEFILE { "O_LARGEFILE",O_LARGEFILE }, #endif #ifdef O_CREAT { "O_CREAT", O_CREAT }, #endif #ifdef O_TRUNC { "O_TRUNC", O_TRUNC }, #endif #ifdef O_EXCL { "O_EXCL", O_EXCL }, #endif #ifdef O_NOCTTY { "O_NOCTTY", O_NOCTTY }, #endif { NULL, 0 } }; LOCAL int rmtoflags(omode) char *omode; { register char *p = omode; register struct oflags *op; register int slen; register int nmodes = 0; /* * First skip numeric open modes... */ while (*p != '\0' && *p == ' ') p++; if (*p != 'O') while (*p != '\0' && *p != ' ') p++; while (*p != '\0' && *p != 'O') p++; do { if (p[0] != 'O' || p[1] != '_') return (-1); for (op = oflags; op->fname; op++) { slen = strlen(op->fname); if ((strncmp(op->fname, p, slen) == 0) && (p[slen] == '|' || p[slen] == ' ' || p[slen] == '\0')) { nmodes |= op->fval; break; } } p = strchr(p, '|'); } while (p && *p++ == '|'); return (nmodes); } LOCAL void closetape() { int ret; char device[CMD_SIZE]; DEBUG("rmtd: C\n"); readarg(device, sizeof (device)); ret = close(tape_fd); rmtrespond((long)ret, geterrno()); tape_fd = -1; } LOCAL void readtape() { int n; long ret; char *buf; char count[CMD_SIZE]; readarg(count, sizeof (count)); DEBUG1("rmtd: R %s\n", count); n = atoi(count); /* Only an int because of setsockopt */ buf = preparebuffer(n); ret = _niread(tape_fd, buf, n); rmtrespond(ret, geterrno()); if (ret >= 0) { (void) _nixwrite(STDOUT_FILENO, buf, ret); } } LOCAL void writetape() { int n; long ret; char *buf; char count[CMD_SIZE]; readarg(count, sizeof (count)); n = atoi(count); /* Only an int because of setsockopt */ DEBUG1("rmtd: W %s\n", count); buf = preparebuffer(n); readbuf(buf, n); ret = _niwrite(tape_fd, buf, n); rmtrespond(ret, geterrno()); } /* * Definitions for the new RMT Protocol version 1 * * The new Protocol version tries to make the use * of rmtioctl() more portable between different platforms. */ #define RMTIVERSION -1 #define RMT_VERSION 1 /* * Support for commands beyond MTWEOF..MTNOP (0..7) */ #define RMTICACHE 0 #define RMTINOCACHE 1 #define RMTIRETEN 2 #define RMTIERASE 3 #define RMTIEOM 4 #define RMTINBSF 5 #ifndef HAVE_SYS_MTIO_H LOCAL void ioctape(cmd) int cmd; { char count[CMD_SIZE]; char opcode[CMD_SIZE]; readarg(opcode, sizeof (opcode)); readarg(count, sizeof (count)); DEBUG3("rmtd: %c %s %s\n", cmd, opcode, count); if (atoi(opcode) == RMTIVERSION) { rmtrespond((long)RMT_VERSION, 0); } else { rmtrespond((long)-1, ENOTTY); } } #else LOCAL void ioctape(cmd) int cmd; { long ret = 0; int i; char count[CMD_SIZE]; char opcode[CMD_SIZE]; struct mtop mtop; static BOOL version_seen = FALSE; readarg(opcode, sizeof (opcode)); readarg(count, sizeof (count)); DEBUG3("rmtd: %c %s %s\n", cmd, opcode, count); mtop.mt_op = atoi(opcode); ret = atol(count); mtop.mt_count = ret; if (mtop.mt_count != ret) { rmtrespond((long)-1, EINVAL); return; } /* * Only Opcodes 0..7 are unique across different architectures. * But as in many cases Linux does not even follow this rule. * If we know that we have been called by a VERSION 1 client, * we may safely assume that the client is not using Linux mapping * but the standard mapping. */ ret = 0; if (cmd == 'I' && version_seen && (mtop.mt_op != RMTIVERSION)) { i = rmtmapold(mtop.mt_op); if (i < 0) { /* * Should we rather give it a chance instead * of aborting the command? */ rmtrespond((long)-1, EINVAL); return; } mtop.mt_op = i; } if (cmd == 'i') { i = rmtmapnew(mtop.mt_op); if (i < 0) { ret = -1; seterrno(EINVAL); } else { mtop.mt_op = i; } } DEBUG4("rmtd: %c %d %ld ret: %ld (mapped)\n", cmd, mtop.mt_op, (long)mtop.mt_count, ret); if (ret == 0) { if (mtop.mt_op == RMTIVERSION) { /* * Client must retrieve RMTIVERSION directly after * opening the drive using the 'O' rmt command. */ ret = mtop.mt_count = RMT_VERSION; version_seen = TRUE; } else { ret = ioctl(tape_fd, MTIOCTOP, (char *)&mtop); } } if (ret < 0) { rmtrespond(ret, geterrno()); } else { ret = mtop.mt_count; rmtrespond(ret, geterrno()); } } /* * Map all old /etc/rmt (over the wire) opcodes that should be in range 0..7 * to numbers that are understood by the local driver. * This is needed because Linux does not follow the UNIX conventions. */ LOCAL int rmtmapold(cmd) int cmd; { switch (cmd) { case 0: #ifdef MTWEOF return (MTWEOF); #else return (-1); #endif case 1: #ifdef MTFSF return (MTFSF); #else return (-1); #endif case 2: #ifdef MTBSF return (MTBSF); #else return (-1); #endif case 3: #ifdef MTFSR return (MTFSR); #else return (-1); #endif case 4: #ifdef MTBSR return (MTBSR); #else return (-1); #endif case 5: #ifdef MTREW return (MTREW); #else return (-1); #endif case 6: #ifdef MTOFFL return (MTOFFL); #else return (-1); #endif case 7: #ifdef MTNOP return (MTNOP); #else return (-1); #endif } return (-1); } /* * Map all new /etc/rmt (over the wire) opcodes from 'i' command * to numbers that are understood by the local driver. */ LOCAL int rmtmapnew(cmd) int cmd; { switch (cmd) { #ifdef MTCACHE case RMTICACHE: return (MTCACHE); #endif #ifdef MTNOCACHE case RMTINOCACHE: return (MTNOCACHE); #endif #ifdef MTRETEN case RMTIRETEN: return (MTRETEN); #endif #ifdef MTERASE case RMTIERASE: return (MTERASE); #endif #ifdef MTEOM case RMTIEOM: return (MTEOM); #endif #ifdef MTNBSF case RMTINBSF: return (MTNBSF); #endif } return (-1); } #endif /* * Old MTIOCGET copies a binary version of struct mtget back * over the wire. This is highly non portable. * MTS_* retrieves ascii versions (%d format) of a single * field in the struct mtget. * NOTE: MTS_ERREG may only be valid on the first call and * must be retrived first. */ #define MTS_TYPE 'T' /* mtget.mt_type */ #define MTS_DSREG 'D' /* mtget.mt_dsreg */ #define MTS_ERREG 'E' /* mtget.mt_erreg */ #define MTS_RESID 'R' /* mtget.mt_resid */ #define MTS_FILENO 'F' /* mtget.mt_fileno */ #define MTS_BLKNO 'B' /* mtget.mt_blkno */ #define MTS_FLAGS 'f' /* mtget.mt_flags */ #define MTS_BF 'b' /* mtget.mt_bf */ #ifndef HAVE_SYS_MTIO_H LOCAL void statustape(cmd) int cmd; { char subcmd; if (cmd == 's') { if (readchar(&subcmd) != 1) return; DEBUG2("rmtd: %c%c\n", cmd, subcmd); } else { DEBUG1("rmtd: %c\n", cmd); } rmtrespond((long)-1, ENOTTY); } #else LOCAL void statustape(cmd) int cmd; { int ret; char subcmd; struct mtget mtget; /* * Only the first three fields of the struct mtget (mt_type, mt_dsreg * and mt_erreg) are identical on all platforms. The original struct * mtget is 16 bytes. All client implementations except the one from * star will overwrite other data and probably die if the remote struct * mtget is bigger than the local one. * In addition, there are byte order problems. */ if (cmd == 's') { if (readchar(&subcmd) != 1) return; DEBUG2("rmtd: %c%c\n", cmd, subcmd); } else { DEBUG1("rmtd: %c\n", cmd); } ret = ioctl(tape_fd, MTIOCGET, (char *)&mtget); if (ret < 0) { rmtrespond((long)ret, geterrno()); } else { if (cmd == 's') switch (subcmd) { #ifdef HAVE_MTGET_TYPE case MTS_TYPE: rmtrespond(mtget.mt_type, geterrno()); break; #endif #ifdef HAVE_MTGET_DSREG case MTS_DSREG: rmtrespond(mtget.mt_dsreg, geterrno()); break; #endif #ifdef HAVE_MTGET_ERREG case MTS_ERREG: rmtrespond(mtget.mt_erreg, geterrno()); break; #endif #ifdef HAVE_MTGET_RESID case MTS_RESID: rmtrespond(mtget.mt_resid, geterrno()); break; #endif #ifdef HAVE_MTGET_FILENO case MTS_FILENO: rmtrespond(mtget.mt_fileno, geterrno());break; #endif #ifdef HAVE_MTGET_BLKNO case MTS_BLKNO: rmtrespond(mtget.mt_blkno, geterrno()); break; #endif #ifdef HAVE_MTGET_FLAGS case MTS_FLAGS: rmtrespond(mtget.mt_flags, geterrno()); break; #endif #ifdef HAVE_MTGET_BF case MTS_BF: rmtrespond(mtget.mt_bf, geterrno()); break; #endif default: rmtrespond((long)-1, EINVAL); break; } else { /* * Do not expect that this interface makes any sense. * With UNIX, you may at least trust the first two * struct members, but Linux is completely incompatible */ ret = sizeof (mtget); rmtrespond((long)ret, geterrno()); (void) _nixwrite(STDOUT_FILENO, (char *)&mtget, sizeof (mtget)); } } } #endif LOCAL void seektape() { off_t ret; char count[CMD_SIZE]; char whence[CMD_SIZE]; Llong offset = (Llong)0; int iwhence; readarg(count, sizeof (count)); readarg(whence, sizeof (whence)); DEBUG2("rmtd: L %s %s\n", count, whence); (void) astoll(count, &offset); iwhence = atoi(whence); switch (iwhence) { case 0: iwhence = SEEK_SET; break; case 1: iwhence = SEEK_CUR; break; case 2: iwhence = SEEK_END; break; default: DEBUG1("rmtd: Illegal lseek() whence %d\n", iwhence); rmtrespond((long)-1, EINVAL); return; } ret = (off_t)offset; if (ret != offset) { DEBUG1("rmtd: Illegal seek offset %lld\n", offset); rmtrespond((long)-1, EINVAL); return; } ret = lseek(tape_fd, (off_t)offset, iwhence); if ((ret != (off_t)-1) && (sizeof (ret) > sizeof (long))) { DEBUG1("rmtd: A %lld\n", (Llong)ret); (void) js_snprintf(count, sizeof (count), "A%lld\n", (Llong)ret); (void) _nixwrite(STDOUT_FILENO, count, strlen(count)); return; } rmtrespond((long)ret, geterrno()); } LOCAL void doversion() { char arg[CMD_SIZE]; readarg(arg, sizeof (arg)); /* We may like to add an arg later */ DEBUG1("rmtd: v %s\n", arg); rmtrespond((long)RMT_VERSION, 0); } #define READB_SIZE 128 LOCAL char readb[READB_SIZE]; LOCAL char *readbptr; LOCAL int readbcnt; LOCAL int fillrdbuf() { readbptr = readb; return (readbcnt = _niread(STDIN_FILENO, readb, READB_SIZE)); } LOCAL int readchar(cp) char *cp; { if (--readbcnt < 0) { if (fillrdbuf() <= 0) return (readbcnt); --readbcnt; } *cp = *readbptr++; return (1); } LOCAL void readbuf(buf, n) register char *buf; register int n; { register int i = 0; register int amt; if (readbcnt > 0) { amt = readbcnt; if (amt > n) amt = n; movebytes(readbptr, buf, amt); readbptr += amt; readbcnt -= amt; i += amt; } for (; i < n; i += amt) { amt = _niread(STDIN_FILENO, &buf[i], n - i); if (amt <= 0) { DEBUG("rmtd: premature eof\n"); rmterror("Premature eof"); exit(EX_BAD); } } } LOCAL void readarg(buf, n) char *buf; int n; { int i; for (i = 0; i < n; i++) { if (readchar(&buf[i]) != 1) exit(0); if (buf[i] == '\n') break; } buf[i] = '\0'; } LOCAL char * preparebuffer(size) int size; { static char *buffer = 0; static int buffersize = 0; if (buffer != 0 && size <= buffersize) return (buffer); if (buffer != 0) free(buffer); buffer = malloc(size); if (buffer == 0) { DEBUG("rmtd: cannot allocate buffer space\n"); rmterror("Cannot allocate buffer space"); exit(EX_BAD); } buffersize = size; #ifdef SO_SNDBUF while (size > 512 && setsockopt(STDOUT_FILENO, SOL_SOCKET, SO_SNDBUF, (char *)&size, sizeof (size)) < 0) size -= 512; DEBUG1("rmtd: sndsize: %d\n", size); #endif #ifdef SO_RCVBUF while (size > 512 && setsockopt(STDIN_FILENO, SOL_SOCKET, SO_RCVBUF, (char *)&size, sizeof (size)) < 0) size -= 512; DEBUG1("rmtd: rcvsize: %d\n", size); #endif return (buffer); } /* * If we are not root and there is no /usr/local/etc/srmt.conf * we will only allow to access files in /dev. * We do this because we may assume that non-root access to files * in /dev is only granted if it does not open security holes. * Accessing files (e.g. /etc/passwd) is not possible. * Otherwise permissions depend on the content of /usr/local/etc/srmt.conf. */ LOCAL int checktape(device) char *device; { if (!found_dfltfile) { if (strncmp(device, "/dev/", 5) == 0) return (1); return (0); } return (checkaccess(device)); } LOCAL void rmtrespond(ret, err) long ret; int err; { char rbuf[2*CMD_SIZE]; if (ret >= 0) { DEBUG1("rmtd: A %ld\n", ret); (void) js_snprintf(rbuf, sizeof (rbuf), "A%ld\n", ret); } else { DEBUG2("rmtd: E %d (%s)\n", err, errmsgstr(err)); (void) js_snprintf(rbuf, sizeof (rbuf), "E%d\n%s\n", err, errmsgstr(err)); } (void) _nixwrite(STDOUT_FILENO, rbuf, strlen(rbuf)); } LOCAL void rmterror(str) char *str; { char rbuf[2*CMD_SIZE]; DEBUG1("rmtd: E 0 (%s)\n", str); (void) js_snprintf(rbuf, sizeof (rbuf), "E0\n%s\n", str); (void) _nixwrite(STDOUT_FILENO, rbuf, strlen(rbuf)); }