Ejemplo n.º 1
0
/*
 * A simple routine to determine the job number for a print job based on
 * the name of its control file.  The algorithm used here may look odd, but
 * the main issue is that all parts of `lpd', `lpc', `lpq' & `lprm' must be
 * using the same algorithm, whatever that algorithm may be.  If the caller
 * provides a non-null value for ''hostpp', then this returns a pointer to
 * the start of the hostname (or IP address?) as found in the filename.
 *
 * Algorithm: The standard `cf' file has the job number start in position 4,
 * but some implementations have that as an extra file-sequence letter, and
 * start the job number in position 5.  The job number is usually three bytes,
 * but may be as many as five.  Confusing matters still more, some Windows
 * print servers will append an IP address to the job number, instead of
 * the expected hostname.  So, if the job number ends with a '.', then
 * assume the correct jobnum value is the first three digits.
 */
int
calc_jobnum(const char *cfname, const char **hostpp)
{
	int jnum;
	const char *cp, *numstr, *hoststr;

	numstr = cfname + 3;
	if (!isdigitch(*numstr))
		numstr++;
	jnum = 0;
	for (cp = numstr; (cp < numstr + 5) && isdigitch(*cp); cp++)
		jnum = jnum * 10 + (*cp - '0');
	hoststr = cp;

	/*
	 * If the filename was built with an IP number instead of a hostname,
	 * then recalculate using only the first three digits found.
	 */
	while(isdigitch(*cp))
		cp++;
	if (*cp == '.') {
		jnum = 0;
		for (cp = numstr; (cp < numstr + 3) && isdigitch(*cp); cp++)
			jnum = jnum * 10 + (*cp - '0');
		hoststr = cp;
	}
	if (hostpp != NULL)
		*hostpp = hoststr;
	return (jnum);
}
Ejemplo n.º 2
0
/*
 * This routine renames the temporary control file as received from some
 * other (remote) host.  That file will almost always with `tfA*', because
 * recvjob.c creates the file by changing `c' to `t' in the original name
 * for the control file.  Now if you read the RFC, you would think that all
 * control filenames start with `cfA*'.  However, it seems there are some
 * implementations which send control filenames which start with `cf'
 * followed by *any* letter, so this routine can not assume what the third
 * letter will (or will not) be.  Sigh.
 *
 * So this will rewrite the temporary file to `rf*' (correcting any lines
 * which need correcting), rename that `rf*' file to `cf*', and then remove
 * the original `tf*' temporary file.
 *
 * The *main* purpose of this routine is to be paranoid about the contents
 * of that control file.  It is partially meant to protect against people
 * TRYING to cause trouble (perhaps after breaking into root of some host
 * that this host will accept print jobs from).  The fact that we're willing
 * to print jobs from some remote host does not mean that we should blindly
 * do anything that host tells us to do.
 *
 * This is also meant to protect us from errors in other implementations of
 * lpr, particularly since we may want to use some values from the control
 * file as environment variables when it comes time to print, or as parameters
 * to commands which will be exec'ed, or values in statistics records.
 *
 * This may also do some "conversions" between how different versions of
 * lpr or lprNG define the contents of various lines in a control file.
 *
 * If there is an error, it returns a pointer to a descriptive error message.
 * Error messages which are RETURNED (as opposed to syslog-ed) do not include
 * the printer-queue name.  Let the caller add that if it is wanted.
 */
