peek(1) #93
8
Makefile
@ -102,11 +102,15 @@ mm: build/bin/mm
|
||||
build/bin/mm: src/mm.c build
|
||||
$(CC) $(CFLAGS) -o $@ src/mm.c
|
||||
|
||||
|
||||
.PHONY: npc
|
||||
npc: build/bin/npc
|
||||
build/bin/npc: src/npc.c build
|
||||
$(CC) $(CFLAGAS) -o $@ src/npc.c
|
||||
$(CC) $(CFLAGS) -o $@ src/npc.c
|
||||
|
||||
.PHONY: peek
|
||||
peek: build/bin/peek
|
||||
build/bin/peek: src/peek.c build
|
||||
$(CC) $(CFLAGS) -o $@ src/peek.c
|
||||
|
||||
.PHONY: rpn
|
||||
rpn: build/bin/rpn
|
||||
|
116
docs/peek.1
Normal file
@ -0,0 +1,116 @@
|
||||
.\" Copyright (c) 2023-2024 DTB <trinity@trinity.moe>
|
||||
.\"
|
||||
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
|
||||
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
|
||||
|
||||
.TH PEEK 1
|
||||
|
||||
trinity marked this conversation as resolved
Outdated
emma
commented
Outdated
Review
> .TH PEEK 1 [whenever you make the change] "Harakit X.X.X"
|
||||
.SH NAME
|
||||
|
||||
peek \(en read from standard input, furtively
|
||||
|
||||
.SH SYNOPSIS
|
||||
|
||||
peek
|
||||
.RB ( -1enot )
|
||||
.RB ( -p
|
||||
.RB [ program
|
||||
.RB [ arguments... ]])
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
Peek reads input from standard input with terminal echo disabled, which may be
|
||||
emma marked this conversation as resolved
Outdated
emma
commented
Read input from the standard input with terminal echo disabled.
Should this be placed in a RATIONALE section? > Peek reads input from standard input with terminal echo disabled,
Read input from the standard input with terminal echo disabled.
> which may be useful to prevent secrets being spied upon by adversaries watching a user's
screen.
Should this be placed in a RATIONALE section?
|
||||
useful to prevent secrets being spied upon by adversaries watching a user's
|
||||
trinity marked this conversation as resolved
Outdated
silt
commented
This appears to imply that there exists a multitude of other possible sources, but it's not made clear what those are or how to make use of them. Does it support reading input from… files? TCP sockets? IRC messages?? This appears to imply that there exists a multitude of other possible sources, but it's not made clear what those are or how to make use of them. Does it support reading input from… files? TCP sockets? IRC messages??
trinity
commented
The "i.e." stands for "id est" and can be read as "in other words". Perhaps I should use the English. I mean to say that the source other than a terminal, in this case, is a pipe. The "i.e." stands for "id est" and can be read as "in other words". Perhaps I should use the English. I mean to say that the source other than a terminal, in this case, is a pipe.
trinity
commented
Changed to (pipes) which I think is more clear. Changed to (pipes) which I think is more clear.
|
||||
screen.
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
.B -1
|
||||
.RS
|
||||
Limits input to a single line (stopping when the newline character is read).
|
||||
emma marked this conversation as resolved
Outdated
emma
commented
For parity with the man pages in the > In the event of an error, a debug message will be printed and the program will
exit with the appropriate sysexits.h(3) error code.
For parity with the man pages in the `docs` branch. For example, see [`npc.1`](https://git.tebibyte.media/bonsai/coreutils/src/commit/b7f52902b6bde54876b5fd8efafa433e2cf0df9a/docs/npc.1#L44).
|
||||
.RE
|
||||
|
||||
.B -e
|
||||
emma marked this conversation as resolved
Outdated
emma
commented
If it encounters a SIGINT signal (^C), the program will attempt to handle it to ensure the user’s terminal is still usable after premature termination; if the signal can’t be handled, it prints an error message and continues. If the program is interrupted, it exits unsuccessfully and without printing an error message. If it encounters a SIGINT signal (^C), the program will attempt to handle it to ensure the user’s terminal is still usable after premature termination; if the signal can’t be handled, it prints an error message and continues. If the program is interrupted, it exits unsuccessfully and without printing an error message.
|
||||
.RS
|
||||
Configures peek to output to standard error.
|
||||
.RE
|
||||
|
||||
.B -n
|
||||
.RS
|
||||
Prints ("iNcludes") the terminating character in outputs. This is nonsensical
|
||||
if not combined with the
|
||||
.B -1
|
||||
option.
|
||||
.RE
|
||||
|
||||
.B -o
|
||||
.RS
|
||||
emma marked this conversation as resolved
Outdated
emma
commented
This being in the BUGS section implies that this can be fixed by the program. Therefore, this belongs in a CAVEATS section because it is definitionally a caveat. This being in the BUGS section implies that this can be fixed by the program. Therefore, this belongs in a CAVEATS section because it is definitionally a caveat.
|
||||
Configures peek to output to standard output.
|
||||
trinity marked this conversation as resolved
Outdated
emma
commented
“[...] the standard output” “[...] the standard output”
|
||||
.RE
|
||||
|
||||
.B -p
|
||||
trinity marked this conversation as resolved
Outdated
emma
commented
What is the benefit to this over using a shell pipe? What is the benefit to this over using a shell pipe?
|
||||
.RS
|
||||
Configures peek to pipe output to an executed child program, e.g. a password
|
||||
hashing utility. This is provided as a convenience and to avoid potential
|
||||
insecurities resulting from programmer error in scripts.
|
||||
.RE
|
||||
|
||||
.B -t
|
||||
.RS
|
||||
Makes peek exit if not run within a terminal.
|
||||
.RE
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
|
||||
Peek prints an error message and exits with the appropriate status from
|
||||
sysexits.h(3) if it encounters an unrecoverable error.
|
||||
|
||||
Peek tries to handle the signal SIGINT (^C) to ensure the user's terminal is
|
||||
still usable after premature termination; if the signal can't be handled, it
|
||||
prints an error message and continues. If peek is interrupted, it exits
|
||||
unsuccessfully, without an error message.
|
||||
|
||||
emma marked this conversation as resolved
Outdated
emma
commented
> It uses mm(1) to pipe the hidden input to both the standard error and the regular file writing.txt.
|
||||
.SH BUGS
|
||||
|
||||
Accepting secrets in shell scripts is not adviseable in any context.
|
||||
|
||||
If used in a safety-critical application it must be ensured that the
|
||||
environment with which peek is used is not compromised.
|
||||
|
||||
On systems that support it, the ioctl(2) command TIOCSTI can be used to insert
|
||||
characters into the standard input going to peek. This doesn't allow snooping
|
||||
but can be used for general mischief.
|
||||
|
||||
Peek will happily run without outputs, slurping input and doing nothing with
|
||||
it.
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
This is a command line for POSIX sh(1), using POSIX env(1) to discard the
|
||||
environment except for PATH, and htpassword(1) from Apache's utilities to hash
|
||||
the input with the bcrypt algorithm, printing the resulting hash:
|
||||
|
||||
.RS
|
||||
.R $ env -i PATH="$PATH" peek -1tp htpasswd -nBi _ | cut -d : -f 2
|
||||
.RE
|
||||
|
||||
This is a POSIX sh(1) command line that lets a user blindly write into a text
|
||||
trinity marked this conversation as resolved
Outdated
emma
commented
Either:
or:
Either:
> This is a POSIX shell [...]
or:
> This is an sh(1p) [...]
|
||||
file, only able to see written lines. Some writers have the habit of
|
||||
prematurely revising their work and use tools like this to prevent it:
|
||||
|
||||
.RS
|
||||
.R $ echo Input ^D to quit. && peek -eot >writing.txt
|
||||
.RE
|
||||
|
||||
.SH AUTHOR
|
||||
|
||||
Written by DTB <trinity@trinity.moe>.
|
||||
|
||||
.SH COPYRIGHT
|
||||
|
||||
Copyright (c) 2023-2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
|
||||
<https://gnu.org/licenses/gpl.html>.
|
||||
|
||||
.SH SEE ALSO
|
||||
|
||||
env(1), ioctl(2), ioctl_tty(2), read(1), sh(1)
|
159
src/peek.c
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 2023–2024 DTB <trinity@trinity.moe>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
#include <signal.h> /* sigaction(2), signal(2), struct sigaction, SIGINT */
|
||||
#include <stdio.h> /* fclose(3), fdopen(3), fprintf(3), getc(3), perror(3),
|
||||
* putc(3), stderr, stdin, stdout, EOF, NULL */
|
||||
#include <stdlib.h> /* exit(3), size_t, EXIT_FAILURE */
|
||||
#include <string.h> /* strerror(3) */
|
||||
#if !defined EX_OK || !defined EX_OSERR || !defined EX_USAGE
|
||||
# include <sysexits.h>
|
||||
#endif
|
||||
#include <termios.h> /* tcgetattr(3), tcsetattr(3), struct termios, ECHO */
|
||||
#include <unistd.h> /* dup(2), execvp(3), fork(2), getopt(3), isatty(3),
|
||||
* pipe(2), STDIN_FILENO */
|
||||
|
||||
static int oserr(char *s){ perror(s); return EX_OSERR; }
|
||||
|
||||
static char *program_name = "peek";
|
||||
|
||||
static int usage(char *s){
|
||||
fprintf(stderr, "Usage: %s (-1enot) (-p [program [arguments...]])\n",
|
||||
s == NULL ? program_name : s);
|
||||
return EX_USAGE;
|
||||
}
|
||||
|
||||
/* Restores terminal echo; otherwise when a user ^Cs the terminal would
|
||||
* continue to not display typed text. If sig isn't zero, this will terminate
|
||||
* the program. */
|
||||
void restore_echo(int sig){
|
||||
static struct termios t;
|
||||
|
||||
tcgetattr(STDIN_FILENO, &t);
|
||||
t.c_lflag |= ECHO;
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);
|
||||
|
||||
if(sig != 0)
|
||||
exit(EXIT_FAILURE);
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
int eof;
|
||||
char include_eof;
|
||||
FILE *outputs[] = {
|
||||
NULL /* stdout */,
|
||||
NULL /* stderr */,
|
||||
NULL /* -p */
|
||||
};
|
||||
int p[2] = {0, 0};
|
||||
|
||||
if(argc < 1)
|
||||
trinity marked this conversation as resolved
Outdated
emma
commented
Remember to handle the possible errors here! Remember to handle the possible errors here!
|
||||
usage(argv[0]);
|
||||
|
||||
eof = EOF;
|
||||
include_eof = 0;
|
||||
{ /* options parsing */
|
||||
int c;
|
||||
|
||||
while((c = getopt(argc, argv, "1enopt")) != -1)
|
||||
switch(c){
|
||||
case '1': eof = '\n'; break;
|
||||
case 'n': include_eof = 1; break;
|
||||
case 'o': outputs[0] = stdout; break;
|
||||
case 'e': outputs[1] = stderr; break;
|
||||
case 'p':
|
||||
if(pipe(p) != 0 || (outputs[2] = fdopen(p[1], "ab")) == NULL)
|
||||
return oserr(argv[0]);
|
||||
break;
|
||||
case 't':
|
||||
if(isatty(STDIN_FILENO) != 1){
|
||||
fprintf(stderr, "%s: Must be run in a terminal"
|
||||
" (option -t specified)\n", argv[0]);
|
||||
return EX_USAGE;
|
||||
}
|
||||
default: return usage(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/* If -p is used there must be additional arguments. getopt(3) wouldn't
|
||||
* work for this because optarg would have to be one string to give to
|
||||
* system(3) or an equivalent and it would be a mess of parsing and
|
||||
* security issues. Any intended usage works with this slightly funkier
|
||||
* argument parsing, unintended usages work as happy coincidence. */
|
||||
if((argc > optind) == (outputs[2] == 0))
|
||||
return usage(argv[0]);
|
||||
|
||||
if(outputs[2] != 0)
|
||||
switch(fork()){
|
||||
case 0:
|
||||
if(close(p[1]) == 0 && dup2(p[0], STDIN_FILENO) == STDIN_FILENO)
|
||||
execvp(argv[optind], &argv[optind]);
|
||||
case -1:
|
||||
return oserr(argv[0]);
|
||||
default:
|
||||
if(close(p[0]) != 0)
|
||||
return oserr(argv[0]);
|
||||
}
|
||||
|
||||
{ /* install signal handler */
|
||||
/* There isn't a difference in functionality between the signal(2) and
|
||||
* sigaction(2) methods. sigaction(2) is vastly preferred for
|
||||
* portability but some configurations can only use signal(2). */
|
||||
/* Errors aren't terminating because the worst that happens is some
|
||||
* terminal phooeyness if things go awry. */
|
||||
#if defined _POSIX_C_SOURCE
|
||||
struct sigaction act = { 0 };
|
||||
|
||||
act.sa_handler = restore_echo;
|
||||
if(sigaction(SIGINT, &act, NULL) != 0)
|
||||
perror(argv[0]);
|
||||
#else
|
||||
if(signal(SIGINT, restore_echo) == SIG_ERR)
|
||||
perror(argv[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
{ /* banish terminal echo */
|
||||
struct termios t;
|
||||
|
||||
tcgetattr(STDIN_FILENO, &t);
|
||||
t.c_lflag ^= ECHO;
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);
|
||||
}
|
||||
|
||||
{ /* actual input loop */
|
||||
int c;
|
||||
size_t i;
|
||||
|
||||
do{ if((c = getc(stdin)) != eof || include_eof)
|
||||
for(i = 0; i < (sizeof outputs)/(sizeof *outputs); ++i)
|
||||
if(outputs[i] != NULL && putc(c, outputs[i]) == EOF){
|
||||
perror(argv[0]);
|
||||
if(outputs[i] != stdout && outputs[i] != stderr)
|
||||
fclose(outputs[i]);
|
||||
outputs[i] = NULL;
|
||||
}
|
||||
}while(c != eof);
|
||||
}
|
||||
|
||||
restore_echo(0);
|
||||
|
||||
return EX_OK;
|
||||
}
|