Created
October 2, 2017 16:27
-
-
Save elliott-beach/77743f251c98a2870db7ce220318ce55 to your computer and use it in GitHub Desktop.
Version 1.4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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