/*
 * Copyright (c) 1990,1994 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 *
 * Portions:
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Portions:
 * Copyright (C) 1998 WIDE Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Interface to lpr system.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <sys/param.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <ctype.h>
#include <unistd.h>

#include <sys/file.h>
#include <sys/un.h>
#include <netinet/in.h>
#undef s_net

#ifdef ABS_PRINT
#include <math.h>
#endif /* ABS_PRINT */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <fcntl.h>
#include <pwd.h>

#include <atalk/logger.h>
#include <netatalk/at.h>
#include <atalk/atp.h>
#include <atalk/paths.h>
#include <atalk/unicode.h>

#include "printer.h"
#include "file.h"
#include "lp.h"

#ifdef HAVE_CUPS
#include  "print_cups.h"
#endif

#ifndef HAVE_RRESVPORT
int
rresvport (int *alport)
{
	union {
		struct sockaddr generic;
		struct sockaddr_in in;
		struct sockaddr_in6 in6;
	} ss;
	int s;
	size_t len;
	uint16_t *sport;

	len = sizeof(struct sockaddr_in);
	sport = &ss.in.sin_port;
	/* NB: No SOCK_CLOEXEC for backwards compatibility.  */
	s = socket(AF_INET, SOCK_STREAM, 0);
	if (s < 0)
		return -1;

	memset (&ss, '\0', sizeof(ss));
#ifdef SALEN
	ss.generic.__ss_len = len;
#endif
	ss.generic.sa_family = AF_INET;

	/* Ignore invalid values.  */
	if (*alport < IPPORT_RESERVED / 2)
		*alport = IPPORT_RESERVED / 2;
	else if (*alport >= IPPORT_RESERVED)
		*alport = IPPORT_RESERVED - 1;

	int start = *alport;
	do {
		*sport = htons((uint16_t) *alport);
		if (bind(s, &ss.generic, len) >= 0)
			return s;
		if (errno != EADDRINUSE) {
			(void)close(s);
			return -1;
		}
		if ((*alport)-- == IPPORT_RESERVED/2)
			*alport = IPPORT_RESERVED - 1;
	} while (*alport != start);
	(void)close(s);
	return -1;
}
#endif

/* These functions aren't used outside of lp.c */
int lp_conn_inet();
int lp_disconn_inet( int );
int lp_conn_unix();
int lp_disconn_unix( int );

static char hostname[ MAXHOSTNAMELEN ];

extern struct sockaddr_at *sat;

static struct lp {
    int			lp_flags;
    FILE		*lp_stream;
    int			lp_seq;
    int 		lp_origin;
    char		lp_letter;
    char		*lp_person;
    char		*lp_created_for; /* Holds the content of the Postscript %%For Comment if available */
    char		*lp_host;
    char		*lp_job;
    char		*lp_spoolfile;
} lp;
#define LP_INIT		(1<<0)
#define LP_OPEN		(1<<1)
#define LP_PIPE		(1<<2)
#define LP_CONNECT	(1<<3)
#define LP_QUEUE	(1<<4)
#define LP_JOBPENDING	(1<<5)

void lp_origin (int origin)
{
    lp.lp_origin = origin;
}

/* the converted string should always be shorter, but ... FIXME! */
static void convert_octal (char *string, charset_t dest)
{
    unsigned char *p, *q;
    char temp[4];
    long int ch;

    q=p=(unsigned char *)string;
    while ( *p != '\0' ) {
        ch = 0;
        if ( *p == '\\' ) {
            p++;
            if (dest && isdigit(*p) && isdigit(*(p+1)) && isdigit(*(p+2)) ) {
                temp[0] = *p;
                temp[1] = *(p+1);
                temp[2] = *(p+2);
                temp[3] = 0;
                ch = strtol( temp, NULL, 8);
                if ( ch && ch < 0xff)
                    *q = ch;
              	else
                    *q = '.';
                p += 2;
            }
       	    else
                *q = '.';
       }
       else {
           *q = *p;
       }
           p++;
           q++;
    }
    *q = 0;
}


static void translate(charset_t from, charset_t dest, char **option)
{
    char *translated;

    if (*option != NULL) {
        convert_octal(*option, from);
        if (from) {
             if ((size_t) -1 != (convert_string_allocate(from, dest, *option, -1, &translated)) ) {
                 free (*option);
                 *option = translated;
             }
        }
    }
}