char *
ctl_renametf(const char *ptrname, const char *tfname)
{
    int chk3rd, has_uc, newfd, nogood, res;
    FILE *newcf;
    struct cjobinfo *cjinf;
    char *lbuff, *slash, *cp;
    char tfname2[NAME_MAX+1], cfname2[NAME_MAX+1];
    char errm[CTI_LINEMAX];

#ifdef TRIGGERTEST_FNAME
    struct stat tstat;
    res = stat(TRIGGERTEST_FNAME, &tstat);
    if (res == -1) {
        /*
         * if the trigger file does NOT exist in this spool directory,
         * then do the exact same steps that the pre-ctlinfo code had
         * been doing.  Ie, very little.
         */
        strlcpy(cfname2, tfname, sizeof(cfname2));
        cfname2[0] = 'c';
        res = link(tfname, cfname2);
        if (res < 0) {
            snprintf(errm, sizeof(errm),
                     "ctl_renametf error link(%s,%s): %s", tfname,
                     cfname2, strerror(errno));
            return strdup(errm);
        }
        unlink(tfname);
        return NULL;
    }
#endif
    cjinf = NULL;		/* in case of early jump to error_ret */
    newcf = NULL;		/* in case of early jump to error_ret */
    *errm = '\0';		/* in case of early jump to error_ret */

    chk3rd = tfname[2];
    if ((tfname[0] != 't') || (tfname[1] != 'f') || (!isalpha(chk3rd))) {
        snprintf(errm, sizeof(errm),
                 "ctl_renametf invalid filename: %s", tfname);
        goto error_ret;
    }

    cjinf = ctl_readcf(ptrname, tfname);
    if (cjinf == NULL) {
        snprintf(errm, sizeof(errm),
                 "ctl_renametf error cti_readcf(%s)", tfname);
        goto error_ret;
    }

    /*
     * This uses open+fdopen instead of fopen because that combination
     * gives us greater control over file-creation issues.
     */
    strlcpy(tfname2, tfname, sizeof(tfname2));
    tfname2[0] = 'r';		/* rf<letter><job><hostname> */
    newfd = open(tfname2, O_WRONLY|O_CREAT|O_TRUNC, 0660);
    if (newfd == -1) {
        snprintf(errm, sizeof(errm),
                 "ctl_renametf error open(%s): %s", tfname2,
                 strerror(errno));
        goto error_ret;
    }
    newcf = fdopen(newfd, "w");
    if (newcf == NULL) {
        close(newfd);
        snprintf(errm, sizeof(errm),
                 "ctl_renametf error fopen(%s): %s", tfname2,
                 strerror(errno));
        goto error_ret;
    }

    /*
     * Do extra sanity checks on some key job-attribute fields, and
     * write them out first (thus making sure they are written in the
     * order we generally expect them to be in).
     */
    /*
     * Some lpr implementations on PC's set a null-string for their
     * hostname.  A MacOS 10 system which has not correctly setup
     * /etc/hostconfig will claim a hostname of 'localhost'.  Anything
     * with blanks in it would be an invalid value for hostname.  For
     * any of these invalid hostname values, replace the given value
     * with the name of the host that this job is coming from.
     */
    nogood = 0;
    if (cjinf->cji_accthost == NULL)
        nogood = 1;
    else if (strcmp(cjinf->cji_accthost, ".na.") == 0)
        nogood = 1;
    else if (strcmp(cjinf->cji_accthost, "localhost") == 0)
        nogood = 1;
    else {
        for (cp = cjinf->cji_accthost; *cp != '\0'; cp++) {
            if (*cp <= ' ') {
                nogood = 1;
                break;
            }
        }
    }
    if (nogood)
        fprintf(newcf, "H%s\n", from_host);
    else
        fprintf(newcf, "H%s\n", cjinf->cji_accthost);

    /*
     * Now do some sanity checks on the 'P' (original userid) value.  Note
     * that the 'P'erson line is the second line which is ALWAYS supposed
     * to be present in a control file.
     *
     * There is no particularly good value to use for replacements, but
     * at least make sure the value is something reasonable to use in
     * environment variables and statistics records.  Again, some PC
     * implementations send a null-string for a value.  Various Mac
     * implementations will set whatever string the user has set for
     * their 'Owner Name', which usually includes blanks, etc.
     */
    nogood = 0;
    if (cjinf->cji_acctuser == NULL)
        nogood = 1;
    else if (strcmp(cjinf->cji_acctuser, ".na.") == 0)
        ;			/* No further checks needed... */
    else {
        has_uc = 0;
        cp = cjinf->cji_acctuser;
        if (*cp == '-')
            *cp++ = '_';
        for (; *cp != '\0'; cp++) {
            if (islowerch(*cp) || isdigitch(*cp))
                continue;	/* Standard valid characters */
            if (strchr(OTHER_USERID_CHARS, *cp) != NULL)
                continue;	/* Some more valid characters */
            if (isupperch(*cp)) {
                has_uc = 1;	/* These may be valid... */
                continue;
            }
            *cp = '_';
        }
        /*
         * Some Windows hosts send print jobs where the correct userid
         * has been converted to uppercase, and that can cause trouble
         * for sites that expect the correct value (for something like
         * accounting).  On the other hand, some sites do use uppercase
         * in their userids, so we can't blindly convert to lowercase.
         */
        if (has_uc && (getpwnam(cjinf->cji_acctuser) == NULL)) {
            for (cp = cjinf->cji_acctuser; *cp != '\0'; cp++) {
                if (isupperch(*cp))
                    *cp = tolowerch(*cp);
            }
        }
    }
    if (nogood)
        fprintf(newcf, "P%s\n", ".na.");
    else
        fprintf(newcf, "P%s\n", cjinf->cji_acctuser);

    /* No need for sanity checks on class, jobname, "literal" user. */
    if (cjinf->cji_class != NULL)
        fprintf(newcf, "C%s\n", cjinf->cji_class);
    if (cjinf->cji_jobname != NULL)
        fprintf(newcf, "J%s\n", cjinf->cji_jobname);
    if (cjinf->cji_headruser != NULL)
        fprintf(newcf, "L%s\n", cjinf->cji_headruser);

    /*
     * This should probably add more sanity checks on mailto value.
     * Note that if the mailto value is "wrong", then there's no good
     * way to know what the "correct" value would be, and we should not
     * semd email to some random address.  At least for now, just ignore
     * any invalid values.
     */
    nogood = 0;
    if (cjinf->cji_mailto == NULL)
        nogood = 1;
    else {
        for (cp = cjinf->cji_mailto; *cp != '\0'; cp++) {
            if (*cp <= ' ') {
                nogood = 1;
                break;
            }
        }
    }
    if (!nogood)
        fprintf(newcf, "M%s\n", cjinf->cji_mailto);

    /*
     * Now go thru the old control file, copying all information which
     * hasn't already been written into the new file.
     */
    ctl_rewindcf(cjinf);
    lbuff = ctl_getline(cjinf);
    while (lbuff != NULL) {
        switch (lbuff[0]) {
        case 'H':
        case 'P':
        case 'C':
        case 'J':
        case 'L':
        case 'M':
            /* already wrote values for these to the newcf */
            break;
        case 'N':
            /* see comments under 'U'... */
            if (cjinf->cji_dfcount == 0) {
                /* in this case, 'N's will be done in 'U' */
                break;
            }
            fprintf(newcf, "%s\n", lbuff);
            break;
        case 'U':
            /*
             * check for the very common case where the remote
             * host had to process 'lpr -s -r', but it did not
             * remove the Unlink line from the control file.
             * Such Unlink lines will legitimately have a '/' in
             * them, but it is the original lpr host which would
             * have done the unlink of such files, and not any
             * host receiving that job.
             */
            slash = strchr(lbuff, '/');
            if (slash != NULL) {
                break;		/* skip this line */
            }
            /*
             * Okay, another kind of broken lpr implementation
             * is one which send datafiles, and Unlink's those
             * datafiles, but never includes any PRINT request
             * for those files.  Experimentation shows that one
             * copy of those datafiles should be printed with a
             * format of 'f'.  If this is an example of such a
             * screwed-up control file, fix it here.
             */
            if (cjinf->cji_dfcount == 0) {
                lbuff++;
                if (strncmp(lbuff, "df", (size_t)2) == 0) {
                    fprintf(newcf, "f%s\n", lbuff);
                    fprintf(newcf, "U%s\n", lbuff);
                    fprintf(newcf, "N%s\n", lbuff);
                }
                break;
            }
            fprintf(newcf, "%s\n", lbuff);
            break;
        default:
            fprintf(newcf, "%s\n", lbuff);
            break;
        }
        lbuff = ctl_getline(cjinf);
    }

    ctl_freeinf(cjinf);
    cjinf = NULL;

    res = fclose(newcf);
    newcf = NULL;
    if (res != 0) {
        snprintf(errm, sizeof(errm),
                 "ctl_renametf error fclose(%s): %s", tfname2,
                 strerror(errno));
        goto error_ret;
    }

    strlcpy(cfname2, tfname, sizeof(cfname2));
    cfname2[0] = 'c';		/* rename new file to 'cfA*' */
    res = link(tfname2, cfname2);
    if (res != 0) {
        snprintf(errm, sizeof(errm),
                 "ctl_renametf error link(%s,%s): %s", tfname2, cfname2,
                 strerror(errno));
        goto error_ret;
    }

    /* All the important work is done.  Now just remove temp files */
#ifdef LEAVE_TMPCF_FILES
    {
        struct stat tfstat;
        size_t size1;
        tfstat.st_size = 1;	/* certainly invalid value */
        res = stat(tfname, &tfstat);
        size1 = tfstat.st_size;
        tfstat.st_size = 2;	/* certainly invalid value */
        res = stat(tfname2, &tfstat);
        /*
         * If the sizes do not match, or either stat call failed,
         * then do not remove the temp files, but just move them
         * out of the way.  This is so I can see what this routine
         * had changed (and the files won't interfere with some
         * later job coming in from the same host).  In this case,
         * we don't care if we clobber some previous file.
         */
        if (size1 != tfstat.st_size) {
            strlcpy(cfname2, tfname, sizeof(cfname2));
            strlcat(cfname2, "._T", sizeof(cfname2));
            rename(tfname, cfname2);
            strlcpy(cfname2, tfname2, sizeof(cfname2));
            strlcat(cfname2, "._T", sizeof(cfname2));
            rename(tfname2, cfname2);
            return NULL;
        }
    }
#endif
    unlink(tfname);
    unlink(tfname2);

    return NULL;

error_ret:
    if (cjinf != NULL)
        ctl_freeinf(cjinf);
    if (newcf != NULL)
        fclose(newcf);

    if (*errm != '\0')
        return strdup(errm);
    return strdup("ctl_renametf internal (missed) error");
}