diff --git a/cups/files/drivers/cups6.inf b/cups/files/drivers/cups6.inf new file mode 100644 index 0000000..03908b2 --- /dev/null +++ b/cups/files/drivers/cups6.inf @@ -0,0 +1,43 @@ +; +; "$Id: cups6.inf,v 1.1 2005/06/16 20:42:20 mike Exp $" +; +; INF file for CUPS driver for Windows +; +; Copyright 2001-2005 by Easy Software Products +; + + +[Version] +Signature="$Windows NT$" +Provider=Easy Software Products +LayoutFile=ntprint.inf +ClassGUID={4D36E979-E325-11CE-BFC1-08002BE10318} +Class=Printer +DriverVer=06/16/2005 + +[Manufacturer] +"Easy Software Products" + +[Easy Software Products] +"CUPS Test Driver v6" = CUPS6.PS + +[CUPS6.PS] +CopyFiles=@CUPS6.PPD +CopyFiles=@CUPS6.INI +CopyFiles=@CUPSPS6.DLL +CopyFiles=@CUPSUI6.DLL +DataSection=PSCRIPT_DATA +DataFile=CUPS6.PPD +Include=NTPRINT.INF +Needs=PSCRIPT.OEM + +[DestinationDirs] +DefaultDestDir=66000 + +[ControlFlags] +ExcludeFromSelect = 2369 + + +; +; End of "$Id: cups6.inf,v 1.1 2005/06/16 20:42:20 mike Exp $". +; diff --git a/cups/files/drivers/cups6.ini b/cups/files/drivers/cups6.ini new file mode 100644 index 0000000..87a4459 --- /dev/null +++ b/cups/files/drivers/cups6.ini @@ -0,0 +1,5 @@ +[OEMFiles] +OEMConfigFile1=CUPSUI6.DLL +OEMDriverFile1=CUPSPS6.DLL + + diff --git a/cups/files/drivers/cupsps6.dll b/cups/files/drivers/cupsps6.dll new file mode 100644 index 0000000..c40bd04 Binary files /dev/null and b/cups/files/drivers/cupsps6.dll differ diff --git a/cups/files/drivers/cupsui6.dll b/cups/files/drivers/cupsui6.dll new file mode 100644 index 0000000..fae2222 Binary files /dev/null and b/cups/files/drivers/cupsui6.dll differ diff --git a/cups/files/drivers/ps5ui.dll b/cups/files/drivers/ps5ui.dll new file mode 100644 index 0000000..5e44128 Binary files /dev/null and b/cups/files/drivers/ps5ui.dll differ diff --git a/cups/files/drivers/pscript.hlp b/cups/files/drivers/pscript.hlp new file mode 100644 index 0000000..34ee8ba Binary files /dev/null and b/cups/files/drivers/pscript.hlp differ diff --git a/cups/files/drivers/pscript.ntf b/cups/files/drivers/pscript.ntf new file mode 100644 index 0000000..db38e98 Binary files /dev/null and b/cups/files/drivers/pscript.ntf differ diff --git a/cups/files/drivers/pscript5.dll b/cups/files/drivers/pscript5.dll new file mode 100644 index 0000000..757aec5 Binary files /dev/null and b/cups/files/drivers/pscript5.dll differ diff --git a/cups/files/drivers/x64/cups6.inf b/cups/files/drivers/x64/cups6.inf new file mode 100644 index 0000000..415ef9d --- /dev/null +++ b/cups/files/drivers/x64/cups6.inf @@ -0,0 +1,52 @@ +; +; "$Id: cups6.inf,v 1.1 2005/06/16 20:42:20 mike Exp $" +; +; INF file for CUPS driver for Windows +; +; Copyright 2001-2005 by Easy Software Products +; + + +[Version] +Signature="$Windows NT$" +Provider=Easy Software Products +LayoutFile=ntprint.inf +ClassGUID={4D36E979-E325-11CE-BFC1-08002BE10318} +Class=Printer +DriverVer=06/27/2009 + +[Manufacturer] +"Easy Software Products"=Easy Software Products,NTx86,NTamd64,NTia64 + +[Easy Software Products] +"CUPS Test Driver v6" = CUPS6.PS + +[Easy Software Products.NTx86] +"CUPS Test Driver v6 for x86 (i386)" = CUPS6.PS + +[Easy Software Products.NTamd64] +"CUPS Test Driver v6 for x64 (amd64)" = CUPS6.PS + +[Easy Software Products.NTia64] +"CUPS Test Driver v6 for IA64" = CUPS6.PS + +[CUPS6.PS] +CopyFiles=@CUPS6.PPD +CopyFiles=@CUPS6.INI +CopyFiles=@CUPSPS6.DLL +CopyFiles=@CUPSUI6.DLL +DataSection=PSCRIPT_DATA +DataFile=CUPS6.PPD +Include=NTPRINT.INF +Needs=PSCRIPT.OEM + +[DestinationDirs] +DefaultDestDir=66000 + +[ControlFlags] +ExcludeFromSelect = 2369 + + +; +; End of "$Id: cups6.inf,v 1.1 2005/06/16 20:42:20 mike Exp $". +; diff --git a/cups/files/drivers/x64/cups6.ini b/cups/files/drivers/x64/cups6.ini new file mode 100644 index 0000000..87a4459 --- /dev/null +++ b/cups/files/drivers/x64/cups6.ini @@ -0,0 +1,5 @@ +[OEMFiles] +OEMConfigFile1=CUPSUI6.DLL +OEMDriverFile1=CUPSPS6.DLL + + diff --git a/cups/files/drivers/x64/cupsps6.dll b/cups/files/drivers/x64/cupsps6.dll new file mode 100755 index 0000000..58dab02 Binary files /dev/null and b/cups/files/drivers/x64/cupsps6.dll differ diff --git a/cups/files/drivers/x64/cupsui6.dll b/cups/files/drivers/x64/cupsui6.dll new file mode 100755 index 0000000..6ade9df Binary files /dev/null and b/cups/files/drivers/x64/cupsui6.dll differ diff --git a/cups/files/drivers/x64/ps5ui.dll b/cups/files/drivers/x64/ps5ui.dll new file mode 100644 index 0000000..6b6e788 Binary files /dev/null and b/cups/files/drivers/x64/ps5ui.dll differ diff --git a/cups/files/drivers/x64/pscript.hlp b/cups/files/drivers/x64/pscript.hlp new file mode 100644 index 0000000..34ee8ba Binary files /dev/null and b/cups/files/drivers/x64/pscript.hlp differ diff --git a/cups/files/drivers/x64/pscript.ntf b/cups/files/drivers/x64/pscript.ntf new file mode 100644 index 0000000..c3545fa Binary files /dev/null and b/cups/files/drivers/x64/pscript.ntf differ diff --git a/cups/files/drivers/x64/pscript5.dll b/cups/files/drivers/x64/pscript5.dll new file mode 100644 index 0000000..d9b5610 Binary files /dev/null and b/cups/files/drivers/x64/pscript5.dll differ diff --git a/cups/files/printer-details.py b/cups/files/printer-details.py new file mode 100755 index 0000000..da88a19 --- /dev/null +++ b/cups/files/printer-details.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python + +# Copyright (c) 2009 Timo Makinen +# 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. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + + +import cups +from optparse import OptionParser +import os +import re +from subprocess import Popen, PIPE +import sys +from time import strftime + + +def main(): + os.environ['LANG'] = 'en_US' + parser = OptionParser() + parser.add_option('-v', '--verbose', + action='store_true', dest='verbose', default=False, + help='run in verbose mode') + parser.add_option('-t', '--templates', + dest='templates', default='/usr/share/cups/templates', + help='cups template directory') + parser.add_option('-d', '--datadir', + dest='datadir', default='/usr/share/doc/cups-1.3.7/details', + help='directory where to store html pages') + (options, args) = parser.parse_args() + + c = cups.Connection() + printers = [] + for (name, details) in c.getPrinters().items(): + if len(args) > 0 and name not in args: + continue + m = re.match('^([a-z]+)://(\S+):\d+.*', details['device-uri']) + if m is None or m.group(1) not in ('http', 'ipp', 'lpd', 'socket'): + continue + printers.append( (name, m.group(2)) ) + + for (name, addr) in printers: + if options.verbose: + print 'retrieving data for printer %s (%s)' % (name, addr) + data = getdata(name, addr, options.templates) + outfile = os.path.join(options.datadir, '%s.html' % name) + if data is None: + if options.verbose: + print ' failed to retrieve snmp data' + print ' updating last check timestamp' + data = '' + r = re.compile('(Last updated: \d+-\d+-\d+ \d+:\d+:\d+)(.*)
') + try: + for l in open(outfile, 'r').readlines(): + l = r.sub(r'\1 (last checked ' \ + + strftime('%Y-%m-%d %H:%M:%S') \ + + ')
\n', l) + data += l + except IOError: + continue + else: + if options.verbose: + print ' saving data to %s' % outfile + savedata(outfile, data) + + +def getdata(name, addr, templates): + data = readtemplate(os.path.join(templates, 'header.tmpl'), + title='%s details' % name) + data += '