static void lp_setup_comments (charset_t dest)
{
    charset_t from=0;

    switch (lp.lp_origin) {
	case 1:
		from=CH_MAC;
		break;
	case 2:
		from=CH_UTF8_MAC;
		break;
    }

    if (lp.lp_job) {
        LOG(log_debug9, logtype_papd, "job: %s", lp.lp_job );
        translate(from, dest, &lp.lp_job);
    }
    if (lp.lp_created_for) {
        LOG(log_debug9, logtype_papd, "for: %s", lp.lp_created_for );
        translate(from, dest, &lp.lp_created_for);
    }
    if (lp.lp_person) {
        LOG(log_debug9, logtype_papd, "person: %s", lp.lp_person );
        translate(from, dest, &lp.lp_person);
    }
}

#define is_var(a, b) (strncmp((a), (b), 2) == 0)

#if 0
/* removed, it's not used and a pain to get it right from a security POV */
static size_t quote(char *dest, char *src, const size_t bsize, size_t len)
{
size_t used = 0;

    while (len && used < bsize ) {
        switch (*src) {
          case '$':
          case '\\':
          case '"':
          case '`':
            if (used + 2 > bsize )
              return used;
            *dest = '\\';
            dest++;
            used++;
            break;
        }
        *dest = *src;
        src++;
        dest++;
        len--;
        used++;
    }
    return used;
}

static char* pipexlate(char *src)
{
    char *p, *q, *dest;
    static char destbuf[MAXPATHLEN +1];
    size_t destlen = MAXPATHLEN;
    int len = 0;

    dest = destbuf;

    if (!src)
	return NULL;

    memset(dest, 0, MAXPATHLEN +1);
    if ((p = strchr(src, '%')) == NULL) { /* nothing to do */
        strncpy(dest, src, MAXPATHLEN);
        return destbuf;
    }
    /* first part of the path. copy and forward to the next variable. */
    len = MIN((size_t)(p - src), destlen);
    if (len > 0) {
        strncpy(dest, src, len);
        destlen -= len;
        dest += len;
    }

    while (p && destlen > 0) {
        /* now figure out what the variable is */
        q = NULL;
        if (is_var(p, "%U")) {
	    q = lp.lp_person;
        } else if (is_var(p, "%C") || is_var(p, "%J") ) {
            q = lp.lp_job;
        } else if (is_var(p, "%F")) {
            q =  lp.lp_created_for;
        } else if (is_var(p, "%%")) {
            q = "%";
        }

        /* copy the stuff over. if we don't understand something that we
         * should, just skip it over. */
        if (q) {
            len = MIN(strlen(q), destlen);
            len = quote(dest, q, destlen, len);
        }
        else {
            len = MIN(2, destlen);
            strncpy(dest, q, len);
        }
        dest += len;
        destlen -= len;

        /* stuff up to next % */
        src = p + 2;
        p = strchr(src, '%');
        len = p ? MIN((size_t)(p - src), destlen) : destlen;
        if (len > 0) {
            strncpy(dest, src, len);
            dest += len;
            destlen -= len;
        }
    }
    if (!destlen) {
        /* reach end of buffer, maybe prematurely, give up */
        return NULL;
    }
    return destbuf;
}
#endif

void lp_person(char *person)
{
    if ( lp.lp_person != NULL ) {
	free( lp.lp_person );
    }
    if (( lp.lp_person = (char *)malloc( strlen( person ) + 1 )) == NULL ) {
	LOG(log_error, logtype_papd, "malloc: %s", strerror(errno) );
	exit( 1 );
    }
    strcpy( lp.lp_person, person );
}

#ifdef ABS_PRINT
int lp_pagecost(void)
{
    char	cost[ 22 ];
    char	balance[ 22 ];
    int		err;

    if ( lp.lp_person == NULL ) {
	return( -1 );
    }
    err = ABS_canprint( lp.lp_person, printer->p_role, printer->p_srvid,
	    cost, balance );
    printer->p_pagecost = floor( atof( cost ) * 10000.0 );
    printer->p_balance = atof( balance ) + atof( cost );
    return( err < 0 ? -1 : 0 );
}
#endif /* ABS_PRINT */

void lp_host( char *host)
{
    if ( lp.lp_host != NULL ) {
	free( lp.lp_host );
    }
    if (( lp.lp_host = (char *)malloc( strlen( host ) + 1 )) == NULL ) {
	LOG(log_error, logtype_papd, "malloc: %s", strerror(errno) );
	exit( 1 );
    }
    strcpy( lp.lp_host, host );
    LOG(log_debug, logtype_papd, "host: %s", lp.lp_host );
}

