File: [local] / src / usr.sbin / lpd / lp.c (download)
Revision 1.4, Wed Dec 28 21:30:17 2022 UTC (17 months ago) by jmc
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD Changes since 1.3: +3 -3 lines
spelling fixes; from paul tagliamonte
any parts of his diff not taken are noted on tech
|
/* $OpenBSD: lp.c,v 1.4 2022/12/28 21:30:17 jmc Exp $ */
/*
* Copyright (c) 2017 Eric Faurot <eric@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lp.h"
#include "log.h"
#define XXX_PID_MAX 99999
struct qentry {
char *name;
time_t mtime;
};
static int readent(struct lp_printer *, char *);
static int qentrycmp(const void *, const void *);
static int fullpath(struct lp_printer *, const char *, char *, size_t);
static int checksize(struct lp_printer *, size_t);
static int scanning;
static char *db[] = {
_PATH_PRINTCAP,
NULL,
};
/*
* Fill a printer structure from its /etc/printcap entry.
* Return 0 on success, or -1 on error.
*/
int
lp_getprinter(struct lp_printer *lp, const char *name)
{
char *buf = NULL;
int r;
memset(lp, 0, sizeof(*lp));
r = cgetent(&buf, db, name);
if (r == 0)
r = readent(lp, buf);
free(buf);
switch (r) {
case -3:
log_warnx("%s: potential reference loop", name);
break;
case -2:
log_warn("%s", name);
break;
case -1:
log_warnx("%s: unknown printer", name);
break;
case 0:
return 0;
case 1:
log_warnx("%s: unresolved tc expansion", name);
break;
default:
log_warnx("%s: unexpected return value %d", name, r);
}
lp_clearprinter(lp);
return -1;
}
/*
* Iterate over /etc/printcap and fill the printer structure from the next
* entry, if any.
*
* Return 0 if no entry is found.
* 1 if a printer is filled.
* -1 on error, and set errno.
*/
int
lp_scanprinters(struct lp_printer *lp)
{
char *buf;
int r, saved_errno;
if (scanning++ == 0)
r = cgetfirst(&buf, db);
else
r = cgetnext(&buf, db);
if (r == 0) {
cgetclose();
scanning = 0;
return 0;
} else if (r == 1) {
memset(lp, 0, sizeof(*lp));
r = readent(lp, buf);
free(buf);
if (r == -2)
goto fail;
return 1;
} else if (r == -1)
fatal("cannot open %s", _PATH_PRINTCAP);
else if (r == -2)
errno = ELOOP; /* potential reference loop */
fail:
saved_errno = errno;
lp_clearprinter(lp);
cgetclose();
scanning = 0;
errno = saved_errno;
return -1;
}
static int
readent(struct lp_printer *lp, char *buf)
{
char *s;
s = strchr(buf, ':');
if (s)
*s = '\0';
lp->lp_name = strdup(buf);
if (s)
*s = ':';
if (lp->lp_name == NULL)
return -2;
s = lp->lp_name;
while ((s = strchr(s, '|'))) {
*s++ = '\0';
if (lp->lp_aliascount < LP_MAXALIASES)
lp->lp_aliases[lp->lp_aliascount++] = s;
}
#define CGETSTR(x) if (cgetstr(buf, #x, &(lp->lp_ ## x)) == -2) \
goto fail
#define CGETNUM(x, d) if (cgetnum(buf, #x, &(lp->lp_ ## x)) == -1) \
(lp->lp_ ## x) = d;
#define CGETBOOL(x) lp->lp_ ## x = cgetcap(buf, #x, ':') ? 1 : 0
CGETSTR(af);
CGETNUM(br, 0);
CGETSTR(cf);
CGETSTR(df);
CGETSTR(ff);
CGETBOOL(fo);
CGETSTR(gf);
CGETBOOL(hl);
CGETBOOL(ic);
CGETSTR(if);
CGETSTR(lf);
CGETSTR(lo);
CGETSTR(lp);
CGETNUM(mc, 0);
CGETSTR(ms);
CGETNUM(mx, 0);
CGETSTR(nd);
CGETSTR(nf);
CGETSTR(of);
CGETNUM(pc, DEFAULT_PC);
CGETNUM(pl, DEFAULT_PL);
CGETNUM(pw, DEFAULT_PW);
CGETNUM(px, 0);
CGETNUM(py, 0);
CGETSTR(rf);
CGETSTR(rg);
CGETSTR(rm);
CGETSTR(rp);
CGETBOOL(rs);
CGETBOOL(rw);
CGETBOOL(sb);
CGETBOOL(sc);
CGETSTR(sd);
CGETBOOL(sf);
CGETBOOL(sh);
CGETSTR(st);
CGETSTR(tf);
CGETSTR(tr);
CGETSTR(vf);
if (lp->lp_rm && lp->lp_rm[0]) {
lp->lp_type = PRN_LPR;
lp->lp_host = lp->lp_rm;
lp->lp_port = NULL;
}
else if (strchr(LP_LP(lp), '@')) {
lp->lp_type = PRN_NET;
lp->lp_port = strdup(LP_LP(lp));
lp->lp_host = strchr(lp->lp_port, '@');
*lp->lp_host++ = '\0';
}
else {
lp->lp_type = PRN_LOCAL;
}
return 0;
fail:
return -2;
}
void
lp_clearprinter(struct lp_printer *lp)
{
free(lp->lp_name);
free(lp->lp_port);
if (lp->lp_lock)
fclose(lp->lp_lock);
free(lp->lp_af);
free(lp->lp_cf);
free(lp->lp_df);
free(lp->lp_ff);
free(lp->lp_gf);
free(lp->lp_if);
free(lp->lp_lf);
free(lp->lp_lo);
free(lp->lp_lp);
free(lp->lp_ms);
free(lp->lp_nd);
free(lp->lp_nf);
free(lp->lp_of);
free(lp->lp_rf);
free(lp->lp_rg);
free(lp->lp_rm);
free(lp->lp_rp);
free(lp->lp_sd);
free(lp->lp_st);
free(lp->lp_tf);
free(lp->lp_tr);
free(lp->lp_vf);
memset(lp, 0, sizeof(*lp));
}
static int
qentrycmp(const void *aa, const void *bb)
{
const struct qentry *a = aa, *b = bb;
if (a->mtime < b->mtime)
return -1;
if (a->mtime > b->mtime)
return 1;
return strcmp(a->name, b->name);
}
/*
* Read the printer queue content.
* Return the task count, or -1 on error.
*/
int
lp_readqueue(struct lp_printer *lp, struct lp_queue *q)
{
struct qentry *qe = NULL, *tqe;
struct dirent *d;
struct stat st;
size_t len, sz, nqi, nqe = 0, nqa = 0, n, i;
char path[PATH_MAX], *end;
DIR *dp= NULL;
len = strlcpy(path, LP_SD(lp), sizeof(path));
if (len == 0 || len >= sizeof(path)) {
log_warn("%s: %s: invalid spool directory name",
__func__, LP_SD(lp));
goto fail;
}
if ((dp = opendir(path)) == NULL) {
log_warn("%s: opendir", __func__);
goto fail;
}
if (fstat(dirfd(dp), &st) == -1) {
log_warn("%s: fstat", __func__);
goto fail;
}
/* Assume half the files are cf files. */
nqi = st.st_nlink / 2;
if (path[len-1] != '/') {
len = strlcat(path, "/", sizeof(path));
if (len >= sizeof(path)) {
errno = ENAMETOOLONG;
log_warn("%s: strlcat", __func__);
goto fail;
}
}
end = path + len;
sz = sizeof(path) - len;
errno = 0;
while ((d = readdir(dp))) {
if (d->d_name[0] != 'c' || d->d_name[1] != 'f')
continue;
if (strlen(d->d_name) < 7)
continue;
if (!isdigit((unsigned char)d->d_name[3]) ||
!isdigit((unsigned char)d->d_name[4]) ||
!isdigit((unsigned char)d->d_name[5]))
continue;
if (strlcpy(end, d->d_name, sz) >= sz) {
errno = ENAMETOOLONG;
log_warn("%s: strlcat", __func__);
goto fail;
}
if (stat(path, &st) == -1) {
log_warn("%s: stat", __func__);
goto fail;
}
if (nqe == nqa) {
n = nqa ? (nqa + 5) : nqi;
tqe = recallocarray(qe, nqa, n, sizeof(*qe));
if (tqe == NULL) {
log_warn("%s: recallocarray", __func__);
goto fail;
}
qe = tqe;
nqa = n;
}
if ((qe[nqe].name = strdup(d->d_name)) == NULL) {
log_warn("%s: strdup", __func__);
goto fail;
}
qe[nqe].mtime = st.st_mtime;
nqe++;
}
if (errno) {
log_warn("%s: readdir", __func__);
goto fail;
}
closedir(dp);
dp = NULL;
qsort(qe, nqe, sizeof(*qe), qentrycmp);
q->count = nqe;
q->cfname = calloc(nqe, sizeof(*q->cfname));
if (q->cfname == NULL) {
log_warn("%s: calloc", __func__);
goto fail;
}
for (i = 0; i < nqe; i++)
q->cfname[i] = qe[i].name;
free(qe);
return nqe;
fail:
if (dp)
closedir(dp);
for (i = 0; i < nqe; i++)
free(qe[i].name);
free(qe);
lp_clearqueue(q);
return -1;
}
void
lp_clearqueue(struct lp_queue *q)
{
int i;
for (i = 0; i < q->count; i++)
free(q->cfname[i]);
free(q->cfname);
}
static int
fullpath(struct lp_printer *lp, const char *fname, char *dst, size_t sz)
{
int r;
r = snprintf(dst, sz, "%s/%s", LP_SD(lp), fname);
if (r < 0 || (size_t)r >= sz) {
errno = ENAMETOOLONG;
return -1;
}
return 0;
}
/*
* fopen(3) a file in the printer spooldir for reading.
*/
FILE *
lp_fopen(struct lp_printer *lp, const char *fname)
{
char path[PATH_MAX];
if (fullpath(lp, fname, path, sizeof(path)) == -1)
return NULL;
return fopen(path, "r");
}
/*
* unlink(2) a file in the printer spooldir.
*/
int
lp_unlink(struct lp_printer *lp, const char *fname)
{
char path[PATH_MAX];
if (fullpath(lp, fname, path, sizeof(path)) == -1)
return -1;
return unlink(path);
}
/*
* stat(2) a file in the printer spooldir.
*/
int
lp_stat(struct lp_printer *lp, const char *fname, struct stat *st)
{
char path[PATH_MAX];
if (fullpath(lp, fname, path, sizeof(path)) == -1)
return -1;
return stat(path, st);
}
/*
* Grab the lockfile for this printer, and associate it to the printer.
* Return -1 on error or 0 otherwise.
*/
int
lp_lock(struct lp_printer *lp)
{
char path[PATH_MAX];
struct stat st;
int fd, saved_errno;
if (lp->lp_lock) {
errno = EWOULDBLOCK;
return -1;
}
if (fullpath(lp, LP_LO(lp), path, sizeof(path)) == -1) {
log_warn("%s: %s", __func__, LP_LO(lp));
return -1;
}
fd = open(path, O_WRONLY|O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_EXLOCK|O_TRUNC,
0640);
if (fd == -1) {
if (errno != EWOULDBLOCK)
log_warn("%s: open", __func__);
return -1;
}
if (fstat(fd, &st) == -1) {
log_warn("%s: fstat", __func__);
saved_errno = errno;
(void)close(fd);
errno = saved_errno;
return -1;
}
if (!S_ISREG(st.st_mode)) {
errno = EFTYPE;
log_warnx("%s: %s: Not a regular file", __func__, path);
(void)close(fd);
return -1;
}
if ((lp->lp_lock = fdopen(fd, "w")) == NULL) {
log_warn("%s: fdopen", __func__);
saved_errno = errno;
(void)close(fd);
errno = saved_errno;
return -1;
}
lp_setcurrtask(lp, NULL);
return 0;
}
/*
* Truncate the lock file and close it.
*/
void
lp_unlock(struct lp_printer *lp)
{
if (ftruncate(fileno(lp->lp_lock), 0) == -1)
log_warn("%s: ftruncate", __func__);
if (fclose(lp->lp_lock) == EOF)
log_warn("%s: fclose", __func__);
lp->lp_lock = NULL;
}
int
lp_getqueuestate(struct lp_printer *lp, int reset, int *qstate)
{
FILE *fp;
struct stat st;
int saved_errno;
*qstate = 0;
fp = lp->lp_lock;
if (lp->lp_lock == NULL) {
fp = lp_fopen(lp, LP_LO(lp));
if (fp == NULL) {
if (errno == ENOENT)
return 0;
log_warn("%s: lp_fopen", __func__);
return -1;
}
}
if (fstat(fileno(fp), &st) == -1) {
log_warn("%s: fstat", __func__);
if (lp->lp_lock == NULL) {
saved_errno = errno;
(void)fclose(fp);
errno = saved_errno;
}
return -1;
}
if (st.st_mode & S_IXUSR)
*qstate |= LPQ_PRINTER_DOWN;
if (st.st_mode & S_IXGRP)
*qstate |= LPQ_QUEUE_OFF;
if (st.st_mode & S_IXOTH) {
*qstate |= LPQ_QUEUE_UPDATED;
if (reset) {
st.st_mode &= ~S_IXOTH & 0777;
if (fchmod(fileno(lp->lp_lock), st.st_mode) == -1)
log_warn("%s: fchmod", __func__);
}
}
if (lp->lp_lock == NULL)
fclose(fp);
return 0;
}
/*
* Update the current task description in the lock file.
* The lock file must be opened.
*/
void
lp_setcurrtask(struct lp_printer *lp, const char *cfile)
{
int r;
if (ftruncate(fileno(lp->lp_lock), 0) == -1)
log_warn("%s: ftruncate", __func__);
if (cfile)
r = fprintf(lp->lp_lock, "%d\n%s\n", (int)getpid(), cfile);
else
r = fprintf(lp->lp_lock, "%d\n", (int)getpid());
if (r < 0)
log_warn("%s: fprintf", __func__);
if (fflush(lp->lp_lock) != 0)
log_warn("%s: fflush", __func__);
}
/*
* Find the pid of the running process if any, and the task being printed.
*
* Returns -1 on error (errno set).
* 0 if no process is running.
* 1 if a printer process is up.
* 2 if a printer process is up and a file is being printed.
*/
int
lp_getcurrtask(struct lp_printer *lp, pid_t *ppid, char *dst, size_t dstsz)
{
FILE *fp;
const char *errstr;
char *line = NULL;
size_t linesz = 0;
ssize_t len;
pid_t pid;
int r, saved_errno;
pid = *ppid = 0;
dst[0] = '\0';
r = -1;
fp = lp_fopen(lp, LP_LO(lp));
if (fp == NULL) {
if (errno == ENOENT)
return 0;
log_warn("%s: lp_fopen", __func__);
return -1;
}
while ((len = getline(&line, &linesz, fp)) != -1) {
if (line[len-1] == '\n')
line[len-1] = '\0';
/* Read filename. */
if (pid) {
(void)strlcpy(dst, line, dstsz);
break;
}
pid = strtonum(line, 2, XXX_PID_MAX, &errstr);
if (errstr) {
log_warn("%s: strtonum", __func__);
goto done;
}
}
if ((errno = ferror(fp))) {
log_warn("%s: getline", __func__);
goto done;
}
r = 0;
if (pid == 0)
goto done;
if (kill(pid, 0) == -1 && errno != EPERM) {
if (errno != ESRCH)
log_warn("%s: kill", __func__);
goto done;
}
*ppid = pid;
r = dst[0] ? 2 : 1;
done:
free(line);
saved_errno = errno;
(void)fclose(fp);
errno = saved_errno;
return r;
}
/*
* Read the current printer status file.
* Return -1 on error, 0 on success.
*/
int
lp_getstatus(struct lp_printer *lp, char *buf, size_t bufsz)
{
size_t len;
char *line;
FILE *fp;
int saved_errno;
buf[0] = '\0';
fp = lp_fopen(lp, LP_ST(lp));
if (fp == NULL) {
if (errno == ENOENT)
return 0;
log_warn("%s: lp_fopen", __func__);
return -1;
}
line = fgetln(fp, &len);
if (line) {
if (len >= bufsz)
len = bufsz - 1;
else if (line[len - 1] == '\n')
len--;
memmove(buf, line, len);
buf[len] = '\0';
}
else if ((errno = ferror(fp))) {
log_warn("%s: fgetln", __func__);
saved_errno = errno;
(void)fclose(fp);
errno = saved_errno;
return -1;
}
(void)fclose(fp);
return 0;
}
/*
* Update the current printer status.
*/
void
lp_setstatus(struct lp_printer *lp, const char *fmt, ...)
{
va_list ap;
FILE *fp;
char path[PATH_MAX], *s;
int fd = -1, r, saved_errno;
va_start(ap, fmt);
r = vasprintf(&s, fmt, ap);
va_end(ap);
if (r == -1) {
log_warn("%s: vasprintf", __func__);
return;
}
if (fullpath(lp, LP_ST(lp), path, sizeof(path)) == -1) {
log_warn("%s: fullpath", __func__);
free(s);
return;
}
fd = open(path, O_WRONLY|O_CREAT|O_NOFOLLOW|O_EXLOCK|O_TRUNC, 0660);
if (fd == -1) {
log_warn("%s: open", __func__);
free(s);
return;
}
fp = fdopen(fd, "w");
if (fp == NULL) {
log_warn("%s: fdopen", __func__);
saved_errno = errno;
(void)close(fd);
free(s);
errno = saved_errno;
return;
}
r = fprintf(fp, "%s\n", s);
if (r <= 0) {
log_warn("%s: fprintf", __func__);
saved_errno = errno;
(void)fclose(fp);
free(s);
errno = saved_errno;
return;
}
(void)fclose(fp);
free(s);
}
/*
* Check that the given string is a valid filename for the spooler.
*/
int
lp_validfilename(const char *fname, int cf)
{
size_t len, i;
len = strlen(fname);
if (len <= 6) /* strlen("cfA000") */
return 0;
if (fname[0] != (cf ? 'c' : 'd') ||
fname[1] != 'f' ||
fname[2] < 'A' ||
fname[2] > 'Z' ||
!isdigit((unsigned char)fname[3]) ||
!isdigit((unsigned char)fname[4]) ||
!isdigit((unsigned char)fname[5]))
return 0;
for (i = 6; i < len; i++)
if (!isalpha((unsigned char)fname[i]) &&
!isdigit((unsigned char)fname[i]) &&
strchr("-_.", (unsigned char)fname[i]) == NULL)
return 0;
return 1;
}
/*
* Create a new control or data file in the printer spooldir.
* Control files have there name changed to a temporary name. They are renamed
* to their final name by lp_commit().
*/
int
lp_create(struct lp_printer *lp, int cf, size_t sz, const char *fname)
{
struct stat st;
char path[PATH_MAX];
int fd;
/* Make sure the file size is acceptable. */
if (checksize(lp, sz) == -1)
return -1;
if (fullpath(lp, fname, path, sizeof(path)) == -1) {
log_warn("%s: %s", __func__, fname);
return -1;
}
if (cf) {
/*
* Create a temporary file, but we want to avoid
* a collision with the final cf filename.
*/
/* XXX this would require a lock on .seq */
path[strlen(LP_SD(lp)) + 1] = 'c';
if (stat(path, &st) == 0) {
errno = EEXIST;
log_warn("%s: %s", __func__, fname);
return -1;
}
path[strlen(LP_SD(lp)) + 1] = 't';
}
fd = open(path, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, 0660);
if (fd == -1)
log_warn("%s: open", __func__);
return fd;
}
/*
* Commit the job given by its temporary CF name.
* This is done by renaming the temporary CF file name to its final name.
* The functions return 0 on success, or -1 on error and set errno.
*/
int
lp_commit(struct lp_printer *lp, const char *cf)
{
char ipath[PATH_MAX], opath[PATH_MAX];
if (fullpath(lp, cf, ipath, sizeof(ipath)) == -1 ||
fullpath(lp, cf, opath, sizeof(opath)) == -1)
return -1;
ipath[strlen(LP_SD(lp)) + 1] = 't';
opath[strlen(LP_SD(lp)) + 1] = 'c';
return rename(ipath, opath);
}
/*
* Check if a file size is acceptable.
* Return -1 on error or if false (EFBIG or ENOSPC), 0 otherwise.
*/
static int
checksize(struct lp_printer *lp, size_t sz)
{
struct statfs st;
ssize_t len;
size_t linesz = 0;
off_t req, avail, minfree;
char *line = NULL;
const char *errstr;
FILE *fp;
int saved_errno;
if (sz == 0) {
errno = EINVAL;
return -1;
}
/* Check printer limit. */
if (lp->lp_mx && sz > (size_t)lp->lp_mx) {
errno = EFBIG;
return -1;
}
/*
* Check for minfree. Note that it does not really guarantee the
* directory will not be filled up anyway, since it's not taking
* other incoming files into account.
*/
fp = lp_fopen(lp, "minfree");
if (fp == NULL) {
if (errno == ENOENT)
return 0;
log_warn("%s: lp_fopen", __func__);
return -1;
}
len = getline(&line, &linesz, fp);
saved_errno = errno;
(void)fclose(fp);
if (len == -1) {
errno = saved_errno;
return 0;
}
if (line[len - 1] == '\n')
line[len - 1] = '\0';
minfree = strtonum(line, 0, INT_MAX, &errstr);
free(line);
if (errstr)
return 0;
if (statfs(LP_SD(lp), &st) == -1)
return 0;
req = sz / 512 + (sz % 512) ? 1 : 0;
avail = st.f_bavail * (st.f_bsize / 512);
if (avail < minfree || (avail - minfree) < req) {
errno = ENOSPC;
return -1;
}
return 0;
}