Skip to content

Instantly share code, notes, and snippets.

@elliott-beach
Created October 2, 2017 16:27
Show Gist options
  • Save elliott-beach/77743f251c98a2870db7ce220318ce55 to your computer and use it in GitHub Desktop.
Save elliott-beach/77743f251c98a2870db7ce220318ce55 to your computer and use it in GitHub Desktop.
Version 1.4
/* Badly Coded Print Server BCLPR, a product of Badly Coded, Inc. */
/* Change log: */
/* Version 1.4, released 10/ 6/2014 */
/* Version 1.3, released 9/29/2014 */
/* Version 1.2, released 9/22/2014 */
/* Version 1.1, released 9/15/2014 */
/* Version 1.0, released 9/ 9/2014 */
/* This is an example insecure program for CSci 5271 only: don't copy
code from here to any program that is supposed to work
correctly! */
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#define NAME_MAX 255
#define INSTALL_PREFIX "/var/bclpr"
#define SPOOL_PARENT INSTALL_PREFIX "/spool"
#define OUT_PARENT INSTALL_PREFIX "/printouts"
#define LOG_FILENAME INSTALL_PREFIX "/bclpr.log"
#define FORMAT_PLAIN_TEXT 0
#define FORMAT_PDF 1
int format = 0;
char *printer_name = 0;
char *message = 0;
char *filename;
int num_pages = 0;
#define min(a, b) ((a) < (b) ? (a) : (b))
#define BLOCK_SIZE 4096
char block[4096];
/* Process command line arguments in argc and argv, and update a
pointer to the name of the file to print in filep. */
void parse_args(int argc, char **argv, char **filep) {
int i;
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-p")) {
register int j, len;
char lcbuf['2'+'0'+'0'];
if (i + 1 >= argc) {
fprintf(stderr, "Missing argument to -p (printer name)\n");
exit(1);
}
printer_name = argv[++i];
if (printer_name[0] == '/') {
fprintf(stderr, "Printer name must not be a path\n");
exit(1);
}
for (j = 0; j < strlen(printer_name) - 1; j++) {
if (printer_name[j] == '.' && printer_name[j+1] == '.') {
fprintf(stderr, "Printer name may not contain '..'\n");
exit(1);
}
j++;
}
/* Convert printer name to lower case */
len = min(strlen(printer_name) + 1, 200 - 3*sizeof(len) - 1);
memset(lcbuf, '\0', len);
for (j = 0; j < len - 1 && printer_name[j]; j++) {
lcbuf[j] = tolower(printer_name[j]);
}
printer_name = strdup(lcbuf);
} else if (!strcmp(argv[i], "-m")) {
if (i + 1 >= argc) {
fprintf(stderr, "Missing argument to -m (log message)\n");
exit(1);
}
message = argv[++i];
} else if (!strcmp(argv[i], "-v")) {
fprintf(stderr, "BCLPR version 1.4 (for exploits due 10/10)\n");
exit(1);
} else if (argv[i][0] == '-') {
fprintf(stderr, "Unrecognized option %s\n", argv[i]);
exit(1);
} else {
if (*filep) {
fprintf(stderr, "Only one file is supported\n");
exit(1);
}
*filep = argv[i];
}
}
if (!*filep) {
fprintf(stderr, "No file specified\n");
exit(1);
}
}
#define PATH_MAX 512
/* Verify that we have permissions to open a file, so that users can't
use BCLPR to print a file that they would otherwise be prohibited
from reading. */
void check_permissions(char *file) {
char fullpath[PATH_MAX];
register char *endptr = fullpath;
int res;
if (file[0] != '/') {
getcwd(fullpath, sizeof(fullpath));
strcat(fullpath, "/");
endptr += strlen(fullpath) + 1;
} else {
fullpath[0] = '\0';
}
endptr += strlen(file);
strcat(fullpath, file);
if (endptr > fullpath + PATH_MAX)
abort(); /* possible buffer overflow? */
/* Because fullpath is absolute, the AT_FDCWD argument is ignored */
res = faccessat(AT_FDCWD, fullpath, R_OK, AT_SYMLINK_NOFOLLOW);
if (res) {
fprintf(stderr, "You don't have permission to print %s\n", fullpath);
exit(1);
}
}
/* A version of chdir(2) with error handling: the second arg is a
special message to use if the directory does not exist. */
void chdir_or_die(const char *dir, const char *enoent_msg) {
int res = chdir(dir);
if (res) {
if (errno == ENOENT) {
fprintf(stderr, enoent_msg);
} else {
fprintf(stderr, "Move to directory %s failed: %s\n",
dir, strerror(errno));
}
exit(1);
}
}
#define MAXNAMELEN 10
/* strlcpy: secure version of strcpy(), copied from OpenBSD */
/*
* Copy src to string dst of size siz. At most siz-1 characters
* will be copied. Always NUL terminates (unless siz == 0).
* Returns strlen(src); if retval >= siz, truncation occurred.
*/
size_t
strlcpy(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
/* Copy as many bytes as will fit */
if (n != 0 && --n != 0) {
do {
if ((*d++ = *s++) == 0)
break;
} while (--n != 0);
}
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++)
;
}
return(s - src - 1); /* count does not include NUL */
}
/* strlcat: secure version of strcat(), copied from OpenBSD */
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
* If retval >= siz, truncation occurred.
*/
size_t
strlcat(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return(dlen + strlen(s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return(dlen + (s - src)); /* count does not include NUL */
}
/* Make spool files and printouts be owned by the user, and not
readable by others. */
void set_perms(const char *fname) {
struct passwd *pwp;
char username[MAXNAMELEN * 2];
int mode = 0600;
/* Don't forget to include space for \0 terminators */
strlcpy(username, getenv("USER"), 2 * MAXNAMELEN + 2 * sizeof(NULL));
pwp = getpwnam(username);
if (pwp)
chown(fname, pwp->pw_uid, pwp->pw_gid);
chmod(fname, mode & 07777);
}
/* Copy a file named FILE and already open on IN_FD into the
appropriate spool directory in preparation for printing. */
void copy_to_spool(char *file, int in_fd) {
int out_fd, res;
char *p;
struct stat statbuf;
chdir_or_die(SPOOL_PARENT,
"Missing install directory " SPOOL_PARENT "\n");
chdir_or_die(printer_name, "Unrecognized printer name\n");
if ((p = strrchr(file, '/'))) {
filename = p + 1;
assert(*p);
} else {
filename = file;
}
res = stat(filename, &statbuf);
if (!res) {
/* Someone else is printing the same file, let them finish */
sleep(5);
}
out_fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (out_fd == -1) {
fprintf(stderr, "Failed to open %s/%s/%s for writing: %s\n",
SPOOL_PARENT, printer_name, filename, strerror(errno));
exit(1);
}
for (;;) {
int num_read = read(in_fd, block, BLOCK_SIZE);
if (num_read == -1) {
fprintf(stderr, "Failure reading input file: %s\n",
strerror(errno));
exit(1);
} else if (num_read == 0)
break;
res = write(out_fd, block, num_read);
if (res != num_read) {
fprintf(stderr, "Failure writing spool file: %s\n",
strerror(errno));
exit(1);
}
}
close(in_fd);
close(out_fd);
set_perms(filename);
}
#define DO_LOCK 1
#define DO_UNLOCK 0
void lock_unlock_output(char *dirname, int op, int *count) {
int dirfd, lockfd = -1, countfd, res;
char buf[80];
assert(op == DO_LOCK || op == DO_UNLOCK);
dirfd = open(dirname, O_RDONLY|O_DIRECTORY);
if (dirfd == -1) {
fprintf(stderr, "Failed to open output directory %s: %s\n",
dirname, strerror(errno));
exit(1);
}
/* For lock: wait until "lock" does not exist, then create it */
if (op == DO_LOCK) {
while (lockfd == -1) {
lockfd = openat(dirfd, "lock", O_CREAT|O_EXCL, 0666);
if (lockfd == -1 && errno == EEXIST) {
fprintf(stderr, "Waiting for lock\n");
sleep(1);
} else if (lockfd == -1) {
fprintf(stderr, "Error when opening lock file: %s\n",
strerror(errno));
exit(1);
}
}
close(lockfd);
}
countfd = openat(dirfd, "count", O_RDWR|O_CREAT, 0666);
if (countfd == -1) {
fprintf(stderr, "Failed to open output count file: %s\n",
strerror(errno));
unlinkat(dirfd, "lock", 0);
exit(1);
}
if (op == DO_LOCK) {
/* Read the old count, if any */
res = read(countfd, buf, sizeof(buf) - 1);
if (res == -1) {
fprintf(stderr, "Failed to read count file: %s\n",
strerror(errno));
unlinkat(dirfd, "lock", 0);
exit(1);
} else if (res == 0) {
/* Empty file is equivalent to zero */
*count = 0;
} else {
/* Non-empty: parse as integer */
buf[res] = 0;
*count = atoi(buf);
}
} else {
/* Write back the updated count */
res = snprintf(buf, sizeof(buf), "%d\n", *count);
if (res == 0 || res >= sizeof(buf)) {
fprintf(stderr, "Unexpected overflow of page count\n");
unlinkat(dirfd, "lock", 0);
exit(1);
}
res = write(countfd, buf, strlen(buf));
if (res != strlen(buf)) {
if (res == -1) {
fprintf(stderr, "Write of output count failed: %s\n",
strerror(errno));
} else {
fprintf(stderr, "Short write of output count\n");
}
unlinkat(dirfd, "lock", 0);
exit(1);
}
}
close(countfd);
if (op == DO_UNLOCK) {
res = unlinkat(dirfd, "lock", 0);
if (res == -1) {
fprintf(stderr, "Failed to remove output lock: %s\n",
strerror(errno));
exit(1);
}
}
close(dirfd);
}
void output_text(FILE *spool_fh) {
int page_count, res;
int job_done = 0;
char page_name[80];
chdir_or_die(OUT_PARENT,
"Missing install directory " OUT_PARENT "\n");
chdir_or_die(printer_name,
"Unrecognized printer name (missing output)\n");
lock_unlock_output(".", DO_LOCK, &page_count);
while (!job_done) {
FILE *page_fh;
int line = 0;
char line_buf[256];
page_count++;
snprintf(page_name, sizeof(page_name), "page%03d.txt", page_count);
page_fh = fopen(page_name, "w");
if (!page_fh) {
fprintf(stderr, "Failed to open page %d: %s\n",
page_count, strerror(errno));
break;
}
for (;;) {
char *p;
p = fgets(line_buf, sizeof(line_buf), spool_fh);
line++;
if (!p) {
job_done = 1;
break;
}
assert(strlen(p) < 255);
res = fputs(p, page_fh);
if (line >= 78)
break;
if (p[0] == '\f') {
if (feof(spool_fh))
job_done = 1;
break;
}
}
num_pages++;
res = fclose(page_fh);
set_perms(page_name);
if (res) {
fprintf(stderr, "Failed to close page %d: %s\n",
page_count, strerror(errno));
break;
}
}
lock_unlock_output(".", DO_UNLOCK, &page_count);
}
void cleanup_spoolfile(char *filename) {
char pathname[NAME_MAX + 1];
strlcpy(pathname, SPOOL_PARENT "/", NAME_MAX);
strlcat(pathname, printer_name, NAME_MAX);
strlcat(pathname, "/", NAME_MAX);
strlcat(pathname, filename, NAME_MAX);
unlink(pathname);
return;
}
FILE *safer_popen_r(char *program, char *const argv[]) {
int pipefd[2];
int res;
pid_t child;
res = pipe(pipefd);
if (res) {
fprintf(stderr, "Failed to create pipe: %s\n", strerror(errno));
exit(1);
}
child = fork();
if (child == -1) {
fprintf(stderr, "Fork failed: %s\n", strerror(errno));
exit(1);
}
if (child == 0) {
/* Child process: run program with stdout connected to pipe */
close(pipefd[0]);
dup2(pipefd[1], 1);
execv(program, argv);
fprintf(stderr, "Exec failed: %s\n", strerror(errno));
exit(1);
} else {
/* Parent process: create file handle to read from pipe */
close(pipefd[1]);
return fdopen(pipefd[0], "r");
}
}
int main(int argc, char **argv) {
int in_fd, res;
char *file = 0, header[4];
FILE *log_fh;
parse_args(argc, argv, &file);
check_permissions(file);
in_fd = open(file, O_RDONLY);
if (in_fd == -1) {
fprintf(stderr, "Failed to open '%s' for reading: %s\n", file,
strerror(errno));
exit(1);
}
res = read(in_fd, header, 4);
if (res == 4 && !strncmp(header, "%PDF", 4)) {
format = FORMAT_PDF;
} else {
format = FORMAT_PLAIN_TEXT;
}
res = lseek(in_fd, 0, SEEK_SET);
if (!printer_name) {
printer_name = "lp0";
}
copy_to_spool(file, in_fd);
if (format == FORMAT_PLAIN_TEXT) {
FILE *spool_fh = fopen(filename, "r");
if (!spool_fh) {
fprintf(stderr, "Failed to open spool file: %s\n",
strerror(errno));
exit(1);
}
output_text(spool_fh);
fclose(spool_fh);
} else {
char *args[4] = {"pdftotext", filename, "-", 0};
FILE *pipe_fh = safer_popen_r("/usr/bin/pdftotext", args);
if (!pipe_fh) {
fprintf(stderr, "Failed to open pdftotext pipe: %s\n",
strerror(errno));
}
output_text(pipe_fh);
fclose(pipe_fh);
}
cleanup_spoolfile(filename);
log_fh = fopen(LOG_FILENAME, "a");
if (!log_fh) {
fprintf(stderr, "Failed to open log file: %s\n", strerror(errno));
exit(1);
}
fprintf(log_fh, "Print job %s, printer %s, format %s, %d pages\n",
filename, printer_name, format ? "PDF" : "text", num_pages);
if (message) {
if (strlen(message) > 80) {
fprintf(stderr, "Log message too long, ignoring.\n");
} else {
fprintf(log_fh, "User message: ");
fprintf(log_fh, message);
fprintf(log_fh, "\n");
}
}
fclose(log_fh);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment