I am considering writing a BBS-like program in C and thinking about exactly how the I/O architecture would work with such a program. I'm familiar with sockets programming already, more specifically the master/remote model (not sure if there's a more official name for it) where a master process running as a daemon runs the vast majority of the application in a main process. When remote TTYs connect, they do so in a separate process that communicates with the main process via a Unix domain socket, and there's a thread on the main process for each remote TTY's I/O. All the modules and functionality are running in the main process.
This works well for things like CLIs for some kind of process, but I don't think it's as well suited for a significantly richer/more interactive program, where I think it'd make much more sense for all the TTYs to be managed in the same process rather than communicating over a socket. For example, you can't run ncurses over a socket, since the termios that we care about is in that remote process, not in our main process or usable over the socket. So taking the master/remote model further, you'd need to move a lot of logic from the main program to the remote processes.
The problem I'm a little stuck on is exactly how you can have the main process handling all the TTYs without itself handling all of the network socket traffic. For example, say we want to allow telnet and SSH connections. With the master/remote model, it might look like this:
Telnet:
- Inbound telnet connection
- Telnet server launches
/usr/sbin/remote_process(custom login shell) remote_process(a C program, shell script, etc.) begins executing, communicating withmain_process
SSH:
- Inbound SSH connection
- Authentication
- SSH server launches
/usr/sbin/remote_process(custom login shell) remote_process(a C program, shell script, etc.) begins executing, communicating withmain_process
Importantly, with the master/remote model we consider above, the telnet/SSH protocol is abstracted away from the program in question. It doesn't care if the incoming connection is from Telnet, SSH, a serial port, etc. We don't need to handle the details of these protocols ourselves.
Naively trying to apply this to the single-process model, handling all the TTYs directly, I would think the thing to do would be that step # 3/4 somehow needs to have the main process take over its terminal/PTY. main_process can't be called directly though, since it's already running, and I'm not sure if anything like that would be possible since somehow it would be moving the master/slave for the pty between processes, but the goal would be to have main_process doing everything remote_process was doing in the other model, directly handling the I/O from the Telnet server, SSH server, etc.
The standard way of doing this kind of thing seems to be having the main_process directly run its own listeners - that is, instead of listening for UNIX domain socket connections, directly accept Telnet/SSH traffic, etc. But then, the program is now responsible for handling the details of each individual protocol.
You can see an example of this with SyncrhonetBBS: https://github.com/SynchronetBBS/sbbs/tree/b35365c2e470bde58838cbb7445fe7e8c4bc1beb/src/syncterm
The BBS program itself has code to handle each supported protocol: SSH, TELNET, TELNETS, etc.
(I suppose there is a third model: have the main daemon process itself be quite minimal in what it does, and just have each individual TTY process contain the bulk of all the logic, and just use the daemon process for IPC between the TTYs... but then that gets tricky if you want to do stuff like dynamically loadable and unloadable modules that are really at a "system" level as opposed to per-TTY... so I'm not really considering this other extreme).
Is there any way to have the best of both worlds - be able to control all the different TTYs from a single process, but without having to directly implement protocol-specific handling? And if so, how does the TTY setup occur? I'm not looking for code examples here so much as a general high-level explanation/guidance of what this would likely look and how the different components - processes, sockets, TTYs - would interact.
remote_processcould create the PTY pair (posix_openpt(),grantpt(),unlockpt()) and pass the name of the slave (ptsname()) and the terminal type (from the SSH or TELNET protocol) over the socket tomain_process.main_processcan then open the slave and set upncursesfor the correct terminal type. Note:grantpt()will set the owner of the slave to the UID of theremote_processprocess, soremote_processandmain_processshould run as the same user. HandlingSIGWINCHmay be tricky. - Ian Abbottremote_process-- soremote_processjust needs to pass the pty slave it is connected to tomain_process, which can then open it (probabliy withO_NOCTTY) and deal with it directly. - Chris Doddremote_processfree to exit after passing that along to the main process (and let's say also receiving a confirmation from main that it's taken over), or does it need to keep running afterwards? As in, if remote exits at that point, the SSH session will keep going fine, even though that thing in the "middle" just disappeared? I'm not sure what else it would be doing at that point, if it had to stay up - InterLinkedremote_processexits, the sshd/telnetd server will shut down the connection (unless you modify them). It will also get any signals associated with the pty (such as SIGWINCH), which it should somehow tellmain_processabout. Could just forward them, butmain_processcan't then easily tell which pty the signal is associated with. - Chris Dodd