Credential Generator

Customization: Credential Generators

Version

This document describes the state as of versions 0.116 – 0.134 of the program.

Goal

Provide means for generating and delivering credentials (any of PIN, Card Assignment Code (formerly known as PUK), password) for end users of Dispatcher Paragon, without any need for manual intervention of the end user or the administrator.

As a result, users will be able to release their print jobs from the secure queue – using the PIN or username and password authentication, or by self-assigning a card using the Card Assignment Code.

Dispatcher Paragon License Modules

This customization requires the below modules to be present in Dispatcher Paragon license:

  • Authentication

  • Mobile Print

  • Print Roaming

  • Rule-Based Engine

Justification

Existing Dispatcher Paragon functionality covers the following related scenarios:

  • Mass-import of PIN codes from LDAP/AD or CSV

    • images/plugins/servlet/confluence/placeholder/error-i18nkey-editor-placeholder-broken-image-locale-en_gb-version-2.png but there are no pre-generated unique PIN codes to import.

  • Generation of PIN code on Dispatcher Paragon management interface by administrator

    • images/plugins/servlet/confluence/placeholder/error-i18nkey-editor-placeholder-broken-image-locale-en_gb-version-2.png but PIN codes have to be generated for every user one-by-one.

  • Generation of PIN code on Dispatcher Paragon management interface by users themselves

    • images/plugins/servlet/confluence/placeholder/error-i18nkey-editor-placeholder-broken-image-locale-en_gb-version-2.png but the users do not have a username and password yet to log into the Dispatcher Paragon management interface.

  • Secure mobile print for visitors only: Dispatcher Paragon can create a local user account for not yet known owners of jobs sent to a direct queue. This account is copied from template user (templateUserLogin) and username is set to print job owner's e-mail address.

    • images/plugins/servlet/confluence/placeholder/error-i18nkey-editor-placeholder-broken-image-locale-en_gb-version-2.png but their password, cards, PIN and CAC are not generated, assigned and communicated to the user. Also, jobs sent to secure/shared queue do not trigger the user account creation.

Solution

