/* * consoled - console daemon * * This is a daemon which grabs all console output and sends it to all * its clients. When there are no clients, it releases the console; * when a new connection is accepted after having no clients, it * re-takes the console. * * By default, it listens on an IPv4 socket bound to 127.0.0.1 and * nothing else. Optionally, it can listen on additional IPv4 * addresses, not listen on the default address, and use one or more * AF_LOCAL sockets. For IP sockets, the port number is, by default, * DEFAULT_PORT as defined below. This can be overridden on the * command line. (If DEFAULT_PORT is not defined, it does not listen * anywhere by default and will not run without a listening point * being specified. It will also then require a port number for IP * sockets.) * * If too many clients try to connect, the extra ones will see their * connections being unceremoniously dropped. This is unlikely to * occur in practice. * * There is also a mode in which everything looks normal to network * clients, but no console output is ever seen. In this mode, the * console is not taken and clients never get anything. * * By default, backgrounds itself once startup is complete. This can * be suppressed. * * Options: * * -fg Don't background at startup. * * -bg Background at startup. * * -dummy Don't take the console and don't send clients anything. * * -real Take the console and send clients output. * * -nodefault * Don't listen on the default place(s). * * -ip address[:port] * Listen on the IPv4 address/port specified. The :port * is optional if DEFAULT_PORT is defined below. * * -local path * Listen on the AF_LOCAL socket named by path. * * If conflicting options, such as -fg and -bg, are given, whichever * occurs last on the command line wins. * * This file is in the public domain. */ /* Default port number for IP sockets. */ #define DEFAULT_PORT 6 /* Max amount of data we're willing to buffer. */ #define MAXBUF 65536 /* String sent when data lost from buffer overflow. */ const char * const overflow_msg = "\n**** Data lost, queue overflow\n"; /* End of configuration. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; typedef struct client CLIENT; typedef struct chunk CHUNK; typedef struct listen LISTEN; struct listen { LISTEN *link; unsigned int type; #define LT_IPV4 1 #define LT_LOCAL 2 int fd; int pfx; void *addr; const char *str; } ; struct client { CLIENT *link; int fd; int pfx; CHUNK *q; CHUNK **qt; int qlen; } ; struct chunk { CHUNK *link; int len; int ptr; char *data; } ; static LISTEN *listens = 0; static int nlisten; static CLIENT *clients = 0; static int nclients = 0; static int havecons; static int conspty; static int conspfxp; static int conssfd; static int npfds = 0; static struct pollfd *pfds = 0; static int Off = 0; #define off (&Off) static int On = 1; #define on (&On) static int no_daemonize = 0; static int dummy_mode = 0; static int no_default = 0; static void add_listen_ipv4_1(struct sockaddr_in *sin, const char *str) { LISTEN *l; l = malloc(sizeof(LISTEN)); l->type = LT_IPV4; l->fd = -1; l->addr = malloc(sizeof(*sin)); *(struct sockaddr_in *)l->addr = *sin; l->str = str; l->link = listens; listens = l; } static void add_listen_ipv4(const char *s) { const char *colon; struct sockaddr_in sin; int i; long int p; int n; char *e; bzero(&sin,sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; colon = index(s,':'); if (colon) { n = colon - s; p = strtol(colon+1,&e,0); if (*e || (e == colon+1) || (p < 0) || (p > 65535)) { fprintf(stderr,"%s: %s: invalid port number\n",__progname,colon+1); exit(1); } } else { #ifdef DEFAULT_PORT n = strlen(s); p = DEFAULT_PORT; #else fprintf(stderr,"%s: %s: need a port number\n",__progname,s); exit(1); #endif } sin.sin_port = htons(p); { char buf[n+1]; bcopy(s,&buf[0],n); buf[n] = '\0'; if (inet_aton(&buf[0],&sin.sin_addr)) { add_listen_ipv4_1(&sin,strdup(&buf[0])); } else { struct hostent *h; h = gethostbyname(&buf[0]); if (! h) { fprintf(stderr,"%s: %s: unknown host\n",__progname,&buf[0]); exit(1); } if (h->h_addrtype != AF_INET) { fprintf(stderr,"%s: %s: non-IPv4 host\n",__progname,&buf[0]); exit(1); } if (h->h_length != sizeof(struct in_addr)) { fprintf(stderr,"%s: %s: address length wrong\n",__progname,&buf[0]); exit(1); } for (i=0;h->h_addr_list[i];i++) { char *t; bcopy(h->h_addr_list[i],&sin.sin_addr,sizeof(struct in_addr)); asprintf(&t,"%s [%s]",&buf[0],inet_ntoa(sin.sin_addr)); add_listen_ipv4_1(&sin,t); } if (i == 0) { fprintf(stderr,"%s: %s: no addresses\n",__progname,&buf[0]); exit(1); } } } } static void add_listen_local(const char *s) { int len; int nb; LISTEN *l; struct sockaddr_un *s_un; len = strlen(s); nb = sizeof(struct sockaddr_un) - sizeof(s_un->sun_path) + len + 1; s_un = malloc(nb); bzero(s_un,nb); s_un->sun_len = nb; s_un->sun_family = AF_LOCAL; bcopy(s,&s_un->sun_path[0],len); l = malloc(sizeof(LISTEN)); l->type = LT_LOCAL; l->fd = -1; l->addr = s_un; l->str = s; l->link = listens; listens = l; } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-fg")) { no_daemonize = 1; continue; } if (!strcmp(*av,"-bg")) { no_daemonize = 0; continue; } if (!strcmp(*av,"-dummy")) { dummy_mode = 1; continue; } if (!strcmp(*av,"-real")) { dummy_mode = 0; continue; } if (!strcmp(*av,"-nodefault")) { no_default = 1; continue; } if (!strcmp(*av,"-ip")) { WANTARG(); add_listen_ipv4(av[skip]); continue; } if (!strcmp(*av,"-local")) { WANTARG(); add_listen_local(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void open_listeners(void) { LISTEN *l; int s; int af; nlisten = 0; for (l=listens;l;l=l->link) { nlisten ++; switch (l->type) { case LT_IPV4: af = AF_INET; break; case LT_LOCAL: af = AF_LOCAL; break; default: abort(); break; } s = socket(af,SOCK_STREAM,0); if (s < 0) { fprintf(stderr,"%s: %s: socket: %s\n",__progname,l->str,strerror(errno)); exit(1); } if (bind(s,l->addr,((struct sockaddr *)l->addr)->sa_len) < 0) { fprintf(stderr,"%s: %s: bind: %s\n",__progname,l->str,strerror(errno)); exit(1); } if (listen(s,10) < 0) { fprintf(stderr,"%s: %s: listen: %s\n",__progname,l->str,strerror(errno)); exit(1); } l->fd = s; } } static void become_console(int fd) { #ifdef TIOCCONSOLE { int cfd; cfd = open("/dev/console",O_RDWR,0); if (cfd >= 0) { if (ioctl(fd,TIOCCONSOLE,&cfd) == 0) { close(cfd); return; } close(cfd); } } #endif #ifdef TIOCCONS ioctl(fd,TIOCCONS,on); #endif } static void take_console(void) { int i; int mfd; int sfd; int cfd; pid_t pg; char ptyname[64]; struct termios tio; if (! dummy_mode) { for (i=0;;i++) { sprintf(&ptyname[0],"/dev/pty%c%x",'p'+(i/16),i%16); mfd = open(&ptyname[0],O_RDWR|O_NOCTTY,0); if (mfd < 0) { if (errno == ENOENT) { syslog(LOG_ERR,"all pseudo-ttys in use"); exit(1); } continue; } ptyname[5] = 't'; /* sprintf(&ptyname[0],"/dev/tty%c%x",...); */ sfd = open(&ptyname[0],O_RDWR|O_NOCTTY,0); if (sfd < 0) { close(mfd); continue; } ioctl(mfd,TIOCREMOTE,off); ioctl(mfd,TIOCPKT,off); ioctl(mfd,FIONBIO,on); ioctl(mfd,FIOASYNC,off); cfd = open("/dev/console",O_RDWR|O_NOCTTY,0); if (cfd < 0) { syslog(LOG_ERR,"can't open /dev/console: %s",strerror(errno)); exit(1); } ioctl(sfd,FIONBIO,off); ioctl(sfd,FIOASYNC,off); ioctl(cfd,TIOCGPGRP,&pg); ioctl(sfd,TIOCSPGRP,&pg); tcgetattr(cfd,&tio); tcsetattr(sfd,TCSANOW,&tio); become_console(sfd); conspty = mfd; conssfd = sfd; break; } } havecons = 1; } static void drop_console(void) { if (conspty >= 0) close(conspty); conspty = -1; if (conssfd >= 0) close(conssfd); conssfd = -1; havecons = 0; } static void q_append(CLIENT *c, const void *data, int len) { CHUNK *ch; ch = malloc(sizeof(CHUNK)); ch->len = len; ch->ptr = 0; ch->data = malloc(len); bcopy(data,ch->data,len); ch->link = 0; *c->qt = ch; c->qt = &ch->link; c->qlen += len; } static void q_prepend(CLIENT *c, const void *data, int len) { CHUNK *ch; ch = malloc(sizeof(CHUNK)); ch->len = len; ch->ptr = 0; ch->data = malloc(len); bcopy(data,ch->data,len); if (c->q == 0) c->qt = &ch->link; ch->link = c->q; c->q = ch; c->qlen += len; } static void q_drop(CLIENT *c, int n) { int m; CHUNK *ch; while (n > 0) { ch = c->q; if (! ch) abort(); m = ch->len - ch->ptr; if (m > n) { ch->ptr += n; break; } c->q = ch->link; if (! c->q) c->qt = &c->q; free(ch->data); free(ch); n -= m; } } static void console_output(const char *s, int len) { CLIENT *c; int n; const char *qs; int qlen; if (len < 0) len = strlen(s); for (c=clients;c;c=c->link) { if (c->q) { qs = s; qlen = len; } else { n = write(c->fd,s,len); if (n == len) continue; qs = s + n; qlen = len - n; } q_append(c,qs,qlen); n = c->qlen - MAXBUF; if (n > 0) { n += strlen(overflow_msg); q_drop(c,n); q_prepend(c,overflow_msg,-1); } } } static void conspty_read(void) { int n; char buf[512]; n = read(conspty,&buf[0],sizeof(buf)); if (n < 0) { if ((errno == EWOULDBLOCK) || (errno == EINTR)) return; console_output("\n**** Console pty read error: ",-1); console_output(strerror(errno),-1); console_output("\n",1); drop_console(); take_console(); return; } if (n == 0) { console_output("\n**** Console pty read EOF\n",-1); drop_console(); take_console(); return; } console_output(&buf[0],n); } static void client_dead(CLIENT *c) { close(c->fd); c->fd = -1; } static void client_write(CLIENT *c) { CHUNK *ch; int n; int w; ch = c->q; n = ch->len - ch->ptr; w = write(c->fd,ch->data+ch->ptr,n); if (w < 0) { if ((errno == EWOULDBLOCK) || (errno == EINTR)) return; client_dead(c); return; } if (w > 0) q_drop(c,w); } static void client_read(CLIENT *c) { char buf[64]; int n; n = read(c->fd,&buf[0],sizeof(buf)); if (n < 0) { if ((errno == EWOULDBLOCK) || (errno == EINTR)) return; client_dead(c); return; } if (n == 0) { client_dead(c); return; } } static void listener_accept(LISTEN *l) { CLIENT *c; int fd; struct sockaddr_storage ss; socklen_t sslen; sslen = sizeof(ss); fd = accept(l->fd,(void *)&ss,&sslen); if (fd < 0) return; c = malloc(sizeof(CLIENT)); c->link = clients; clients = c; c->fd = fd; c->q = 0; c->qt = &c->q; c->qlen = 0; nclients ++; } static void drop_dead_clients(void) { CLIENT *c; CLIENT **cp; cp = &clients; while ((c = *cp)) { if (c->fd < 0) { *cp = c->link; q_drop(c,c->qlen); if (c->q) abort(); nclients --; } else { cp = &c->link; } } } static void step(void) { int i; LISTEN *l; CLIENT *c; i = nlisten + nclients + 1; if (npfds < i) { free(pfds); npfds = i; pfds = malloc(i*sizeof(struct pollfd)); } i = 0; for (l=listens;l;l=l->link) { pfds[i].fd = l->fd; pfds[i].events = POLLIN | POLLRDNORM; l->pfx = i++; } for (c=clients;c;c=c->link) { pfds[i].fd = c->fd; pfds[i].events = POLLIN | POLLRDNORM; if (c->q) pfds[i].events |= POLLOUT | POLLWRNORM; c->pfx = i++; } if (i != nlisten+nclients) abort(); if ((nclients > 0) && !havecons) { take_console(); } else if ((nclients < 1) && havecons) { drop_console(); } if (! havecons) { conspty = -1; conssfd = -1; } if (conspty >= 0) { pfds[i].fd = conspty; pfds[i].events = POLLIN | POLLRDNORM; conspfxp = i++; } i = poll(pfds,i,INFTIM); if (i < 0) { if (errno == EINTR) return; fprintf(stderr,"%s: poll: %s\n",__progname,strerror(errno)); exit(1); } if (i == 0) return; if ((conspty >= 0) && (pfds[conspfxp].revents & (POLLIN|POLLRDNORM))) { conspty_read(); } for (c=clients;c;c=c->link) { if (c->q && (pfds[c->pfx].revents & (POLLOUT|POLLWRNORM))) { client_write(c); } if (pfds[c->pfx].revents & (POLLIN|POLLRDNORM)) { client_read(c); } } for (l=listens;l;l=l->link) { if (pfds[l->pfx].revents & (POLLIN|POLLRDNORM)) { listener_accept(l); } } drop_dead_clients(); } static void block_signals(void) { sigset_t m; sigemptyset(&m); sigaddset(&m,SIGTTOU); sigaddset(&m,SIGTTIN); sigprocmask(SIG_BLOCK,&m,0); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); #ifdef DEFAULT_PORT if (! no_default) add_listen_ipv4("127.0.0.1"); #endif if (! listens) { fprintf(stderr,"%s: nowhere to listen on\n",__progname); exit(1); } open_listeners(); havecons = 0; block_signals(); if (! no_daemonize) daemon(0,0); while (1) step(); }