%s

\n' % name + + oids = ( + ('Product Name', '.1.3.6.1.2.1.25.3.2.1.3.1'), + ('Printer Serial Number', '.1.3.6.1.2.1.43.5.1.1.17.1'), + ('Page Count', '.1.3.6.1.2.1.43.10.2.1.4.1.1'), + ) + snmpdata = snmpget('public', addr, *map(lambda x: x[1], oids)) + if len(snmpdata) == 0: + return None + data += '

Printer Information

\n' + data += '

\n' + for (k, v) in oids: + try: + data += '%s: %s
\n' % (k, snmpdata[v]) + except KeyError: + pass + snmpdata = snmpwalk('public', addr, '.1.3.6.1.2.1.43.13.4.1.10') + duplex = 'Not installed' + for key, value in snmpdata.items(): + if re.search('Duplex', value): + duplex = 'Installed' + data += 'Duplexer: %s
\n' % duplex + snmpdata = snmpwalk('public', addr, '.1.3.6.1.4.1.11.2.3.9.4.2.1.1.4.1') + totalmemory = 0 + memoryslots = [] + for key, value in snmpdata.items(): + if re.match('^\.1\.3\.6\.1\.4\.1\.11\.2\.3\.9\.4\.2\.1\.1\.4\.1\.\d+\.5\.0$', key): + totalmemory += int(value) / 1024 / 1024 + memoryslots.append('%s MB' % str(int(value) / 1024 / 1024)) + if totalmemory > 0: + data += 'Installed memory: %s MB total (%s)
\n' % ( + totalmemory, ', '.join(memoryslots)) + data += 'Last updated: %s
\n' % ( + strftime('%Y-%m-%d %H:%M:%S')) + data += '