/* Currently lp_job and lp_for will not handle the
 * conversion of macroman chars > 0x7f correctly
 * This should be added.
 */

void lp_job(char *job)
{
    if ( lp.lp_job != NULL ) {
	free( lp.lp_job );
    }

    lp.lp_job = strdup(job);
    LOG(log_debug9, logtype_papd, "job: %s", lp.lp_job );
}

void lp_for (char *lpfor)
{
    if ( lp.lp_created_for != NULL ) {
	free( lp.lp_created_for );
    }

    lp.lp_created_for = strdup(lpfor);
}


static int lp_init(struct papfile *out, struct sockaddr_at *sat)
{
    int		authenticated = 0;
#ifndef HAVE_CUPS
    int		fd, n, len;
    char	*cp, buf[ BUFSIZ ];
    struct stat	st;
    struct flock lock;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_whence = SEEK_SET;
#endif /* HAVE_CUPS */
#ifdef ABS_PRINT
    char	cost[ 22 ];
    char	balance[ 22 ];
#endif /* ABS_PRINT */

    if ( printer->p_flags & P_AUTH ) {
	authenticated = 0;

	/* cap style "log on to afp server before printing" authentication */

	if ( printer->p_authprintdir && (printer->p_flags & P_AUTH_CAP) ) {
	    int addr_net = ntohs( sat->sat_addr.s_net );
	    int addr_node  = sat->sat_addr.s_node;
	    char addr_filename[256];
	    char auth_string[256];
	    char *username, *afpdpid;
	    struct stat cap_st;
	    FILE *cap_file;

	    memset( auth_string, 0, 256 );
	    sprintf(addr_filename, "%s/net%d.%dnode%d",
		printer->p_authprintdir, addr_net/256, addr_net%256,
		addr_node);
	    if (stat(addr_filename, &cap_st) == 0) {
		if ((cap_file = fopen(addr_filename, "r")) != NULL) {
		    if (fgets(auth_string, 256, cap_file) != NULL) {
			username = auth_string;
			if ((afpdpid = strrchr( auth_string, ':' )) != NULL) {
			    *afpdpid = '\0';
			    afpdpid++;
			}
			if (getpwnam(username) != NULL ) {
			    LOG(log_info, logtype_papd, "CAP authenticated %s", username);
			    lp_person(username);
			    authenticated = 1;
			} else {
			    LOG(log_info, logtype_papd, "CAP error: invalid username: '%s'", username);
			}
		    } else {
			LOG(log_info, logtype_papd, "CAP error: could not read username");
		    }
		} else {
		    LOG(log_info, logtype_papd, "CAP error: %s", strerror(errno));
		}
	    } else {
		LOG(log_info, logtype_papd, "CAP error: %s", strerror(errno));
	    }
	}

	if ( printer->p_flags & P_AUTH_PSSP ) {
	    if ( lp.lp_person != NULL ) {
		authenticated = 1;
	    }
	}

	if ( authenticated == 0 ) {
	    LOG(log_error, logtype_papd, "lp_init: must authenticate" );
	    spoolerror( out, "Authentication required." );
	    return( -1 );
	}

#ifdef ABS_PRINT
	if (( printer->p_flags & P_ACCOUNT ) && printer->p_pagecost > 0 &&
		! ABS_canprint( lp.lp_person, printer->p_role,
		printer->p_srvid, cost, balance )) {
	    LOG(log_error, logtype_papd, "lp_init: no ABS funds" );
	    spoolerror( out, "No ABS funds available." );
	    return( -1 );
	}
#endif /* ABS_PRINT */
    }

    if ( gethostname( hostname, sizeof( hostname )) < 0 ) {
	LOG(log_error, logtype_papd, "gethostname: %s", strerror(errno) );
	exit( 1 );
    }

    if ( lp.lp_flags & LP_INIT ) {
	LOG(log_error, logtype_papd, "lp_init: already inited, die!" );
	abort();
    }

    lp.lp_flags = 0;
    lp.lp_stream = NULL;
    lp.lp_letter = 'A';

    if ( printer->p_flags & P_SPOOLED ) {

#ifndef HAVE_CUPS
	/* check if queuing is enabled: mode & 010 on lock file */
	if ( stat( printer->p_lock, &st ) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_init: %s: %s", printer->p_lock, strerror(errno) );
	    spoolerror( out, NULL );
	    return( -1 );
	}
	if ( st.st_mode & 010 ) {
	    LOG(log_info, logtype_papd, "lp_init: queuing is disabled" );
	    spoolerror( out, "Queuing is disabled." );
	    return( -1 );
	}

	if (( fd = open( ".seq", O_RDWR|O_CREAT, 0661 )) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_init: can't create .seq" );
	    spoolerror( out, NULL );
	    return( -1 );
	}

	lock.l_type = F_WRLCK;
	if (( fcntl(fd, F_SETLK, &lock)) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_init: can't lock .seq" );
	    spoolerror( out, NULL );
	    return( -1 );
	}

	n = 0;
	if (( len = read( fd, buf, sizeof( buf ))) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_init read: %s", strerror(errno) );
	    spoolerror( out, NULL );
	    return( -1 );
	}
	if ( len > 0 ) {
	    for ( cp = buf; len; len--, cp++ ) {
		if ( *cp < '0' || *cp > '9' ) {
		    break;
		}
		n = n * 10 + ( *cp - '0' );
	    }
	}
	lp.lp_seq = n;

	n = ( n + 1 ) % 1000;
	sprintf( buf, "%03d\n", n );
	lseek( fd, 0L, 0 );
	if (write(fd, buf, strlen(buf)) < 0)
		LOG(log_error, logtype_papd, "write failed (%s)", strerror(errno));
	close( fd );
#else

	if (cups_get_printer_status ( printer ) == 0)
	{
	    spoolerror( out, "Queuing is disabled." );
	    return( -1 );
	}

	lp.lp_seq = getpid();
#endif /* HAVE CUPS */
    } else {
	lp.lp_flags |= LP_PIPE;
	lp.lp_seq = getpid();
    }

    lp.lp_flags |= LP_INIT;
    return( 0 );
}

