Howto: AirPrint in pfSense 2.0
-
Adding AirPrint to pfSense 2.0
iOS devices(iPhone, iPad, iETC…) can print to the CUPS service previosly set up in pfSense (See my other tutorial http://forum.pfsense.org/index.php/topic,44941.0.html ).
Steps:
1.) Install "Avahi" from the "Packages" screen in pfSense.
2. ) Install dependencies:
pkg_add -r gutenprint-cups py26-cups
3.) Go to: https://github.com/tjfontaine/airprint-generate/blob/master/airprint-generate.py and copy the code to a text file "airprint-generate.py".
a.) Change the first line in the file:
#!/usr/bin/env python
to
#!/usr/local/bin/python
If you did this in windows you may need to run it through a dos2unix conversion. I used.
The one available here. Just dodos2unix.exe airprint-generate.py
in the windows console.4.) Generate your printer service config for Avahi:
./airprint-generate.py (This will output a file for every printer setup in your CUPS server)
5.) Remove all default service files under: /usr/local/etc/avahi/services/ :
rm -Rf /usr/local/etc/avahi/services/
6.) Move your newly created *.service file to /usr/local/etc/avahi/services/ :
mv *.service /usr/local/etc/avahi/services/
7.)
chmod 755 /usr/local/etc/avahi/services/*.service
8.) Edit the default avahi-daemon.c template so that /usr/local/etc/avahi/avahi-daemon.conf is overwritten with our custom config whenever the service is started/stopped.
vi /usr/local/pkg/avahi.inc
Comment out:
#host-name={$hostname}
#domain-name={$domain}
#browse-domains={$browsedomains}9.) Add "ServerAlias *" line without the double quotes to /usr/local/etc/cups/cupsd.conf like so:
Allow remote access
ServerAlias *
Port 63110.) Now from here you can just reboot the router. Or you can continue with manually restarting services.
11.) restart cups:
/usr/local/etc/rc.d/cupsd stop
/usr/local/etc/rc.d/cupsd onestart
12.) Disable, then Enable Avahi from the Services menu.
a.) Be sure that your local domain is listed in the "Browse domains" field. (mine is localdomain)
b.) Select WAN in the "Deny Interfaces" field just to be safe.
c.) uncheck "Enable" and save.
d.) check "Enable" and save. (But you probably already guessed this part) :p
That should do it. Now try and print from your iOS device while it is on the pfSense router LAN.
I do believe that you will need to have an iOS 4.2 or higher device from what I am reading.
-
Here is the most current version of airprint-generate.py from the time of this post. Just in case the original link goes away.
Give props to Timothy J Fontaine for the script. I did not create it. https://github.com/tjfontaine ;D
#!/usr/bin/env python """ Copyright (c) 2010 Timothy J Fontaine <tjfontaine@atxconsulting.com>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import cups, os, optparse, re, urlparse import os.path from StringIO import StringIO from xml.dom.minidom import parseString from xml.dom import minidom import sys try: import lxml.etree as etree from lxml.etree import Element, ElementTree, tostring except: try: from xml.etree.ElementTree import Element, ElementTree, tostring etree = None except: try: from elementtree import Element, ElementTree, tostring etree = None except: raise 'Failed to find python libxml or elementtree, please install one of those or use python >= 2.5' XML_TEMPLATE = """ <service-group><name replace-wildcards="yes"></name> <service><type>_ipp._tcp</type> <subtype>_universal._sub._ipp._tcp</subtype> <port>631</port> <txt-record>txtvers=1</txt-record> <txt-record>qtotal=1</txt-record> <txt-record>Transparent=T</txt-record> <txt-record>URF=none</txt-record></service></service-group> """ #TODO XXX FIXME #<txt-record>ty=AirPrint Ricoh Aficio MP 6000</txt-record> #<txt-record>Binary=T</txt-record> #<txt-record>Duplex=T</txt-record> #<txt-record>Copies=T</txt-record> DOCUMENT_TYPES = { # These content-types will be at the front of the list 'application/pdf': True, 'application/postscript': True, 'application/vnd.cups-raster': True, 'application/octet-stream': True, 'image/urf': True, 'image/png': True, 'image/tiff': True, 'image/png': True, 'image/jpeg': True, 'image/gif': True, 'text/plain': True, 'text/html': True, # These content-types will never be reported 'image/x-xwindowdump': False, 'image/x-xpixmap': False, 'image/x-xbitmap': False, 'image/x-sun-raster': False, 'image/x-sgi-rgb': False, 'image/x-portable-pixmap': False, 'image/x-portable-graymap': False, 'image/x-portable-bitmap': False, 'image/x-portable-anymap': False, 'application/x-shell': False, 'application/x-perl': False, 'application/x-csource': False, 'application/x-cshell': False, } class AirPrintGenerate(object): def __init__(self, host=None, user=None, port=None, verbose=False, directory=None, prefix='AirPrint-', adminurl=False): self.host = host self.user = user self.port = port self.verbose = verbose self.directory = directory self.prefix = prefix self.adminurl = adminurl if self.user: cups.setUser(self.user) def generate(self): if not self.host: conn = cups.Connection() else: if not self.port: self.port = 631 conn = cups.Connection(self.host, self.port) printers = conn.getPrinters() for p, v in printers.items(): if v['printer-is-shared']: attrs = conn.getPrinterAttributes(p) uri = urlparse.urlparse(v['printer-uri-supported']) tree = ElementTree() tree.parse(StringIO(XML_TEMPLATE.replace('\n', '').replace('\r', '').replace('\t', ''))) name = tree.find('name') name.text = 'AirPrint %s @ %%h' % (p) service = tree.find('service') port = service.find('port') port_no = None if hasattr(uri, 'port'): port_no = uri.port if not port_no: port_no = self.port if not port_no: port_no = cups.getPort() port.text = '%d' % port_no if hasattr(uri, 'path'): rp = uri.path else: rp = uri[2] re_match = re.match(r'^//(.*):(\d+)(/.*)', rp) if re_match: rp = re_match.group(3) #Remove leading slashes from path #TODO XXX FIXME I'm worried this will match broken urlparse #results as well (for instance if they don't include a port) #the xml would be malform'd either way rp = re.sub(r'^/+', '', rp) path = Element('txt-record') path.text = 'rp=%s' % (rp) service.append(path) desc = Element('txt-record') desc.text = 'note=%s' % (v['printer-info']) service.append(desc) product = Element('txt-record') product.text = 'product=(GPL Ghostscript)' service.append(product) state = Element('txt-record') state.text = 'printer-state=%s' % (v['printer-state']) service.append(state) ptype = Element('txt-record') ptype.text = 'printer-type=%s' % (hex(v['printer-type'])) service.append(ptype) pdl = Element('txt-record') fmts = [] defer = [] for a in attrs['document-format-supported']: if a in DOCUMENT_TYPES: if DOCUMENT_TYPES[a]: fmts.append(a) else: defer.append(a) fmts = ','.join(fmts+defer) dropped = [] # TODO XXX FIXME all fields should be checked for 255 limit while len('pdl=%s' % (fmts)) >= 255: (fmts, drop) = fmts.rsplit(',', 1) dropped.append(drop) if len(dropped) and self.verbose: sys.stderr.write('%s Losing support for: %s%s' % (p, ','.join(dropped), os.linesep)) pdl.text = 'pdl=%s' % (fmts) service.append(pdl) if self.adminurl: admin = Element('txt-record') admin.text = 'adminurl=%s' % (v['printer-uri-supported']) service.append(admin) fname = '%s%s.service' % (self.prefix, p) if self.directory: fname = os.path.join(self.directory, fname) f = open(fname, 'w') if etree: tree.write(f, pretty_print=True, xml_declaration=True, encoding="UTF-8") else: xmlstr = tostring(tree.getroot()) doc = parseString(xmlstr) dt= minidom.getDOMImplementation('').createDocumentType('service-group', None, 'avahi-service.dtd') doc.insertBefore(dt, doc.documentElement) doc.writexml(f) f.close() if self.verbose: sys.stderr.write('Created: %s%s' % (fname, os.linesep)) if __name__ == '__main__': parser = optparse.OptionParser() parser.add_option('-H', '--host', action="store", type="string", dest='hostname', help='Hostname of CUPS server (optional)', metavar='HOSTNAME') parser.add_option('-P', '--port', action="store", type="int", dest='port', help='Port number of CUPS server', metavar='PORT') parser.add_option('-u', '--user', action="store", type="string", dest='username', help='Username to authenticate with against CUPS', metavar='USER') parser.add_option('-d', '--directory', action="store", type="string", dest='directory', help='Directory to create service files', metavar='DIRECTORY') parser.add_option('-v', '--verbose', action="store_true", dest="verbose", help="Print debugging information to STDERR") parser.add_option('-p', '--prefix', action="store", type="string", dest='prefix', help='Prefix all files with this string', metavar='PREFIX', default='AirPrint-') parser.add_option('-a', '--admin', action="store_true", dest="adminurl", help="Include the printer specified uri as the adminurl") (options, args) = parser.parse_args() # TODO XXX FIXME -- if cups login required, need to add # air=username,password from getpass import getpass cups.setPasswordCB(getpass) if options.directory: if not os.path.exists(options.directory): os.mkdir(options.directory) apg = AirPrintGenerate( user=options.username, host=options.hostname, port=options.port, verbose=options.verbose, directory=options.directory, prefix=options.prefix, adminurl=options.adminurl, ) apg.generate()</tjfontaine@atxconsulting.com>
-
Can you please link this as a feature request on redmine.pfsense.org under the Pacakges section?
-
posting such a long listings should be between```
[ code ][ /code ]-brackets -
@ermal:
Can you please link this as a feature request on redmine.pfsense.org under the Pacakges section?
Feature request created. Go vote for it. :)
http://redmine.pfsense.org/issues/2170
-
posting such a long listings should be between```
[ code ][ /code ]-bracketsFixed. Thank You.
-
Thank you for this!
Anyone working on this as a package? been 9 months since it was added (http://redmine.pfsense.org/issues/2170)