\n' + data += '\n' + + baseoid = '.1.3.6.1.2.1.43.11.1.1' + data += '

Supplies

\n' + snmpdata = snmpwalk('public', addr, baseoid) + keys = map(lambda x: x[-1], filter(lambda x: x.startswith( + baseoid + '.6.1'), snmpdata.keys())) + data += '\n' + for k in keys: + data += '\n' + data += ' \n' % ( + snmpdata[baseoid + '.6.1.' + k]) + data += ' %s\n' % getstatus(snmpdata['%s.9.1.%s' % (baseoid, k)], + snmpdata['%s.8.1.%s' % (baseoid, k)]) + data += '\n' + data += '
%s:
\n' + data += '\n' + + baseoid = '.1.3.6.1.2.1.43.8.2.1' + data += '

Paper trays

\n' + snmpdata = snmpwalk('public', addr, baseoid) + keys = list(set(map(lambda x: int(x.split('.')[-1]), snmpdata.keys()))) + data += '\n' + for idx in keys: + data += '\n' + if '%s.18.1.%s' % (baseoid, idx) in snmpdata: + description = snmpdata['%s.18.1.%s' % (baseoid, idx)] + elif '%s.13.1.%s' % (baseoid, idx) in snmpdata: + description = snmpdata['%s.13.1.%s' % (baseoid, idx)] + else: + description = 'Unknown' + if '%s.12.1.%s' % (baseoid, idx) in snmpdata: + description += ' (%s)' % snmpdata['%s.12.1.%s' % (baseoid, idx)] + data += ' \n' % description + data += ' %s' % getstatus(snmpdata['%s.10.1.%s' % (baseoid, idx)], + snmpdata['%s.9.1.%s' % (baseoid, idx)]) + data += '\n' + data += '
%s
\n' + data += '\n' + + baseoid = '.1.3.6.1.4.1.11.2.3.9.4.2.1.1.11' + snmpdata = snmpwalk('public', addr, baseoid) + keys = list(set(map(lambda x: int(x.split('.')[-3]), snmpdata.keys()))) + if len(keys) > 0: + data += '

Event Log