int lp_open(struct papfile *out, struct sockaddr_at *sat)
{
    char	name[ MAXPATHLEN ];
    int		fd;
    struct passwd	*pwent;

    LOG (log_debug9, logtype_papd, "lp_open");

    if ( lp.lp_flags & LP_JOBPENDING ) {
	lp_print();
    }

    if (( lp.lp_flags & LP_INIT ) == 0 && lp_init( out, sat ) != 0 ) {
	return( -1 );
    }
    if ( lp.lp_flags & LP_OPEN ) {
	/* LOG(log_error, logtype_papd, "lp_open already open" ); */
	/* abort(); */
	return (-1);
    }

    if ( lp.lp_flags & LP_PIPE ) {
        char *pipe_cmd;

	/* go right to program */
	if (lp.lp_person != NULL) {
	    if((pwent = getpwnam(lp.lp_person)) != NULL) {
		if(setreuid(pwent->pw_uid, pwent->pw_uid) != 0) {
		    LOG(log_error, logtype_papd, "setreuid error: %s", strerror(errno));
		    exit(1);
		}
	    } else {
		LOG(log_error, logtype_papd, "Error getting username (%s)", lp.lp_person);
                exit(1);
	    }
	}

	lp_setup_comments(CH_UNIX);
	pipe_cmd = printer->p_printer;
	if (!pipe_cmd) {
	    LOG(log_error, logtype_papd, "lp_open: no pipe cmd" );
	    spoolerror( out, NULL );
	    return( -1 );
	}
	if (( lp.lp_stream = popen(pipe_cmd, "w" )) == NULL ) {
	    LOG(log_error, logtype_papd, "lp_open popen %s: %s", printer->p_printer, strerror(errno) );
	    spoolerror( out, NULL );
	    return( -1 );
	}
        LOG(log_debug, logtype_papd, "lp_open: opened %s",  pipe_cmd );
    } else {
	sprintf( name, "df%c%03d%s", lp.lp_letter++, lp.lp_seq, hostname );

	if (( fd = open( name, O_WRONLY|O_CREAT|O_EXCL, 0660 )) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_open %s: %s", name, strerror(errno) );
	    spoolerror( out, NULL );
	    return( -1 );
	}

	if ( NULL == (lp.lp_spoolfile = (char *) malloc (strlen (name) +1)) ) {
	    LOG(log_error, logtype_papd, "malloc: %s", strerror(errno));
	    exit(1);
	}
	strcpy ( lp.lp_spoolfile, name);

	if (lp.lp_person != NULL) {
	    if ((pwent = getpwnam(lp.lp_person)) == NULL) {
		LOG(log_error, logtype_papd, "getpwnam %s: no such user", lp.lp_person);
		spoolerror( out, NULL );
		return( -1 );
	    }
	} else {
	    if ((pwent = getpwnam(printer->p_operator)) == NULL) {
		LOG(log_error, logtype_papd, "getpwnam %s: no such user", printer->p_operator);
		spoolerror( out, NULL );
		return( -1 );
	    }
	}

	if (fchown(fd, pwent->pw_uid, -1) < 0) {
	    LOG(log_error, logtype_papd, "chown %s %s: %s", pwent->pw_name, name, strerror(errno));
	    spoolerror( out, NULL );
	    return( -1 );
	}

	if (( lp.lp_stream = fdopen( fd, "w" )) == NULL ) {
	    LOG(log_error, logtype_papd, "lp_open fdopen: %s", strerror(errno) );
	    spoolerror( out, NULL );
	    return( -1 );
	}
	LOG(log_debug9, logtype_papd, "lp_open: opened %s", name );
    }
    lp.lp_flags |= LP_OPEN;
    return( 0 );
}