The generation of credentials is possible in two modes:

  • Batch credential generator – deployed to a single location and periodically executed from Windows Task Scheduler, credentials are generated for all users not having the credentials yet and matching the configured criteria.

    • Typical situation: There is no external source of existing PIN codes that could be imported by Dispatcher Paragon LDAP or CSV replicator. Asking the users to generate the credentials themselves/generating the credentials one-by-one by the administrator is not acceptable.

    • Typical situation: The users have/receive cards compatible with readers on the devices but the user accounts and card numbers are not available in any external source. Asking the administrator to generate CAC codes for the users one-by-one is not acceptable.

  • Credential generator – deployed to every Dispatcher Paragon Site server node and executed on-demand from Rule-Based Engine, credentials are generated for one user – the owner of given print job (user's login is passed as a parameter to the RBE script).

    • Typical situation: Secure Mobile Print for Visitors. The visitors are one-time or from-time-to-time users of Dispatcher Paragon, thus configuring the print drivers, logical printers and issuing a card for them is not acceptable.

    • Optional performance optimization for the Rule-Based Engine script: Credential generator script is executed only for users who still need to have the PIN generated:

      • Template user belongs to a cost center "Needs PIN" (users created from the template will inherit the cost center "Needs PIN"),

      • Credential generator moves the user to cost center "Has PIN",

      • Rule-Based Engine rule matches only to jobs owned by a user belonging to cost center "Needs PIN".

Both modes deliver the generated credentials to users via e-mail.

Note: If the randomly generated credentials for given user are not unique, the generation is repeated. After reaching the configured limit of attempts (max_generation_attempts), the generation is given up. The configured PIN length thus must correspond to the number of active Dispatcher Paragon users.

  • Example: The likelihood of generating a unique PIN code for 9000 users with PIN length of 4-digits within 100 attempts may not be high enough.

Note: The administrator can optionally receive e-mail notifications about any issues encountered while running.

Note: The optional performance optimization cannot be used when rewrite_pin = Expired or Extend or Always.

Use Case – Secure Mobile Print for Visitors

  1. User sends an e-mail to dedicated Dispatcher Paragon Mobile Print Server e-mail address with the the document to be printed attached.

  2. Dispatcher Paragon Mobile Print Server downloads the e-mail via IMAP, extracts the attachment and verifies that the sender's e-mail address doesn't belong to any of existing Dispatcher Paragon users.

  3. Dispatcher Paragon Mobile Print Server asks Dispatcher Paragon Site Server to create a new user account with username set to the e-mail sender.

  4. Dispatcher Paragon Mobile Print Server converts the attached document into PDF and submits a print job to Dispatcher Paragon Site Server with job owner set to the e-mail sender and with a tag "mobile".

  5. Dispatcher Paragon Site Server accepts the print job, holds the print job in secure queue and applies Rule-Based Engine rules to the print job's metadata.

  6. One of the Rule-Based Engine rules matches and executes the custom script.

  7. Custom script generates random and unique PIN code, assigns the PIN code to the job owner and sends an e-mail containing the generated PIN code to the user.

  8. User walks to a device with terminal, authenticates using the PIN code obtained via e-mail, releases the job to the device and picks up the printed pages.

Implementation Details

Credential Generator:

  1. The script checks that all conditions below are met:

    1. given user exists (*),

    2. cost_center exists if move_to_cost_center is non-empty and cost_center_from_username is empty,

    3. user does not belong to cost_center yet if move_to_cost_center is non-empty and cost_center_from_username is empty,

    4. role exists if remove_role is non-empty,

    5. user has role assigned if remove_role is non-empty,

    6. user's username matches the allowed_usernames pattern if allowed_usernames is non-empty,

    7. user's email address matches the allowed_emails pattern if allowed_emails is non-empty,

    8. generate_pin is enabled and pin_expiration_days is set if pin_rewrite is set to Extend.

  2. The script generates a random unique numeric PIN if generate_pin is True:

    1. that is pin_length digits long,

    2. option A: checks that the user does not have any PIN if pin_rewrite is set to Never,

    3. option B: deletes all existing PIN codes belonging to given user if pin_rewrite is set to Always,

    4. option C: deletes all existing PIN codes belonging to given user if the user has any expired PIN codes and pin_rewrite is set to Expired,

    5. option D: extends the expiration of the existing PIN code by pin_generation_days if the PIN is expired and pin_rewrite is set to Extend,

    6. option A-C only: sets the expiration of the generated PIN to pin_expiration_days if pin_expiration_days is non-empty,

    7. option A-C only: stores the MD5 hash of the generated PIN to the database.

  3. The script generates a random unique numeric Card Assignment Code if generate_puk is True:

    1. that is puk_length digits long,

    2. stores it to the database encrypted and encoded by Base64.

  4. The script generates a random unique alphanumeric password if generate_password is True:

    1. that is password_length characters long,

    2. stores its MD5 hash to the database.

    3. If password_rewrite is set to Never, the password is only generated if the user does not have any password yet. If password_rewrite is set to Always, a new password is generated even if the user already has one. (This is a new option since version 0.131.)

  5. The script checks that all cost centers mentioned in cost_center_from_username exist if cost_center_from_username is non-empty.

  6. The script moves the user to cost center named cost_center if move_to_cost_center is non-empty and cost_center_from_username is empty.

    1. if cost_center_from_username is non-empty:

      1. The script finds the first cost center from cost_center_from_username with a pattern that matches to the username and moves the user to this cost center.

      2. If the username does not match to any pattern in cost_center_from_username, the user is not moved to any cost center.

  7. The script removes the role named role from user if remove_role is non-empty.

  8. The script notifies the user about his newly generated credentials via e-mail if email_notifications is non-empty:

    1. e-mail recipient is set to user's username if email_recipient_from_username is True, otherwise to user's e-mail address configured in Dispatcher Paragon.

    2. If pin_send_existing is set to False, the PIN is only sent if a new PIN was generated. A pre-existing PIN is not sent.

    3. If pin_send_existing is set to True and an existing and valid PIN exists, the program will attempt to decode it and send it to the user. Note that depending on the configured pin_length and the available CPU power, this may take some time. (This is a new option since version 0.131.)

(*) If the user is not found and db_retries is a number, the script will wait db_seconds_before_retry seconds and retry the operation up to db_retries times before failing the check.

Note: All steps above are performed within a database transaction. If any of the checks fails or the script encounters an error, the whole transaction is rolled back and the user remains unchanged.

Batch credential generator:

  1. The script checks that generate_pin is True.

  2. The script finds all users that do not have any PIN code assigned.

  3. For each user found, the script:

    1. Skips the user if the username does not match the allowed_usernames pattern when allowed_usernames is non-empty,

    2. Skips the user if the email address does not match the allowed_emails pattern when allowed_emails is non-empty,

    3. Skips the user if the e-mail is empty and email_recipient_from_username is False,

    4. Generates a random unique numeric PIN that is pin_length digits long,

    5. Sets its expiration to pin_expiration_days if pin_expiration_days is non-empty,

    6. Stores its MD5 hash to the database.

    7. The script checks that all cost centers mentioned in cost_center_from_username exist if cost_center_from_username is non-empty.

    8. The script moves the user to cost center named cost_center if move_to_cost_center is non-empty and cost_center_from_username is empty.

      1. if cost_center_from_username is non-empty:

        1. The script finds the first cost center from cost_center_from_username with a pattern that matches to the username and moves the user to this cost center.

        2. If the username does not match to any pattern in cost_center_from_username, the user is not moved to any cost center.

    9. Notifies the user about his newly generated credentials via e-mail if email_notifications is non-empty, where:

      1. e-mail recipient is set to user's username if email_recipient_from_username is True, otherwise to user's e-mail address configured in Dispatcher Paragon.

Note: Step 3 is performed within one database transaction per each user. If the script encounters an error, the transaction is rolled back (given user remains unchanged) and the script is interrupted (all remaining users are skipped, all already processed users remain modified and notified).

Known Limitations

  • Only the following options are supported for the Dispatcher Paragon conversionPIN setting: "MD5" (the default), "MD5WithoutPrefix", "PrefixPIN" and " Plain ".

    • Note: In the case of the "Plain" option, all PINs must have no more than X digits, and all card numbers must have more than X characters. This is because Credential Generator needs to know X (the threshold), to distinguish a PIN from a card number in the database.

  • PIN history settings and puk-ignore-pin System Settings are ignored.

  • Sending the e-mail notification about errors encountered to the administrator may fail (for example due to misconfigured e-mail settings), thus this option can not fully replace observing the log file.

  • The e-mail message text is a global setting, common to all users.

  • The configured cost center is a global setting, common to all users.

  • Database failover is not supported.

  • Batch credential generator supports only generation of PIN codes (not the CACs or passwords).

  • Encrypted configuration option values other than DB password may be mentioned in the debugging messages and thus may leak to the logfile if log level is set to DEBUG.

  • Performance of the batch credential generator depends on the speed of SMTP communication – the script itself was able to process 100k users (PostgreSQL on localhost) in less than 10 minutes when e-mail notifications were disabled.

  • Client certificate for SSL/TLS connections is not supported.

Installation and configuration

Pre-requisites

  • Dispatcher Paragon installed and configured

    • Both PostgreSQL and Microsoft SQL Server are supported

  • Microsoft Visual C++ 2015 Redistributable (latest version)

  • SMTP server

  • Credential Generator MSI package

  • Network access to SMTP server

  • Network access to Dispatcher Paragon database

  • Secure Mobile Print for visitors only:

    • Installed and enabled Mobile Print Server (enableMobilePrintServer = True)

    • Mailbox on IMAP server

    • Print Management Suite license (technically, at least the Mobile Print, Rule-Based Engine, Pull Printing and Accounting modules; in practice, this means the Print Management Suite)

Installation

  • Install Credential Generator from MSI package (and optionally change the location of the installed files)

  • Adjust Credential Generator's INI configuration file (located in the same folder as credential_generator.exe)

  • Uncomment (i.e., delete the hash/pound ("#") character at the start of the line) the line db_schema = tenant_1 and, in the case of a multi-tenant installation, change the database schema name as relevant for the tenant.

  • Make sure the log file location (specified in credential_generator.ini on the line log_file = ...) is correct.

  • If you want to generate passwords, enable the allowMD5PasswordEncoder system property in Dispatcher Paragon.

  • Batch credential generator only:

    • If desired, register a new task for Windows Task Scheduler using the command line (images/s/477gek/8804/1yuue1v/_/images/icons/emoticons/warning.svg make sure to replace "XXX" with the full path to the installation directory):

>

>

schtasks /create /tn "Generate PIN codes for users who do not have them yet" /ru "SYSTEM" /tr "\"XXX\credential_generator\batch_credential_generator.exe\"" /sc daily /st 03 : 00

  • Secure Mobile Print for visitors only:

    • Create two cost centers: one for users that do not have the credentials yet and one for users that already received the credentials (the procedure below uses names "Needs PIN" and "Has PIN" for these cost centers)

    • Create a user with username set to "template", belonging to the cost center "Needs PIN"

    • In System Settings:

      • enableAnonymousMobilePrint = True

      • templateUserLogin = template

    • Create RBE rule:

      • triggers on job reception

      • the print job must have a tag "Mobile"

      • optional: the print job must belong to a user from cost center "needs PIN"

        • Note: this condition is an optional optimization to avoid executing the Credential Generator for every single mobile print job for performance reasons. However, it is incompatible with pin_rewrite values other than Never.

      • executes "XXX\credential_generator\credential_generator.exe [USER_LOGIN]" (without the double-quotes, and make sure to replace "XXX" with the full path to the installation directory)

    • The default configuration is to avoid generating new credentials if the same user already had them generated less than two minutes ago. This is done to avoid sending multiple credentials in the case of an email with multiple attachments, which would only confuse users (they would have to use the credential that was generated last). You can change this using the configuration option min_seconds_between_runs.

  • When running the Credential Generator on server(s) different from the database server, especially when the database is installed locally on a Dispatcher Paragon server:

    • Make sure that connections to the database are allowed from outside the database server (both on the firewall and in the database configuration). For PostgreSQL, add the following line to pg_hba.conf:

      • host all all <ip address>/<netmask> md5

INI file configuration

INI file encoding

Please note that the credential_generator.ini file, if any non-ASCII characters are used, should be encoded in UTF-8. (A Byte-Order Mark is optional.)

Please see the default credential_generator.ini file and comments within it, which are indicated by the hash/pound ("#") character.

  • Options that need to be adjusted for particular environment:

    • Example for a PostgreSQL database:

      • db_type = PostgreSQL

      • db_connection_string = host=pgsql.domain.local port=5433 dbname=DBNAME user=postgres password=topsecret

    • Example for a Microsoft SQL Server database (SQL authentication):

    • db_type = MS SQL

    • db_connection_string = DRIVER={SQL Server};SERVER=safeq\SQLINSTANCE;DATABASE=DBNAME;UID=sa;PWD=topsecret

  • Example for a Microsoft SQL Server database (domain authentication):

    • db_type = MS SQL

    • db_connection_string = DRIVER={SQL Server};SERVER=MyServer;DATABASE=MyDB;Trusted_Connection=True

    • images/plugins/servlet/confluence/placeholder/error-i18nkey-editor-placeholder-broken-image-locale-en_gb-version-2.png In this case the SPOC service has to run under a domain account which has proper rights to the database.

  • db_schema = tenant_1

  • smtp_server = mailserver.domain.local

  • smtp_port = 25

  • # change smtp_port usually to 465 if using smtp_ssl

  • smtp_ssl = False

  • smtp_user = mailuser

  • smtp_password = topsecret

  • # STARTTLS uses the default SMTP port

  • smtp_starttls = False

  • # If you enable this, no validation will be done on the SSL/TLS server certificate. Totally insecure.

  • smtp_insecure_dont_validate_certificate = False

  • # path to a file of concatenated CA certificates in PEM format

  • smtp_ca_certificates_pem_file =

  • # path to a directory containing several CA certificates in PEM format, following an OpenSSL specific layout

  • smtp_ca_certificates_pem_dir =

  • email_from = credential.generator@domain.local

  • admin_email = admin@domain.local

  • email_subject = Dispatcher Paragon Visitor Print: Access granted, pick up your documents

  • email_body = Your documents have been queued. To print the documents, please authenticate at any printer by typing your PIN code '{0}'.\nKeep the PIN code for future use.

  • email_body_1 = Your documents have been queued. To print the documents, please authenticate at any printer by swiping your card. Use the code '{1}' to assign a card to yourself.

  • email_body_2 = Your documents have been queued. To print the documents, please authenticate at any printer by typing your email address as the username and the following as password: '{2}'.

  • email_body_is_html = False

  • allowed_usernames = [^@]+@[^@]+\.[^@]+

  • conversionPIN = MD5 or MD5WithoutPrefix or PrefixPIN or Plain_ X

    • In the case of the "Plain" option, all PINs must have no more than X digits, and all card numbers must have more than X characters. Credential Generator needs to know "X" (the threshold), to distinguish a PIN from a card number in the database. Therefore, if the conversionPIN property is configured to Plain in Dispatcher Paragon, then you must configure conversionPIN=Plain_ X here, where X is the maximum length of PINs. Example: conversionPIN=Plain_6 if PINs have up to 6 digits and all card numbers have at least 7 characters.

  • Options specific for Credential generator:

    • move_to_cost_center = True

    • cost_center = Has PIN

    • email_recipient_from_username = True

    • db_retries = 3

    • db_seconds_before_retry = 10

    • pin_send_existing = False/True

    • generate_password = False/True

    • password_rewrite = Never/Always

    • generate_puk = False/True

    • min_seconds_between_runs = 120

  • Options specific for Batch credential generator:

    • email_recipient_from_username = False

  • Options where default values should be sufficient:

    • generate_pin = True

    • pin_length = 6

    • pin_rewrite = Always

    • max_generation_attempts = 100

    • email_notifications = True

    • email_encoding = UTF-8

    • email_errors = True

    • error_subject = SafeQ Credential Generator encountered an error

    • log_file = C:\\XXX\\logs\\credential_generator.log

    • max_log_size = 8388608

    • log_backups = 9

    • log_format = %(asctime)s %(levelname)9s %(process)d %(message)s

    • log_encoding = utf-8

    • log_level = INFO

  • Options that are needed only for special use cases:

    • allowed_usernames = .*@(domain1.com|domain2.com)

    • allowed_emails = [^@]+@example\.com$

    • cost_center_from_username = .*@domain1.com -> Employees; .*@domain2.com -> Contractors; .* -> Guests

    • generate_puk = True

    • puk_length = 6

    • generate_password = True

    • password_length = 10

    • remove_role = True

    • role = Needs PIN

    • remote_logging = True

    • remote_logging_server = cml1.domain.local

    • remote_logging_port = 9020

Note: If allowed_usernames and allowed_emails properties have empty values (only whitespaces after the equal sign) or the value ".*" (period and asterisk), all users are processed (no users are skipped as not matching the criteria). The value is a Python regular expression – see https://docs.python.org/3/howto/regex.html

Note: generate_puk option generates CAC (card activation codes, formerly known as PUK codes).

Note: email_body option can use \n for continuing on another line.

Note: email_body can use placeholders for the actual value of the credentials: {0} for the PIN code, {1} for the CAC and {2} for the password.

Note: email_body can be used multiple times (email_body, email_body_0, email_body_1, email_body_2, etc.). Those that include a placeholder for a credential type that was not generated will be filtered out, and the remaining ones concatenated in order.

Note: Backslashes in paths must be doubled (the backslash itself is an escape character).

Note: True and False values are case-sensitive. None value (case sensitive) is interpreted the same way as empty value. Do not delete unused keys (names of the variables with equal sign). Lines starting with # are treated as a comment and ignored. Lines without equal sign are ignored.

Note: Any configuration option's value can contain an encrypted value (in format code,number,number...) which is automatically decrypted. Example of partially encrypted connection string:

db_connection_string = host=localhost port=5433 dbname=DBNAME user=postgres password=code,-3,5,98,45,18,-7,-125,-92

Note: cost_center_from_username option contains a list of mappings delimited by semicolons. Each mapping consists of two parts delimited by an arrow (minus and greater-than sign), first part is a regular expression applied to the username and second part is cost center name. Leading and trailing whitespaces in first and second part are ignored, whitespaces inside the first and second part are preserved.

Note: Denying unknown e-mail senders (=not generating/sending any credentials for them) can be done using the allowed_usernames option. Since the cost centers in cost_center_from_username are searched sequentially and first matching wins, "catch-all" cost center can be achieved by setting the last pattern to .* (dot and star). Users that do not match any pattern are not moved to any cost center (they retain the cost center of the templateUserLogin).

Troubleshooting

If you get the error "ImportError: DLL load failed: The application has failed to start because its side-by-side configuration is incorrect", you may need to download and install the "Microsoft Visual C++ 2008 Redistributable x86 9.0".

Change log

0.134: Added min_seconds_between_runs to solve multiple credentials per email with multiple attachments.

0.133: Added password_chars to let the administrator configure what characters will be used in generated passwords.

0.132: Avoids easily mistakable characters (0/O, l/I/1) in generated passwords. Fixed bugs in self-test.

0.131: Added pin_send_existing=True and password_rewrite=Always.

0.130: Hopefully fixed a problem on Microsoft SQL Server (message in log: pyodbc.DataError: ('String data, right truncation: length ... buffer ...', '22001')) presumably caused by https://github.com/mkleehammer/pyodbc/issues/334

0.129: Fixed bugs in cost center membership handling for Dispatcher Paragon. In addition, two minor improvements in the INI file handling: commenting out cost_center_from_username won't cause an error anymore, and True and False values are not case-sensitive anymore.

0.128: Added the "allowed_emails" configuration property. In addition: bug fix for failing self-test if db_schema is configured, and a run-time database connection check and advice for db_schema configuration.

0.127: Bug fix for "ImportError: DLL load failed" when the Credential Generator is started from outside its directory.

0.126: Bug fix for Dispatcher Paragon where Card Assignment Codes were generated in a wrong format and thus weren't working.

0.125: Bug fix for Dispatcher Paragon error with cost centers.

0.124: Bug fix for batch_credential_generator and pyodbc.Error: Connection is busy with results for another hstmt.

0.123: Merge of the Dispatcher Paragon version, start of support of Dispatcher Paragon in one version of Credential Generator.

0.122: Bug fix for multiple PINs generated per email by way of a system-wide mutex.

0.121: Added support for multiple email_body_X options and their concatenation and filtering by credential type.

0.120: A bugfix release for 0.119.

0.119: Added support for conversionPIN=Plain and PrefixPIN. (Do not use, upgrade to 0.120 due to a bug.)

0.118: Bug fix for reading a configuration file with the wrong encoding or with a UTF-8 BOM, and for pre-existing PINs without an expiration date.

0.117: Bug fix for conversionPIN settings, fixed the cost_center_from_username setting template in the INI file.

0.116: Added support for certificate validation options (smtp_insecure_dont_validate_certificate, smtp_ca_certificates_pem_file, and smtp_ca_certificates_pem_dir), for smtp_starttls (TLS for SMTP – needed e.g. for Exchange 365), and email_body_is_html. Some bug fixes.

0.115: Added support for conversionPIN=MD5WithoutPrefix.

0.114: Fixed credential uniqueness check. Fixed some issues with the Batch Credential Generator.

Log samples

credential_generator.log sample for Success

>

>

2015 - 09 - 01 12 : 50 : 22 , 799 INFO 4104 *** Credential generator 0.102 started with arguments: username: user123456 @domain .local
2015 - 09 - 01 12 : 50 : 22 , 832 INFO 4104 PIN generated and assigned to user 'user123456@domain.local'
2015 - 09 - 01 12 : 50 : 25 , 538 INFO 4104 User 'user123456@domain.local' notified about new credentials via e-mail
2015 - 09 - 01 12 : 50 : 25 , 551 INFO 4104 *** Credential generator successfully finished in 2.8110530376434326 seconds.

Note: The 4104 number in the log output above indicates the Windows process ID of the script.

Note: Before reporting any issues to Konica Minolta, please set the log_level = DEBUG in the INI file, reproduce the incorrect behavior and include the relevant part of the credential_generator.log in the incident report.

credential_generator.log sample for Failure

>

>

2015 - 09 - 02 13 : 20 : 02 , 302 INFO 5844 *** Batch credential generator 0.102 started
2015 - 09 - 02 13 : 20 : 02 , 334 INFO 5844 PIN generated for user 'user123456@domain.local'
2015 - 09 - 02 13 : 20 : 04 , 345 INFO 5844 Rolled back
2015 - 09 - 02 13 : 20 : 04 , 347 ERROR 5844 *** Exception encountered, Batch credential generator aborted.
Traceback (most recent call last):
File "batch_credential_generator.py" , line 54 , in <module>
File "batch_credential_generator.py" , line 30 , in generate_for_all
File "...base_credential_generator.py" , line 59 , in notify_user
self.send_email(self.config[ "email_from" ], recipient, self.config[ "email_subject" ], body)
File "...base_credential_generator.py" , line 43 , in send_email
with contextlib.closing(self.smtp_class(self.config[ "smtp_server" ], self.config[ "smtp_port" ])) as smtp:
File "C:\Python34\lib\smtplib.py" , line 242 , in __init__
(code, msg) = self.connect(host, port)
File "C:\Python34\lib\smtplib.py" , line 321 , in connect
self.sock = self._get_socket(host, port, self.timeout)
File "C:\Python34\lib\smtplib.py" , line 292 , in _get_socket
self.source_address)
File "C:\Python34\lib\socket.py" , line 509 , in create_connection
raise err
File "C:\Python34\lib\socket.py" , line 500 , in create_connection
sock.connect(sa)
ConnectionRefusedError: [WinError 10061 ] No connection could be made because the target machine actively refused it

Note: The last line shows the error message (ConnectionRefusedError) and the first line above it that doesn't contain "Python34" shows the part of the script's code that failed (with contextlib.closing(self.smtp_class(self.config["smtp_server"], self.config["smtp_port"])) as smtp:) which means that the connection was refused when trying to send an e-mail via SMTP. The "Rolled back" line indicates that the generated PIN was not committed to the database regardless of the line "PIN generated for user" above.

Note: Parts replaced by ellipsis (...) in the log output above may contain non-existing beginning of the filesystem path – this is caused by packaging the script into an EXE, please ignore it.