\n' + keys.sort(lambda x, y: y-x) + data += '\n' + data += '\n' + data += ' \n' + data += ' \n' + data += ' \n' + data += '\n' + for idx in keys: + if snmpdata['%s.%s.1.0' % (baseoid, idx)] == 0: + continue + data += '\n' + data += ' \n' % idx + data += ' \n' % snmpdata['%s.%s.1.0' % (baseoid, idx)] + error = hex(snmpdata['%s.%s.2.0' % (baseoid, idx)]) + if len(error) == 7: + error = '%s.%s.%s\n' % (str(long(error[0:3], 16)), + error[3:5], error[5:7]) + else: + error = '%s.%s.%s\n' % (str(long(error[0:4], 16)), + error[4:6], error[6:8]) + data += ' \n' % error.upper() + data += '\n' + data += '
NumberPage CountError code
%s%s%s
\n' + data += '\n' + + data += readtemplate(os.path.join(templates, 'trailer.tmpl')) + return data + + +def getstatus(cur, max): + if cur > 0 and max > 0: + used = cur*100/max + ret = ' %s percent left\n' % used + ret += ' \n' + if used < 10: + color = 'red' + elif used < 30: + color = 'yellow' + else: + color = 'green' + ret += '
 
\n' % ( + color, used) + ret += ' \n' + else: + if cur == -3: + cur = '
OK
' + elif cur == -2: + cur = 'OK' + elif cur == 0: + cur = '
Empty
' + ret = ' %s\n' % cur + return ret + + +def savedata(name, data): + f = open(name, 'w') + f.write(data) + f.close() + os.chmod(name, 0644) + + +## cups related functions +def readtemplate(name, *args, **kwargs): + f = open(name, 'r') + ret = '' + for line in f.readlines(): + for (k, v) in kwargs.items(): + line = re.sub('{%s}' % k, v, line) + line = re.sub('{.+\?.+:}', '', line) + ret += line + f.close() + return ret + + +## snmp functions +def snmpget(community, hostname, *args): + cmdline = [ 'snmpget', '-On', '-v', '1', '-c', community, hostname ] + for o in args: + cmdline.append(o) + ret = {} + cmd = Popen(cmdline, stdout=PIPE, stderr=PIPE, close_fds=True) + for line in cmd.stdout.readlines(): + line = snmpparse(line) + if line is not None: + ret[line[0]] = line[1] + return ret + + +def snmpwalk(community, hostname, oid): + cmdline = [ 'snmpwalk', '-On', '-v', '1', '-c', community, hostname, oid ] + ret = {} + cmd = Popen(cmdline, stdout=PIPE, stderr=PIPE, close_fds=True) + for line in cmd.stdout.readlines(): + line = snmpparse(line) + if line is not None: + ret[line[0]] = line[1] + return ret + + +def snmpparse(data): + data = data.strip().split(' ', 3) + try: + if data[2] in ('INTEGER:', 'Counter32:'): + ret = int(data[3]) + elif data[2] == 'STRING:': + if data[3][0] == '"' and data[3][-1] == '"': + data[3] = data[3][1:-1] + ret = data[3] + elif data[2] == 'Hex-STRING:': + ret = data[3] + elif data[2] == 'NULL': + ret = None + elif data[2] == '""': + return None + else: + raise ValueError('Invalid value type %s' % data[2][0:-1]) + except IndexError: + return None + return (data[0], ret) + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + sys.exit(1) diff --git a/cups/files/update-printer-inf.sh b/cups/files/update-printer-inf.sh new file mode 100644 index 0000000..63f6b86 --- /dev/null +++ b/cups/files/update-printer-inf.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +# Copyright (c) 2009 Timo Makinen +# 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. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + + +# read samba config with testparm and determine path to print$ share +samba_driver_dir() { + (echo "" | testparm) 2> /dev/null | awk ' + { + if (/^\[print\$\]/) { + sect=1 + } else if (/^\[/) { + sect=0 + } + if (sect == 1 && /^[ \t]*path = /) { + print $3 + } + }' +} + +# function which replaces printer specific values from cups driver +parse_driver_inf() { + cat "$1" | sed \ + -e "s/CUPS6\.\(PPD\|PS\)/${printer}.\1/g" \ + -e "s/CUPS Test Driver v\([0-9]*\)/CUPS Driver for ${printer}/" +} + + +CUPSDRIVERS="/usr/share/cups/drivers" +if [ ! -d "${CUPSDRIVERS}" ]; then + echo "ERR: Cannot find CUPS driver directory ${CUPSDRIVERS}" 1>&2 + exit 1 +fi +SAMBADRIVERS="`samba_driver_dir`" +if [ "${SAMBADRIVERS}" = "" ]; then + echo 'ERR: Cannot find directory for share print$' 1>&2 + exit 1 +elif [ ! -d "${SAMBADRIVERS}" ]; then + echo 'ERR: Directory for share print$ does not exist' 1>&2 + exit 1 +fi + + +# loop through printers +lpstat -p | sed -n 's/^printer \(.*\) is .*$/\1/p' | while read printer ; do + for platform in "W32X86" "x64" "ia64" ; do + # skip platform if no samba drivers are installed + [ -d "${SAMBADRIVERS}/${platform}" ] || continue + # set source inf and check that it's found + if [ "${platform}" = "W32X86" ]; then + source="${CUPSDRIVERS}/cups6.inf" + else + source="${CUPSDRIVERS}/${platform}/cups6.inf" + fi + [ -f "${source}" ] || continue + + # create new inf if needed + target="${SAMBADRIVERS}/${platform}/3/${printer}.inf" + if [ -f "${target}" ]; then + parse_driver_inf "${source}" | diff "${target}" - > /dev/null + if [ $? -eq 0 ]; then + continue + fi + fi + echo "Updating driver INF file ${target}" + parse_driver_inf "${source}" > "${target}" + done +done diff --git a/cups/manifests/init.pp b/cups/manifests/init.pp new file mode 100644 index 0000000..c305060 --- /dev/null +++ b/cups/manifests/init.pp @@ -0,0 +1,119 @@ + +# Install and configure cups client +# +class cups::client { + + package { "cups": + ensure => installed, + } + + file { "/etc/cups/client.conf": + ensure => present, + content => template("cups/client.conf.erb"), + mode => 0644, + owner => root, + group => lp, + require => Package["cups"], + } + +} + +# Install cups server +# +class cups::server inherits cups::client { + + package { [ "ghostscript", "system-config-printer" ]: + ensure => installed, + } + + service { "cups": + ensure => running, + enable => true, + require => Package["cups"], + } + + File["/etc/cups/client.conf"] { + content => "ServerName 127.0.0.1\n", + } + +} + + +# Install samba support into cups +# +class cups::samba { + + include samba::server + + file { [ "/etc/samba/drivers", + "/usr/share/cups/drivers", + "/usr/share/cups/drivers/x64", ]: + ensure => directory, + mode => 0755, + owner => root, + group => root, + require => [ Package["samba"], + Package["cups"], ], + } + + define driverfile() { + file { "/usr/share/cups/drivers/${name}": + ensure => present, + source => "puppet:///cups/drivers/${name}", + mode => 0644, + owner => root, + group => root, + require => [ File["/usr/share/cups/drivers"], + File["/usr/share/cups/drivers/x64"], ], + } + } + + driverfile { "cups6.inf": } + driverfile { "cups6.ini": } + driverfile { "cupsps6.dll": } + driverfile { "cupsui6.dll": } + + driverfile { "ps5ui.dll": } + driverfile { "pscript.hlp": } + driverfile { "pscript.ntf": } + driverfile { "pscript5.dll": } + + driverfile { "x64/cups6.inf": } + driverfile { "x64/cups6.ini": } + driverfile { "x64/cupsps6.dll": } + driverfile { "x64/cupsui6.dll": } + + driverfile { "x64/ps5ui.dll": } + driverfile { "x64/pscript.hlp": } + driverfile { "x64/pscript.ntf": } + driverfile { "x64/pscript5.dll": } + + file { "/etc/cron.hourly/update-printer-inf.sh": + ensure => present, + source => "puppet:///cups/update-printer-inf.sh", + mode => 0755, + owner => root, + group => root, + } + +} + + +# Install SNMP polling into cups server +# +class cups::snmp { + + package { "net-snmp-utils": + ensure => installed, + } + + file { "/etc/cron.hourly/printer-details.py": + ensure => present, + source => "puppet:///cups/printer-details.py", + mode => 0755, + owner => root, + group => root, + require => Package["net-snmp-utils"], + } + +} diff --git a/cups/templates/client.conf.erb b/cups/templates/client.conf.erb new file mode 100644 index 0000000..14a2625 --- /dev/null +++ b/cups/templates/client.conf.erb @@ -0,0 +1,4 @@ +ServerName <%= cups_server %> +<% if cups_ssl -%> +Encryption Required +<% end -%>