int lp_close(void)
{
    if (( lp.lp_flags & LP_INIT ) == 0 || ( lp.lp_flags & LP_OPEN ) == 0 ) {
	return 0;
    }
    fclose( lp.lp_stream );
    lp.lp_stream = NULL;
    lp.lp_flags &= ~LP_OPEN;
    lp.lp_flags |= LP_JOBPENDING;
    return 0;
}



int lp_write(struct papfile *in, char *buf, size_t len)
{
#define BUFSIZE 32768
    static char tempbuf[BUFSIZE];
    static char tempbuf2[BUFSIZE];
    static size_t bufpos = 0;
    static int last_line_translated = 1; /* if 0, append a \n a the start */
    char *tbuf = buf;

    /* Before we write out anything check for a pending job, e.g. cover page */
    if (lp.lp_flags & LP_JOBPENDING)
	lp_print();

    /* foomatic doesn't handle mac line endings, so we convert them for
     * the Postscript headers
     * REALLY ugly hack, remove ASAP again */
    if ((printer->p_flags & P_FOOMATIC_HACK) && (in->pf_state & PF_TRANSLATE) &&
        (buf[len-1] != '\n') ) {
        if (len <= BUFSIZE) {
	    if (!last_line_translated) {
	    	tempbuf2[0] = '\n';
            	memcpy(tempbuf2+1, buf, len++);
	    }
	    else
            	memcpy(tempbuf2, buf, len);

            if (tempbuf2[len-1] == '\r')
	        tempbuf2[len-1] = '\n';
            tempbuf2[len] = 0;
            tbuf = tempbuf2;
            last_line_translated = 1;
            LOG(log_debug9, logtype_papd, "lp_write: %s", tbuf );
        }
        else {
            LOG(log_error, logtype_papd, "lp_write: conversion buffer too small" );
            abort();
        }
    }
    else {
        if (printer->p_flags & P_FOOMATIC_HACK && buf[len-1] == '\n') {
            last_line_translated = 1;
	}
        else
	    last_line_translated = 0;
    }

    /* To be able to do commandline substitutions on piped printers
     * we store the start of the print job in a buffer.
     * %%EndComment triggers writing to file */
    if (( lp.lp_flags & LP_OPEN ) == 0 ) {
        LOG(log_debug9, logtype_papd, "lp_write: writing to temporary buffer" );
        if ((bufpos+len) > BUFSIZE) {
            LOG(log_error, logtype_papd, "lp_write: temporary buffer too small" );
            /* FIXME: call lp_open here? abort isn't nice... */
            abort();
        }
	else {
            memcpy(tempbuf + bufpos, tbuf, len);
            bufpos += len;
            if (bufpos > BUFSIZE/2)
                in->pf_state |= PF_STW; /* we used half of the buffer, start writing */
            return(0);
	}
    }
    else if ( bufpos) {
        if ( fwrite( tempbuf, 1, bufpos, lp.lp_stream ) != bufpos ) {
            LOG(log_error, logtype_papd, "lp_write: %s", strerror(errno) );
            abort();
        }
        bufpos=0;
    }

    if ( fwrite( tbuf, 1, len, lp.lp_stream ) != len ) {
	LOG(log_error, logtype_papd, "lp_write: %s", strerror(errno) );
	abort();
    }
    return( 0 );
}

