/*
* Copyright conserver.com, 2000
*
* Maintainer/Enhancer: Bryan Stansell (bryan@conserver.com)
*
* Copyright GNAC, Inc., 1998
*/
/*
* Copyright 1992 Purdue Research Foundation, West Lafayette, Indiana
* 47907. All rights reserved.
*
* Written by Kevin S Braunsdorf, ksb@cc.purdue.edu, purdue!ksb
*
* This software is not subject to any license of the American Telephone
* and Telegraph Company or the Regents of the University of California.
*
* Permission is granted to anyone to use this software for any purpose on
* any computer system, and to alter it and redistribute it freely, subject
* to the following restrictions:
*
* 1. Neither the authors nor Purdue University are responsible for any
* consequences of the use of this software.
*
* 2. The origin of this software must not be misrepresented, either by
* explicit claim or by omission. Credit to the authors and Purdue
* University must appear in documentation and sources.
*
* 3. Altered versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 4. This notice may not be removed or altered.
*/
#include <compat.h>
#include <cutil.h>
#include <consent.h>
#include <access.h>
#include <client.h>
#include <group.h>
#include <readcfg.h>
#if USE_IPV6
# include <sys/socket.h>
# include <netdb.h>
#endif /* USE_IPV6 */
#if defined(USE_LIBWRAP)
# include <syslog.h>
# include <tcpd.h>
int allow_severity = LOG_INFO;
int deny_severity = LOG_WARNING;
#endif
/* find the next guy who wants to write on the console (ksb)
*/
void
FindWrite(CONSENT *pCE)
{
CONSCLIENT *pCLfound = (CONSCLIENT *)0;
CONSCLIENT *pCL;
/* make the first guy (last on the list) to have the `want write' bit set
* the writer (tell him of the promotion, too) we could look for the most
* recent or some such... I guess it doesn't matter that much.
*/
if (pCE->pCLwr != (CONSCLIENT *)0 || pCE->fronly)
return;
for (pCL = pCE->pCLon; (CONSCLIENT *)0 != pCL; pCL = pCL->pCLnext) {
if (pCL->fwantwr && !pCL->fro)
pCLfound = pCL;
}
if (pCLfound != (CONSCLIENT *)0) {
pCLfound->fwantwr = 0;
pCLfound->fwr = 1;
if (pCE->nolog) {
FileWrite(pCLfound->fd, FLAGFALSE,
"\r\n[attached (nologging)]\r\n", -1);
} else {
FileWrite(pCLfound->fd, FLAGFALSE, "\r\n[attached]\r\n", -1);
}
TagLogfileAct(pCE, "%s attached", pCLfound->acid->string);
pCE->pCLwr = pCLfound;
}
}
void
BumpClient(CONSENT *pCE, char *message)
{
if ((CONSCLIENT *)0 == pCE->pCLwr)
return;
if ((char *)0 != message)
FileWrite(pCE->pCLwr->fd, FLAGFALSE, message, -1);
pCE->pCLwr->fwantwr = 0;
pCE->pCLwr->fwr = 0;
pCE->pCLwr = (CONSCLIENT *)0;
}
/* replay last 'back' lines of the log file upon connect to console (ksb)
*
* NB: we know the console might be spewing when the replay happens,
* we want to just output what is in the log file and get out,
* so we don't drop chars...
*/
#define REPLAYBUFFER 4096
void
Replay(CONSENT *pCE, CONSFILE *fdOut, unsigned short back)
{
CONSFILE *fdLog = (CONSFILE *)0;
STRING *line = (STRING *)0;
off_t file_pos;
off_t buf_pos;
char *buf = (char *)0;
char *bp = (char *)0;
int ch;
struct stat stLog;
int ln;
int was_mark = 0;
#if HAVE_DMALLOC && DMALLOC_MARK_REPLAY
unsigned long dmallocMarkReplay = 0;
#endif
if (pCE != (CONSENT *)0 && pCE->logfile != (char *)0)
fdLog = FileOpen(pCE->logfile, O_RDONLY, 0644);
if (fdLog == (CONSFILE *)0) {
FileWrite(fdOut, FLAGFALSE, "[no log file on this console]\r\n",
-1);
return;
}
#if HAVE_DMALLOC && DMALLOC_MARK_REPLAY
dmallocMarkReplay = dmalloc_mark();
#endif
/* find the size of the file
*/
if (0 != FileStat(fdLog, &stLog))
goto common_exit;
file_pos = stLog.st_size - 1; /* point at last byte */
buf_pos = file_pos + 1;
if ((char *)0 == (buf = malloc(REPLAYBUFFER)))
OutOfMem();
bp = buf + 1; /* just give it something - it resets below */
line = AllocString();
/* loop as long as there is data in the file or we have not found
* the requested number of lines
*/
ln = -1;
for (; file_pos >= 0; file_pos--, bp--) {
if (file_pos < buf_pos) {
int r;
/* read one buffer worth of data a buffer boundary
*
* the first read will probably not get a full buffer but
* the rest (as we work our way back in the file) should be
*/
buf_pos = (file_pos / REPLAYBUFFER) * REPLAYBUFFER;
if (FileSeek(fdLog, buf_pos, SEEK_SET) < 0) {
goto common_exit;
}
if ((r = FileRead(fdLog, buf, REPLAYBUFFER)) < 0) {
goto common_exit;
}
bp = buf + r - 1;
}
/* process the next character
*/
if ((ch = *bp) == '\n') {
if (ln >= 0) {
int i;
int u;
int is_mark = 0;
/* reverse the text to put it in forward order
*/
u = line->used - 1;
for (i = 0; i < u / 2; i++) {
int temp;
temp = line->string[i];
line->string[i] = line->string[u - i - 1];
line->string[u - i - 1] = temp;
}
/* see if this line is a MARK
*/
if (line->used > 0 && line->string[0] == '[') {
char dummy[4];
int j;
i = sscanf(line->string + 1,
"-- MARK -- %3c %3c %d %d:%d:%d %d]\r\n",
dummy, dummy, &j, &j, &j, &j, &j);
is_mark = (i == 7);
}
/* process this line
*/
if (is_mark && was_mark) {
/* this is a mark and the previous line is also
* a mark, so reduce the line count 'cause it'll
* go up by one and we're joining them on output.
*/
ln--;
}
was_mark = is_mark;
}
/* advance to the next line and break if we have enough
*/
ln++;
BuildString((char *)0, line);
if (ln >= back) {
break;
}
}
/* if we have a character but no lines yet, the last text in the
* file does not end with a newline, so start the first line anyway
*/
if (ln < 0) {
ln = 0;
}
BuildStringChar(ch, line);
/* if we've processed "a lot" of data for a line, then bail
* why? there must be some very long non-newline terminated
* strings and if we just keep going back, we could spew lots
* of data and chew up lots of memory
*/
if (line->used > MAXREPLAYLINELEN) {
break;
}
}
/* move forward. either we hit the beginning of the file and we
* move to the first byte, or we hit a \n and we move past it
*/
file_pos++;
/* Now output the lines, starting from where we stopped */
if (FileSeek(fdLog, file_pos, SEEK_SET) >= 0) {
int eof = 0;
int i = 0;
int r = 0;
STRING *mark_beg = (STRING *)0;
STRING *mark_end = (STRING *)0;
mark_beg = AllocString();
mark_end = AllocString();
ln = 0; /* number of lines output */
BuildString((char *)0, line);
while (ln < back && !eof) {
if (r <= 0) {
if ((r = FileRead(fdLog, buf, REPLAYBUFFER)) < 0)
eof = 1;
i = 0;
}
if (!eof)
BuildStringChar(buf[i], line);
if (buf[i] == '\n' || eof) {
int is_mark = 0;
if (line->used > 0 && line->string[0] == '[') {
char dummy[4];
int j;
int i;
i = sscanf(line->string + 1,
"-- MARK -- %3c %3c %d %d:%d:%d %d]\r\n",
dummy, dummy, &j, &j, &j, &j, &j);
is_mark = (i == 7);
}
if (is_mark) {
if (mark_beg->used > 1) {
BuildString((char *)0, mark_end);
BuildString(line->string, mark_end);
} else
BuildString(line->string, mark_beg);
} else {
if (mark_beg->used > 1) {
if (mark_end->used > 1) {
char *s;
/* output the start of the range, stopping at the ']' */
s = strrchr(mark_beg->string, ']');
if ((char *)0 != s)
*s = '\000';
FileWrite(fdOut, FLAGTRUE, mark_beg->string,
-1);
FileWrite(fdOut, FLAGTRUE, " .. ", 4);
/* build the end string by removing the leading "[-- MARK -- "
* and replacing "]\r\n" on the end with " -- MARK --]\r\n"
*/
s = strrchr(mark_end->string, ']');
if ((char *)0 != s)
*s = '\000';
FileWrite(fdOut, FLAGTRUE,
mark_end->string +
sizeof("[-- MARK -- ") - 1, -1);
FileWrite(fdOut, FLAGFALSE, " -- MARK --]\r\n",
-1);
} else {
FileWrite(fdOut, FLAGFALSE, mark_beg->string,
mark_beg->used - 1);
}
BuildString((char *)0, mark_beg);
BuildString((char *)0, mark_end);
ln++;
if (ln >= back)
break;
}
FileWrite(fdOut, FLAGFALSE, line->string,
line->used - 1);
ln++;
}
BuildString((char *)0, line);
}
/* move the counters */
i++;
r--;
}
DestroyString(mark_end);
DestroyString(mark_beg);
}
common_exit:
if (line != (STRING *)0)
DestroyString(line);
if (buf != (char *)0)
free(buf);
if (fdLog != (CONSFILE *)0)
FileClose(&fdLog);
#if HAVE_DMALLOC && DMALLOC_MARK_REPLAY
CONDDEBUG((1, "Replay(): dmalloc / MarkReplay"));
dmalloc_log_changed(dmallocMarkReplay, 1, 0, 1);
#endif
}
/* these bit tell us which parts of the Truth to tell the client (ksb)
*/
#define WHEN_SPY 0x01
#define WHEN_ATTACH 0x02
#define WHEN_EXPERT 0x04 /* ZZZ no way to set his yet */
#define WHEN_ALWAYS 0x40
#define IS_LIMITED 0x100
#define HALFLINE 40
typedef struct HLnode {
int iwhen;
char *actext;
} HELP;
static HELP aHLTable[] = {
{WHEN_ALWAYS, ". disconnect"},
{WHEN_ALWAYS | IS_LIMITED, "; move to another console"},
{WHEN_ALWAYS, "a attach read/write"},
{WHEN_ALWAYS, "b send broadcast message"},
{WHEN_ATTACH, "c toggle flow control"},
{WHEN_ATTACH, "d down a console"},
{WHEN_ALWAYS, "e change escape sequence"},
{WHEN_ALWAYS, "f force attach read/write"},
{WHEN_ALWAYS, "g group info"},
{WHEN_ALWAYS, "i information dump"},
{WHEN_ATTACH, "L toggle logging on/off"},
{WHEN_ATTACH, "l? break sequence list"},
{WHEN_ATTACH, "l0 send break per config file"},
{WHEN_ATTACH, "l1-9a-z send specific break sequence"},
{WHEN_ALWAYS, "m display message of the day"},
{WHEN_ALWAYS, "n write a note to the logfile"},
{WHEN_ALWAYS, "o (re)open the tty and log file"},
{WHEN_ALWAYS, "p playback the last %hu lines"},
{WHEN_ALWAYS, "P set number of playback lines"},
{WHEN_ALWAYS, "r replay the last %hu lines"},
{WHEN_ALWAYS, "R set number of replay lines"},
{WHEN_ATTACH, "s spy mode (read only)"},
{WHEN_ALWAYS, "u show host status"},
{WHEN_ALWAYS, "v show version info"},
{WHEN_ALWAYS, "w who is on this console"},
{WHEN_ALWAYS, "x show console baud info"},
{WHEN_ALWAYS | IS_LIMITED, "z suspend the connection"},
{WHEN_ATTACH, "! invoke task"},
{WHEN_ATTACH | IS_LIMITED, "| attach local command"},
{WHEN_ALWAYS, "? print this message"},
{WHEN_ALWAYS, "<cr> ignore/abort command"},
{WHEN_ALWAYS, "^R replay the last line"},
{WHEN_ATTACH, "\\ooo send character by octal code"},
};
/* list the commands we know for the user (ksb)
*/
void
HelpUser(CONSCLIENT *pCL)
{
int i, j, iCmp;
static char
acH1[] = "help]\r\n", acH2[] = "help spy mode]\r\n", acEoln[] =
"\r\n";
static STRING *acLine = (STRING *)0;
if (acLine == (STRING *)0)
acLine = AllocString();
iCmp = WHEN_ALWAYS | WHEN_SPY;
if (pCL->fwr) {
FileWrite(pCL->fd, FLAGTRUE, acH1, sizeof(acH1) - 1);
iCmp |= WHEN_ATTACH;
} else {
FileWrite(pCL->fd, FLAGTRUE, acH2, sizeof(acH2) - 1);
}
BuildString((char *)0, acLine);
for (i = 0; i < sizeof(aHLTable) / sizeof(HELP); ++i) {
char *text;
if (aHLTable[i].iwhen & IS_LIMITED &&
ConsentUserOk(pLUList, pCL->username->string) == 1)
continue;
if (0 == (aHLTable[i].iwhen & iCmp))
continue;
text = aHLTable[i].actext;
if (text[0] == 'p') {
BuildTmpString((char *)0);
text = BuildTmpStringPrint(text, pCL->playback);
} else if (text[0] == 'r') {
BuildTmpString((char *)0);
text = BuildTmpStringPrint(text, pCL->replay);
}
if (acLine->used != 0) { /* second part of line */
if (strlen(text) < HALFLINE) {
for (j = acLine->used; j <= HALFLINE; ++j) {
BuildStringChar(' ', acLine);
}
BuildString(text, acLine);
BuildString(acEoln, acLine);
FileWrite(pCL->fd, FLAGTRUE, acLine->string,
acLine->used - 1);
BuildString((char *)0, acLine);
continue;
} else {
BuildString(acEoln, acLine);
FileWrite(pCL->fd, FLAGTRUE, acLine->string,
acLine->used - 1);
BuildString((char *)0, acLine);
}
}
if (acLine->used == 0) { /* at new line */
BuildStringChar(' ', acLine);
BuildString(text, acLine);
if (acLine->used > HALFLINE) {
BuildString(acEoln, acLine);
FileWrite(pCL->fd, FLAGTRUE, acLine->string,
acLine->used - 1);
BuildString((char *)0, acLine);
}
}
}
if (acLine->used != 0) {
BuildString(acEoln, acLine);
FileWrite(pCL->fd, FLAGTRUE, acLine->string, acLine->used - 1);
}
FileWrite(pCL->fd, FLAGFALSE, (char *)0, 0);
}
int
ClientAccessOk(CONSCLIENT *pCL)
{
char *peername = (char *)0;
int retval = 1;
#if USE_IPV6 || !USE_UNIX_DOMAIN_SOCKETS
socklen_t so;
int cfd;
# if USE_IPV6
int error;
char addr[NI_MAXHOST];
# endif
SOCKADDR_STYPE in_port;
int getpeer = -1;
cfd = FileFDNum(pCL->fd);
pCL->caccess = 'r';
# if defined(USE_LIBWRAP)
{
struct request_info request;
CONDDEBUG((1, "ClientAccessOk(): doing tcpwrappers check"));
request_init(&request, RQ_DAEMON, progname, RQ_FILE, cfd, 0);
fromhost(&request);
if (!hosts_access(&request)) {
FileWrite(pCL->fd, FLAGFALSE,
"access from your host refused\r\n", -1);
retval = 0;
goto setpeer;
}
}
# endif
so = sizeof(in_port);
if (-1 ==
(getpeer = getpeername(cfd, (struct sockaddr *)&in_port, &so))) {
FileWrite(pCL->fd, FLAGFALSE, "getpeername failed\r\n", -1);
retval = 0;
goto setpeer;
}
pCL->caccess = AccType(
# if USE_IPV6
&in_port,
# else
&in_port.sin_addr,
# endif
&peername);
if (pCL->caccess == 'r') {
FileWrite(pCL->fd, FLAGFALSE, "access from your host refused\r\n",
-1);
retval = 0;
}
setpeer:
#else
struct in_addr addr;
# if HAVE_INET_ATON
inet_aton("127.0.0.1", &addr);
# else
addr.s_addr = inet_addr("127.0.0.1");
# endif
pCL->caccess = AccType(&addr, &peername);
if (pCL->caccess == 'r') {
FileWrite(pCL->fd, FLAGFALSE, "access from your host refused\r\n",
-1);
retval = 0;
}
#endif
if (pCL->peername != (STRING *)0) {
BuildString((char *)0, pCL->peername);
if (peername != (char *)0)
BuildString(peername, pCL->peername);
#if USE_IPV6
else if (getpeer != -1) {
error =
getnameinfo((struct sockaddr *)&in_port, so, addr,
sizeof(addr), NULL, 0, NI_NUMERICHOST);
if (error) {
FileWrite(pCL->fd, FLAGFALSE, "getnameinfo failed\r\n",
-1);
Error("ClientAccessOk(): gatenameinfo: %s",
gai_strerror(error));
retval = 0;
}
BuildString(addr, pCL->peername);
} else
BuildString("<unknown>", pCL->peername);
#elif USE_UNIX_DOMAIN_SOCKETS
else
BuildString("127.0.0.1", pCL->peername);
#else
else if (getpeer != -1)
BuildString(inet_ntoa(in_port.sin_addr), pCL->peername);
else
BuildString("<unknown>", pCL->peername);
#endif
}
if (peername != (char *)0)
free(peername);
return retval;
}