quote chars correctly
---
 etc/papd/lp.c |  214 ++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 127 insertions(+), 87 deletions(-)

--- a/etc/papd/lp.c
+++ b/etc/papd/lp.c
@@ -211,99 +211,137 @@ static void lp_setup_comments (charset_t
 }
 
 #define is_var(a, b) (strncmp((a), (b), 2) == 0)
+enum quote_state {
+	NONE,
+	QUOTEDBL,
+	APOSTROPHE,
+};
 
-static size_t quote(char *dest, char *src, const size_t bsize, size_t len)
+static size_t quote_var(char *dest, size_t destlen, enum quote_state qs, const char *untrusted)
 {
-size_t used = 0;
+	char c = 0;
+	size_t len = 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;
+	if (qs == NONE) {
+		*dest='\'';
+		dest++;
+		len++;
+		if (destlen > 1)
+			destlen--;
+	}
+	while (untrusted && (destlen - len) > 1 && (c = *untrusted)) {
+		if (c == '\'' && (qs == NONE || qs == APOSTROPHE)) {
+			/* man bash:
+			 * A single quote may not occur between single quotes,
+			 * even when preceded by a backslash.
+			 */
+			c = '_';
+		} else if (qs == QUOTEDBL) {
+			switch (c) {
+				/* subshells are escaped
+				 * no need to handle "& * # ' ; { } < >" */
+				case '"':
+				case '`':
+				case '$':
+				case '(':
+				case ')':
+				case '\\':
+				case '!':
+					*dest = '\\';
+					dest++;
+					len++;
+				default:
+					break;
+			}
+		} else if (c == '\\') {
+			*dest = '\\';
+			dest++;
+			len++;
+		}
+		*dest = c;
+		dest++;
+		len++;
+		untrusted++;
+	}
+	if (qs == NONE) {
+		*dest='\'';
+		dest++;
+		len++;
+	}
+	return len;
 }
 
-
-static char* pipexlate(char *src)
+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;
-        }
-    }
-    return destbuf;
+	char *dest, *p;
+	char c, *untrusted;
+	static char destbuf[MAXPATHLEN];
+	size_t destlen, vlen;
+	enum quote_state qs;
+
+	if (!src)
+		return NULL;
+
+	p = src;
+	dest = destbuf;
+	destlen = sizeof(destbuf) - 1;
+	qs = NONE;
+
+	while (destlen && (c = *src)) {
+		untrusted = NULL;
+		if (c == '%') {
+			switch (src[1]) {
+				/* %U */
+				case 'U':
+					untrusted = lp.lp_person;
+					break;
+				/* %C/%J */
+				case 'C':
+				case 'J':
+					untrusted = lp.lp_job;
+					break;
+				/* %F */
+				case 'F':
+					untrusted =  lp.lp_created_for;
+					break;
+				/* %% */
+				case '%':
+					src++; /* forward to the % and continue copying */
+				/* Unhandled variable, just copy */
+				default:
+				break;
+			}
+		}
+		/* expand variables */
+		if (untrusted) {
+			vlen = quote_var(dest, destlen, qs, untrusted);
+			src += 2;
+			dest += vlen;
+			destlen -= vlen;
+			continue;
+		}
+		if (c == '\'') {
+			if (qs == NONE) {
+				qs = APOSTROPHE;
+			} else if (qs == APOSTROPHE) {
+				qs = NONE;
+			}
+		} else if (c == '"') {
+			if (qs == NONE) {
+				qs = QUOTEDBL;
+			} else if (qs == QUOTEDBL) {
+				qs = NONE;
+			}
+		}
+		*dest = c;
+		src++;
+		dest++;
+		destlen--;
+	}
+	*dest = '\0';
+	return destbuf;
 }
 
-
 void lp_person( person )
     char	*person;
 {
@@ -570,6 +608,7 @@ int lp_open( out, sat )
     }
 
     if ( lp.lp_flags & LP_PIPE ) {
+        char *pipe_cmd;
 
 	/* go right to program */
 	if (lp.lp_person != NULL) {
@@ -583,12 +622,13 @@ int lp_open( out, sat )
 	}
 
 	lp_setup_comments(CH_UNIX);
-	if (( lp.lp_stream = popen( pipexlate(printer->p_printer), "w" )) == NULL ) {
+	pipe_cmd = pipexlate(printer->p_printer);
+	if ((lp.lp_stream = popen(pipe_cmd, "w")) == NULL) {
 	    LOG(log_error, logtype_papd, "lp_open popen %s: %m", printer->p_printer );
 	    spoolerror( out, NULL );
 	    return( -1 );
 	}
-        LOG(log_debug, logtype_papd, "lp_open: opened %s",  pipexlate(printer->p_printer) );
+        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 );
 