int lp_cancel(void)
{
    char	name[ MAXPATHLEN ];
    char	letter;

    if (( lp.lp_flags & LP_INIT ) == 0 || lp.lp_letter == 'A' ) {
	return 0;
    }

    if ( lp.lp_flags & LP_OPEN ) {
	lp_close();
    }

    for ( letter = 'A'; letter < lp.lp_letter; letter++ ) {
	sprintf( name, "df%c%03d%s", letter, lp.lp_seq, hostname );
	if ( unlink( name ) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_cancel unlink %s: %s", name, strerror(errno) );
	}
    }

    return 0;
}

/*
 * Create printcap control file, signal printer.  Errors here should
 * remove queue files.
 *
 * XXX piped?
 */
int lp_print(void)
{
#ifndef HAVE_CUPS
    char		buf[ MAXPATHLEN ];
    char		tfname[ MAXPATHLEN ];
    char		cfname[ MAXPATHLEN ];
    char		letter;
    int			fd, n, s;
    FILE		*cfile;
#endif /* HAVE_CUPS */

    if (( lp.lp_flags & LP_INIT ) == 0 || lp.lp_letter == 'A' ) {
	return 0;
    }
    lp_close();
    lp.lp_flags &= ~LP_JOBPENDING;

    if ( printer->p_flags & P_SPOOLED ) {
#ifndef HAVE_CUPS
	sprintf( tfname, "tfA%03d%s", lp.lp_seq, hostname );
	if (( fd = open( tfname, O_WRONLY|O_EXCL|O_CREAT, 0660 )) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_print %s: %s", tfname, strerror(errno) );
	    return 0;
	}
	if (( cfile = fdopen( fd, "w" )) == NULL ) {
	    LOG(log_error, logtype_papd, "lp_print %s: %s", tfname, strerror(errno) );
	    return 0;
	}
	fprintf( cfile, "H%s\n", hostname );	/* XXX lp_host? */

	if ( lp.lp_person ) {
	    fprintf( cfile, "P%s\n", lp.lp_person );
	} else {
	    fprintf( cfile, "P%s\n", printer->p_operator );
	}

	if ( lp.lp_job && *lp.lp_job ) {
	    fprintf( cfile, "J%s\n", lp.lp_job );
	    fprintf( cfile, "T%s\n", lp.lp_job );
	} else {
	    fprintf( cfile, "JMac Job\n" );
	    fprintf( cfile, "TMac Job\n" );
	}

	fprintf( cfile, "C%s\n", hostname );	/* XXX lp_host? */

	if ( lp.lp_person ) {
	    fprintf( cfile, "L%s\n", lp.lp_person );
	} else {
	    fprintf( cfile, "L%s\n", printer->p_operator );
	}

	for ( letter = 'A'; letter < lp.lp_letter; letter++ ) {
	    fprintf( cfile, "fdf%c%03d%s\n", letter, lp.lp_seq, hostname );
	    fprintf( cfile, "Udf%c%03d%s\n", letter, lp.lp_seq, hostname );
	}

	if ( lp.lp_job && *lp.lp_job ) {
	    fprintf( cfile, "N%s\n", lp.lp_job );
	} else {
	    fprintf( cfile, "NMac Job\n" );
	}
	fclose( cfile );

	sprintf( cfname, "cfA%03d%s", lp.lp_seq, hostname );
	if ( link( tfname, cfname ) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_print can't link %s to %s: %s", cfname,
		    tfname, strerror(errno) );
	    return 0;
	}
	unlink( tfname );

	if (( s = lp_conn_unix()) < 0 ) {
	    LOG(log_error, logtype_papd, "lp_print: lp_conn_unix: %s", strerror(errno) );
	    return 0;
	}

	sprintf( buf, "\1%s\n", printer->p_printer );
	n = strlen( buf );
	if ( write( s, buf, n ) != n ) {
	    LOG(log_error, logtype_papd, "lp_print write: %s" , strerror(errno));
	    return 0;
	}
	if ( read( s, buf, 1 ) != 1 ) {
	    LOG(log_error, logtype_papd, "lp_print read: %s" , strerror(errno));
	    return 0;
	}

	lp_disconn_unix( s );

	if ( buf[ 0 ] != '\0' ) {
	    LOG(log_error, logtype_papd, "lp_print lpd said %c: %s", buf[ 0 ], strerror(errno) );
	    return 0;
	}
#else
        if ( ! (lp.lp_job && *lp.lp_job) ) {
            lp.lp_job = strdup("Mac Job");
        }

        lp_setup_comments(add_charset(cups_get_language ()));

        if (lp.lp_person != NULL) {
	    cups_print_job ( printer->p_printer, lp.lp_spoolfile, lp.lp_job, lp.lp_person, printer->p_cupsoptions);
        } else if (lp.lp_created_for != NULL) {
            cups_print_job ( printer->p_printer, lp.lp_spoolfile, lp.lp_job, lp.lp_created_for, printer->p_cupsoptions);
        } else {
            cups_print_job ( printer->p_printer, lp.lp_spoolfile, lp.lp_job, printer->p_operator, printer->p_cupsoptions);
        }

	/*LOG(log_info, logtype_papd, "lp_print unlink %s", lp.lp_spoolfile );*/
        unlink ( lp.lp_spoolfile );
	return 0;
#endif /* HAVE_CUPS*/
    }
    LOG(log_info, logtype_papd, "lp_print queued" );
    return 0;
}

