1 /*
2  *  Copyright conserver.com, 2000
3  *
4  *  Maintainer/Enhancer: Bryan Stansell (bryan@conserver.com)
5  *
6  *  Copyright GNAC, Inc., 1998
7  */
8 
9 /*
10  * Copyright 1992 Purdue Research Foundation, West Lafayette, Indiana
11  * 47907.  All rights reserved.
12  *
13  * Written by Kevin S Braunsdorf, ksb@cc.purdue.edu, purdue!ksb
14  *
15  * This software is not subject to any license of the American Telephone
16  * and Telegraph Company or the Regents of the University of California.
17  *
18  * Permission is granted to anyone to use this software for any purpose on
19  * any computer system, and to alter it and redistribute it freely, subject
20  * to the following restrictions:
21  *
22  * 1. Neither the authors nor Purdue University are responsible for any
23  *    consequences of the use of this software.
24  *
25  * 2. The origin of this software must not be misrepresented, either by
26  *    explicit claim or by omission.  Credit to the authors and Purdue
27  *    University must appear in documentation and sources.
28  *
29  * 3. Altered versions must be plainly marked as such, and must not be
30  *    misrepresented as being the original software.
31  *
32  * 4. This notice may not be removed or altered.
33  */
34 
35 #include <compat.h>
36 
37 #include <cutil.h>
38 #include <consent.h>
39 #include <access.h>
40 #include <client.h>
41 #include <group.h>
42 #include <readcfg.h>
43 
44 #if USE_IPV6
45 # include <sys/socket.h>
46 # include <netdb.h>
47 #endif /* USE_IPV6 */
48 
49 #if defined(USE_LIBWRAP)
50 # include <syslog.h>
51 # include <tcpd.h>
52 int allow_severity = LOG_INFO;
53 int deny_severity = LOG_WARNING;
54 #endif
55 
56 
57 /* find the next guy who wants to write on the console			(ksb)
58  */
59 void
FindWrite(CONSENT * pCE)60 FindWrite(CONSENT *pCE)
61 {
62     CONSCLIENT *pCLfound = (CONSCLIENT *)0;
63     CONSCLIENT *pCL;
64 
65     /* make the first guy (last on the list) to have the `want write' bit set
66      * the writer (tell him of the promotion, too)  we could look for the most
67      * recent or some such... I guess it doesn't matter that much.
68      */
69     if (pCE->pCLwr != (CONSCLIENT *)0 || pCE->fronly)
70 	return;
71 
72     for (pCL = pCE->pCLon; (CONSCLIENT *)0 != pCL; pCL = pCL->pCLnext) {
73 	if (pCL->fwantwr && !pCL->fro)
74 	    pCLfound = pCL;
75     }
76 
77     if (pCLfound != (CONSCLIENT *)0) {
78 	pCLfound->fwantwr = 0;
79 	pCLfound->fwr = 1;
80 	if (pCE->nolog) {
81 	    FileWrite(pCLfound->fd, FLAGFALSE,
82 		      "\r\n[attached (nologging)]\r\n", -1);
83 	} else {
84 	    FileWrite(pCLfound->fd, FLAGFALSE, "\r\n[attached]\r\n", -1);
85 	}
86 	TagLogfileAct(pCE, "%s attached", pCLfound->acid->string);
87 	pCE->pCLwr = pCLfound;
88     }
89 }
90 
91 void
BumpClient(CONSENT * pCE,char * message)92 BumpClient(CONSENT *pCE, char *message)
93 {
94     if ((CONSCLIENT *)0 == pCE->pCLwr)
95 	return;
96 
97     if ((char *)0 != message)
98 	FileWrite(pCE->pCLwr->fd, FLAGFALSE, message, -1);
99     pCE->pCLwr->fwantwr = 0;
100     pCE->pCLwr->fwr = 0;
101     pCE->pCLwr = (CONSCLIENT *)0;
102 }
103 
104 /* replay last 'back' lines of the log file upon connect to console	(ksb)
105  *
106  * NB: we know the console might be spewing when the replay happens,
107  * we want to just output what is in the log file and get out,
108  * so we don't drop chars...
109  */
110 #define REPLAYBUFFER 4096
111 
112 void
Replay(CONSENT * pCE,CONSFILE * fdOut,unsigned short back)113 Replay(CONSENT *pCE, CONSFILE *fdOut, unsigned short back)
114 {
115     CONSFILE *fdLog = (CONSFILE *)0;
116     STRING *line = (STRING *)0;
117     off_t file_pos;
118     off_t buf_pos;
119     char *buf = (char *)0;
120     char *bp = (char *)0;
121     int ch;
122     struct stat stLog;
123     int ln;
124     int was_mark = 0;
125 #if HAVE_DMALLOC && DMALLOC_MARK_REPLAY
126     unsigned long dmallocMarkReplay = 0;
127 #endif
128 
129     if (pCE != (CONSENT *)0 && pCE->logfile != (char *)0)
130 	fdLog = FileOpen(pCE->logfile, O_RDONLY, 0644);
131 
132     if (fdLog == (CONSFILE *)0) {
133 	FileWrite(fdOut, FLAGFALSE, "[no log file on this console]\r\n",
134 		  -1);
135 	return;
136     }
137 #if HAVE_DMALLOC && DMALLOC_MARK_REPLAY
138     dmallocMarkReplay = dmalloc_mark();
139 #endif
140 
141     /* find the size of the file
142      */
143     if (0 != FileStat(fdLog, &stLog))
144 	goto common_exit;
145 
146     file_pos = stLog.st_size - 1;	/* point at last byte */
147     buf_pos = file_pos + 1;
148 
149     if ((char *)0 == (buf = malloc(REPLAYBUFFER)))
150 	OutOfMem();
151     bp = buf + 1;		/* just give it something - it resets below */
152 
153     line = AllocString();
154 
155     /* loop as long as there is data in the file or we have not found
156      * the requested number of lines
157      */
158     ln = -1;
159     for (; file_pos >= 0; file_pos--, bp--) {
160 	if (file_pos < buf_pos) {
161 	    int r;
162 
163 	    /* read one buffer worth of data a buffer boundary
164 	     *
165 	     * the first read will probably not get a full buffer but
166 	     * the rest (as we work our way back in the file) should be
167 	     */
168 	    buf_pos = (file_pos / REPLAYBUFFER) * REPLAYBUFFER;
169 	    if (FileSeek(fdLog, buf_pos, SEEK_SET) < 0) {
170 		goto common_exit;
171 	    }
172 	    if ((r = FileRead(fdLog, buf, REPLAYBUFFER)) < 0) {
173 		goto common_exit;
174 	    }
175 	    bp = buf + r - 1;
176 	}
177 
178 	/* process the next character
179 	 */
180 	if ((ch = *bp) == '\n') {
181 	    if (ln >= 0) {
182 		int i;
183 		int u;
184 		int is_mark = 0;
185 
186 		/* reverse the text to put it in forward order
187 		 */
188 		u = line->used - 1;
189 		for (i = 0; i < u / 2; i++) {
190 		    int temp;
191 
192 		    temp = line->string[i];
193 		    line->string[i] = line->string[u - i - 1];
194 		    line->string[u - i - 1] = temp;
195 		}
196 
197 		/* see if this line is a MARK
198 		 */
199 		if (line->used > 0 && line->string[0] == '[') {
200 		    char dummy[4];
201 		    int j;
202 		    i = sscanf(line->string + 1,
203 			       "-- MARK -- %3c %3c %d %d:%d:%d %d]\r\n",
204 			       dummy, dummy, &j, &j, &j, &j, &j);
205 		    is_mark = (i == 7);
206 		}
207 
208 		/* process this line
209 		 */
210 		if (is_mark && was_mark) {
211 		    /* this is a mark and the previous line is also
212 		     * a mark, so reduce the line count 'cause it'll
213 		     * go up by one and we're joining them on output.
214 		     */
215 		    ln--;
216 		}
217 		was_mark = is_mark;
218 	    }
219 
220 	    /* advance to the next line and break if we have enough
221 	     */
222 	    ln++;
223 	    BuildString((char *)0, line);
224 	    if (ln >= back) {
225 		break;
226 	    }
227 	}
228 
229 	/* if we have a character but no lines yet, the last text in the
230 	 * file does not end with a newline, so start the first line anyway
231 	 */
232 	if (ln < 0) {
233 	    ln = 0;
234 	}
235 	BuildStringChar(ch, line);
236 
237 	/* if we've processed "a lot" of data for a line, then bail
238 	 * why?  there must be some very long non-newline terminated
239 	 * strings and if we just keep going back, we could spew lots
240 	 * of data and chew up lots of memory
241 	 */
242 	if (line->used > MAXREPLAYLINELEN) {
243 	    break;
244 	}
245     }
246 
247     /* move forward.  either we hit the beginning of the file and we
248      * move to the first byte, or we hit a \n and we move past it
249      */
250     file_pos++;
251 
252     /* Now output the lines, starting from where we stopped */
253     if (FileSeek(fdLog, file_pos, SEEK_SET) >= 0) {
254 	int eof = 0;
255 	int i = 0;
256 	int r = 0;
257 	STRING *mark_beg = (STRING *)0;
258 	STRING *mark_end = (STRING *)0;
259 
260 	mark_beg = AllocString();
261 	mark_end = AllocString();
262 
263 	ln = 0;			/* number of lines output */
264 	BuildString((char *)0, line);
265 
266 	while (ln < back && !eof) {
267 	    if (r <= 0) {
268 		if ((r = FileRead(fdLog, buf, REPLAYBUFFER)) < 0)
269 		    eof = 1;
270 		i = 0;
271 	    }
272 
273 	    if (!eof)
274 		BuildStringChar(buf[i], line);
275 
276 	    if (buf[i] == '\n' || eof) {
277 		int is_mark = 0;
278 		if (line->used > 0 && line->string[0] == '[') {
279 		    char dummy[4];
280 		    int j;
281 		    int i;
282 		    i = sscanf(line->string + 1,
283 			       "-- MARK -- %3c %3c %d %d:%d:%d %d]\r\n",
284 			       dummy, dummy, &j, &j, &j, &j, &j);
285 		    is_mark = (i == 7);
286 		}
287 		if (is_mark) {
288 		    if (mark_beg->used > 1) {
289 			BuildString((char *)0, mark_end);
290 			BuildString(line->string, mark_end);
291 		    } else
292 			BuildString(line->string, mark_beg);
293 		} else {
294 		    if (mark_beg->used > 1) {
295 			if (mark_end->used > 1) {
296 			    char *s;
297 
298 			    /* output the start of the range, stopping at the ']' */
299 			    s = strrchr(mark_beg->string, ']');
300 			    if ((char *)0 != s)
301 				*s = '\000';
302 			    FileWrite(fdOut, FLAGTRUE, mark_beg->string,
303 				      -1);
304 			    FileWrite(fdOut, FLAGTRUE, " .. ", 4);
305 
306 			    /* build the end string by removing the leading "[-- MARK -- "
307 			     * and replacing "]\r\n" on the end with " -- MARK --]\r\n"
308 			     */
309 			    s = strrchr(mark_end->string, ']');
310 			    if ((char *)0 != s)
311 				*s = '\000';
312 			    FileWrite(fdOut, FLAGTRUE,
313 				      mark_end->string +
314 				      sizeof("[-- MARK -- ") - 1, -1);
315 			    FileWrite(fdOut, FLAGFALSE, " -- MARK --]\r\n",
316 				      -1);
317 			} else {
318 			    FileWrite(fdOut, FLAGFALSE, mark_beg->string,
319 				      mark_beg->used - 1);
320 			}
321 			BuildString((char *)0, mark_beg);
322 			BuildString((char *)0, mark_end);
323 			ln++;
324 			if (ln >= back)
325 			    break;
326 		    }
327 		    FileWrite(fdOut, FLAGFALSE, line->string,
328 			      line->used - 1);
329 		    ln++;
330 		}
331 		BuildString((char *)0, line);
332 	    }
333 
334 	    /* move the counters */
335 	    i++;
336 	    r--;
337 	}
338 	DestroyString(mark_end);
339 	DestroyString(mark_beg);
340     }
341 
342   common_exit:
343 
344     if (line != (STRING *)0)
345 	DestroyString(line);
346     if (buf != (char *)0)
347 	free(buf);
348     if (fdLog != (CONSFILE *)0)
349 	FileClose(&fdLog);
350 
351 #if HAVE_DMALLOC && DMALLOC_MARK_REPLAY
352     CONDDEBUG((1, "Replay(): dmalloc / MarkReplay"));
353     dmalloc_log_changed(dmallocMarkReplay, 1, 0, 1);
354 #endif
355 }
356 
357 
358 /* these bit tell us which parts of the Truth to tell the client	(ksb)
359  */
360 #define WHEN_SPY	0x01
361 #define WHEN_ATTACH	0x02
362 #define WHEN_EXPERT	0x04	/* ZZZ no way to set his yet    */
363 #define WHEN_ALWAYS	0x40
364 #define IS_LIMITED	0x100
365 
366 #define HALFLINE	40
367 
368 typedef struct HLnode {
369     int iwhen;
370     char *actext;
371 } HELP;
372 
373 static HELP aHLTable[] = {
374     {WHEN_ALWAYS, ".       disconnect"},
375     {WHEN_ALWAYS | IS_LIMITED, ";       move to another console"},
376     {WHEN_ALWAYS, "a       attach read/write"},
377     {WHEN_ALWAYS, "b       send broadcast message"},
378     {WHEN_ATTACH, "c       toggle flow control"},
379     {WHEN_ATTACH, "d       down a console"},
380     {WHEN_ALWAYS, "e       change escape sequence"},
381     {WHEN_ALWAYS, "f       force attach read/write"},
382     {WHEN_ALWAYS, "g       group info"},
383     {WHEN_ALWAYS, "i       information dump"},
384     {WHEN_ATTACH, "L       toggle logging on/off"},
385     {WHEN_ATTACH, "l?      break sequence list"},
386     {WHEN_ATTACH, "l0      send break per config file"},
387     {WHEN_ATTACH, "l1-9a-z send specific break sequence"},
388     {WHEN_ALWAYS, "m       display message of the day"},
389     {WHEN_ALWAYS, "n       write a note to the logfile"},
390     {WHEN_ALWAYS, "o       (re)open the tty and log file"},
391     {WHEN_ALWAYS, "p       playback the last %hu lines"},
392     {WHEN_ALWAYS, "P       set number of playback lines"},
393     {WHEN_ALWAYS, "r       replay the last %hu lines"},
394     {WHEN_ALWAYS, "R       set number of replay lines"},
395     {WHEN_ATTACH, "s       spy mode (read only)"},
396     {WHEN_ALWAYS, "u       show host status"},
397     {WHEN_ALWAYS, "v       show version info"},
398     {WHEN_ALWAYS, "w       who is on this console"},
399     {WHEN_ALWAYS, "x       show console baud info"},
400     {WHEN_ALWAYS | IS_LIMITED, "z       suspend the connection"},
401     {WHEN_ATTACH, "!       invoke task"},
402     {WHEN_ATTACH | IS_LIMITED, "|       attach local command"},
403     {WHEN_ALWAYS, "?       print this message"},
404     {WHEN_ALWAYS, "<cr>    ignore/abort command"},
405     {WHEN_ALWAYS, "^R      replay the last line"},
406     {WHEN_ATTACH, "\\ooo    send character by octal code"},
407 };
408 
409 /* list the commands we know for the user				(ksb)
410  */
411 void
HelpUser(CONSCLIENT * pCL)412 HelpUser(CONSCLIENT *pCL)
413 {
414     int i, j, iCmp;
415     static char
416       acH1[] = "help]\r\n", acH2[] = "help spy mode]\r\n", acEoln[] =
417 	"\r\n";
418     static STRING *acLine = (STRING *)0;
419 
420     if (acLine == (STRING *)0)
421 	acLine = AllocString();
422 
423     iCmp = WHEN_ALWAYS | WHEN_SPY;
424     if (pCL->fwr) {
425 	FileWrite(pCL->fd, FLAGTRUE, acH1, sizeof(acH1) - 1);
426 	iCmp |= WHEN_ATTACH;
427     } else {
428 	FileWrite(pCL->fd, FLAGTRUE, acH2, sizeof(acH2) - 1);
429     }
430 
431     BuildString((char *)0, acLine);
432     for (i = 0; i < sizeof(aHLTable) / sizeof(HELP); ++i) {
433 	char *text;
434 
435 	if (aHLTable[i].iwhen & IS_LIMITED &&
436 	    ConsentUserOk(pLUList, pCL->username->string) == 1)
437 	    continue;
438 
439 	if (0 == (aHLTable[i].iwhen & iCmp))
440 	    continue;
441 
442 	text = aHLTable[i].actext;
443 	if (text[0] == 'p') {
444 	    BuildTmpString((char *)0);
445 	    text = BuildTmpStringPrint(text, pCL->playback);
446 	} else if (text[0] == 'r') {
447 	    BuildTmpString((char *)0);
448 	    text = BuildTmpStringPrint(text, pCL->replay);
449 	}
450 
451 	if (acLine->used != 0) {	/* second part of line */
452 	    if (strlen(text) < HALFLINE) {
453 		for (j = acLine->used; j <= HALFLINE; ++j) {
454 		    BuildStringChar(' ', acLine);
455 		}
456 		BuildString(text, acLine);
457 		BuildString(acEoln, acLine);
458 		FileWrite(pCL->fd, FLAGTRUE, acLine->string,
459 			  acLine->used - 1);
460 		BuildString((char *)0, acLine);
461 		continue;
462 	    } else {
463 		BuildString(acEoln, acLine);
464 		FileWrite(pCL->fd, FLAGTRUE, acLine->string,
465 			  acLine->used - 1);
466 		BuildString((char *)0, acLine);
467 	    }
468 	}
469 	if (acLine->used == 0) {	/* at new line */
470 	    BuildStringChar(' ', acLine);
471 	    BuildString(text, acLine);
472 	    if (acLine->used > HALFLINE) {
473 		BuildString(acEoln, acLine);
474 		FileWrite(pCL->fd, FLAGTRUE, acLine->string,
475 			  acLine->used - 1);
476 		BuildString((char *)0, acLine);
477 	    }
478 	}
479     }
480     if (acLine->used != 0) {
481 	BuildString(acEoln, acLine);
482 	FileWrite(pCL->fd, FLAGTRUE, acLine->string, acLine->used - 1);
483     }
484     FileWrite(pCL->fd, FLAGFALSE, (char *)0, 0);
485 }
486 
487 int
ClientAccessOk(CONSCLIENT * pCL)488 ClientAccessOk(CONSCLIENT *pCL)
489 {
490     char *peername = (char *)0;
491     int retval = 1;
492 
493 #if USE_IPV6 || !USE_UNIX_DOMAIN_SOCKETS
494     socklen_t so;
495     int cfd;
496 # if USE_IPV6
497     int error;
498     char addr[NI_MAXHOST];
499 # endif
500     SOCKADDR_STYPE in_port;
501     int getpeer = -1;
502 
503     cfd = FileFDNum(pCL->fd);
504     pCL->caccess = 'r';
505 # if defined(USE_LIBWRAP)
506     {
507 	struct request_info request;
508 	CONDDEBUG((1, "ClientAccessOk(): doing tcpwrappers check"));
509 	request_init(&request, RQ_DAEMON, progname, RQ_FILE, cfd, 0);
510 	fromhost(&request);
511 	if (!hosts_access(&request)) {
512 	    FileWrite(pCL->fd, FLAGFALSE,
513 		      "access from your host refused\r\n", -1);
514 	    retval = 0;
515 	    goto setpeer;
516 	}
517     }
518 # endif
519 
520     so = sizeof(in_port);
521     if (-1 ==
522 	(getpeer = getpeername(cfd, (struct sockaddr *)&in_port, &so))) {
523 	FileWrite(pCL->fd, FLAGFALSE, "getpeername failed\r\n", -1);
524 	retval = 0;
525 	goto setpeer;
526     }
527     pCL->caccess = AccType(
528 # if USE_IPV6
529 			      &in_port,
530 # else
531 			      &in_port.sin_addr,
532 # endif
533 			      &peername);
534     if (pCL->caccess == 'r') {
535 	FileWrite(pCL->fd, FLAGFALSE, "access from your host refused\r\n",
536 		  -1);
537 	retval = 0;
538     }
539   setpeer:
540 #else
541     struct in_addr addr;
542 
543 # if HAVE_INET_ATON
544     inet_aton("127.0.0.1", &addr);
545 # else
546     addr.s_addr = inet_addr("127.0.0.1");
547 # endif
548     pCL->caccess = AccType(&addr, &peername);
549     if (pCL->caccess == 'r') {
550 	FileWrite(pCL->fd, FLAGFALSE, "access from your host refused\r\n",
551 		  -1);
552 	retval = 0;
553     }
554 #endif
555 
556     if (pCL->peername != (STRING *)0) {
557 	BuildString((char *)0, pCL->peername);
558 	if (peername != (char *)0)
559 	    BuildString(peername, pCL->peername);
560 #if USE_IPV6
561 	else if (getpeer != -1) {
562 	    error =
563 		getnameinfo((struct sockaddr *)&in_port, so, addr,
564 			    sizeof(addr), NULL, 0, NI_NUMERICHOST);
565 	    if (error) {
566 		FileWrite(pCL->fd, FLAGFALSE, "getnameinfo failed\r\n",
567 			  -1);
568 		Error("ClientAccessOk(): gatenameinfo: %s",
569 		      gai_strerror(error));
570 		retval = 0;
571 	    }
572 
573 	    BuildString(addr, pCL->peername);
574 	} else
575 	    BuildString("<unknown>", pCL->peername);
576 #elif USE_UNIX_DOMAIN_SOCKETS
577 	else
578 	    BuildString("127.0.0.1", pCL->peername);
579 #else
580 	else if (getpeer != -1)
581 	    BuildString(inet_ntoa(in_port.sin_addr), pCL->peername);
582 	else
583 	    BuildString("<unknown>", pCL->peername);
584 #endif
585     }
586     if (peername != (char *)0)
587 	free(peername);
588     return retval;
589 }
590