#ifndef HAVE_CUPS
int lp_disconn_unix( int fd )
{
    return( close( fd ));
}

int lp_conn_unix(void)
{
    int			s;
    struct sockaddr_un	saun;

    if (( s = socket( AF_UNIX, SOCK_STREAM, 0 )) < 0 ) {
	LOG(log_error, logtype_papd, "lp_conn_unix socket: %s", strerror(errno) );
	return( -1 );
    }
    memset( &saun, 0, sizeof( struct sockaddr_un ));
    saun.sun_family = AF_UNIX;
    strcpy( saun.sun_path, _PATH_DEVPRINTER );
    if ( connect( s, (struct sockaddr *)&saun,
	    strlen( saun.sun_path ) + 2 ) < 0 ) {
	LOG(log_error, logtype_papd, "lp_conn_unix connect %s: %s", saun.sun_path, strerror(errno) );
	close( s );
	return( -1 );
    }

    return( s );
}

int lp_disconn_inet( int fd )
{
    return( close( fd ));
}

int lp_conn_inet(void)
{
    int			privfd, port = IPPORT_RESERVED - 1;
    struct sockaddr_in	sin;
    struct servent	*sp;
    struct hostent	*hp;

    if (( sp = getservbyname( "printer", "tcp" )) == NULL ) {
	LOG(log_error, logtype_papd, "printer/tcp: unknown service" );
	return( -1 );
    }

    if ( gethostname( hostname, sizeof( hostname )) < 0 ) {
	LOG(log_error, logtype_papd, "gethostname: %s", strerror(errno) );
	exit( 1 );
    }

    if (( hp = gethostbyname( hostname )) == NULL ) {
	LOG(log_error, logtype_papd, "%s: unknown host", hostname );
	return( -1 );
    }

    if (( privfd = rresvport( &port )) < 0 ) {
	LOG(log_error, logtype_papd, "lp_connect: socket: %s", strerror(errno) );
	close( privfd );
	return( -1 );
    }

    memset( &sin, 0, sizeof( struct sockaddr_in ));
    sin.sin_family = AF_INET;
/*    sin.sin_addr.s_addr = htonl( INADDR_LOOPBACK ); */
    memcpy( &sin.sin_addr, hp->h_addr, hp->h_length );
    sin.sin_port = sp->s_port;

    if ( connect( privfd, (struct sockaddr *)&sin,
	    sizeof( struct sockaddr_in )) < 0 ) {
	LOG(log_error, logtype_papd, "lp_connect: %s", strerror(errno) );
	close( privfd );
	return( -1 );
    }

    return( privfd );
}

int lp_rmjob( int job)
{
    char	buf[ 1024 ];
    int		n, s;

    if (( s = lp_conn_inet()) < 0 ) {
	LOG(log_error, logtype_papd, "lp_rmjob: %s", strerror(errno) );
	return( -1 );
    }

    if ( lp.lp_person == NULL ) {
	return( -1 );
    }

    sprintf( buf, "\5%s %s %d\n", printer->p_printer, lp.lp_person, job );
    n = strlen( buf );
    if ( write( s, buf, n ) != n ) {
	LOG(log_error, logtype_papd, "lp_rmjob write: %s", strerror(errno) );
	lp_disconn_inet( s );
	return( -1 );
    }
    while (( n = read( s, buf, sizeof( buf ))) > 0 ) {
	LOG(log_debug, logtype_papd, "read %.*s", n, buf );
    }

    lp_disconn_inet( s );
    return( 0 );
}

char	*kw_rank = "Rank";
char	*kw_active = "active";

char	*tag_rank = "rank: ";
char	*tag_owner = "owner: ";
char	*tag_job = "job: ";
char	*tag_files = "files: ";
char	*tag_size = "size: ";
char	*tag_status = "status: ";

int lp_queue( struct papfile *out)
{
    char			buf[ 1024 ], *start, *stop, *p, *q;
    int				linelength, crlflength;
    static struct papfile	pf;
    int				s;
    size_t			len;
    ssize_t			n;

    if (( s = lp_conn_unix()) < 0 ) {
	LOG(log_error, logtype_papd, "lp_queue: %s", strerror(errno) );
	return( -1 );
    }

    sprintf( buf, "\3%s\n", printer->p_printer );
    n = strlen( buf );
    if ( write( s, buf, n ) != n ) {
	LOG(log_error, logtype_papd, "lp_queue write: %s", strerror(errno) );
	lp_disconn_unix( s );
	return( -1 );
    }
    pf.pf_state = PF_BOT;

    while (( n = read( s, buf, sizeof( buf ))) > 0 ) {
	append( &pf, buf, n );
    }

    for (;;) {
	if ( markline( &pf, &start, &linelength, &crlflength ) > 0 ) {
	    /* parse */
	    stop = start + linelength;
	    for ( p = start; p < stop; p++ ) {
		if ( *p == ' ' || *p == '\t' ) {
		    break;
		}
	    }
	    if ( p >= stop ) {
		CONSUME( &pf , linelength + crlflength);
		continue;
	    }

	    /*
	     * Keys: "Rank", a number, "active"
	     * Anything else is status.
	     */
	    len = p - start;
	    if ( len == strlen( kw_rank ) &&
		    strncmp( kw_rank, start, len ) == 0 ) {
		CONSUME( &pf, linelength + crlflength );
		continue;
	    }
	    if (( len == strlen( kw_active ) &&
		    strncmp( kw_active, start, len ) == 0 ) ||
		    isdigit( *start )) {		/* a job line */
		append( out, tag_rank, strlen( tag_rank ));
		append( out, start, p - start );
		append( out, "\n", 1 );

		for ( ; p < stop; p++ ) {
		    if ( *p != ' ' && *p != '\t' ) {
			break;
		    }
		}
		for ( q = p; p < stop; p++ ) {
		    if ( *p == ' ' || *p == '\t' ) {
			break;
		    }
		}
		if ( p >= stop ) {
		    append( out, ".\n", 2 );
		    CONSUME( &pf, linelength + crlflength );
		    continue;
		}
		append( out, tag_owner, strlen( tag_owner ));
		append( out, q, p - q );
		append( out, "\n", 1 );

		for ( ; p < stop; p++ ) {
		    if ( *p != ' ' && *p != '\t' ) {
			break;
		    }
		}
		for ( q = p; p < stop; p++ ) {
		    if ( *p == ' ' || *p == '\t' ) {
			break;
		    }
		}
		if ( p >= stop ) {
		    append( out, ".\n", 2 );
		    CONSUME( &pf , linelength + crlflength );
		    continue;
		}
		append( out, tag_job, strlen( tag_job ));
		append( out, q, p - q );
		append( out, "\n", 1 );

		for ( ; p < stop; p++ ) {
		    if ( *p != ' ' && *p != '\t' ) {
			break;
		    }
		}
		for ( q = p, p = stop; p > q; p-- ) {
		    if ( *p == ' ' || *p == '\t' ) {
			break;
		    }
		}
		for ( ; p > q; p-- ) {
		    if ( *p != ' ' && *p != '\t' ) {
			break;
		    }
		}
		for ( ; p > q; p-- ) {
		    if ( *p == ' ' || *p == '\t' ) {
			break;
		    }
		}
		if ( p <= q ) {
		    append( out, ".\n", 2 );
		    CONSUME( &pf, linelength + crlflength );
		    continue;
		}
		append( out, tag_files, strlen( tag_files ));
		append( out, q, p - q );
		append( out, "\n", 1 );

		for ( ; p < stop; p++ ) {
		    if ( *p != ' ' && *p != '\t' ) {
			break;
		    }
		}
		append( out, tag_size, strlen( tag_size ));
		append( out, p, stop - p );
		append( out, "\n.\n", 3 );

		CONSUME( &pf, linelength + crlflength );
		continue;
	    }

	    /* status */
	    append( out, tag_status, strlen( tag_status ));
	    append( out, start, linelength );
	    append( out, "\n.\n", 3 );

	    CONSUME( &pf, linelength + crlflength );
	} else {
	    append( out, "*\n", 2 );
	    lp_disconn_unix( s );
	    return( 0 );
	}
    }
}
#endif /* HAVE_CUPS */
