pax_global_header00006660000000000000000000000064135376501640014524gustar00rootroot0000000000000052 comment=6357aa66a8c1cfb4abffd5837de7053c13d06408 schleuder-3.4.1/000077500000000000000000000000001353765016400135075ustar00rootroot00000000000000schleuder-3.4.1/.gitattributes000066400000000000000000000001111353765016400163730ustar00rootroot00000000000000.gitlab-ci.yml export-ignore utils/ export-ignore .gitlab/ export-ignore schleuder-3.4.1/.gitignore000066400000000000000000000002131353765016400154730ustar00rootroot00000000000000.bundle vendor *.swp db/*.sqlite3 testmails/* .pc/ coverage/ /*.gem /*.gem.sig /*.tar.gz /*.tar.gz.sig Gemfile.lock spec/list-defaults.yml schleuder-3.4.1/.rspec000066400000000000000000000000361353765016400146230ustar00rootroot00000000000000--color --require spec_helper schleuder-3.4.1/CHANGELOG.md000066400000000000000000000565111353765016400153300ustar00rootroot00000000000000Change Log ========== This project adheres to [Semantic Versioning](http://semver.org/). ## [3.4.1] / 2019-09-16 ### Fixed * Do not crash on protected header emails generated by mutt (#430) * Show an error message if `refresh_keys` is called with an email address for which no list exists. * Fix recognizing keywords with "protected headers" and empty subject. Previously, if the subject was unset, keywords were not recognized and the original "protected headers" could leak. (#431) ### Changed * Filter third party signatures on user-IDs when fetching or refreshing keys to mitigate against signature flooding. This works only if the version of gpg is 2.1.15 or newer. If the version is older, an email is being sent to the superadmin each time a key is fetched or keys are refreshed. See for background information. ## [3.4.0] / 2019-02-14 ### Fixed * Stop leaking keywords to third parties by stripping HTML from multipart/alternative messages if they contain keywords. (#399) * Avoid shelling out in a test-case to avoid an occasional error occurring in CI runs that complains about invalid data in ASCII-8BIT strings. ### Changed * Update the dependency 'mail' to version 2.7.x., and allow carriage returns (CR) in test-cases as mail-2.7 puts those out. * Update the dependency 'sqlite3' to version 1.3.x. * Adapt fixtures and factories for factorybot version 5.x. * Let schleuder-code load the filter files in test-mode, avoid explicit path names (which make headaches when running tests on installed packages). ## [3.3.0] / 2018-09-04 ### Fixed * Handle missing arguments for several keywords and reply with a helpful error-message. * Send replies to keyword-usage and notices to admins regardless of the delivery-flag of their subscription. (#354) * X-UNSUBSCRIBE will refuse to unsubscribe the last admin of a list. (#357) * Handle "protected subjects" in a way that Thunderbird/Enigmail recognize. (#74) * X-SET-FINGERPRINT will not anymore allow setting an empty fingerprint. (#360) ### Added * To remove a fingerprint from a subscription one can use the new keyword X-UNSET-FINGERPRINT (#360). * Extend the pseudoheaders configuration option to support 'sig' and 'enc' as configurable and sortable fields. ### Changed * The output of the keywords 'X-ADD-KEY' and 'X-DELETE-KEY' now also show the "oneline"-format to represent keys (which includes fingerprint, primary email-address, date of generation and possible expiry). (#295) * In the response to 'X-ADD-KEY', differentiate between 'newly imported' and 'updated' keys. * Parse keywords up to the first line detected as mail content, this addresses a first part of #249. ## [3.2.3] / 2018-05-14 ### Fixed * `X-SUBSCRIBE` now in all cases correctly sets the values for admin and delivery_enabled, if they are given as third and fourth argument, respectively. * To identify broken Microsoft Exchange messages, check if the headers include 'X-MS-Exchange' instead of specific domain names. Before this, we've missed mails sent by Exchange installations not operated by Microsoft or mails with a different "originating organisation domain" than Hotmail or Outlook. (#333) * Do not anymore fail on emails containing any PGP boundaries as part of their plain text. As a sideeffect we will not anymore validate an email a second time. Hence, a message part containing an additional signature within an encrypted (and possibly signed) email won't be validated and removed. (#261) * Exit with code 1 if a CLI-subcommand was not found (#339). * Fix finding keywords in request-messages that were sent from Thunderbird/Enigmail with enabled "protected subject". * Fix leaking the "protected subject" sent from Thunderbird/Enigmail. * Error messages are converted into human readable text now, instead of giving their class-name. (#338) * Require mail-gpg >= 0.3.3, which fixes a bug that let some equal-signs disappear under specific circumstances. (#287) ### Known issues * With the current used mail library version schleuder uses, there are certain malformed emails that can't be parsed. See #334 for background. This will be fixed in future releases of the mail library. ### Added * Enable to load external filters, similar to how we allow external plugins. (#282) ### Changed * Use schleuder.org as website and team@schleuder.org as contact email. * Check environment variable if code coverage check should be executed. (#342) * Transform GPG fingerprints to upper case before saving to database. (#327) * CLI-commands that (potentially) change data now remind the system admin to check file system permission if the command was run with root privileges. (#326) ## [3.2.2] / 2018-02-06 ### Changed * Temporarily depend on the ruby-library "mail" version 2.6. 2.7.0 seems to be a rough release (broke 8bit-characters, changed newline-styles) that needs to be ironed out before we can use it. * Changed wording of error-message in case of a missing or incorrect "X-LIST-NAME"-keyword. (Thanks, anarcat!) * Keys are now shuffled before refreshing them. This randomizes the way how we are querying keyservers for updated keys to avoid fingerprinting of a list's keyring. * Be more robust when dirmngr fails while refreshing keys, especially when updating over an onion service. Fixes #309. ### Fixed * Fix handling of emails with large first mime parts. We removed the code that limited the parsing of keywords to the first 1000 lines, as that broke the handling of certain large emails. * Fix output of Keys with a broken character set. This mainly affected schleuder-api. * Exit install-script if setting up the database failed. * Reveal less errors to public, and improve messages to admins. Previously errors about list-config etc. would have been included in bounces to the sender of the incoming email. Now only the admins get to know the details (which now also include the list the error happened with). Email-bounces only tell about a fatal error — except if the list could not be found, that information is still sent to the sender of the incoming email. * Make sure dirmngr is killed for a list after refreshing a list's keyring. Avoids servers getting memory exhausted. Fixes #289 * Fixed the API-daemon's interpretation of listnames that start with a number (previously the listname "1list" caused errors because it was taken as the integer 1). ## [3.2.1] / 2017-10-24 ### Changed * Explicitly depend on the latest version of ruby-gpgme (2.0.13) to force existing setups to update. This fixes the problem where unusable keys were not identified as such. (Previous versions of ruby-gpgme failed to properly provide the capabilities of a key.) ## [3.2.0] / 2017-10-23 ### Added * Internal footer: to be appended to each email that is sent to a subscribed address. Will not be included in messages to non-subscribed addresses. This change requires a change to the database, don't forget to run `schleuder install` after updating the code. * Optionally use an OS-wide defined keyserver by configuring a blank value for the keyserver. * Added keywords `X-RESEND-UNENCRYPTED` and `X-RESEND-CC-UNENCRYPTED` to enforce outgoing email(s) in cleartext regardless of whether we would find a key for the recipient or not. ### Changed * Public footer: Whitespace is not anymore stripped from the value of public_footer. * The API does not include anymore each key's key-data in response to `/keys.json`. This avoids performance problems with even medium sized keyrings. * The short representation of GnuPG keys became more human-friendly. Besides the fingerprint we now show the email-address of the first UID, the generation-date, and optionally the expiration-date. * Log the full exception when sending a message fails. (Thanks, Lunar!) * When creating a new list, we do not anymore look for a matching key for the admin-address in the list's keyring. We don't want to look up keys for subscriptions by email at all. (This was anyway only useful in the corner case where you prefilled a keyring to use for the new list.) * API: Access to `/status.json` is now allowed without authentication. * Deprecate X-LISTNAME in favour of X-LIST-NAME, for the sake of consistency in spelling keywords (but X-LISTNAME is still supported). (Thanks, maxigas!) ### Fixed * X-SUBSCRIBE now handles the combination of space-separated fingerprint and additional arguments (admin-flag, delivery-enabled-flag) correctly. * Fixed broken encoding of certain character-sequences in encrypted+signed messages. * X-LIST-KEYS again works without arguments. * X-RESEND now checks the given arguments to be valid email-addresses, and blocks resending if any one is found invalid. * X-RESEND now respects the encoding the mail was sent with. (Thanks, Lunar!) ## [3.1.2] / 2017-07-13 ### Changed * Sort lists alphabetically by email per default. ### Fixed * Fix dropping mails on certain headers (e.g. spam), as the headers weren't checked properly so far. * Fix processing of bounced messages. If a bounced messaged contained a PGP message (which most messages sent by schleuder have), schleuder tried to decrypt it before processing as a bounced message. This failed in nearly all cases, leading to double bounces. (#234) * Fix reading messages with empty Content-Type-header. Some automatically sent messages don't have one. * Do not try to fix text/plain messages from outlook (#246) ## [3.1.1] / 2017-06-24 ### Added * New cli-command `pin_keys` to pin the subscriptions of a list to a respective key (#225). Running this fixes the shortcoming of the code for list-migration mentioned below. ### Changed * Allow to run `refresh_keys` only for a given list. ### Fixed * **When migrating a v2-list, lookup keys for subscriptions** and assign the fingerprint if it was a distinct match. Otherwise people that had no fingerprint set before will receive plaintext emails — because in v3 we're not anymore looking up keys for subscriptions by email address. (To fix this for already migrated lists please use `schleuder pin_keys $listname`). * When migrating a v2-list, assign the looked up fingerprint to an admin only if it was a distinct match. * When migrating a v2-list, do not enable delivery for admins that weren't a member. (#213) * When migrating a v2-list, subscribe duplicated members only once (#208) * When migrating a v2-list, properly deal with admins that have no (valid) key. (#207) * When creating a list, only use distinctly found keys for admins. * Skip unusable keys when resending. * Don't report unchanged keys when refreshing keys. * Fix adding the subject-prefix to an empty subject (#226) * Do not detect emails sent from cron-scripts as bounces (#205) * Fix working with multipart/alternative-messages that contain inline OpenPGP-data. We're now stripping the HTML-part to enable properly handling the ciphertext. * Validate that an email address can be subscribed only once per list. * Fixed settings subscription-attributes (admin, delivery_enabled) when suscribing through schleuder-web. * schleuder-api-daemon SysV init script: Fix formatting and styling, add recommend and required commands {status,reload,force-reload} by Lintian. (#230) * Don't require database-adapter early. Helps when using a different database-system than sqlite. * Fix text of admin-notification from plugin-runners. * Avoid loops on notifying list admins (#229) ## [3.1.0] / 2017-05-21 ### Added * `X-GET-LOGFILE`. Sends you the logfile of the list. * `X-ATTACH-LISTKEY`. Attaches the list's key to a message. Useful in combination with `X-RESEND`. * `X-GET-VERSION`. Sends you the version of Schleuder that is running your list. * API-endpoint to trigger sending the list's key to all subscriptions. ### Changed * Don't write errors of list-plugins into the list of pseudo-headers. List-plugins must handle errors on their own. * Allow request-plugins to return attachments. * Fix x-get-key for multiple keys per match, and attach the resulting keys. * Tolerate 0x-prefix on input for fingerprints of subscriptions. * Tolerate spaces on input for fingerprints in keywords. * `X-GET-KEY` returns keys as attachments now. * `X-SIGN-THIS` returns attachments now, too. * The texts that describe the forwarded automated messages now reflect that not all of those were bounces. * Use single SQL-query instead of five, in select-statement in postfix/schleuder_sqlite.cf. * Use sender() to specify the return-address, instead of setting a Return-Path. ### Fixed * Make `public_footer` appear at the bottom of messages, not at the top. * Remove excessive empty lines in output of refresh-keys. * Amended list of dependencies in README. * Fix `X-GET-KEY` for multiple keys per match. * Also report if a key-import didn't change a present key. * Fix bounce-address in postfix/schleuder_sqlite.cf. ## [3.0.4] / 2017-04-15 ### Changed * Harmonize format of key `check` and `update` texts. ### Fixed * Fix unlegible messages (we mis-handled base64-encoded message-parts under some circumstances). * Avoid run-on paragraphs in key `check` and `update` reports (Thanks, dkg!) * Let schleuder-cli request check keys (allow /keys/check_keys.json to be reached). ## [3.0.3] / 2017-02-16 ### Changed * Require fingerprints of lists and subscriptions to be at least 32 characters long. Previously it was possible to assign shorter hexadecimal strings. * Key lookup for arguments to X-keywords is stricter than before: if you supply a string containing an "@", gnupg will be told to only match it against email-addresses; if you send a hexadecimal string, gnupg will be told to only match it against fingerprints. * Fixed and improved X-DELETE-KEY to only allow deletion of a single key, and only if no matching secret key is present. * Fixed and improved X-FETCH-KEY to use the configured keyserver; to handle URLs, fingerprints, and email-addresses alike; and to send internationalized messages. * Go back to make mock SKS-server listen on 127.0.0.1 — the former IP resulted in errors on some systems. ### Fixed * Don't break multipart/alternative-parts when inserting our pseudo-headers. * X-ADD-KEY handles inline content from Thunderbird correctly. * X-SIGN-THIS now looks recursively for attachments to sign. * Fixed unsubscribing oneself with X-UNSUBSCRIBE. * Fixed setting fingerprint for other subscription than oneself with X-SET-FINGERPRINT. * Better output of X-LIST-SUBSCRIPTIONS if no subscriptions are present. * Sensible error message if X-GET-KEY doesn't find a matching key. * Allow '0x'-prefix of fingerprints when given as keyword-argument. * If no keyword generates output, a sensible error message is used. ### Added * More rspec-tests. ## [3.0.2] / 2017-02-01 ### Changed * Use less usual IP and port number for mock SKS-server. Previously this conflicted with actual SKS-servers running on the same machine. ### Added * Call refresh_keys in provided crontab-script. ### Fixed * Fixed importing member-fingerprints when migrating a list. * Fix clearing passphrase during list-migration with GnuPG 2.0.x by actually shipping the required pinentry-script. * Corrected english phrasing and spelling for error message in case of wrong argument to listname-keyword. (Thanks, dkg!) ## [3.0.1] / 2017-01-26 ### Fixed * Fixed setting admin- and delivery-flags on subscription. Requests from schleuder-cli were interpreted wrongly, which led to new lists having no admins. * A short description for the man-page of schleuder-api-daemon to satisfy the lintian. * Listing openssl as dependency in README. If the openssl header-files are not present when eventmachine compiles its native code, schleuder-api-daemon cannot use TLS. * Removed reference to Github from Code of Conduct. ## [3.0.0] / 2017-01-26 ### Changed * **API-keys always required!** From now on all requests to schleuder-api-daemon require API-keys, even via localhost. This helps protecting against rogue non-root-accounts or -scripts on the local machine. * **TLS always used!** schleuder-api-daemon now always uses TLS. * Switched project-site and git-repository to . * Set proper usage flags when creating a new OpenPGP-key: the primary key gets "SC", the subkey "E". (Thanks, dkg!) * Avoid possible future errors by ignoring every unknown output of gpg (like GnuPG's doc/DETAILS recommends). (Thanks, dkg!) * Friendlier error message if delivery to subscription fails. * Set list-email as primary address after adding UIDs. Previously it was a little random, for reasons only known to GnuPG. * Only use temporary files where necessary, and with more secure paths. * Tighten requirements for valid email-addresses a little: The domain-part may now only contain alphanumeric characters, plus these: `._-` * Required version of schleuder-cli: 0.0.2. ### Added * X-LISTNAME: A **new mandatory keyword** to accompany all keywords. From now on every message containing keywords must also include the listname-keyword like this: `X-LISTNAME: list@hostname` The other keywords will only be run if the given listname matches the email-address of the list that the message is sent to. This mitigates replay-attacks among different lists. * Also send helpful message if a subscription's key is present but unusable. * Provide simpler postfix integration, now using virtual_domains and an sql-script. (Thanks, dkg!) * Enable refreshing keys from keyservers: A script that is meant to be run regularly from cron. It refreshes each key of each list one by one from a configurable keyserver, and sends the result to the respective list-admins. * Import attached, ascii-armored keys from messages with `add-key`-keyword. (Thanks, Kéfir!) * Check possible key-material for expected format before importing it. (Thanks, Kéfir!) ### Fixed * Allow fingerprints to be prefixed with '0x' in `subscribe`-keyword. * Also delete directory of list-logfile on deletion if that resides outside of the list-dir. * Sign and possibly encrypt error notifications. * Fix setting admin- and delivery-flags while subscribing. * Fix subscribing from schleuder-cli. * Fix finding subscriptions from signatures made by a signing-capable sub-key. ## [3.0.0.beta17] / 2017-01-12 ### Changed * Stopped using SCHLEUDER_ROOT in specs. Those make life difficult for packaging for debian. * While running specs, ensure smtp-daemon.rb has been stopped before starting it anew. ### Added * A Code of Conduct. ## [3.0.0.beta16] / 2017-01-11 ### Fixed * Fix running `schleuder migrate...`. * Fix assigning list-attributes when migrating a list. ### Added * Import the secret key and clear its passphrase when migrating a list from v2. * More tests. ## [3.0.0.beta15] / 2017-01-10 ### Changed * Default `lists_dir` and `listlogs_dir` to `/var/lib/schleuder`. * Use '/usr/local/bin' as daemon PATH in schleuder-api-daemon sysvinit script. ### Fixed * Fix running for fresh lists if `lists_dir` is different from `listlogs_dir` (by creating logfile-basedir, closes Debian bug #850545). * Fix error-message from ListBuilder if given email is invalid. * Fix checking for sufficient gpg-version (previously '2.1' didn't suffice if '2.1.0' was required). ### Added * Cron job file to check keys. * Show when delivery is disabled for a subscription (in reply to 'list-subscriptions'-keyword). * Add timeout to default sqlite-config (avoids errors in the case that the DB-file is locked on first attempt). * Provide method to call gpg-executable. * Also add additional UIDs to generated PGP-keys when using gpg 2.0. * Specs for ListBuilder. ## [3.0.0.beta14] / 2016-12-29 ### Fixed * Fix key expiry check * Fix link to schleuder.nadir.org in List-Help header * Fix deleting listdir ### Added * Runner and integration tests * More fixtures ## [3.0.0.beta13] / 2016-12-22 ### Fixed * Fix creating new lists. ## [3.0.0.beta12] / 2016-12-22 ### Changed * Show file permission warning if cert is being generated as root. * Use hard-coded defaults as base to merge config-file over. ### Added * New keyword `x-resend-cc` to send a message to multiple recipients that should know of each another. The ciphertext will be encrypted only once to all recipients, too. * More specs. * Skript for schleuder-api-daemon under sysvinit. ### Fixed * Fix tests for non-default listlogs_dir. * Fix pseudo-header "Sig" for unknown keys. * Fix adding subject_prefix_in for unencrypted messages. * Fix checking permissions of listdir and list.log for newly created lists. * Fix occasionally empty 'date'-pseudo-header. ## [3.0.0.beta11] / 2016-12-07 ### Changed * Fixed recognition and validation of clearsigned-inline messages. * Fix log-file rotation (for list.log). * Show hint to set `use_tls: true` after generation of certificate. ### Added * During installation, show error message and exit if data of an installation of schleuder-2.x is found in the configured lists_dir. * More tests. ## [3.0.0.beta10] / 2016-12-05 ### Changed * Fixed tarball to contain correct version and state of changelog. ## [3.0.0.beta9] / 2016-12-02 ### Added * Include tarball into release. * Make basedir of list-logs configurable (`listlogs_dir`). No operational change with the default value. * Recognize "encapsulated" signatures (RFC 3156, 6.1). (These signatures might still be reported as invalid, that's a bug in mail-gpg which will probably be fixed in their next release.) * Make installed schleuder-files accessible for owner and group only. * Make list-logs accessible to owner and group only. ### Changed * Improved documentation. ### Fixed * Fix checking for empty messages for nested multiparts (e.g. Thunderbird with memoryhole-headers). * Fix `schleuder install` to respect config settings (e.g. `lists_dir`) ## [3.0.0.beta8] / 2016-11-27 ### Changed * Add network and local-filesystem as dependencies in systemd-unit-file. * Improved documentation. ### Fixed * Declare dependency on thin. ## [3.0.0.beta7] / 2016-11-23 ### Added * `man`-page for schleuder(8). * schleuder-api-daemon: optionally use TLS. * schleuder-api-daemon: authenticate client by API-key if TLS is used. ### Changed * Sign git-tags, gems, and tarballs as 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3. * Rename schleuderd to schleuder-api-daemon. * schleuder-api-daemon: bind to `localhost` by default. * schleuder-api-daemon: changed name of `bind` config option to `host`. * schleuder-api-daemon: return 204 if not content is being sent along. * Refactor and improve model validations. ### Fixed * Fixed creating lists. * Fixed default config. * Log errors to syslog-logger in case of problems with list-dir. ## [3.0.0.beta6] / 2016-11-13 ### Added * Add `-v`, `--version` arguments to CLI. * New model validators. * Translations (de, en) and better wording for validation error messages. * Specs (test-cases) for the list model. * Use Travis to automate testing. * Test listname to be a valid email address before creating list. * A simple contribution guide. * Check that GnuPG >= 2.0 is being used. * Enable to specify path to gpg-executable in GPGBIN environment variable. * A simple schleuder-only MTA to help with development. ### Changed * schleuderd: use GET instead of OPTIONS to work around bug in ruby 2.1. * Allow "inline"-pgp for request-messages (mail-gpg 0.2.7 fixed their issue). ### Fixed * Fix testing nested messages for emptiness. * Fix bouncing a message if it was found to be empty. * Fix truncated 'adding UID failed' message (transported via HTTP-headers). ## ... --------- The format of this file is based on [Keep a Changelog](http://keepachangelog.com/). Template, please ignore: ## [x.x.x] / YYYY-MM-DD ### Added ### Changed ### Deprecated ### Removed ### Fixed ### Security schleuder-3.4.1/CODE_OF_CONDUCT.md000066400000000000000000000044031353765016400163070ustar00rootroot00000000000000# Schleuder's Code of Conduct The Schleuder development team wants to provide an open and welcoming environment for everyone who participates in the development of Schleuder regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, physical appearance, race, ethnicity, economic or social status, age, religion, or nationality. Harassment in code and discussion or violation of physical boundaries is completely unacceptable anywhere in the Schleuder project. We welcome contributions from everyone that adheres to these principles. Violators will be warned, blocked or banned by the development team. ### In detail Harassment includes offensive verbal comments related to level of experience, gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, ethnicity, age, religion, nationality, the use of sexualized language or imagery, deliberate intimidation, stalking, sustained disruption, and unwelcome sexual attention. Individuals asked to stop any harassing behavior are expected to comply immediately. Maintainers, including the development team, are also subject to the anti-harassment policy. If anyone engages in abusive, harassing, or otherwise unacceptable behavior, including maintainers, we will take appropriate action, up to and including warning the offender, deletion of comments, removal from the project’s codebase and communication systems. If you are being harassed, notice that someone else is being harassed, or have any other concerns, please [contact the development team](https://schleuder.org/contact.html) immediately. We expect everyone to follow these rules anywhere in the Schleuder project’s codebases, issue trackers, IRC channel, group chat, mailing lists, meetups, and any other events. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Finally, don't forget that it is human to make mistakes! We all do. Let’s work together to help each other, resolve issues, and learn from the mistakes that we will all inevitably make from time to time. ### Thanks Thanks to the [Bundler Code of Conduct](https://bundler.io/conduct.html), which we adopted for ourselves. schleuder-3.4.1/CONTRIBUTING.md000066400000000000000000000011631353765016400157410ustar00rootroot00000000000000Contributing ============ To contribute please follow this workflow: 1. Talk to us! E.g. create an issue about your idea or problem. 2. Fork the repository and work in a meaningful named branch that is based off of our "master". 3. Enable CI: In your fork, go to Settings -> CI / CD -> Expand next to Runners settings -> Enable shared Runners. 4. Commit in rather small chunks but don't split depending code across commits. Please write sensible commit messages. 5. Please add tests for your feature or bugfix. 6. If in doubt request feedback from us! 7. When finished create a merge request. Thank you for your interest! schleuder-3.4.1/Dockerfile000066400000000000000000000020771353765016400155070ustar00rootroot00000000000000FROM ruby:2.4 MAINTAINER Friedrich Lindenberg , Michał "rysiek" Woźniak ENV DEBIAN_FRONTEND noninteractive RUN apt-get update && apt-get install -y \ libmagic-dev \ libgpgme11-dev \ wget \ git \ git-core \ inotify-tools \ --no-install-recommends && rm -rf /var/lib/apt/lists/* # install schleuder WORKDIR /opt/schleuder ADD . /opt/schleuder RUN cd /opt/schleuder && \ bundle install # get and install schleuder-cli # not required, but helpful for CLI-based list administration RUN git clone https://0xacab.org/schleuder/schleuder-cli.git /opt/schleuder-cli && \ cd /opt/schleuder-cli && \ bundle install # entrypoint script COPY docker/entrypoint.sh /sbin/entrypoint.sh RUN chmod a+x /sbin/entrypoint.sh # we need to be able to mount the code into other containers # like the SMTPD container # so that schleuder work can be run if needed VOLUME ["/usr/local/bundle", "/etc/schleuder", "/var/lib/schleuder/lists"] # final config EXPOSE 4443 ENTRYPOINT ["/sbin/entrypoint.sh"] CMD ["schleuder-api-daemon"] schleuder-3.4.1/Gemfile000066400000000000000000000001371353765016400150030ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in schleuder.gemspec gemspec schleuder-3.4.1/LICENSE.txt000066400000000000000000001045131353765016400153360ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . schleuder-3.4.1/MISSION_STATEMENT.md000066400000000000000000000014361353765016400166020ustar00rootroot00000000000000# Schleuder's Mission Statement We give our time and knowledge to build and maintain this project in order to help people with their daily private communication and in the struggle for their personal emancipation, social and economic justice and political freedom. A more human world, in which anyone can be different without fear, is what we strive for. We work transparently and responsibly, and don't collaborate with surveillance actors. We do not endorse any form of authoritarian movement or totalitarian system, and wish for that Schleuder may not ever be used to promote or enable them. Instead we are committed to ideas and principles outlined in our [Code of Conduct](https://0xacab.org/schleuder/schleuder/blob/master/CODE_OF_CONDUCT.md). If you do not agree, do not use Schleuder. schleuder-3.4.1/README.md000066400000000000000000000112371353765016400147720ustar00rootroot00000000000000Schleuder, version 3 ====================================== Schleuder is a gpg-enabled mailing list manager with resending-capabilities. Subscribers can communicate encrypted (and pseudonymously) among themselves, receive emails from non-subscribers and send emails to non-subscribers via the list. Version 3 of schleuder is a complete rewrite, which aims to be more robust, flexible, and internationalized. It also provides an API for the optional web interface called [schleuder-web](https://0xacab.org/schleuder/schleuder-web). For more details see . Requirements ------------ * ruby >=2.1 * gnupg 2.0.x, or >=2.1.16 * gpgme * sqlite3 * openssl *If you use Debian stretch or CentOS 7, please have a look at the [installation docs](https://schleuder.org/docs/#installation). We do provide packages for those platforms, which simplify the installation a lot.* *🛈 A note regarding Ubuntu: All Ubuntu versions up to and including 17.10 don't meet the requirements with their packaged versions of gnupg! To run Schleuder on Ubuntu you currently have to install a more recent version of gnupg manually. Only Ubuntu 18.04 ("bionic") provides modern enough versions of Schleuder's requirements.* On systems that base on Debian 9 ("stretch"), install the dependencies via apt-get install ruby-dev gnupg2 libgpgme-dev libsqlite3-dev libssl-dev build-essential We **recommend** to also run a random number generator like [haveged](http://www.issihosts.com/haveged/). This ensures Schleuder won't be blocked by lacking entropy, which otherwise might happen especially during key generation. On Debian based systems, install it via apt-get install haveged Additionally these **rubygems** are required (will be installed automatically unless present): * rake * active_record * sqlite3 * thor * thin * mail-gpg * sinatra * sinatra-contrib Installing Schleuder ------------ 1. Download [the gem](https://schleuder.org/download/schleuder-3.4.1.gem) and [the OpenPGP-signature](https://schleuder.org/download/schleuder-3.4.1.gem.sig) and verify: ``` gpg --recv-key 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 gpg --verify schleuder-3.4.1.gem.sig ``` 2. If all went well install the gem: ``` gem install schleuder-3.4.1.gem ``` 3. Set up schleuder: ``` schleuder install ``` This creates necessary directories, copies example configs, etc. If you see errors about missing write permissions please follow the advice given. For further information on setup and configuration please read . Command line usage ----------------- See `schleuder help`. E.g.: Commands: schleuder check_keys # Check all lists for unusable or expiring keys and send the results to the list-admins. (This is supposed... schleuder help [COMMAND] # Describe available commands or one specific command schleuder install # Set up Schleuder initially. Create folders, copy files, fill the database, etc. schleuder version # Show version of schleuder schleuder work list@hostname < message # Run a message through a list. List administration ------------------- Please use [schleuder-cli](https://0xacab.org/schleuder/schleuder-cli) to create and manage lists from the command line. Optionally consider installing [schleuder-web](https://0xacab.org/schleuder/schleuder-web), the web interface for schleuder. It enables list-admins to manage their lists through the web instead of using [request-keywords](https://schleuder.org/docs/#subscription-and-key-management). Todo ---- See . Testing ------- We use rspec to test our code. To setup the test environment run: SCHLEUDER_ENV=test SCHLEUDER_CONFIG=spec/schleuder.yml bundle exec rake db:init To execute the test suite run: bundle exec rspec Please note: Some of the specs use 'pgrep'. On systems that base on Debian 9 ("stretch") install it via apt-get install procps We are working on extendig the test coverage. Contributing ------------ Please see [CONTRIBUTING.md](CONTRIBUTING.md). Mission statement ----------------- Please see [MISSION_STATEMENT.md](MISSION_STATEMENT.md). Code of Conduct --------------- We adopted a code of conduct. Please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). License ------- GNU GPL 3.0. Please see [LICENSE.txt](LICENSE.txt). Alternative Download -------------------- Alternatively to the gem-files you can download the latest release as [a tarball](https://schleuder.org/download/schleuder-3.4.1.tar.gz) and [its OpenPGP-signature](https://schleuder.org/download/schleuder-3.4.1.tar.gz.sig). schleuder-3.4.1/Rakefile000066400000000000000000000065751353765016400151710ustar00rootroot00000000000000project = 'schleuder' require_relative "lib/#{project}.rb" @version = Schleuder::VERSION @tagname = "#{project}-#{@version}" @gpguid = 'team@schleuder.org' @filename_gem = "#{@tagname}.gem" @filename_tarball = "#{@tagname}.tar.gz" load "active_record/railties/databases.rake" # Configure ActiveRecord ActiveRecord::Tasks::DatabaseTasks.tap do |config| config.root = File.dirname(__FILE__) config.db_dir = 'db' config.migrations_paths = ['db/migrate'] config.env = ENV['SCHLEUDER_ENV'] config.database_configuration = Schleuder::Conf.databases end # ActiveRecord requires this task to be present Rake::Task.define_task("db:environment") namespace :db do # A shortcut. task init: ['db:create', 'db:schema:load'] end def edit_and_add_file(filename) puts "Please edit #{filename} to refer to version #{@version}" if system("gvim -f #{filename}.md") `git add #{filename}.md` else exit 1 end end task :console do exec "irb -r #{File.dirname(__FILE__)}/lib/schleuder.rb" end task :publish_gem => :website task :git_tag => :check_version desc "Build new version: git-tag and gem-file" task :new_version => [ :check_version, :edit_readme, :edit_changelog, :git_add_version, :git_commit, :build_gem, :sign_gem, :build_tarball, :sign_tarball, :ensure_permissions, :git_tag ] do end desc "Edit CHANGELOG.md" task :edit_changelog do edit_and_add_file('CHANGELOG') end desc "Edit README" task :edit_readme do edit_and_add_file('README') end desc 'git-tag HEAD as new version' task :git_tag do `git tag -u #{@gpguid} -s -m "Version #{@version}" #{@tagname}` end desc "Add changed version to git-index" task :git_add_version do `git add lib/#{project}/version.rb` end desc "Commit changes as new version" task :git_commit do `git commit -m "Version #{@version}"` end desc 'Build, sign and commit a gem-file.' task :build_gem do `gem build #{project}.gemspec` end desc 'OpenPGP-sign gem and tarball' task :sign_tarball do `gpg -u #{@gpguid} -b #{@filename_tarball}` end desc 'OpenPGP-sign gem' task :sign_gem do `gpg -u #{@gpguid} -b #{@filename_gem}` end desc 'Ensure download-files have correct permissions' task :ensure_permissions do File.chmod(0644, *Dir.glob("#{@tagname}*")) end desc 'Upload download-files (gem, tarball, signatures) to schleuder.org.' task :upload_files do puts `echo "put -p #{@tagname}* www/download/" | sftp schleuder.org@ftp.schleuder.org 2>&1` end desc 'Publish gem-file to rubygems.org' task :publish_gem do puts "Really push #{@filename_gem} to rubygems.org? [yN]" if gets.match(/^y/i) puts "Pushing..." `gem push #{@filename_gem}` else puts "Not pushed." end end desc 'Build and sign a tarball' task :build_tarball do `git archive --format tar.gz --prefix "#{@tagname}/" -o #{@filename_tarball} master` end desc 'Describe manual release-tasks' task :website do puts "Please remember to publish the release-notes on the website and on schleuder-announce." end desc 'Check if version-tag already exists' task :check_version do # Check if Schleuder::VERSION has been updated since last release if `git tag`.match?(/^#{@tagname}$/) $stderr.puts "Warning: Tag '#{@tagname}' already exists. Did you forget to update #{project}/version.rb?" $stderr.print "Delete tag to continue? [yN] " if $stdin.gets.match(/^y/i) `git tag -d #{@tagname}` else exit 1 end end end schleuder-3.4.1/bin/000077500000000000000000000000001353765016400142575ustar00rootroot00000000000000schleuder-3.4.1/bin/pinentry-clearpassphrase000077500000000000000000000025201353765016400212320ustar00rootroot00000000000000#!/usr/bin/env ruby # This file can be deleted once we cease to support gnupg 2.0. require 'fileutils' require 'cgi' require 'openssl' def respond(msg, flush=true) $stdout.puts msg if flush $stdout.flush end end def send_ok(flush=true) respond 'OK', flush end def send_password if File.exist?(OLDPWDSENTFILE) pwd = '' if File.exist?(EMPTYPWDSENTFILE1) FileUtils.touch(EMPTYPWDSENTFILE2) else FileUtils.touch(EMPTYPWDSENTFILE1) end else pwd = OLDPASSWD FileUtils.touch(OLDPWDSENTFILE) end respond "D #{pwd}" end def do_exit if File.exist?(EMPTYPWDSENTFILE2) FileUtils.rm_rf(TMPDIR) end exit 0 end OLDPASSWD = CGI.escape(ENV['PINENTRY_USER_DATA'].to_s) if OLDPASSWD.empty? respond "Fatal error: passed PINENTRY_USER_DATA was empty, cannot continue" exit 1 end # We need a static directory name to maintain the state across invocations of # this file. TMPDIR = File.join(ENV['GNUPGHOME'], '.tmp-pinentry-clearpassphrase') OLDPWDSENTFILE = File.join(TMPDIR, '1') EMPTYPWDSENTFILE1 = File.join(TMPDIR, '2') EMPTYPWDSENTFILE2 = File.join(TMPDIR, '3') if ! Dir.exist?(TMPDIR) Dir.mkdir(TMPDIR) end respond "OK - what's up?" while line = $stdin.gets do case line when /^GETPIN/ send_password send_ok when /^BYE/ send_ok false do_exit else send_ok end end schleuder-3.4.1/bin/schleuder000077500000000000000000000005531353765016400161660ustar00rootroot00000000000000#!/usr/bin/env ruby # Don't emit any warnings, they would be sent back to the email-sender as an # error-message. $VERBOSE=nil trap("INT") { exit 1 } begin $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) require 'schleuder/cli' Schleuder::Cli.start rescue => exc $stderr.puts "Technical Error: #{exc}\n#{exc.backtrace.first}" exit 1 end schleuder-3.4.1/bin/schleuder-api-daemon000077500000000000000000000002101353765016400201640ustar00rootroot00000000000000#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) require 'schleuder-api-daemon' SchleuderApiDaemon.run! schleuder-3.4.1/bin/schleuder-bundler000077500000000000000000000003631353765016400176160ustar00rootroot00000000000000#!/bin/bash -l export HOME=/home/$(id -nu) # Little helper script to call schleuder through bundler. Useful e.g. for # hooking up development code into postfix. bindir="$(realpath $(dirname $0))" cd $bindir/.. bundle exec ./bin/schleuder $@ schleuder-3.4.1/bin/schleuder-smtpd.rb000077500000000000000000000032761353765016400177220ustar00rootroot00000000000000#!/usr/bin/env ruby # # This script is a very simple SMTP-daemon, that delivers every incoming email # to a given schleuder-list. It's meant to help developing without a real, # local MTA. require 'socket' require 'open3' trap ("INT") { exit 0 } def usage puts "Usage: #{File.basename(__FILE__)} [-p portnum]" exit 1 end # get args case ARGV.first when '-h', '--help', 'help' usage when '-p' port = ARGV[1].to_i if port == 0 usage end end port ||= 25 schleuderbin = File.join(File.dirname(__FILE__), 'schleuder') begin # run the server server = TCPServer.new("127.0.0.1", port) # receive input while (connection = server.accept) input = '' recipient = '' connection.puts "220 localhost SMTP" begin while line = connection.gets line.chomp! case line[0..3].downcase when 'ehlo', 'helo' connection.puts "250 localhost" when 'mail', 'rset' connection.puts "250 ok" when 'rcpt' recipient = line.split(':').last.gsub(/[<>\s]*/, '') connection.puts "250 ok" when 'data' connection.puts "354 go ahead" when 'quit' connection.puts "221 localhost" when '.' puts "New message to #{recipient}" err, status = Open3.capture2e("#{schleuderbin} work #{recipient}", {stdin_data: input}) if status.exitstatus > 0 puts "Error from schleuder: #{err}." connection.puts "550 #{err}" else connection.puts "250 ok" end else input << line + "\n" end end rescue IOError end connection.close end rescue => exc $stderr.puts exc exit 1 end schleuder-3.4.1/db/000077500000000000000000000000001353765016400140745ustar00rootroot00000000000000schleuder-3.4.1/db/migrate/000077500000000000000000000000001353765016400155245ustar00rootroot00000000000000schleuder-3.4.1/db/migrate/20140501103532_create_lists.rb000066400000000000000000000031701353765016400221650ustar00rootroot00000000000000class CreateLists < ActiveRecord::Migration def up if ! table_exists?(:lists) create_table :lists do |t| t.timestamps t.string :email t.string :fingerprint t.string :gpg_passphrase t.string :log_level, default: 'warn' t.string :default_mime, default: 'mime' t.string :subject_prefix, default: '' t.string :subject_prefix_in, default: '' t.string :subject_prefix_out, default: '' t.string :openpgp_header_preference, default: 'signencrypt' t.text :public_footer, default: '' t.text :headers_to_meta, default: '["from","to","date",":cc"]' t.text :bounces_drop_on_headers, default: '{"x-spam-flag":"yes"}' t.text :keywords_admin_only, default: '["unsubscribe", "unsubscribe", "delete-key"]' t.text :keywords_admin_notify, default: '["add-key"]' t.boolean :send_encrypted_only, default: false t.boolean :receive_encrypted_only, default: false t.boolean :receive_signed_only, default: false t.boolean :receive_authenticated_only, default: false t.boolean :receive_from_subscribed_emailaddresses_only, default: false t.boolean :receive_admin_only, default: false t.boolean :keep_msgid, default: true t.boolean :bounces_drop_all, default: false t.boolean :bounces_notify_admins, default: true t.boolean :include_list_headers, default: true t.boolean :include_openpgp_header, default: true t.integer :max_message_size_kb, default: 10240 end end def down drop_table(:lists) end end end schleuder-3.4.1/db/migrate/20140501112859_create_subscriptions.rb000066400000000000000000000010131353765016400237440ustar00rootroot00000000000000class CreateSubscriptions < ActiveRecord::Migration def up if ! table_exists?(:subscriptions) create_table :subscriptions do |t| t.integer :list_id t.string :email t.string :fingerprint t.boolean :admin, default: false t.boolean :delivery_disabled, default: false t.timestamps end add_index :subscriptions, :list_id add_index :subscriptions, [:email, :list_id], unique: true end end def down drop_table(:subscriptions) end end schleuder-3.4.1/db/migrate/201508092100_add_language_to_lists.rb000066400000000000000000000003531353765016400236600ustar00rootroot00000000000000class AddLanguageToLists < ActiveRecord::Migration def up if ! column_exists?(:lists, :language) add_column :lists, :language, :string, default: 'en' end end def down remove_column(:lists, :language) end end schleuder-3.4.1/db/migrate/20150812165700_change_keywords_admin_only_defaults.rb000066400000000000000000000004741353765016400267770ustar00rootroot00000000000000class ChangeKeywordsAdminOnlyDefaults < ActiveRecord::Migration def up change_column_default :lists, :keywords_admin_only, "[\"subscribe\", \"unsubscribe\", \"delete-key\"]" end def down change_column_default :lists, :keywords_admin_only, "[\"unsubscribe\", \"unsubscribe\", \"delete-key\"]" end end schleuder-3.4.1/db/migrate/20150813235800_add_forward_all_incoming_to_admins.rb000066400000000000000000000004721353765016400265450ustar00rootroot00000000000000class AddForwardAllIncomingToAdmins < ActiveRecord::Migration def up if ! column_exists?(:lists, :forward_all_incoming_to_admins) add_column :lists, :forward_all_incoming_to_admins, :boolean, default: false end end def down remove_column(:lists, :forward_all_incoming_to_admins) end end schleuder-3.4.1/db/migrate/201508141727_change_send_encrypted_only_default.rb000066400000000000000000000003361353765016400264400ustar00rootroot00000000000000class ChangeSendEncryptedOnlyDefault < ActiveRecord::Migration def up change_column_default :lists, :send_encrypted_only, true end def down change_column_default :lists, :send_encrypted_only, false end end schleuder-3.4.1/db/migrate/201508222143_add_logfiles_to_keep_to_lists.rb000066400000000000000000000004071353765016400254110ustar00rootroot00000000000000class AddLogfilesToKeepToLists < ActiveRecord::Migration def up if ! column_exists?(:lists, :logfiles_to_keep) add_column :lists, :logfiles_to_keep, :integer, default: 2 end end def down remove_column(:lists, :logfiles_to_keep) end end 201508261723_rename_delivery_disabled_to_delivery_enabled_and_change_default.rb000066400000000000000000000007321353765016400342130ustar00rootroot00000000000000schleuder-3.4.1/db/migrateclass RenameDeliveryDisabledToDeliveryEnabledAndChangeDefault < ActiveRecord::Migration def up if column_exists?(:subscriptions, :delivery_disabled) rename_column :subscriptions, :delivery_disabled, :delivery_enabled change_column_default :subscriptions, :delivery_enabled, true end end def down rename_column :subscriptions, :delivery_enabled, :delivery_disabled change_column_default :subscriptions, :delivery_disabled, false end end schleuder-3.4.1/db/migrate/201508261815_strip_gpg_passphrase.rb000066400000000000000000000003531353765016400236070ustar00rootroot00000000000000class StripGpgPassphrase < ActiveRecord::Migration def up if column_exists?(:lists, :gpg_passphrase) remove_column :lists, :gpg_passphrase end end def down add_column :lists, :gpg_passphrase, :string end end schleuder-3.4.1/db/migrate/201508261827_remove_default_mime.rb000066400000000000000000000002761353765016400233770ustar00rootroot00000000000000class RemoveDefaultMime < ActiveRecord::Migration def up remove_column :lists, :default_mime end def down add_column :lists, :default_mime, :string, default: 'mime' end end schleuder-3.4.1/db/migrate/20160501172700_fix_headers_to_meta_defaults.rb000066400000000000000000000002611353765016400253670ustar00rootroot00000000000000class FixHeadersToMetaDefaults < ActiveRecord::Migration def up change_column_default :lists, :headers_to_meta, '["from", "to", "date", "cc"]' end def down end end schleuder-3.4.1/db/migrate/20170713215059_add_internal_footer_to_list.rb000066400000000000000000000004011353765016400252550ustar00rootroot00000000000000class AddInternalFooterToList < ActiveRecord::Migration def up if ! column_exists?(:lists, :internal_footer) add_column :lists, :internal_footer, :text, default: '' end end def down remove_column(:lists, :internal_footer) end end schleuder-3.4.1/db/migrate/20180110203100_add_sig_enc_to_headers_to_meta_defaults.rb000066400000000000000000000017351353765016400275140ustar00rootroot00000000000000class AddSigEncToHeadersToMetaDefaults < ActiveRecord::Migration def up change_column_default :lists, :headers_to_meta, '["from", "to", "cc", "date", "sig", "enc"]' list_klass = create_list_klass list_klass.reset_column_information list_klass.find_each do |list| if (list.headers_to_meta & ['sig', 'enc']).empty? list.update(headers_to_meta: list.headers_to_meta + ['sig', 'enc']) end end end def down change_column_default :lists, :headers_to_meta, '["from", "to", "cc", "date"]' list_klass = create_list_klass list_klass.reset_column_information list_klass.find_each do |list| list.update(headers_to_meta: list.headers_to_meta - ['enc','sig']) end end def create_list_klass # Use a temporary class-definition to be independent of the # complexities of the actual class. Class.new(ActiveRecord::Base) do self.table_name = 'lists' self.serialize :headers_to_meta, JSON end end end schleuder-3.4.1/db/schema.rb000066400000000000000000000077061353765016400156730ustar00rootroot00000000000000# encoding: UTF-8 # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # Note that this schema.rb definition is the authoritative source for your # database schema. If you need to create the application database on another # system, you should be using db:schema:load, not running all the migrations # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 20180110203100) do create_table "lists", force: :cascade do |t| t.datetime "created_at" t.datetime "updated_at" t.string "email", limit: 255 t.string "fingerprint", limit: 255 t.string "log_level", limit: 255, default: "warn" t.string "subject_prefix", limit: 255, default: "" t.string "subject_prefix_in", limit: 255, default: "" t.string "subject_prefix_out", limit: 255, default: "" t.string "openpgp_header_preference", limit: 255, default: "signencrypt" t.text "public_footer", default: "" t.text "headers_to_meta", default: "[\"from\", \"to\", \"cc\", \"date\", \"sig\", \"enc\"]" t.text "bounces_drop_on_headers", default: "{\"x-spam-flag\":\"yes\"}" t.text "keywords_admin_only", default: "[\"subscribe\", \"unsubscribe\", \"delete-key\"]" t.text "keywords_admin_notify", default: "[\"add-key\"]" t.boolean "send_encrypted_only", default: true t.boolean "receive_encrypted_only", default: false t.boolean "receive_signed_only", default: false t.boolean "receive_authenticated_only", default: false t.boolean "receive_from_subscribed_emailaddresses_only", default: false t.boolean "receive_admin_only", default: false t.boolean "keep_msgid", default: true t.boolean "bounces_drop_all", default: false t.boolean "bounces_notify_admins", default: true t.boolean "include_list_headers", default: true t.boolean "include_openpgp_header", default: true t.integer "max_message_size_kb", default: 10240 t.string "language", limit: 255, default: "en" t.boolean "forward_all_incoming_to_admins", default: false t.integer "logfiles_to_keep", default: 2 t.text "internal_footer", default: "" end create_table "subscriptions", force: :cascade do |t| t.integer "list_id" t.string "email", limit: 255 t.string "fingerprint", limit: 255 t.boolean "admin", default: false t.boolean "delivery_enabled", default: true t.datetime "created_at" t.datetime "updated_at" end add_index "subscriptions", ["email", "list_id"], name: "index_subscriptions_on_email_and_list_id", unique: true add_index "subscriptions", ["list_id"], name: "index_subscriptions_on_list_id" end schleuder-3.4.1/docker/000077500000000000000000000000001353765016400147565ustar00rootroot00000000000000schleuder-3.4.1/docker/entrypoint.sh000066400000000000000000000273171353765016400175370ustar00rootroot00000000000000#!/bin/bash # # entrypoint script for schlocker # # handle signals trap abort SIGHUP SIGINT SIGQUIT SIGTERM SIGSTOP SIGKILL function abort { echo echo "* * * ABORTED * * *" echo exit 0 } # homedir of the schlocker user [ -z ${SCHLOCKER_HOMEDIR+x} ] && SCHLOCKER_HOMEDIR="/var/schlocker" # what to watch [ -z ${SCHLOCKER_MAILDIR+x} ] && SCHLOCKER_MAILDIR="$SCHLOCKER_HOMEDIR/mail" # who to run as [ -z ${SCHLOCKER_USER+x} ] && SCHLOCKER_USER="schlocker" [ -z ${SCHLOCKER_GROUP+x} ] && SCHLOCKER_GROUP="schlocker" # config stuff [ -z ${SCHLOCKER_CONFIG_PATH+x} ] && SCHLOCKER_CONFIG_PATH="/etc/schleuder/schleuder.yml" [ -z ${SCHLOCKER_CONFIG_SUPERADMIN+x} ] && SCHLOCKER_CONFIG_SUPERADMIN="root@localhost" [ -z ${SCHLOCKER_CONFIG_LISTS_DIR+x} ] && SCHLOCKER_CONFIG_LISTS_DIR="$SCHLOCKER_HOMEDIR/lists" [ -z ${SCHLOCKER_CONFIG_PLUGINS_DIR+x} ] && SCHLOCKER_CONFIG_PLUGINS_DIR="/etc/schleuder/plugins" [ -z ${SCHLOCKER_CONFIG_LOG_LEVEL+x} ] && SCHLOCKER_CONFIG_LOG_LEVEL="warn" [ -z ${SCHLOCKER_CONFIG_SMTP_HOST+x} ] && SCHLOCKER_CONFIG_SMTP_HOST="localhost" [ -z ${SCHLOCKER_CONFIG_SMTP_PORT+x} ] && SCHLOCKER_CONFIG_SMTP_PORT="25" # db settings [ -z ${SCHLOCKER_DB_ADAPTER+x} ] && SCHLOCKER_DB_ADAPTER="sqlite3" [ -z ${SCHLOCKER_DB_DATABASE+x} ] && SCHLOCKER_DB_DATABASE="$SCHLOCKER_HOMEDIR/db.sqlite3" # these are unused by default, only useful with mysql/postgresql/etc [ -z ${SCHLOCKER_DB_ENCODING+x} ] && SCHLOCKER_DB_ENCODING="" [ -z ${SCHLOCKER_DB_USERNAME+x} ] && SCHLOCKER_DB_USERNAME="" [ -z ${SCHLOCKER_DB_PASSWORD+x} ] && SCHLOCKER_DB_PASSWORD="" [ -z ${SCHLOCKER_DB_HOST+x} ] && SCHLOCKER_DB_HOST="" # hostname for schleuder lists [ -z ${SCHLOCKER_HOSTNAME+x} ] && SCHLOCKER_HOSTNAME="$( hostname )" # temp directory # NOTICE: has to be on the same filesystem as maildirs [ -z ${SCHLOCKER_TMPDIR+x} ] && SCHLOCKER_TMPDIR="$SCHLOCKER_MAILDIR/.tmp" # # inform echo "+-- working with:" echo " +-- SCHLOCKER_HOMEDIR : $SCHLOCKER_HOMEDIR" echo " +-- SCHLOCKER_MAILDIR : $SCHLOCKER_MAILDIR" echo " +-- SCHLOCKER_USER : $SCHLOCKER_USER" echo " +-- SCHLOCKER_GROUP : $SCHLOCKER_GROUP" echo " +-- SCHLOCKER_HOSTNAME : $SCHLOCKER_HOSTNAME" echo " +-- SCHLOCKER_TMPDIR : $SCHLOCKER_TMPDIR" # # this waits for changes in $SCHLOCKER_MAILDIR directory # and shoves any new messages into schleuder # # TODO: this has to be more sphisticated to handle traffic larger than just testing! function watch_maildir { # inform echo "+-- watching for changes in Maildir at: $SCHLOCKER_MAILDIR" # loopy-loop! # signals handled by trap while true; do # wait for events # we're only interested in "move", as this is what happens when a complete message # gets put in */new # we won't catch ourselves moving stuff around, as when we're doing that, # inotifywait is not running # --format '%w%f' also not required, we're handling all the files hat we can find anyway inotifywait -r -e move -qq "$SCHLOCKER_MAILDIR" # if a watched event occurred, send the signal # done *after* we spot a new file # we can spare the short delay imposed # in return we are sure we actually have some directories to work on if [ $? -eq 0 ]; then # inform echo " +-- Maildir modified, new message(s)!..." # make sure the required directories exist for d in "$SCHLOCKER_MAILDIR"/*; do # we need the basename bd="$( basename $d )" # and we need the tempdir to exist if [ ! -d "$SCHLOCKER_TMPDIR/$bd" ]; then mkdir -p "$SCHLOCKER_TMPDIR/$bd" chown -R "$SCHLOCKER_USER:$SCHLOCKER_GROUP" "$SCHLOCKER_TMPDIR/$bd" fi # move the files out of the way # this is done to let the mailserver continue its work while # the mails are being handled by schleuder find "$d/new" -type f -exec mv '{}' "$SCHLOCKER_TMPDIR/$bd/" \; # handle them for f in "$SCHLOCKER_TMPDIR/$bd"/*; do # TODO: this will not remove failed messages # TODO: we need better handling of failed messages echo " +-- calling schleuder for $bd@$SCHLOCKER_HOSTNAME file $f" su -p -c "PATH=\"$PATH\" BUNDLE_PATH=\"$BUNDLE_PATH\" schleuder work $bd@$SCHLOCKER_HOSTNAME" "$SCHLOCKER_USER" < "$f" && rm "$f" done done fi done } # # root is not what we want as the user to run as # # let's make sure we're not running as root, shall we? if [ "$SCHLOCKER_UID" == "0" ] || [ "$SCHLOCKER_USER" == "root" ] || [ "$SCHLOCKER_GID" == "0" ] || [ "$SCHLOCKER_GROUP" == "root" ]; then echo echo "* * * ERROR: trying to run as root -- I cannot let you do that, Dave!" echo exit 1 fi # get group data, if any, and check if the group exists if GROUP_DATA=`getent group "$SCHLOCKER_GROUP"`; then # it does! do we have the gid given? if [[ "$SCHLOCKER_GID" != "" ]]; then # we do! do these match? if [[ `echo "$GROUP_DATA" | cut -d ':' -f 3` != "$SCHLOCKER_GID" ]]; then # they don't. we have a problem echo "ERROR: group $SCHLOCKER_GROUP already exists, but with a different gid (`echo "$GROUP_DATA" | cut -d ':' -f 3`) than provided ($SCHLOCKER_GID)!" exit 3 fi fi # if no gid given, the existing group satisfies us regardless of the GID # group does not exist else # do we have the gid given? GID_ARGS="" if [[ "$SCHLOCKER_GID" != "" ]]; then # we do! does a group with a given id exist? if getent group "$SCHLOCKER_GID" >/dev/null; then echo "ERROR: a group with a given id ($SCHLOCKER_GID) already exists, can't create group $SCHLOCKER_GROUP with this id" exit 4 fi # prepare the fragment of the groupadd command GID_ARGS="-g $SCHLOCKER_GID" fi # we either have no GID given (and don't care about it), or have a GID given that does not exist in the system # great! let's add the group groupadd $GID_ARGS "$SCHLOCKER_GROUP" fi # get user data, if any, and check if the user exists if USER_DATA=`id -u "$SCHLOCKER_USER" 2>/dev/null`; then # it does! do we have the uid given? if [[ "$SCHLOCKER_UID" != "" ]]; then # we do! do these match? if [[ "$USER_DATA" != "$SCHLOCKER_UID" ]]; then # they don't. we have a problem echo "ERROR: user $SCHLOCKER_USER already exists, but with a different uid ("$USER_DATA") than provided ($SCHLOCKER_UID)!" exit 5 fi fi # if no uid given, the existing user satisfies us regardless of the uid # but is he in the right group? adduser "$SCHLOCKER_USER" "$SCHLOCKER_GROUP" # user does not exist else # do we have the uid given? UID_ARGS="" if [[ "$SCHLOCKER_UID" != "" ]]; then # we do! does a group with a given id exist? if getent passwd "$SCHLOCKER_UID" >/dev/null; then echo "ERROR: a user with a given id ($SCHLOCKER_UID) already exists, can't create user $SCHLOCKER_USER with this id" exit 6 fi # prepare the fragment of the useradd command UID_ARGS="-u $SCHLOCKER_UID" fi # we either have no UID given (and don't care about it), or have a UID given that does not exist in the system # great! let's add the user useradd $UID_ARGS -d "$SCHLOCKER_HOMEDIR" -r -g "$SCHLOCKER_GROUP" "$SCHLOCKER_USER" fi # # directories and permissions # # if the homedir is not there, create if [ ! -d "$SCHLOCKER_HOMEDIR" ]; then echo " +-- homedir missing, creating it at: $SCHLOCKER_HOMEDIR" mkdir -p "$SCHLOCKER_HOMEDIR" || exit 1 fi # make sure the perms are correct chown -R "$SCHLOCKER_USER:$SCHLOCKER_GROUP" "$SCHLOCKER_HOMEDIR" # if the maildir is not there, create if [ ! -d "$SCHLOCKER_MAILDIR" ]; then echo " +-- Maildir missing, creating it at: $SCHLOCKER_MAILDIR" mkdir -p "$SCHLOCKER_MAILDIR" || exit 1 fi # make sure the perms are correct chown -R "$SCHLOCKER_USER:$SCHLOCKER_GROUP" "$SCHLOCKER_MAILDIR" # if the tempdir is not there, create if [ ! -d "$SCHLOCKER_TMPDIR" ]; then echo " +-- Maildir tempdir missing, creating it at: $SCHLOCKER_TMPDIR" mkdir -p "$SCHLOCKER_TMPDIR" || exit 1 fi # make sure the perms are correct chown -R "$SCHLOCKER_USER:$SCHLOCKER_GROUP" "$SCHLOCKER_TMPDIR" # # config # # do we need it? if the config file exists, just use that if [ ! -e "$SCHLOCKER_CONFIG_PATH" ]; then echo "+-- no config file found in $SCHLOCKER_CONFIG_PATH, creating one..." # hopefully the unneeded settings will be ignored ;) SCHLOCKER_CONFIG="--- superadmin: $SCHLOCKER_CONFIG_SUPERADMIN lists_dir: $SCHLOCKER_CONFIG_LISTS_DIR plugins_dir: $SCHLOCKER_CONFIG_PLUGINS_DIR log_level: $SCHLOCKER_CONFIG_LOG_LEVEL smtp_settings: # For explanation see documentation for ActionMailer::smtp_settings, e.g. . address: $SCHLOCKER_CONFIG_SMTP_HOST port: $SCHLOCKER_CONFIG_SMTP_PORT #domain: #enable_starttls_auto: #openssl_verify_mode: #authentication: #user_name: #password: database: production: adapter: $SCHLOCKER_DB_ADAPTER database: $SCHLOCKER_DB_DATABASE encoding: $SCHLOCKER_DB_ENCODING username: $SCHLOCKER_DB_USERNAME password: $SCHLOCKER_DB_PASSWORD host: $SCHLOCKER_DB_HOST api: host: 0.0.0.0 port: 4443 # Certificate and key to use. You can create new ones with 'schleuder cert generate'. tls_cert_file: /etc/schleuder/api-daemon.crt tls_key_file: /etc/schleuder/api-daemon.key # List of api_keys to allow access to the API. # Example: # valid_api_keys: # - abcdef... # - zyxwvu... valid_api_keys: - $SCHLOCKER_CONFIG_API_KEY " mkdir -p "$( dirname "$SCHLOCKER_CONFIG_PATH" )" echo -e "$SCHLOCKER_CONFIG" > "$SCHLOCKER_CONFIG_PATH" # if the list directory is not there, create if [ ! -d "$SCHLOCKER_CONFIG_LISTS_DIR" ]; then echo " +-- List data directory missing, creating it at: $SCHLOCKER_CONFIG_LISTS_DIR" mkdir -p "$SCHLOCKER_CONFIG_LISTS_DIR" || exit 1 fi # make sure the perms are correct chown -R "$SCHLOCKER_USER:$SCHLOCKER_GROUP" "$SCHLOCKER_CONFIG_LISTS_DIR" else echo "+-- config file found in '$SCHLOCKER_CONFIG_PATH', ignoring \$SCHLOCKER_CONFIG_* and \$SCHLOCKER_DB_* envvars" fi # # we need a properly set-up database # # do we have a database? echo "+-- testing if db is set-up properly..." # run schleuder-api-daemon for a short while su -p -c "env PATH=\"$PATH\" schleuder-api-daemon" "$SCHLOCKER_USER" & export SCHLEUDER_PID=$! sleep 5 && kill "$SCHLEUDER_PID" & if ! wait $SCHLEUDER_PID; then echo "+-- setting up schleuder-api-daemon database..." echo y | schleuder install chown -R "$SCHLOCKER_USER:$SCHLOCKER_GROUP" /etc/schleuder # if we're using sqlite, we need to make sure the db file has the right permissions # but *only* when config file had to be created! if [ ! -z ${SCHLOCKER_CONFIG+x} ] && [ "$SCHLOCKER_DB_ADAPTER" == "sqlite3" ]; then echo "+-- config had to be created and we're using sqlite3 -- handling database file permissions..." chown -R "$SCHLOCKER_USER:$SCHLOCKER_GROUP" "$SCHLOCKER_DB_DATABASE" fi fi # # start the watch if [ "$SCHLOCKER_MAILDIR" != "" ]; then echo "+-- \$SCHLOCKER_MAILDIR is not empty, setting up watches" watch_maildir & sleep 1 fi echo -e "\n\n" echo "Note the following fingerprint and use it in the client-applications (schleuder-cli, schleuder-web, ...)." schleuder cert fingerprint echo -e "\n\n" # # run schleuder-api-daemon! export SCHLOCKER_USER echo "+-- starting command: $*..." # cannot use exec here... fails at key generation su -c "PATH=\"$PATH\" BUNDLE_PATH=\"$BUNDLE_PATH\" $*" "$SCHLOCKER_USER" schleuder-3.4.1/etc/000077500000000000000000000000001353765016400142625ustar00rootroot00000000000000schleuder-3.4.1/etc/init.d/000077500000000000000000000000001353765016400154475ustar00rootroot00000000000000schleuder-3.4.1/etc/init.d/schleuder-api-daemon000077500000000000000000000042721353765016400213700ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: schleuder-api-daemon # Required-Start: $local_fs $remote_fs $network $syslog # Required-Stop: $local_fs $remote_fs $network $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Schleuder API daemon # Description: Schleuder API daemon — provides access for schleuder-cli and schleuder-web ### END INIT INFO PATH=/sbin:/usr/sbin:/bin:/usr/bin . /lib/init/vars.sh . /lib/lsb/init-functions NAME=schleuder-api-daemon DAEMON=/usr/local/bin/schleuder-api-daemon DESC="Schleuder API daemon" PIDFILE=/var/run/$NAME.pid USER=schleuder GROUP=schleuder test -x $DAEMON || exit 5 start_schleuder_api_daemon() { if [ -f "$PIDFILE" ]; then echo "" log_failure_msg "$DESC is running already, please stop it first" exit 1 fi if ! id $USER >/dev/null 2>&1; then log_failure_msg "User \"$USER\" does not exist" exit 1 fi if ! getent group $GROUP >/dev/null 2>&1; then log_failure_msg "Group \"$GROUP\" does not exist" exit 1 fi start-stop-daemon --chuid "$USER":"$GROUP" --start --pidfile $PIDFILE --make-pidfile --background --startas $DAEMON -- $NAME } stop_schleuder_api_daemon() { if [ -f "$PIDFILE" ]; then start-stop-daemon --stop --retry TERM/10/KILL/5 --pidfile $PIDFILE --quiet --oknodo --pidfile "$PIDFILE" rm -f $PIDFILE fi } status_schleuder_api_daemon() { if [ ! -e $PIDFILE ]; then status_of_proc "${DAEMON}" "${DESC}" else status_of_proc -p "${PIDFILE}" "${DAEMON}" "${DESC}" fi } case "$1" in start) log_begin_msg "Starting $DESC" start_schleuder_api_daemon log_end_msg $? ;; stop) log_begin_msg "Stopping $DESC" stop_schleuder_api_daemon log_end_msg $? ;; status) status_schleuder_api_daemon ;; restart|reload|force-reload) log_begin_msg "Restarting $DESC" stop_schleuder_api_daemon start_schleuder_api_daemon log_end_msg $? ;; *) echo "Usage: $0 {start|stop|status|restart|reload|force-reload}" exit 1 ;; esac schleuder-3.4.1/etc/list-defaults.yml000066400000000000000000000103361353765016400175700ustar00rootroot00000000000000# Setting default values for newly generated lists. Once a list is created it # is not affected by these settings but has its own set of options in the # database. # # The configuration format is yaml (http://www.yaml.org). # # Options are listed with the behaviour encoded in the database schema. # Only send out encrypted emails to subscriptions? # (This setting does not affect resend-messages.) send_encrypted_only: true # Allow only encrypted emails? If true, any other email will be bounced. receive_encrypted_only: false # Allow only emails that are validly signed? If true, any other email will be # bounced. receive_signed_only: false # Allow only emails that are validly signed by a subscriber's key? If true, # any other email will be bounced. receive_authenticated_only: false # Allow only emails being sent from subscribed addresses? If true, any other # email will be bounced. # NOTE: This is a very weak restriction mechanism on which you should not rely, # as sending addresses can easily be faked! We recommend you to rather # rely on the `receive_authenticated_only` option. Setting the # `receive_authenticated_only` option to true, will authenticate senders # based on the signature on the mail, which is the strongest # authentication mechanism you can get. # This option could be useful, if you would like to have a closed # mailinglist, but could not yet get all subscribers to properly use GPG. receive_from_subscribed_emailaddresses_only: false # Allow only emails that are validly signed by a list-admin's key. # This is useful for newsletters, announce or notification lists receive_admin_only: false # Which headers to include from the original mail. headers_to_meta: - from - to - cc - date - sig - enc # Preserve the Message-IDs (In-Reply-To, References) from the incoming email. # This setting can lead to information leakage, as replies are connectable # and a thread of (encrypted) messages can be built by an eavesdropper. keep_msgid: true # Which keywords ("email-commands") should be restricted to list-admins? keywords_admin_only: - subscribe - unsubscribe - delete-key # For which keywords should the list-admins receive a notice whenever it # triggers a command. keywords_admin_notify: - add-key # Footer to append to each email that is sent to a subscribed address. Will not # be included in messages to non-subscribed addresses. internal_footer: # Footer to append to each email that is sent to non-subscribed addresses. Will # not be included in messages to subscribed addresses. public_footer: # Prefix to be inserted into the subject of every email that is validly signed # by a subscribed address. subject_prefix: # Prefix to be inserted into the subject of every email that is *not* validly # signed by a subscribed address. subject_prefix_in: # Prefix to be inserted into the subject of every email that has been # resent to a non-subscribed address. subject_prefix_out: # Drop any bounces (incoming emails not passing the receive_*_only-rules)? bounces_drop_all: false # Drop bounces if they match one of these headers. Must be a hash, keys # and values are case insensitive. bounces_drop_on_headers: x-spam-flag: yes # Send a notice to the list-admins whenever an email is bounced or dropped? bounces_notify_admins: true # Include RFC-compliant List-* Headers into emails? include_list_headers: true # Include OpenPGP-Header into emails? include_openpgp_header: true # Preferred way to receive emails to note in OpenPGP-Header # ('sign'|'encrypt'|'signencrypt'|'unprotected'|'none') openpgp_header_preference: signencrypt # Maximum size of emails allowed on the list, in kilobyte. All others will be # bounced. max_message_size_kb: 10240 # How verbose to log on the list-level (Notifications will be sent to # list-admins)? Error, warn, info, or debug. log_level: warn # How many logfiles to keep, including the current one. # Logfiles are rotated daily, so 2 means: delete logfiles older than # yesterday. Values lower than 1 are ignored. logfiles_to_keep: 2 # Which language to use for automated replies, error-messages, etc. # Available: en, de. language: en # Forward a raw copy of all incoming emails to the list-admins? # Mainly useful for debugging. forward_all_incoming_to_admins: false schleuder-3.4.1/etc/postfix/000077500000000000000000000000001353765016400157565ustar00rootroot00000000000000schleuder-3.4.1/etc/postfix/schleuder_sqlite.cf000066400000000000000000000022151353765016400216270ustar00rootroot00000000000000# Use this as a table for postfix to select addresses that schleuder # thinks belong to it. This is useful when # smtpd_reject_unlisted_recipient = yes (which is the default for # modern Postfix) # For example, you might dedicate Postfix's "virtual" domains to # schleuder with the following set of configs in main.cf: # # virtual_domains = lists.example.org # virtual_transport = schleuder # virtual_alias_maps = hash:/etc/postfix/virtual_aliases # virtual_mailbox_maps = sqlite:/etc/postfix/schleuder_sqlite.cf # schleuder_destination_recipient_limit = 1 # it is not recommended to use this table for more powerful # configuration options (e.g. transport_maps) because it could give # the schleuder user (which can write the given sqlite database) the # power to change settings for for other mail handled by this Postfix # instance. dbpath = /var/lib/schleuder/db.sqlite query = select 'present' from lists where email = '%s' or email = replace('%s', '-bounce@', '@') or email = replace('%s', '-owner@', '@') or email = replace('%s', '-request@', '@') or email = replace('%s', '-sendkey@', '@') schleuder-3.4.1/etc/schleuder-api-daemon.service000066400000000000000000000002731353765016400216340ustar00rootroot00000000000000[Unit] Description=Schleuder API daemon After=local-fs.target network.target [Service] ExecStart=/usr/local/bin/schleuder-api-daemon User=schleuder [Install] WantedBy=multi-user.target schleuder-3.4.1/etc/schleuder.cron.weekly000066400000000000000000000002731353765016400204240ustar00rootroot00000000000000#!/bin/sh test -x /usr/local/bin/schleuder || exit 0 su -s /bin/sh schleuder -c "/usr/local/bin/schleuder refresh_keys" su -s /bin/sh schleuder -c "/usr/local/bin/schleuder check_keys" schleuder-3.4.1/etc/schleuder.yml000066400000000000000000000052051353765016400167650ustar00rootroot00000000000000# Where are the list-directories stored (contain log-files and GnuPG-keyrings). lists_dir: /var/lib/schleuder/lists # Where to write list-logs. The actual log-file will be ///list.log. listlogs_dir: /var/lib/schleuder/lists # Schleuder reads plugins also from this directory. plugins_dir: /etc/schleuder/plugins # Schleuder reads filters also from this directory path, # in the specific pre_decryption or post_decryption subdirectory. # Filter files must follow the following convention for the # filename: \d+_a_name.rb # Where \d+ is any number, that defines the place in the # list of filters and a_name must match the method name # of the filter. # The built-in filters are using round numbers for their # positioning within the list. Increased by ten. filters_dir: /usr/local/lib/schleuder/filters # How verbose should Schleuder log to syslog? (list-specific messages are written to the list's log-file). log_level: warn # Which keyserver to refresh keys from (used by `schleuder refresh_keys`, meant # to be run from cron weekly). # If you have gnupg 2.1, we strongly suggest to use a hkps-keyserver: #keyserver: hkps://hkps.pool.sks-keyservers.net # If you have gnupg 2.1 and TOR running locally, use a onion-keyserver: #keyserver: hkp://jirk5u4osbsr34t5.onion # If you have an OS-wide defined keyserver, specify a blank value to have that # one used: #keyserver: # The default works for all supported versions of gnupg: keyserver: pool.sks-keyservers.net # Who is maintaining the overall schleuder installation and should be # notified about severe problems with lists. # This address should be a postmaster-like account, especially it should # not be another schleuder list. # Is also used as an envelope sender of admin notifications. superadmin: root@localhost # For these options see documentation for ActionMailer::smtp_settings, e.g. . smtp_settings: address: localhost port: 25 #domain: #enable_starttls_auto: #openssl_verify_mode: #authentication: #user_name: #password: # The database to use. Unless you want to run the tests you only need the `production`-section. database: production: adapter: 'sqlite3' database: /var/lib/schleuder/db.sqlite timeout: 5000 api: host: localhost port: 4443 # Certificate and key to use. You can create new ones with `schleuder cert generate`. tls_cert_file: /etc/schleuder/schleuder-certificate.pem tls_key_file: /etc/schleuder/schleuder-private-key.pem # List of api_keys to allow access to the API. # Example: # valid_api_keys: # - abcdef... # - zyxwvu... valid_api_keys: schleuder-3.4.1/lib/000077500000000000000000000000001353765016400142555ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder-api-daemon.rb000077500000000000000000000034021353765016400205720ustar00rootroot00000000000000#!/usr/bin/env ruby # Make sinatra use production as default-environment ENV['RACK_ENV'] ||= 'production' $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) require 'sinatra/base' require 'sinatra/json' require 'sinatra/namespace' require 'thin' require 'schleuder' require 'schleuder-api-daemon/routes/status' require 'schleuder-api-daemon/routes/version' require 'schleuder-api-daemon/routes/list' require 'schleuder-api-daemon/routes/subscription' require 'schleuder-api-daemon/routes/key' require 'schleuder-api-daemon/helpers/schleuder-api-daemon-helper' %w[tls_cert_file tls_key_file].each do |config_key| path = Conf.api[config_key] if ! File.readable?(path) $stderr.puts "Error: '#{path}' is not a readable file (from #{config_key} in config)." exit 1 end end class SchleuderApiDaemon < Sinatra::Base helpers SchleuderApiDaemonHelper configure do set :server, :thin set :port, Schleuder::Conf.api['port'] || 4443 set :bind, Schleuder::Conf.api['host'] || 'localhost' if settings.development? set :logging, Logger::DEBUG else set :logging, Logger::WARN end end before do authenticate! cast_param_values end after do # Return connection to pool after each request. ActiveRecord::Base.connection.close end error do exc = env['sinatra.error'] logger.error "Error: #{env['sinatra.error'].message}" case exc when Errno::EACCES server_error(exc.message) else client_error(exc.to_s) end end error 404 do 'Not found' end def self.run! super do |server| server.ssl = true server.ssl_options = { :cert_chain_file => Conf.api['tls_cert_file'], :private_key_file => Conf.api['tls_key_file'] } end end end schleuder-3.4.1/lib/schleuder-api-daemon/000077500000000000000000000000001353765016400202435ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder-api-daemon/helpers/000077500000000000000000000000001353765016400217055ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder-api-daemon/helpers/schleuder-api-daemon-helper.rb000066400000000000000000000102041353765016400274720ustar00rootroot00000000000000module SchleuderApiDaemonHelper def valid_credentials? @auth ||= Rack::Auth::Basic::Request.new(request.env) if @auth.provided? && @auth.basic? && @auth.credentials.present? username, api_key = @auth.credentials username == 'schleuder' && Conf.api_valid_api_keys.include?(api_key) else false end end def authenticate! # Be careful to use path_info() — it can be changed by other filters! return if request.path_info == '/status.json' if ! valid_credentials? headers['WWW-Authenticate'] = 'Basic realm="Schleuder API Daemon"' halt 401, "Not authorized\n" end end def list(id_or_email=nil) if id_or_email.blank? if params[:list_id].present? id_or_email = params[:list_id] else client_error "Parameter list_id is required" end end if is_an_integer?(id_or_email) list = List.where(id: id_or_email).first else # list_id is actually an email address list = List.where(email: id_or_email).first end list || halt(404) end def subscription(id_or_email) if is_an_integer?(id_or_email) sub = Subscription.where(id: id_or_email.to_i).first else # Email if params[:list_id].blank? client_error "Parameter list_id is required when using email as identifier for subscriptions." else sub = list.subscriptions.where(email: id_or_email).first end end sub || halt(404) end def requested_list_id # ActiveResource doesn't want to use query-params with create(), so here # list_id might be included in the request-body. params['list_id'] || parsed_body['list_id'] || client_error('Need list_id') end def parsed_body @parsed_body ||= begin b = JSON.parse(request.body.read) logger.debug "parsed body: #{b.inspect}" b end end def server_error(msg) logger.warn msg halt(500, json(error: msg)) end # TODO: unify error messages. This method currently sends an old error format. See . def client_error(obj_or_msg, http_code=400) text = case obj_or_msg when String, Symbol obj_or_msg.to_s when ActiveRecord::Base obj_or_msg.errors.full_messages else obj_or_msg end logger.error "Sending error to client: #{text.inspect}" halt(http_code, json(errors: text)) end # poor persons type casting def cast_param_values params.each do |key, value| params[key] = case value when 'true' then true when 'false' then false when '0' then 0 when is_an_integer?(value) then value.to_i else value end end end def key_to_hash(key, include_keydata=false) hash = { fingerprint: key.fingerprint, email: key.email, expiry: key.expires, generated_at: key.generated_at, primary_uid: key.primary_uid.uid, oneline: key.oneline, trust_issues: key.usability_issue } if include_keydata hash[:description] = key.to_s hash[:ascii] = key.armored end hash end def set_x_messages(messages) if messages.present? headers 'X-Messages' => Array(messages).join(' // ').gsub(/\n/, ' // ') end end def find_key_material key_material = parsed_body['key_material'].presence # By convention key_material is either ASCII or base64-encoded. if key_material && ! key_material.match('BEGIN PGP') key_material = Base64.decode64(key_material) end key_material end def find_attributes_from_body(attribs) Array(attribs).inject({}) do |memo, attrib| if parsed_body.has_key?(attrib) memo[attrib] = parsed_body[attrib] end memo end end def is_an_integer?(input) input.to_s.match(/^[0-9]+$/).present? end end schleuder-3.4.1/lib/schleuder-api-daemon/routes/000077500000000000000000000000001353765016400215645ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder-api-daemon/routes/key.rb000066400000000000000000000014641353765016400227060ustar00rootroot00000000000000class SchleuderApiDaemon < Sinatra::Base register Sinatra::Namespace namespace '/keys' do get '.json' do keys = list.keys.sort_by(&:email).map do |key| key_to_hash(key) end json keys end post '.json' do input = parsed_body['keymaterial'] if ! input.match('BEGIN PGP') input = Base64.decode64(input) end json list(requested_list_id).import_key(input) end get '/check_keys.json' do json result: list.check_keys end get '/:fingerprint.json' do |fingerprint| if key = list.key(fingerprint) json key_to_hash(key, true) else 404 end end delete '/:fingerprint.json' do |fingerprint| if list.delete_key(fingerprint) 200 else 404 end end end end schleuder-3.4.1/lib/schleuder-api-daemon/routes/list.rb000066400000000000000000000030051353765016400230620ustar00rootroot00000000000000class SchleuderApiDaemon < Sinatra::Base register Sinatra::Namespace namespace '/lists' do get '.json' do json List.all, include: :subscriptions end post '.json' do listname = parsed_body['email'] fingerprint = parsed_body['fingerprint'] adminaddress = parsed_body['adminaddress'] adminfingerprint = parsed_body['adminfingerprint'] adminkey = parsed_body['adminkey'] list, messages = ListBuilder.new({email: listname, fingerprint: fingerprint}, adminaddress, adminfingerprint, adminkey).run if list.nil? client_error(messages, 422) elsif ! list.valid? client_error(list, 422) else set_x_messages(messages) body json(list) end end get '/configurable_attributes.json' do json(List.configurable_attributes) + "\n" end post '/send_list_key_to_subscriptions.json' do json(result: list.send_list_key_to_subscriptions) end get '/new.json' do json List.new end get '/:id.json' do |id| json list(id) end put '/:id.json' do |id| list = list(id) if list.update(parsed_body) 204 else client_error(list) end end patch '/:id.json' do |id| list = list(id) if list.update(parsed_body) 204 else client_error(list) end end delete '/:id.json' do |id| list = list(id) if list.destroy 200 else client_error(list) end end end end schleuder-3.4.1/lib/schleuder-api-daemon/routes/status.rb000066400000000000000000000001401353765016400234270ustar00rootroot00000000000000class SchleuderApiDaemon < Sinatra::Base get '/status.json' do json status: :ok end end schleuder-3.4.1/lib/schleuder-api-daemon/routes/subscription.rb000066400000000000000000000052351353765016400246420ustar00rootroot00000000000000class SchleuderApiDaemon < Sinatra::Base register Sinatra::Namespace namespace '/subscriptions' do get '.json' do filterkeys = Subscription.configurable_attributes + [:list_id, :email] filter = params.select do |param| filterkeys.include?(param.to_sym) end logger.debug "Subscription filter: #{filter.inspect}" if filter['list_id'] && ! is_an_integer?(filter['list_id']) # Value is an email-address if list = List.where(email: filter['list_id']).first filter['list_id'] = list.id else status 404 return json(errors: 'No such list') end end json Subscription.where(filter) end post '.json' do begin list = list(requested_list_id) # We don't have to care about nil-values, subscribe() does that for us. sub, msgs = list.subscribe( parsed_body['email'], parsed_body['fingerprint'], parsed_body['admin'], parsed_body['delivery_enabled'], find_key_material ) set_x_messages(msgs) logger.debug "subcription: #{sub.inspect}" if sub.valid? logger.debug "Subscribed: #{sub.inspect}" # TODO: why redirect instead of respond with result? redirect to("/subscriptions/#{sub.id}.json"), 201 else client_error(sub, 422) end rescue ActiveRecord::RecordNotUnique logger.error "Already subscribed" status 422 json errors: {email: ['is already subscribed']} end end get '/configurable_attributes.json' do json(Subscription.configurable_attributes) + "\n" end get '/new.json' do json Subscription.new end get '/:id.json' do |id| json subscription(id) end put '/:id.json' do |id| sub = subscription(id) list = sub.list args = find_attributes_from_body(%w[email fingerprint admin delivery_enabled]) fingerprint, messages = list.import_key_and_find_fingerprint(find_key_material) set_x_messages(messages) # For an already existing subscription, only update fingerprint if a # new one has been selected from the upload. if fingerprint.present? args["fingerprint"] = fingerprint end if sub.update(args) 200 else client_error(sub, 422) end end patch '/:id.json' do |id| sub = subscription(id) if sub.update(parsed_body) 200 else client_error(sub) end end delete '/:id.json' do |id| if sub = subscription(id).destroy 200 else client_error(sub) end end end end schleuder-3.4.1/lib/schleuder-api-daemon/routes/version.rb000066400000000000000000000001611353765016400235740ustar00rootroot00000000000000class SchleuderApiDaemon < Sinatra::Base get '/version.json' do json version: Schleuder::VERSION end end schleuder-3.4.1/lib/schleuder.rb000066400000000000000000000042701353765016400165630ustar00rootroot00000000000000# Stdlib require 'fileutils' require 'singleton' require 'yaml' require 'pathname' require 'syslog/logger' require 'logger' require 'open3' # Require mandatory libs. The database-layer-lib is required below. require 'mail-gpg' require 'active_record' # An extra from mail-gpg require 'hkp' # Load schleuder libdir = Pathname.new(__FILE__).dirname.realpath rootdir = libdir.dirname $:.unshift libdir # Monkeypatches require 'schleuder/mail/parts_list.rb' require 'schleuder/mail/message.rb' require 'schleuder/mail/gpg.rb' require 'schleuder/mail/encrypted_part.rb' require 'schleuder/gpgme/import_status.rb' require 'schleuder/gpgme/key.rb' require 'schleuder/gpgme/sub_key.rb' require 'schleuder/gpgme/ctx.rb' require 'schleuder/gpgme/user_id.rb' # The Code[tm] require 'schleuder/errors/base' Dir["#{libdir}/schleuder/errors/*.rb"].each do |file| require file end # Load schleuder/conf before the other classes, it defines constants! require 'schleuder/conf' require 'schleuder/version' require 'schleuder/logger_notifications' require 'schleuder/logger' require 'schleuder/listlogger' require 'schleuder/plugin_runners/base' require 'schleuder/plugin_runners/list_plugins_runner' require 'schleuder/plugin_runners/request_plugins_runner' Dir["#{libdir}/schleuder/plugins/*.rb"].each do |file| require file end require 'schleuder/filters_runner' Dir["#{libdir}/schleuder/validators/*.rb"].each do |file| require file end require 'schleuder/runner' require 'schleuder/list' require 'schleuder/list_builder' require 'schleuder/subscription' # Setup ENV["SCHLEUDER_CONFIG"] ||= '/etc/schleuder/schleuder.yml' ENV["SCHLEUDER_LIST_DEFAULTS"] ||= '/etc/schleuder/list-defaults.yml' ENV["SCHLEUDER_ENV"] ||= 'production' ENV["SCHLEUDER_ROOT"] = rootdir.to_s GPGME::Ctx.set_gpg_path_from_env GPGME::Ctx.check_gpg_version # TODO: Test if database is writable if sqlite. ActiveRecord::Base.establish_connection(Schleuder::Conf.database) ActiveRecord::Base.logger = Schleuder.logger Mail.defaults do delivery_method :smtp, Schleuder::Conf.smtp_settings.symbolize_keys end I18n.load_path += Dir["#{rootdir}/locales/*.yml"] I18n.enforce_available_locales = true I18n.default_locale = :en File.umask(0027) include Schleuder schleuder-3.4.1/lib/schleuder/000077500000000000000000000000001353765016400162335ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/cli.rb000066400000000000000000000300201353765016400173220ustar00rootroot00000000000000require 'thor' require 'yaml' require 'gpgme' require_relative '../schleuder' require 'schleuder/cli/subcommand_fix' require 'schleuder/cli/schleuder_cert_manager' require 'schleuder/cli/cert' module Schleuder class Cli < Thor register(Cert, 'cert', 'cert ...', 'Generate TLS-certificate and show fingerprint') map '-v' => :version map '--version' => :version desc 'version', 'Show version of schleuder' def version say Schleuder::VERSION end desc 'new_api_key', 'Generate a new API key to be used by a client.' def new_api_key require 'securerandom' puts SecureRandom.hex(32) end desc 'work list@hostname < message', 'Run a message through a list.' def work(listname) message = STDIN.read error = Schleuder::Runner.new.run(message, listname) if error.kind_of?(StandardError) fatal error end rescue => exc begin Schleuder.logger.fatal(exc.message_with_backtrace, message) # Don't use FatalError here to reduce dependency on other code. say I18n.t('errors.fatalerror') rescue => e # Give users a clue what to do in case everything blows up. # As apparently even the logging raised exceptions we can't even store # any information in the logs. fatal "A serious, unhandleable error happened. Please contact the administrators of this system or service and provide them with the following information:\n\n#{e.message}" end exit 1 end desc 'check_keys', 'Check all lists for unusable or expiring keys and send the results to the list-admins. (This is supposed to be run from cron weekly.)' def check_keys List.all.each do |list| I18n.locale = list.language text = list.check_keys if text && ! text.empty? msg = "#{I18n.t('check_keys_intro', email: list.email)}\n\n#{text}" list.logger.notify_admin(msg, nil, I18n.t('check_keys')) end end permission_notice end desc 'refresh_keys [list1@example.com]', "Refresh all keys of all list from the keyservers sequentially (one by one or on the passed list). (This is supposed to be run from cron weekly.)" def refresh_keys(list=nil) GPGME::Ctx.send_notice_if_gpg_does_not_know_import_filter work_on_lists(:refresh_keys,list) permission_notice end desc 'pin_keys [list1@example.com]', "Find keys for subscriptions without a pinned key and try to pin a certain key (one by one or based on the passed list)." def pin_keys(list=nil) work_on_lists(:pin_keys,list) end desc 'install', "Set-up or update Schleuder environment (create folders, copy files, fill the database)." def install config_dir = Pathname.new(ENV['SCHLEUDER_CONFIG']).dirname root_dir = Pathname.new(ENV['SCHLEUDER_ROOT']) # Check if lists_dir contains v2-data. if Dir.glob("#{Conf.lists_dir}/*/*/members.conf").size > 0 msg = "Lists directory #{Conf.lists_dir} appears to contain data from a Schleuder version 2.x installation.\nPlease move it out of the way or configure a different `lists_dir` in `#{ENV['SCHLEUDER_CONFIG']}`.\nTo migrate lists from Schleuder v2 to Schleuder v3 please use `schleuder migrate_v2_list` after the installation succeeded." fatal msg, 2 end [Conf.lists_dir, Conf.listlogs_dir, config_dir].each do |dir| dir = Pathname.new(dir) if ! dir.exist? begin dir.mkpath rescue Errno::EACCES => exc problem_dir = exc.message.split(' - ').last fatal "Cannot create required directory due to lacking write permissions: #{problem_dir}.\nPlease fix the permissions or create the directory manually and then run this command again." end end end Pathname.glob(root_dir.join("etc").join("*.yml")).each do |file| target = config_dir.join(file.basename) if ! target.exist? if target.dirname.writable? FileUtils.cp file, target else fatal "Cannot copy default config file due to lacking write permissions, please copy manually and then run this command again:\n#{file.realpath} → #{target}" end end end if ActiveRecord::SchemaMigration.table_exists? say shellexec("cd #{root_dir} && rake db:migrate") else say shellexec("cd #{root_dir} && rake db:init") if Conf.database['adapter'].match(/sqlite/) say "NOTE: The database was prepared using sqlite. If you prefer to use a different DBMS please edit the 'database'-section in /etc/schleuder/schleuder.yml, create the database, install the corresponding ruby-library (e.g. `gem install mysql`) and run this current command again" end end if ! File.exist?(Conf.api['tls_cert_file']) || ! File.exist?(Conf.api['tls_key_file']) Schleuder::Cert.new.generate end say "Schleuder has been set up. You can now create a new list using `schleuder-cli`.\nWe hope you enjoy!" permission_notice rescue => exc fatal exc.message end desc 'migrate-v2-list /path/to/listdir', 'Migrate list from v2.2 to v3.' def migrate_v2_list(path) dir = Pathname.new(path) if ! dir.readable? || ! dir.directory? fatal "Not a readable directory: `#{path}`." end %w[list.conf members.conf pubring.gpg].each do |file| if ! (dir + file).exist? fatal "Not a complete schleuder v2.2 listdir: missing #{file}" end end conf = YAML.load(File.read(dir + 'list.conf')) if conf.nil? || conf.empty? fatal "list.conf is blank" end listname = conf['myaddr'] if listname.nil? || listname.empty? fatal "myaddr is blank in list.conf" end # Identify list-fingerprint. ENV['GNUPGHOME'] = dir.to_s listkey = GPGME::Key.find(:public, "<#{listname}>").first if listkey.nil? fatal "Failed to identify the list's OpenPGP-key!" end # Create list. begin list, messages = Schleuder::ListBuilder.new({email: listname, fingerprint: listkey.fingerprint}).run rescue => exc fatal exc end if messages fatal messages.values.join(" - ") elsif list.errors.any? fatal list.errors.full_messages.join(" - ") end # Import keys list.import_key(File.read(dir + 'pubring.gpg')) list.import_key(File.read(dir + 'secring.gpg')) # Clear passphrase of imported list-key. output = list.key.clearpassphrase(conf['gpg_password']) if output.present? fatal "while clearing passphrase of list-key: #{output.inspect}" end # Set list-options. List.configurable_attributes.each do |option| option = option.to_s if conf.keys.include?(option) value = case option when /^keywords_/ filter_keywords(conf[option]) when 'log_level' conf[option].to_s.downcase else conf[option] end list.set_attribute(option, value) end end # Set changed options. changed_options = { 'prefix' => 'subject_prefix', 'prefix_in' => 'subject_prefix_in', 'prefix_out' => 'subject_prefix_out', 'dump_incoming_mail' => 'forward_all_incoming_to_admins', 'receive_from_member_emailaddresses_only' => 'receive_from_subscribed_emailaddresses_only', 'bounces_notify_admin' => 'bounces_notify_admins', 'max_message_size' => 'max_message_size_kb' } changed_options.each do |old, new| if conf.keys.include?(old) list.set_attribute(new, conf[old]) end end list.save! # Subscribe members members = YAML.load(File.read(dir + 'members.conf')) members.uniq!{|m| m['email'] } members.each do |member| fingerprint = find_fingerprint(member, list) list.subscribe(member['email'], fingerprint) end # Subscribe or flag admins conf['admins'].each do |member| sub = list.subscriptions.where(email: member['email']).first if sub sub.admin = true sub.save! else adminfpr = find_fingerprint(member, list) # if we didn't find an already imported subscription for the admin # address, it wasn't a member, so we don't enable delivery for it list.subscribe(member['email'], adminfpr, true, false) end end # Notify of removed options say "Please note: the following options have been *removed*: * `default_mime` for lists (we only support pgp/mime for now), * `archive` for lists, * `gpg_passphrase` for lists, * `log_file`, `log_io`, `log_syslog` for lists (we only log to syslog (before list-creation) and a file (after it) for now), * `mime` for subscriptions/members (we only support pgp/mime for now), * `send_encrypted_only` for members/subscriptions. If you really miss any of them please tell us. Please also note that the following keywords have been renamed: * list-members => list-subscriptions * add-member => subscribe * delete-member => unsubscribe Please notify the users and admins of this list of these changes. " say "\nList #{listname} migrated to schleuder v3." if messages.present? say messages.gsub(' // ', "\n") end permission_notice rescue => exc fatal "#{exc}\n#{exc.backtrace.first}" end no_commands do def fatal(msg, exitcode=1) error("Error: #{msg}") exit exitcode end KEYWORDS = { 'add-member' => 'subscribe', 'delete-member' => 'unsubscribe', 'list-members' => 'list-subscriptions', 'subscribe' => 'subscribe', 'unsubscribe' => 'unsubscribe', 'list-subscriptions' => 'list-subscriptions', 'set-finterprint' => 'set-fingerprint', 'add-key' => 'add-key', 'delete-key' => 'delete-key', 'list-keys' => 'list-keys', 'get-key' => 'get-key', 'fetch-key' => 'fetch-key' } def filter_keywords(value) Array(value).map do |keyword| KEYWORDS[keyword.downcase] end.compact end def find_fingerprint(member, list) email = member['email'] fingerprint = member['key_fingerprint'] if fingerprint.present? return fingerprint end key = list.distinct_key(email) if key return key.fingerprint else return nil end end def shellexec(cmd) result = `#{cmd} 2>&1` if $?.exitstatus > 0 exit $?.exitstatus end result end end private def work_on_lists(subj, list=nil) if list.nil? selected_lists = List.all else selected_lists = List.where(email: list) if selected_lists.blank? error("No list with this address exists: #{list.inspect}") end end selected_lists.each do |list| I18n.locale = list.language output = list.send(subj) if output.present? msg = "#{I18n.t("#{subj}_intro", email: list.email)}\n\n#{output}" list.logger.notify_admin(msg, nil, I18n.t(subj)) end end end # Make this class exit with code 1 in case of an error. See . def self.exit_on_failure? true end def permission_notice if Process.euid == 0 dirs = [Conf.lists_dir, Conf.listlogs_dir] if Conf.database['adapter'] == 'sqlite3' dirs << Conf.database['database'] end dirs_sentence = dirs.uniq.map { |dir| enquote(dir) }.to_sentence say "Warning: this process was run as root -- please make sure that all files in #{dirs_sentence} have correct file system permissions for the user that is running both, schleuder from the MTA and `schleuder-api-daemon`." end end def enquote(string) "\`#{string}\`" end end end schleuder-3.4.1/lib/schleuder/cli/000077500000000000000000000000001353765016400170025ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/cli/cert.rb000066400000000000000000000017051353765016400202670ustar00rootroot00000000000000module Schleuder class Cert < Thor extend SubcommandFix desc 'generate', 'Generate a new TLS-certificate.' def generate key = Conf.api['tls_key_file'] cert = Conf.api['tls_cert_file'] fingerprint = SchleuderCertManager.generate('schleuder', key, cert) puts "Fingerprint of generated certificate: #{fingerprint}" puts "Have this fingerprint included into the configuration-file of all clients that want to connect to your Schleuder API." if Process.euid == 0 puts "! Warning: this process was run as root — please make sure the above files are accessible by the user that is running `schleuder-api-daemon`." end end desc 'fingerprint', 'Show fingerprint of configured certificate.' def fingerprint cert = Conf.api['tls_cert_file'] fingerprint = SchleuderCertManager.fingerprint(cert) say "Fingerprint of #{Conf.api['tls_cert_file']}: #{fingerprint}" end end end schleuder-3.4.1/lib/schleuder/cli/schleuder_cert_manager.rb000066400000000000000000000046421353765016400240220ustar00rootroot00000000000000require 'openssl' require 'pathname' class SchleuderCertManager def self.generate(project_name, filename_key, filename_cert) keysize = 2048 subject = "/C=MW/O=Schleuder/OU=#{project_name}" filename_key = Pathname.new(filename_key).expand_path filename_cert = Pathname.new(filename_cert).expand_path key = OpenSSL::PKey::RSA.new(keysize) cert = OpenSSL::X509::Certificate.new cert.subject = OpenSSL::X509::Name.parse(subject) cert.issuer = cert.subject cert.not_before = Time.now cert.not_after = Time.now + 10 * 365 * 24 * 60 * 60 cert.public_key = key.public_key cert.serial = 0x0 cert.version = 2 ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = cert cert.extensions = [ ef.create_extension("basicConstraints","CA:TRUE", true), ef.create_extension("subjectKeyIdentifier", "hash"), ] cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") cert.sign key, OpenSSL::Digest::SHA256.new filename_key = prepare_writing(filename_key) filename_cert = prepare_writing(filename_cert) filename_key.open('w', 400) do |fd| fd.puts key end puts "Private key written to: #{filename_key}" filename_cert.open('w') do |fd| fd.puts cert.to_pem end puts "Certificate written to: #{filename_cert}" fingerprint(cert) rescue => exc error exc.message end def self.fingerprint(cert) if ! cert.is_a?(OpenSSL::X509::Certificate) path = Pathname.new(cert).expand_path if ! path.readable? error "Error: Not a readable file: #{path}" end cert = OpenSSL::X509::Certificate.new(path.read) end OpenSSL::Digest::SHA256.new(cert.to_der).to_s end def self.error(msg) $stderr.puts "Error: #{msg}" exit 1 end def self.note(msg) $stdout.puts "Note: #{msg}" end def self.prepare_writing(filename) if filename.exist? note "File exists: #{filename} — writing to current directory, you should move the file manually or change the configuration file." if filename.basename.exist? error "File exists: #{filename.basename} — (re)move it or fix previous error and try again." end filename = filename.basename end if ! filename.dirname.exist? filename.dirname.mkpath end filename end end schleuder-3.4.1/lib/schleuder/cli/subcommand_fix.rb000066400000000000000000000005071353765016400223270ustar00rootroot00000000000000module Schleuder module SubcommandFix # Fixing a bug in Thor where the actual subcommand wouldn't show up # with some invocations of the help-output. def banner(task, namespace = true, subcommand = true) "#{basename} #{task.formatted_usage(self, true, subcommand).split(':').join(' ')}" end end end schleuder-3.4.1/lib/schleuder/conf.rb000066400000000000000000000064501353765016400175120ustar00rootroot00000000000000require 'erb' module Schleuder class Conf include Singleton EMAIL_REGEXP = /\A.+@[[:alnum:]_.-]+\z/i # TODO: drop v3 keys and only accept length of 40 FINGERPRINT_REGEXP = /\A(0x)?[a-f0-9]{32}([a-f0-9]{8})?\z/i DEFAULTS = { 'lists_dir' => '/var/lib/schleuder/lists', 'listlogs_dir' => '/var/lib/schleuder/lists', 'plugins_dir' => '/etc/schleuder/plugins', 'filters_dir' => '/usr/local/lib/schleuder/filters', 'log_level' => 'warn', 'superadmin' => 'root@localhost', 'keyserver' => 'hkp://pool.sks-keyservers.net', 'smtp_settings' => { 'address' => 'localhost', 'port' => 25, 'domain' => 'localhost', 'enable_starttls_auto' => true, # Don't verify by default because most smtp servers don't include # 'localhost' into their TLS-certificates. 'openssl_verify_mode' => 'none', 'authentication' => nil, 'user_name' => nil, 'password' => nil, }, 'database' => { 'production' => { 'adapter' => 'sqlite3', 'database' => '/var/lib/schleuder/db.sqlite', 'timeout' => 5000 } }, 'api' => { 'host' => 'localhost', 'port' => 4443, 'tls_cert_file' => '/etc/schleuder/schleuder-certificate.pem', 'tls_key_file' => '/etc/schleuder/schleuder-private-key.pem', 'valid_api_keys' => [] } } def config @config ||= load_config(ENV['SCHLEUDER_CONFIG']) end def self.lists_dir instance.config['lists_dir'] end def self.listlogs_dir instance.config['listlogs_dir'] end def self.plugins_dir instance.config['plugins_dir'] end def self.filters_dir instance.config['filters_dir'] end def self.database instance.config['database'][ENV['SCHLEUDER_ENV']] end def self.databases instance.config['database'] end def self.superadmin instance.config['superadmin'] end def self.log_level instance.config['log_level'] || 'WARN' end def self.api instance.config['api'] || {} end def self.api_valid_api_keys Array(api['valid_api_keys']) end # Three legacy options def self.smtp_host instance.config['smtp_host'] end def self.smtp_port instance.config['smtp_port'] end def self.smtp_helo_domain instance.config['smtp_helo_domain'] end def self.smtp_settings settings = instance.config['smtp_settings'] || {} # Support previously used config-options. # Remove this in future versions. {smtp_host: :address, smtp_port: :port, smtp_helo_domain: :domain}.each do |old, new| value = self.send(old) if value.present? Schleuder.logger.warn "Deprecation warning: In schleuder.yml #{old} should be changed to smtp_settings[#{new}]." settings[new] = value end end settings end def self.keyserver instance.config['keyserver'] end private def load_config(filename) DEFAULTS.deep_merge(load_config_file(filename)) end def load_config_file(filename) file = Pathname.new(filename) if file.readable? YAML.load(ERB.new(file.read).result) else {} end end end end schleuder-3.4.1/lib/schleuder/errors/000077500000000000000000000000001353765016400175475ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/errors/active_model_error.rb000066400000000000000000000003721353765016400237420ustar00rootroot00000000000000module Schleuder module Errors class ActiveModelError < Base def initialize(errors) messages = errors.messages.map do |message| message.join(' ') end.join("\n") super messages end end end end schleuder-3.4.1/lib/schleuder/errors/base.rb000066400000000000000000000004251353765016400210070ustar00rootroot00000000000000module Schleuder module Errors class Base < StandardError def t(*args) I18n.t(*args) end def to_s super + t('errors.signoff') end def set_default_locale I18n.locale = I18n.default_locale end end end end schleuder-3.4.1/lib/schleuder/errors/decryption_failed.rb000066400000000000000000000004121353765016400235550ustar00rootroot00000000000000module Schleuder module Errors class DecryptionFailed < Base def initialize(list) set_default_locale super t('errors.decryption_failed', { key: list.key.to_s, email: list.sendkey_address }) end end end end schleuder-3.4.1/lib/schleuder/errors/fatal_error.rb000066400000000000000000000002501353765016400223710ustar00rootroot00000000000000module Schleuder module Errors class FatalError < Base def initialize end def to_s t("errors.fatalerror") end end end end schleuder-3.4.1/lib/schleuder/errors/key_adduid_failed.rb000066400000000000000000000002731353765016400235040ustar00rootroot00000000000000module Schleuder module Errors class KeyAdduidFailed < Base def initialize(errmsg) super t('errors.key_adduid_failed', { errmsg: errmsg }) end end end end schleuder-3.4.1/lib/schleuder/errors/key_generation_failed.rb000066400000000000000000000003421353765016400244020ustar00rootroot00000000000000module Schleuder module Errors class KeyGenerationFailed < Base def initialize(listdir, listname) super t('errors.key_generation_failed', {listdir: listdir, listname: listname}) end end end end schleuder-3.4.1/lib/schleuder/errors/keyword_admin_only.rb000066400000000000000000000002741353765016400237740ustar00rootroot00000000000000module Schleuder module Errors class KeywordAdminOnly < Base def initialize(keyword) super t('errors.keyword_admin_only', keyword: keyword) end end end end schleuder-3.4.1/lib/schleuder/errors/list_not_found.rb000066400000000000000000000002671353765016400231270ustar00rootroot00000000000000module Schleuder module Errors class ListNotFound < Base def initialize(recipient) super t('errors.list_not_found', email: recipient) end end end end schleuder-3.4.1/lib/schleuder/errors/list_property_missing.rb000066400000000000000000000004351353765016400245460ustar00rootroot00000000000000module Schleuder module Errors class ListPropertyMissing < Base def initialize(listdir, property) @listdir = listdir @property = property end def to_s t("errors.list_#{@property}_missing", listdir: @listdir) end end end end schleuder-3.4.1/lib/schleuder/errors/listdir_problem.rb000066400000000000000000000004101353765016400232610ustar00rootroot00000000000000module Schleuder module Errors class ListdirProblem < Base def initialize(dir, problem) problem = t("errors.listdir_problem.#{problem}") super t('errors.listdir_problem.message', dir: dir, problem: problem) end end end end schleuder-3.4.1/lib/schleuder/errors/loading_list_settings_failed.rb000066400000000000000000000004041353765016400257660ustar00rootroot00000000000000module Schleuder module Errors class LoadingListSettingsFailed < Base def initialize config_file = ENV['SCHLEUDER_LIST_DEFAULTS'] super t('errors.loading_list_settings_failed', config_file: config_file) end end end end schleuder-3.4.1/lib/schleuder/errors/message_empty.rb000066400000000000000000000003441353765016400227370ustar00rootroot00000000000000module Schleuder module Errors class MessageEmpty < Base def initialize(list) set_default_locale super t('errors.message_empty', { request_address: list.request_address }) end end end end schleuder-3.4.1/lib/schleuder/errors/message_not_from_admin.rb000066400000000000000000000003031353765016400245670ustar00rootroot00000000000000module Schleuder module Errors class MessageNotFromAdmin < Base def initialize set_default_locale super t('errors.message_not_from_admin') end end end end schleuder-3.4.1/lib/schleuder/errors/message_sender_not_subscribed.rb000066400000000000000000000003211353765016400261410ustar00rootroot00000000000000module Schleuder module Errors class MessageSenderNotSubscribed < Base def initialize set_default_locale super t('errors.message_sender_not_subscribed') end end end end schleuder-3.4.1/lib/schleuder/errors/message_too_big.rb000066400000000000000000000004141353765016400232210ustar00rootroot00000000000000module Schleuder module Errors class MessageTooBig < Base def initialize(list) set_default_locale allowed_size = list.max_message_size_kb super t('errors.message_too_big', { allowed_size: allowed_size }) end end end end schleuder-3.4.1/lib/schleuder/errors/message_unauthenticated.rb000066400000000000000000000003071353765016400247650ustar00rootroot00000000000000module Schleuder module Errors class MessageUnauthenticated < Base def initialize set_default_locale super t('errors.message_unauthenticated') end end end end schleuder-3.4.1/lib/schleuder/errors/message_unencrypted.rb000066400000000000000000000002771353765016400241460ustar00rootroot00000000000000module Schleuder module Errors class MessageUnencrypted < Base def initialize set_default_locale super t('errors.message_unencrypted') end end end end schleuder-3.4.1/lib/schleuder/errors/message_unsigned.rb000066400000000000000000000002711353765016400234140ustar00rootroot00000000000000module Schleuder module Errors class MessageUnsigned < Base def initialize set_default_locale super t('errors.message_unsigned') end end end end schleuder-3.4.1/lib/schleuder/errors/standard_error.rb000066400000000000000000000001541353765016400231050ustar00rootroot00000000000000class StandardError def message_with_backtrace "#{message}\n#{self.backtrace.join("\n")}\n" end end schleuder-3.4.1/lib/schleuder/errors/too_many_keys.rb000066400000000000000000000003231353765016400227520ustar00rootroot00000000000000module Schleuder module Errors class TooManyKeys < Base def initialize(listdir, listname) super t('errors.too_many_keys', {listdir: listdir, listname: listname}) end end end end schleuder-3.4.1/lib/schleuder/filters/000077500000000000000000000000001353765016400177035ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/filters/post_decryption/000077500000000000000000000000001353765016400231305ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/filters/post_decryption/10_request.rb000066400000000000000000000013071353765016400254460ustar00rootroot00000000000000module Schleuder module Filters def self.request(list, mail) return if ! mail.request? list.logger.debug "Request-message" if ! mail.was_encrypted? || ! mail.was_validly_signed? list.logger.debug "Error: Message was not encrypted and validly signed" return Errors::MessageUnauthenticated.new end if mail.keywords.empty? output = I18n.t(:no_keywords_error) else output = PluginRunners::RequestPluginsRunner.run(list, mail) output = output.flatten.map(&:presence).compact if output.blank? output = I18n.t(:no_output_result) end end mail.reply_to_signer(output) exit end end end schleuder-3.4.1/lib/schleuder/filters/post_decryption/20_max_message_size.rb000066400000000000000000000004221353765016400272770ustar00rootroot00000000000000module Schleuder module Filters def self.max_message_size(list, mail) if (mail.raw_source.size / 1024) > list.max_message_size_kb list.logger.info "Rejecting mail as too big" return Errors::MessageTooBig.new(list) end end end end schleuder-3.4.1/lib/schleuder/filters/post_decryption/30_forward_to_owner.rb000066400000000000000000000006711353765016400273430ustar00rootroot00000000000000module Schleuder module Filters def self.forward_to_owner(list, mail) return if ! mail.to_owner? list.logger.debug "Forwarding addressed to -owner" mail.add_pseudoheader(:note, I18n.t(:owner_forward_prefix)) cleanmail = mail.clean_copy(true) list.admins.each do |admin| list.logger.debug "Forwarding message to #{admin}" admin.send_mail(cleanmail) end exit end end end schleuder-3.4.1/lib/schleuder/filters/post_decryption/40_receive_admin_only.rb000066400000000000000000000004621353765016400276150ustar00rootroot00000000000000module Schleuder module Filters def self.receive_admin_only(list, mail) if list.receive_admin_only? && ( ! mail.was_validly_signed? || ! mail.signer.admin? ) list.logger.info "Rejecting mail as not from admin." return Errors::MessageNotFromAdmin.new end end end end schleuder-3.4.1/lib/schleuder/filters/post_decryption/50_receive_authenticated_only.rb000066400000000000000000000005061353765016400313470ustar00rootroot00000000000000module Schleuder module Filters def self.receive_authenticated_only(list, mail) if list.receive_authenticated_only? && ( ! mail.was_encrypted? || ! mail.was_validly_signed? ) list.logger.info "Rejecting mail as unauthenticated" return Errors::MessageUnauthenticated.new end end end end schleuder-3.4.1/lib/schleuder/filters/post_decryption/60_receive_signed_only.rb000066400000000000000000000004151353765016400277760ustar00rootroot00000000000000module Schleuder module Filters def self.receive_signed_only(list, mail) if list.receive_signed_only? && ! mail.was_validly_signed? list.logger.info "Rejecting mail as unsigned" return Errors::MessageUnsigned.new end end end end schleuder-3.4.1/lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb000066400000000000000000000004241353765016400305230ustar00rootroot00000000000000module Schleuder module Filters def self.receive_encrypted_only(list, mail) if list.receive_encrypted_only? && ! mail.was_encrypted? list.logger.info "Rejecting mail as unencrypted" return Errors::MessageUnencrypted.new end end end end 80_receive_from_subscribed_emailaddresses_only.rb000066400000000000000000000005711353765016400346700ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/filters/post_decryptionmodule Schleuder module Filters def self.receive_from_subscribed_emailaddresses_only(list, mail) if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first).blank? list.logger.info "Rejecting mail as not from subscribed address." return Errors::MessageSenderNotSubscribed.new end end end end 90_strip_html_from_alternative_if_keywords_present.rb000066400000000000000000000012331353765016400356600ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/filters/post_decryptionmodule Schleuder module Filters def self.strip_html_from_alternative_if_keywords_present(list, mail) if mail[:content_type].blank? || mail[:content_type].content_type != 'multipart/alternative' || mail.keywords.blank? return false end Schleuder.logger.debug 'Stripping html-part from multipart/alternative-message because it contains keywords' mail.parts.delete_if do |part| part[:content_type].content_type == 'text/html' end mail.content_type = 'multipart/mixed' mail.add_pseudoheader(:note, I18n.t('pseudoheaders.stripped_html_from_multialt_with_keywords')) end end end schleuder-3.4.1/lib/schleuder/filters/pre_decryption/000077500000000000000000000000001353765016400227315ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/filters/pre_decryption/10_forward_bounce_to_admins.rb000066400000000000000000000005531353765016400306150ustar00rootroot00000000000000module Schleuder module Filters def self.forward_bounce_to_admins(list, mail) if mail.automated_message? list.logger.info "Forwarding automated message to admins" list.logger.notify_admin I18n.t(:forward_automated_message_to_admins), mail.original_message, I18n.t('automated_message_subject') exit end end end end schleuder-3.4.1/lib/schleuder/filters/pre_decryption/20_forward_all_incoming_to_admins.rb000066400000000000000000000004431353765016400317740ustar00rootroot00000000000000module Schleuder module Filters def self.forward_all_incoming_to_admins(list, mail) if list.forward_all_incoming_to_admins list.logger.notify_admin I18n.t(:forward_all_incoming_to_admins), mail.original_message, I18n.t('incoming_message') end end end end schleuder-3.4.1/lib/schleuder/filters/pre_decryption/30_send_key.rb000066400000000000000000000011501353765016400253560ustar00rootroot00000000000000module Schleuder module Filters def self.send_key(list, mail) return if ! mail.sendkey_request? list.logger.debug "Sending public key as reply." out = mail.reply out.from = list.email # We're not sending to a subscribed address, so we need to specify a envelope-sender manually. out.sender = list.bounce_address out.body = I18n.t(:list_public_key_attached) out.attach_list_key!(list) # TODO: find out why the gpg-module puts all the headers into the first mime-part, too out.gpg list.gpg_sign_options out.deliver exit end end end schleuder-3.4.1/lib/schleuder/filters/pre_decryption/40_fix_exchange_messages.rb000066400000000000000000000024421353765016400301020ustar00rootroot00000000000000module Schleuder module Filters # Outlook / Hotmail seems to dismantle multipart/encrypted messages and # put them again together as multipart/mixed, which is wrong and makes # it problematic to correctly detect the message as a valid pgp/mime-mail. # Here we fix the mail to be a proper pgp/mime aka. multipart/encrypted # message, so further processing will detect it properly. # This problem seems to be in fact related to the use of Microsoft # Exchange. Accordingly, check if the headers contain 'X-MS-Exchange'. # See #211, #246, #331 and #333 for background. def self.fix_exchange_messages(list, mail) if mail.header_fields.any?{|f| f.name =~ /^X-MS-Exchange-/i } && !mail[:content_type].blank? && mail[:content_type].content_type == 'multipart/mixed' && mail.parts.size > 2 && mail.parts[0][:content_type].content_type == 'text/plain' && mail.parts[0].body.to_s.blank? && mail.parts[1][:content_type].content_type == 'application/pgp-encrypted' && mail.parts[2][:content_type].content_type == 'application/octet-stream' mail.parts.delete_at(0) mail.content_type = [:multipart, :encrypted, {protocol: "application/pgp-encrypted", boundary: mail.boundary}] end end end end schleuder-3.4.1/lib/schleuder/filters/pre_decryption/50_strip_html_from_alternative.rb000066400000000000000000000011561353765016400313730ustar00rootroot00000000000000module Schleuder module Filters def self.strip_html_from_alternative(list, mail) if mail[:content_type].blank? || mail[:content_type].content_type != 'multipart/alternative' || ! mail.to_s.include?('BEGIN PGP ') return false end Schleuder.logger.debug "Stripping html-part from multipart/alternative-message" mail.parts.delete_if do |part| part[:content_type].content_type == 'text/html' end mail.content_type = 'multipart/mixed' mail.add_pseudoheader(:note, I18n.t("pseudoheaders.stripped_html_from_multialt")) end end end schleuder-3.4.1/lib/schleuder/filters_runner.rb000066400000000000000000000051021353765016400216170ustar00rootroot00000000000000module Schleuder module Filters class Runner attr_reader :list, :filter_type def initialize(list, filter_type) @list = list @filter_type = filter_type end def run(mail) filters.map do |cmd| list.logger.debug "Calling filter #{cmd}" response = Filters.send(cmd, list, mail) if stop?(response) if bounce?(response, mail) return response else return nil end end end nil end def filters @filters ||= load_filters end private def stop?(response) response.kind_of?(StandardError) end def bounce?(response, mail) if list.bounces_drop_all list.logger.debug "Dropping bounce as configurated" notify_admins(I18n.t('.bounces_drop_all'), mail.original_message) return false end list.bounces_drop_on_headers.each do |key, value| if mail[key].to_s.match(/#{value}/i) list.logger.debug "Incoming message header key '#{key}' matches value '#{value}': dropping the bounce." notify_admins(I18n.t('.bounces_drop_on_headers', key: key, value: value), mail.original_message) return false end end list.logger.debug "Bouncing message" true end def notify_admins(reason, original_message) if list.bounces_notify_admins? list.logger.notify_admin reason, original_message, I18n.t('notice') end end def load_filters list.logger.debug "Loading #{filter_type}_decryption filters" sorted_filters.map do |filter_name| require all_filter_files[filter_name] filter_name.split('_',2).last end end def sorted_filters @sorted_filters ||= all_filter_files.keys.sort do |a,b| a.split('_',2).first.to_i <=> b.split('_',2).first.to_i end end def all_filter_files @all_filter_files ||= begin files_in_filter_dirs = Dir[*filter_dirs] files_in_filter_dirs.inject({}) do |res,file| filter_name = File.basename(file,'.rb') res[filter_name] = file res end end end def filter_dirs @filter_dirs ||= [File.join(File.dirname(__FILE__),"filters"), Schleuder::Conf.filters_dir].map do |d| File.join(d,"#{filter_type}_decryption/[0-9]*_*.rb") end end end end end schleuder-3.4.1/lib/schleuder/gpgme/000077500000000000000000000000001353765016400173325ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/gpgme/ctx.rb000066400000000000000000000224471353765016400204660ustar00rootroot00000000000000module GPGME class Ctx IMPORT_FLAGS = { 'new_key' => 1, 'new_uids' => 2, 'new_signatures' => 4, 'new_subkeys' => 8 } def keyimport(keydata) self.import_keys(GPGME::Data.new(keydata)) result = self.import_result result.imports.map(&:set_action) result end # TODO: find solution for I18n — could be a different language in API-clients than here! def interpret_import_result(import_result) case import_result.imports.size when 1 import_status = import_result.imports.first if import_status.action == 'error' [nil, "Key #{import_status.fpr} could not be imported!"] else [import_status.fpr, nil] end when 0 [nil, "The given key material did not contain any keys!"] else # TODO: report import-stati of the keys? [nil, "The given key material contained more than one key, could not determine which fingerprint to use. Please set it manually!"] end end def find_keys(input=nil, secret_only=nil) _, input = clean_and_classify_input(input) keys(input, secret_only) end def find_distinct_key(input=nil, secret_only=nil) _, input = clean_and_classify_input(input) keys = keys(input, secret_only) if keys.size == 1 keys.first else nil end end def clean_and_classify_input(input) case input when /.*?([^ <>]+@[^ <>]+).*?/ [:email, "<#{$1}>"] when /^http/ [:url, input] when Conf::FINGERPRINT_REGEXP [:fingerprint, "0x#{input.gsub(/^0x/, '')}"] else [nil, input] end end # Tell gpgme to use the given binary. def self.set_gpg_path_from_env path = ENV['GPGBIN'].to_s if ! path.empty? Schleuder.logger.debug "setting gpg to use #{path}" GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, path, ENV['GNUPGHOME']) if gpg_engine.version.nil? $stderr.puts "Error: The binary you specified doesn't provide a gpg-version." exit 1 end end end def self.sufficient_gpg_version?(required) Gem::Version.new(required) <= Gem::Version.new(gpg_engine.version) end def self.check_gpg_version if ! sufficient_gpg_version?('2.0') $stderr.puts "Error: GnuPG version >= 2.0 required.\nPlease install it and/or provide the path to the binary via the environment-variable GPGBIN.\nExample: GPGBIN=/opt/gpg2/bin/gpg ..." exit 1 end end def self.gpg_engine GPGME::Engine.info.find {|e| e.protocol == GPGME::PROTOCOL_OpenPGP } end def refresh_keys(keys) # reorder keys so the update pattern is random output = keys.shuffle.map do |key| # Sleep a short while to make traffic analysis less easy. sleep rand(1.0..5.0) refresh_key(key.fingerprint).presence end # TODO: drop version check once we killed gpg 2.0 support. if GPGME::Ctx.sufficient_gpg_version?('2.1') `gpgconf --kill dirmngr` end output.compact.join("\n") end def refresh_key(fingerprint) args = "#{keyserver_arg} #{import_filter_arg} --refresh-keys #{fingerprint}" gpgerr, gpgout, exitcode = self.class.gpgcli(args) if exitcode > 0 # Return filtered error messages. Include gpgkeys-messages from stdout # (gpg 2.0 does that), which could e.g. report a failure to connect to # the keyserver. res = [ refresh_key_filter_messages(gpgerr), refresh_key_filter_messages(gpgout).grep(/^gpgkeys: /) ].flatten.compact # if there was an error that we don't filter out, # we better kill dirmngr, so it hopefully won't suffer # from the same error during the next run. # See #309 for background # TODO: drop version check once we killed gpg 2.0 support. if !res.empty? && GPGME::Ctx.sufficient_gpg_version?('2.1') `gpgconf --kill dirmngr` end res.join("\n") else lines = translate_output('key_updated', gpgout).reject do |line| # Reduce the noise a little. line.match(/.* \(unchanged\):$/) end lines.join("\n") end end def fetch_key(input) arguments, error = fetch_key_gpg_arguments_for(input) return error if error self.class.send_notice_if_gpg_does_not_know_import_filter gpgerr, gpgout, exitcode = self.class.gpgcli("#{import_filter_arg} #{arguments}") # Unfortunately gpg doesn't exit with code > 0 if `--fetch-key` fails. if exitcode > 0 || gpgerr.grep(/ unable to fetch /).presence "Fetching #{input} did not succeed:\n#{gpgerr.join("\n")}" else translate_output('key_fetched', gpgout).join("\n") end end def fetch_key_gpg_arguments_for(input) case input when Conf::FINGERPRINT_REGEXP "#{keyserver_arg} --recv-key #{input}" when /^http/ "--fetch-key #{input}" when /@/ # --recv-key doesn't work with email-addresses, so we use --locate-key # restricted to keyservers. "#{keyserver_arg} --auto-key-locate keyserver --locate-key #{input}" else [nil, I18n.t("fetch_key.invalid_input")] end end def translate_output(locale_key, gpgoutput) import_states = translate_import_data(gpgoutput) strings = import_states.map do |fingerprint, states| key = find_distinct_key(fingerprint) I18n.t(locale_key, { key_oneline: key.oneline, states: states.to_sentence }) end strings end def translate_import_data(gpgoutput) result = {} gpgoutput.grep(/IMPORT_OK/) do |import_ok| next if import_ok.blank? import_status, fingerprint = import_ok.split(/\s/).slice(2, 2) import_status = import_status.to_i states = [] if import_status == 0 states << I18n.t("import_states.unchanged") else IMPORT_FLAGS.each do |text, int| if (import_status & int) > 0 states << I18n.t("import_states.#{text}") end end end result[fingerprint] = states end result end # Unfortunately we can't distinguish between a failure to connect the # keyserver, and a failure to find the key on the server. So we try to # filter misleading errors to check if there are any to be reported. def refresh_key_filter_messages(strings) strings.reject do |line| line.chomp == 'gpg: keyserver refresh failed: No data' || line.match(/^gpgkeys: key .* not found on keyserver/) || line.match(/^gpg: refreshing /) || line.match(/^gpg: requesting key /) || line.match(/^gpg: no valid OpenPGP data found/) end end def self.gpgcli(args) exitcode = -1 errors = [] output = [] base_cmd = gpg_engine.file_name base_args = "--no-greeting --no-permission-warning --quiet --armor --trust-model always --no-tty --command-fd 0 --status-fd 1" cmd = [base_cmd, base_args, args].flatten.join(' ') Open3.popen3(cmd) do |stdin, stdout, stderr, thread| if block_given? output = yield(stdin, stdout, stderr) else output = stdout.readlines end stdin.close if ! stdin.closed? errors = stderr.readlines exitcode = thread.value.exitstatus end [errors, output, exitcode] rescue Errno::ENOENT raise 'Need gpg in $PATH or in $GPGBIN' end def self.gpgcli_expect(args) gpgcli(args) do |stdin, stdout, stderr| counter = 0 while line = stdout.gets rescue nil counter += 1 if counter > 1042 return "Too many input-lines from gpg, something went wrong" end output, error = yield(line.chomp) if output == false return error elsif output stdin.puts output end end end end def self.spawn_daemon(name, args) delete_daemon_socket(name) cmd = "#{name} #{args} --daemon > /dev/null 2>&1" if ! system(cmd) return [false, "#{name} exited with code #{$?}"] end end def self.delete_daemon_socket(name) path = File.join(ENV["GNUPGHOME"], "S.#{name}") if File.exist?(path) File.delete(path) end end def keyserver_arg if Conf.keyserver.present? "--keyserver #{Conf.keyserver}" else "" end end def self.gpg_knows_import_filter? sufficient_gpg_version?('2.1.15') end def import_filter_arg if self.class.gpg_knows_import_filter? %{ --import-filter drop-sig='sig_created_d > 0000-00-00'} end end def self.send_notice_if_gpg_does_not_know_import_filter if ! gpg_knows_import_filter? Schleuder.logger.notify_superadmin( subject: 'Schleuder installation problem', message: "Your version of GnuPG is very old, please update!\n\nWith your version of GnuPG we can not protect your setup against signature flooding. Please update to at least version 2.1.15 to fix this problem. See for details on the background." ) '' end end end end schleuder-3.4.1/lib/schleuder/gpgme/import_status.rb000066400000000000000000000017601353765016400226000ustar00rootroot00000000000000module GPGME class ImportStatus attr_reader :action # Unfortunately in initialize() @status and @result are not yet initialized. def set_action @action ||= if self.result > 0 # An error happened. # TODO: Give details by going through the list of errors in # "gpg-errors.h" and find out which is present here. 'error' else # TODO: refactor with Ctx#translate_import_data case self.status when 0 'unchanged' when IMPORT_NEW 'imported' else 'updated' end end self end # Force encoding, some databases save "ASCII-8BIT" as binary data. alias_method :orig_fingerprint, :fingerprint def fingerprint orig_fingerprint.encode(Encoding::US_ASCII) end end end schleuder-3.4.1/lib/schleuder/gpgme/key.rb000066400000000000000000000136341353765016400204560ustar00rootroot00000000000000module GPGME class Key # Overwrite to specify the full fingerprint instead of the short key-ID. def to_s primary_subkey = subkeys[0] s = sprintf("%s %4d%s/%s %s\n", primary_subkey.secret? ? 'sec' : 'pub', primary_subkey.length, primary_subkey.pubkey_algo_letter, primary_subkey.fingerprint, primary_subkey.timestamp.strftime('%Y-%m-%d')) uids.each do |user_id| s << "uid\t\t#{user_id.name} <#{user_id.email}>\n" end subkeys.each do |subkey| s << subkey.to_s end s end def generated_at primary_subkey.timestamp end def expired? expired.present? end def oneline @oneline ||= begin datefmt = '%Y-%m-%d' attribs = [ "0x#{fingerprint}", email, generated_at.strftime(datefmt) ] if usability_issue.present? case usability_issue when :expired attribs << "[expired: #{expires.strftime(datefmt)}]" when :revoked # TODO: add revocation date when it's available. attribs << "[revoked]" else attribs << "[#{usability_issue}]" end end if expires? && ! expired? attribs << "[expires: #{expires.strftime(datefmt)}]" end attribs.join(' ') end end def armored "#{self.to_s}\n\n#{export(armor: true).read}" end # Force encoding, some databases save "ASCII-8BIT" as binary data. alias_method :orig_fingerprint, :fingerprint def fingerprint orig_fingerprint.encode(Encoding::US_ASCII) end def usable? usability_issue.blank? end def usability_issue if trust.present? trust elsif ! usable_for?(:encrypt) "not capable of encryption" else nil end end def set_primary_uid(email) # We rely on the order of UIDs here. Seems to work. index = self.uids.map(&:email).index(email) uid_number = index + 1 primary_set = false args = "--edit-key '#{self.fingerprint}' #{uid_number}" errors, _ = GPGME::Ctx.gpgcli_expect(args) do |line| case line.chomp when /keyedit.prompt/ if ! primary_set primary_set = true "primary" else "save" end else nil end end errors.join end def adduid(uid, email) # This block can be deleted once we cease to support gnupg 2.0. if ! GPGME::Ctx.sufficient_gpg_version?('2.1.4') return adduid_expect(uid, email) end # Specifying the key via fingerprint apparently doesn't work. errors, _ = GPGME::Ctx.gpgcli("--quick-adduid #{uid} '#{uid} <#{email}>'") errors.join end # This method can be deleted once we cease to support gnupg 2.0. def adduid_expect(uid, email) args = "--allow-freeform-uid --edit-key '#{self.fingerprint}' adduid" errors, _ = GPGME::Ctx.gpgcli_expect(args) do |line| case line.chomp when /keygen.name/ uid when /keygen.email/ email when /keygen.comment/ '' when /keyedit.prompt/ "save" else nil end end errors.join end def clearpassphrase(oldpw) # This block can be deleted once we cease to support gnupg 2.0. if ! GPGME::Ctx.sufficient_gpg_version?('2.1.0') return clearpassphrase_v20(oldpw) end oldpw_given = false # Don't use '--passwd', it claims to fail (even though it factually doesn't). args = "--pinentry-mode loopback --edit-key '#{self.fingerprint}' passwd" errors, _, exitcode = GPGME::Ctx.gpgcli_expect(args) do |line| case line when /passphrase.enter/ if ! oldpw_given oldpw_given = true oldpw else "" end when /BAD_PASSPHRASE/ [false, 'bad passphrase'] when /change_passwd.empty.okay/ 'y' when /keyedit.prompt/ "save" else nil end end # Only show errors if something apparently went wrong. Otherwise we might # leak useless strings from gpg and make the caller report errors even # though this method succeeded. if exitcode > 0 errors.join else nil end end # This method can be deleted once we cease to support gnupg 2.0. def clearpassphrase_v20(oldpw) start_gpg_agent(oldpw) # Don't use '--passwd', it claims to fail (even though it factually doesn't). errors, _, exitcode = GPGME::Ctx.gpgcli_expect("--edit-key '#{self.fingerprint}' passwd") do |line| case line when /BAD_PASSPHRASE/ [false, 'bad passphrase'] when /change_passwd.empty.okay/ 'y' when /keyedit.prompt/ "save" else nil end end stop_gpg_agent # Only show errors if something apparently went wrong. Otherwise we might # leak useless strings from gpg and make the caller report errors even # though this method succeeded. if exitcode > 0 errors.join else nil end end # This method can be deleted once we cease to support gnupg 2.0. def stop_gpg_agent # gpg-agent terminates itself if its socket goes away. GPGME::Ctx.delete_daemon_socket('gpg-agent') end def start_gpg_agent(oldpw) ENV['PINENTRY_USER_DATA'] = oldpw pinentry = File.join(ENV['SCHLEUDER_ROOT'], 'bin', 'pinentry-clearpassphrase') GPGME::Ctx.spawn_daemon('gpg-agent', "--use-standard-socket --pinentry-program #{pinentry}") end def self.valid_fingerprint?(fp) fp =~ Schleuder::Conf::FINGERPRINT_REGEXP end end end schleuder-3.4.1/lib/schleuder/gpgme/sub_key.rb000066400000000000000000000005241353765016400213210ustar00rootroot00000000000000module GPGME class SubKey # Overwrite to specify the full fingerprint instead of the short key-ID. def to_s sprintf("%s %4d%s/%s %s\n", secret? ? 'ssc' : 'sub', length, pubkey_algo_letter, fingerprint, timestamp.strftime('%Y-%m-%d')) end end end schleuder-3.4.1/lib/schleuder/gpgme/user_id.rb000066400000000000000000000006461353765016400213170ustar00rootroot00000000000000module GPGME class UserID def name sanitize_encoding(@name) end def comment sanitize_encoding(@comment) end def uid sanitize_encoding(@uid) end private def sanitize_encoding(str) if str.is_a?(String) && str.encoding != 'UTF-8' str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: '') else str end end end end schleuder-3.4.1/lib/schleuder/list.rb000066400000000000000000000244641353765016400175450ustar00rootroot00000000000000module Schleuder class List < ActiveRecord::Base has_many :subscriptions, dependent: :destroy before_destroy :delete_listdirs serialize :headers_to_meta, JSON serialize :bounces_drop_on_headers, JSON serialize :keywords_admin_only, JSON serialize :keywords_admin_notify, JSON validates :email, presence: true, uniqueness: true, email: true validates :fingerprint, presence: true, fingerprint: true validates :send_encrypted_only, :receive_encrypted_only, :receive_signed_only, :receive_authenticated_only, :receive_from_subscribed_emailaddresses_only, :receive_admin_only, :keep_msgid, :bounces_drop_all, :bounces_notify_admins, :include_list_headers, :include_openpgp_header, :forward_all_incoming_to_admins, boolean: true validates_each :headers_to_meta, :keywords_admin_only, :keywords_admin_notify do |record, attrib, value| value.each do |word| if word !~ /\A[a-z_-]+\z/i record.errors.add(attrib, I18n.t("errors.invalid_characters")) end end end validates_each :bounces_drop_on_headers do |record, attrib, value| value.each do |key, val| if key.to_s !~ /\A[a-z-]+\z/i || val.to_s !~ /\A[[:graph:]]+\z/i record.errors.add(attrib, I18n.t("errors.invalid_characters")) end end end validates :subject_prefix, :subject_prefix_in, :subject_prefix_out, no_line_breaks: true validates :openpgp_header_preference, presence: true, inclusion: { in: %w(sign encrypt signencrypt unprotected none), } validates :max_message_size_kb, :logfiles_to_keep, greater_than_zero: true validates :log_level, presence: true, inclusion: { in: %w(debug info warn error), } validates :language, presence: true, inclusion: { # TODO: find out why we break translations and available_locales if we use I18n.available_locales here. in: %w(de en), } validates :public_footer, :internal_footer, allow_blank: true, format: { with: /\A[[:graph:]\s]*\z/i, } default_scope { order(:email) } def self.configurable_attributes @configurable_attributes ||= begin all = self.validators.map(&:attributes).flatten.uniq.compact.sort all - [:email, :fingerprint] end end def logfile @logfile ||= File.join(Conf.listlogs_dir, self.email.split('@').reverse, 'list.log') end def logger @logger ||= Listlogger.new(self) end def to_s email end def admins subscriptions.where(admin: true) end def subscriptions_without_fingerprint subscriptions.without_fingerprint end def key(fingerprint=self.fingerprint) keys(fingerprint).first end def secret_key keys(self.fingerprint, true).first end def keys(identifier=nil, secret_only=nil) gpg.find_keys(identifier, secret_only) end # TODO: find better name for this method. It does more than the current # name suggests (filtering for capability). def distinct_key(identifier) keys = keys(identifier).select { |key| key.usable_for?(:encrypt) } if keys.size == 1 return keys.first else return nil end end def import_key(importable) gpg.keyimport(importable) end def import_key_and_find_fingerprint(key_material) return nil if key_material.blank? import_result = import_key(key_material) gpg.interpret_import_result(import_result) end def delete_key(fingerprint) if key = keys(fingerprint).first key.delete! true else false end end def export_key(fingerprint=self.fingerprint) key = keys(fingerprint).first if key.blank? return false end key.armored end def check_keys now = Time.now checkdate = now + (60 * 60 * 24 * 14) # two weeks unusable = [] expiring = [] keys.each do |key| expiry = key.subkeys.first.expires if expiry && expiry > now && expiry < checkdate # key expires in the near future expdays = ((expiry - now)/86400).to_i expiring << [key, expdays] end if ! key.usable? unusable << [key, key.usability_issue] end end text = '' expiring.each do |key,days| text << I18n.t('key_expires', { days: days, key_oneline: key.oneline }) text << "\n" end unusable.each do |key,usability_issue| text << I18n.t('key_unusable', { usability_issue: usability_issue, key_oneline: key.oneline }) text << "\n" end text end def refresh_keys gpg.refresh_keys(self.keys) end def fetch_keys(input) gpg.fetch_key(input) end def pin_keys updated_emails = subscriptions_without_fingerprint.collect do |subscription| key = distinct_key(subscription.email) if key subscription.update(fingerprint: key.fingerprint) "#{subscription.email}: #{key.fingerprint}" else nil end end updated_emails.compact.join("\n") end def self.by_recipient(recipient) listname = recipient.gsub(/-(sendkey|request|owner|bounce)@/, '@') where(email: listname).first end def sendkey_address @sendkey_address ||= email.gsub('@', '-sendkey@') end def request_address @request_address ||= email.gsub('@', '-request@') end def owner_address @owner_address ||= email.gsub('@', '-owner@') end def bounce_address @bounce_address ||= email.gsub('@', '-bounce@') end def gpg @gpg_ctx ||= begin # TODO: figure out why set it again... # Set GNUPGHOME when list is created. set_gnupg_home GPGME::Ctx.new armor: true end end # TODO: place this somewhere sensible. # Call cleanup when script finishes. #Signal.trap(0, proc { @list.cleanup }) def cleanup if @gpg_agent_pid Process.kill('TERM', @gpg_agent_pid.to_i) end rescue => e $stderr.puts "Failed to kill gpg-agent: #{e}" end def gpg_sign_options {sign: true, sign_as: self.fingerprint} end def fingerprint=(arg) if arg write_attribute(:fingerprint, arg.gsub(/\s*/, '').gsub(/^0x/, '').chomp.upcase) end end def self.listdir(listname) File.join( Conf.lists_dir, listname.split('@').reverse ) end def listdir @listdir ||= self.class.listdir(self.email) end # A convenience-method to simplify other code. def subscribe(email, fingerprint=nil, adminflag=nil, deliveryflag=nil, key_material=nil) messages = nil args = { list_id: self.id, email: email } if key_material.present? fingerprint, messages = import_key_and_find_fingerprint(key_material) end args[:fingerprint] = fingerprint # ActiveRecord does not treat nil as falsy for boolean columns, so we # have to avoid that in order to not receive an invalid object. The # database will use the column's default-value if no value is being # given. (I'd rather not duplicate the defaults here.) if ! adminflag.nil? args[:admin] = adminflag end if ! deliveryflag.nil? args[:delivery_enabled] = deliveryflag end subscription = Subscription.create(args) [subscription, messages] end def unsubscribe(email, delete_key=false) sub = subscriptions.where(email: email).first if sub.blank? false end if ! sub.destroy return sub end if delete_key sub.delete_key end end def keywords_admin_notify Array(read_attribute(:keywords_admin_notify)) end def keywords_admin_only Array(read_attribute(:keywords_admin_only)) end def admin_only?(keyword) keywords_admin_only.include?(keyword) end def from_admin?(mail) return false if ! mail.was_validly_signed? admins.find do |admin| admin.fingerprint == mail.signing_key.fingerprint end.presence || false end def set_attribute(attrib, value) self.send("#{attrib}=", value) end def send_list_key_to_subscriptions mail = Mail.new mail.from = self.email mail.subject = I18n.t('list_public_key_subject') mail.body = I18n.t('list_public_key_attached') mail.attach_list_key!(self) send_to_subscriptions(mail) true end def send_to_subscriptions(mail) logger.debug "Sending to subscriptions." mail.add_internal_footer! self.subscriptions.each do |subscription| begin if subscription.delivery_enabled subscription.send_mail(mail) else logger.info "Not sending to #{subscription.email}: delivery is disabled." end rescue => exc msg = I18n.t('errors.delivery_error', { email: subscription.email, error: exc.to_s }) logger.error msg logger.error exc end end end private def set_gnupg_home ENV['GNUPGHOME'] = listdir end def delete_listdirs if File.exists?(self.listdir) FileUtils.rm_rf(self.listdir, secure: true) Schleuder.logger.info "Deleted #{self.listdir}" end # If listlogs_dir is different from lists_dir, the logfile still exists # and needs to be deleted, too. logfile_dir = File.dirname(self.logfile) if File.exists?(logfile_dir) FileUtils.rm_rf(logfile_dir, secure: true) Schleuder.logger.info "Deleted #{logfile_dir}" end true rescue => exc # Don't use list-logger here — if the list-dir isn't present we can't log to it! Schleuder.logger.error "Error while deleting listdir: #{exc}" return false end end end schleuder-3.4.1/lib/schleuder/list_builder.rb000066400000000000000000000071441353765016400212470ustar00rootroot00000000000000module Schleuder class ListBuilder def initialize(list_attributes, adminemail=nil, adminfingerprint=nil, adminkey=nil) @list_attributes = list_attributes.with_indifferent_access @listname = list_attributes[:email] @fingerprint = list_attributes[:fingerprint] @adminemail = adminemail @adminfingerprint = adminfingerprint @adminkey = adminkey end def read_default_settings hash = YAML.load_file(ENV['SCHLEUDER_LIST_DEFAULTS']) if ! hash.kind_of?(Hash) raise Errors::LoadingListSettingsFailed.new end hash rescue Psych::SyntaxError raise Errors::LoadingListSettingsFailed.new end def run Schleuder.logger.info "Building new list" if @listname.blank? || ! @listname.match(Conf::EMAIL_REGEXP) return [nil, {'email' => ["'#{@listname}' is not a valid email address"]}] end settings = read_default_settings.merge(@list_attributes) list = List.new(settings) @list_dir = list.listdir create_or_test_dir(@list_dir) # In case listlogs_dir != lists_dir we have to create the basedir of the # list's log-file. create_or_test_dir(File.dirname(list.logfile)) if list.fingerprint.blank? list_key = gpg.keys("<#{list.email}>").first if list_key.nil? list_key = create_key(list) end list.fingerprint = list_key.fingerprint end if ! list.valid? return list end list.save! if @adminemail.blank? msg = nil else sub, msg = list.subscribe(@adminemail, @adminfingerprint, true, true, @adminkey) if sub.errors.present? raise Errors::ActiveModelError.new(sub.errors) end end [list, msg] end def gpg @gpg_ctx ||= begin ENV["GNUPGHOME"] = @list_dir GPGME::Ctx.new end end def create_key(list) Schleuder.logger.info "Generating key-pair, this could take a while..." gpg.generate_key(key_params(list)) # Get key without knowing the fingerprint yet. keys = list.keys(@listname) if keys.empty? raise Errors::KeyGenerationFailed.new(@list_dir, @listname) elsif keys.size > 1 raise Errors::TooManyKeys.new(@list_dir, @listname) else adduids(list, keys.first) end keys.first end def adduids(list, key) # Add UIDs for -owner and -request. [list.request_address, list.owner_address].each do |address| err = key.adduid(list.email, address) if err.present? raise err end end # Go through list.key() to re-fetch the key from the keyring, otherwise # we don't see the new UIDs. errors = list.key.set_primary_uid(list.email) if errors.present? raise errors end rescue => exc raise Errors::KeyAdduidFailed.new(exc.to_s) end def key_params(list) " Key-Type: RSA Key-Length: 4096 Key-Usage: sign Subkey-Type: RSA Subkey-Length: 4096 Subkey-Usage: encrypt Name-Real: #{list.email} Name-Email: #{list.email} Expire-Date: 0 %no-protection " end def create_or_test_dir(dir) if File.exists?(dir) if ! File.directory?(dir) raise Errors::ListdirProblem.new(dir, :not_a_directory) end if ! File.writable?(dir) raise Errors::ListdirProblem.new(dir, :not_writable) end else FileUtils.mkdir_p(dir) end end end end schleuder-3.4.1/lib/schleuder/listlogger.rb000066400000000000000000000017261353765016400207410ustar00rootroot00000000000000module Schleuder class Listlogger < ::Logger include LoggerNotifications def initialize(list) super(list.logfile, 'daily') @from = list.email @list = list @adminaddresses = list.admins.map { |sub| [sub.email, sub.key] } @level = ::Logger.const_get(list.log_level.upcase) remove_old_logfiles(list) end # Logger rotates but doesn't delete older files, so we're helping # ourselves. def remove_old_logfiles(list) logfiles_to_keep = list.logfiles_to_keep.to_i if logfiles_to_keep < 1 logfiles_to_keep = list.class.column_defaults['logfiles_to_keep'] end suffix_now = Time.now.strftime("%Y%m%d").to_i del_older_than = suffix_now - logfiles_to_keep Pathname.glob("#{list.logfile}.????????").each do |file| if file.basename.to_s.match(/\.([0-9]{8})$/) if del_older_than.to_i >= $1.to_i file.unlink end end end end end end schleuder-3.4.1/lib/schleuder/logger.rb000066400000000000000000000010411353765016400200330ustar00rootroot00000000000000module Schleuder def logger @logger ||= Logger.new end module_function :logger class Logger < Syslog::Logger include LoggerNotifications def initialize if RUBY_VERSION.to_f < 2.1 super('Schleuder') else super('Schleuder', Syslog::LOG_MAIL) end # We need some sender-address different from the superadmin-address. @from = "#{`whoami`.chomp}@#{`hostname`.chomp}" @adminaddresses = Conf.superadmin @level = ::Logger.const_get(Conf.log_level.upcase) end end end schleuder-3.4.1/lib/schleuder/logger_notifications.rb000066400000000000000000000040301353765016400227650ustar00rootroot00000000000000module Schleuder module LoggerNotifications def adminaddresses @adminaddresses.presence || superadmin end def superadmin Conf.superadmin.presence end def error(string) super(string) notify_admin(string) end def fatal(string, original_message=nil) super(string.to_s + append_original_message(original_message)) notify_admin(string, original_message) end def notify_superadmin(message:, original_message: nil, subject: 'Error') notify_admin(message, original_message, subject, superadmin) end def notify_admin(thing, original_message=nil, subject='Error', recipients=nil) # Minimize using other classes here, we don't know what caused the error. msg_parts = convert_to_msg_parts(thing, original_message) recipients ||= adminaddresses Array(adminaddresses).each do |address, key| mail = Mail.new mail.from = @from mail.to = address mail.subject = subject mail[:Errors_To] = superadmin mail.sender = superadmin msg_parts.each do |msg_part| mail.add_part(msg_part) end if @list.present? gpg_opts = @list.gpg_sign_options if key.present? && key.usable? gpg_opts.merge!(encrypt: true, keys: { address => key.fingerprint }) end mail.gpg gpg_opts end mail.deliver end true end private def convert_to_msg_parts(thing, original_message) msg_parts = Mail::Message.all_to_message_part(thing) if original_message.present? orig_part = Mail::Part.new orig_part.content_type = 'message/rfc822' orig_part.content_description = 'The originally incoming message' orig_part.body = original_message.to_s msg_parts << orig_part end msg_parts end def append_original_message(original_message) if original_message "\n\nOriginal message:\n\n#{original_message.to_s}" else '' end end end end schleuder-3.4.1/lib/schleuder/mail/000077500000000000000000000000001353765016400171555ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/mail/encrypted_part.rb000066400000000000000000000015241353765016400225270ustar00rootroot00000000000000module Mail module Gpg class EncryptedPart < Mail::Part alias_method :initialize_mailgpg, :initialize def initialize(cleartext_mail, options = {}) if cleartext_mail.protected_headers_subject cleartext_mail.content_type_parameters['protected-headers'] = 'v1' end initialize_mailgpg(cleartext_mail, options) end end end end schleuder-3.4.1/lib/schleuder/mail/gpg.rb000066400000000000000000000006151353765016400202610ustar00rootroot00000000000000module Mail module Gpg class << self alias_method :encrypt_mailgpg, :encrypt def encrypt(cleartext_mail, options={}) encrypted_mail = encrypt_mailgpg(cleartext_mail, options) if cleartext_mail.protected_headers_subject encrypted_mail.subject = cleartext_mail.protected_headers_subject end encrypted_mail end end end end schleuder-3.4.1/lib/schleuder/mail/message.rb000066400000000000000000000365651353765016400211450ustar00rootroot00000000000000module Mail # creates a Mail::Message likes schleuder def self.create_message_to_list(msg, recipient, list) mail = Mail.new(msg) mail.list = list mail.recipient = recipient # don't freeze here, as the mail might not be fully # parsed as body is lazy evaluated and might still # be changed later. mail.original_message = mail.dup #.freeze mail end # TODO: Test if subclassing breaks integration of mail-gpg. class Message attr_accessor :recipient attr_accessor :original_message attr_accessor :list attr_accessor :protected_headers_subject # TODO: This should be in initialize(), but I couldn't understand the # strange errors about wrong number of arguments when overriding # Message#initialize. def setup if self.encrypted? new = self.decrypt(verify: true) # Test if there's a signed multipart inside the ciphertext # ("encapsulated" format of pgp/mime). if encapsulated_signed?(new) new = new.verify end elsif self.signed? new = self.verify else new = self end new.list = self.list new.recipient = self.recipient new.gpg list.gpg_sign_options new.original_message = self.dup.freeze # Trigger method early to save the information. Later some information # might be gone (e.g. request-keywords that delete subscriptions or # keys). new.signer self.dynamic_pseudoheaders.each do |str| new.add_pseudoheader(str) end # Store previously protected subject for later access. # mail-gpg pulls headers from the decrypted mime parts "up" into the main # headers, which reveals protected subjects. if self.subject != new.subject new.protected_headers_subject = self.subject.dup end # Delete the protected headers which might leak information. if new.parts.first && new.parts.first.content_type == "text/rfc822-headers; protected-headers=v1" new.parts.shift end new end def clean_copy(with_pseudoheaders=false) clean = Mail.new clean.list = self.list clean.gpg self.list.gpg_sign_options clean.from = list.email clean.subject = self.subject clean.protected_headers_subject = self.protected_headers_subject clean.add_msgids(list, self) clean.add_list_headers(list) clean.add_openpgp_headers(list) if with_pseudoheaders new_part = Mail::Part.new new_part.body = self.pseudoheaders(list) clean.add_part new_part end if self.protected_headers_subject.present? new_part = Mail::Part.new new_part.content_type = "text/rfc822-headers; protected-headers=v1" new_part.body = "Subject: #{self.subject}\n" clean.add_part new_part end # Attach body or mime-parts in a new wrapper-part, to preserve the # original mime-structure. # We can't use self.to_s here — that includes all the headers we *don't* # want to copy. wrapper_part = Mail::Part.new # Copy headers to are relevant for the mime-structure. wrapper_part.content_type = self.content_type wrapper_part.content_transfer_encoding = self.content_transfer_encoding if self.content_transfer_encoding wrapper_part.content_disposition = self.content_disposition if self.content_disposition wrapper_part.content_description = self.content_description if self.content_description # Copy contents. if self.multipart? self.parts.each do |part| wrapper_part.add_part(part) end else # We copied the content-headers, so we need to copy the body encoded. # Otherwise the content might become unlegible. wrapper_part.body = self.body.encoded end clean.add_part(wrapper_part) clean end def prepend_part(part) self.add_part(part) self.parts.unshift(parts.delete_at(parts.size-1)) end def add_public_footer! # Add public_footer unless it's empty?. add_footer!(:public_footer) end def add_internal_footer! add_footer!(:internal_footer) end def was_encrypted? Mail::Gpg.encrypted?(original_message) end def signature case signatures.size when 0 if multipart? signature_multipart_inline else nil end when 1 signatures.first else raise "Multiple signatures found! Cannot handle!" end end def was_validly_signed? signature.present? && signature.valid? && signer.present? end def signer @signer ||= begin if signing_key.present? list.subscriptions.where(fingerprint: signing_key.fingerprint).first end end end # The fingerprint of the signature might be the one of a sub-key, but the # subscription-assigned fingerprints are (should be) the ones of the # primary keys, so we need to look up the key. def signing_key if signature.present? @signing_key ||= list.keys(signature.fpr).first end end def reply_to_signer(output) reply = self.reply self.class.all_to_message_part(output).each do |part| reply.add_part(part) end self.signer.send_mail(reply) end def self.all_to_message_part(input) Array(input).map do |thing| case thing when Mail::Part thing when String, StandardError Mail::Part.new do body thing.to_s end else raise "Don't know how to handle input: #{thing.inspect}" end end end def sendkey_request? @recipient.match(/-sendkey@/) end def to_owner? @recipient.match(/-owner@/) end def request? @recipient.match(/-request@/) end def automated_message? @recipient.match(/-bounce@/).present? || # Empty Return-Path self.return_path.to_s == '<>' || # Auto-Submitted exists and does not equal 'no' and no cron header # present, as cron emails have the auto-submitted header. ( self['Auto-Submitted'].present? && \ self['Auto-Submitted'].to_s.downcase != 'no' && \ !self['X-Cron-Env'].present?) end def keywords return @keywords if @keywords part = first_plaintext_part if part.blank? return [] end @keywords = [] look_for_keywords = true lines = part.decoded.lines.map do |line| # TODO: Find multiline arguments (add-key). Currently add-key has to # read the whole body and hope for the best. if look_for_keywords && (m = line.match(/^x-([^:\s]*)[:\s]*(.*)/i)) command = m[1].strip.downcase arguments = m[2].to_s.strip.downcase.split(/[,; ]{1,}/) @keywords << [command, arguments] nil else if look_for_keywords && line.match(/\S+/i) look_for_keywords = false end line end end # Work around problems with re-encoding the body. If we delete the # content-transfer-encoding prior to re-assigning the body, and let Mail # decide itself how to encode, it works. If we don't, some # character-sequences are not properly re-encoded. part.content_transfer_encoding = nil # Make the converted strings (now UTF-8) match what mime-part's headers say, # fall back to US-ASCII if none is set. # https://tools.ietf.org/html/rfc2046#section-4.1.2 # -> Default charset is US-ASCII part.body = lines.compact.join.encode(part.charset||'US-ASCII') @keywords end def add_subject_prefix! _add_subject_prefix(nil) end def add_subject_prefix_in! _add_subject_prefix(:in) end def add_subject_prefix_out! _add_subject_prefix(:out) end def add_pseudoheader(string_or_key, value=nil) @dynamic_pseudoheaders ||= [] if value.present? @dynamic_pseudoheaders << make_pseudoheader(string_or_key, value) else @dynamic_pseudoheaders << string_or_key.to_s end end def make_pseudoheader(key, value) "#{key.to_s.camelize}: #{value.to_s}" end def dynamic_pseudoheaders @dynamic_pseudoheaders || [] end def signature_state # Careful to add information about the incoming signature. GPGME # throws exceptions if it doesn't know the key. if self.signature.present? # Some versions of gpgme return nil if the key is unknown, so we check # for that manually and provide our own fallback. (Calling # `signature.key` results in an EOFError in that case.) if signing_key.present? signature_state = signature.to_s else signature_state = I18n.t("signature_states.unknown", fingerprint: self.signature.fingerprint) end else signature_state = I18n.t("signature_states.unsigned") end signature_state end def encryption_state if was_encrypted? encryption_state = I18n.t("encryption_states.encrypted") else encryption_state = I18n.t("encryption_states.unencrypted") end encryption_state end def standard_pseudoheaders(list) if @standard_pseudoheaders.present? return @standard_pseudoheaders else @standard_pseudoheaders = [] end Array(list.headers_to_meta).each do |field| value = case field.to_s when 'sig' then signature_state when 'enc' then encryption_state else self.header[field.to_s] end @standard_pseudoheaders << make_pseudoheader(field.to_s, value) end @standard_pseudoheaders end def pseudoheaders(list) (standard_pseudoheaders(list) + dynamic_pseudoheaders).flatten.join("\n") + "\n" end def add_msgids(list, orig) if list.keep_msgid # Don't use `orig['in-reply-to']` here, because that sometimes fails to # parse the original value and then returns it without the # angle-brackets. self.message_id = clutch_anglebrackets(orig.message_id) self.in_reply_to = clutch_anglebrackets(orig.in_reply_to) self.references = clutch_anglebrackets(orig.references) end end def add_list_headers(list) if list.include_list_headers self['List-Id'] = "<#{list.email.gsub('@', '.')}>" self['List-Owner'] = " (Use list's public key)" self['List-Help'] = '' postmsg = if list.receive_admin_only "NO (Admins only)" elsif list.receive_authenticated_only " (Subscribers only)" else "" end self['List-Post'] = postmsg end end def add_openpgp_headers(list) if list.include_openpgp_header if list.openpgp_header_preference == 'none' pref = '' else pref = "preference=#{list.openpgp_header_preference}" # TODO: simplify. pref << ' (' if list.receive_admin_only pref << 'Only encrypted and signed emails by list-admins are accepted' elsif ! list.receive_authenticated_only if list.receive_encrypted_only && list.receive_signed_only pref << 'Only encrypted and signed emails are accepted' elsif list.receive_encrypted_only && ! list.receive_signed_only pref << 'Only encrypted emails are accepted' elsif ! list.receive_encrypted_only && list.receive_signed_only pref << 'Only signed emails are accepted' else pref << 'All kind of emails are accepted' end elsif list.receive_authenticated_only if list.receive_encrypted_only pref << 'Only encrypted and signed emails by subscribers are accepted' else pref << 'Only signed emails by subscribers are accepted' end else pref << 'All kind of emails are accepted' end pref << ')' end fingerprint = list.fingerprint comment = "(Send an email to #{list.sendkey_address} to receive the public-key)" self['OpenPGP'] = "id=0x#{fingerprint} #{comment}; #{pref}" end end def empty? if self.multipart? if self.parts.empty? return true else # Test parts recursively. E.g. Thunderbird with activated # memoryhole-headers send nested parts that might still be empty. return parts.inject(true) { |result, part| result && part.empty? } end else return self.body.empty? end end def first_plaintext_part(part=nil) part ||= self if part.multipart? first_plaintext_part(part.parts.first) elsif part.mime_type == 'text/plain' part else nil end end def attach_list_key!(list) filename = "#{list.email}.asc" self.add_file({ filename: filename, content: list.export_key }) self.attachments[filename].content_type = 'application/pgp-keys' self.attachments[filename].content_description = 'OpenPGP public key' true end private # mail.signed? throws an error if it finds # pgp boundaries, so we must use the Mail::Gpg # methods. def encapsulated_signed?(mail) (mail.verify_result.nil? || mail.verify_result.signatures.empty?) && \ (Mail::Gpg.signed_mime?(mail) || Mail::Gpg.signed_inline?(mail)) end def add_footer!(footer_attribute) if self.list.blank? || self.list.send(footer_attribute).to_s.empty? return end footer_part = Mail::Part.new footer_part.body = self.list.send(footer_attribute).to_s if wrapped_single_text_part? self.parts.first.add_part footer_part else self.add_part footer_part end end def wrapped_single_text_part? parts.size == 1 && parts.first.mime_type == 'multipart/mixed' && parts.first.parts.size == 1 && parts.first.parts.first.mime_type == 'text/plain' end def _add_subject_prefix(suffix) attrib = "subject_prefix" if suffix attrib << "_#{suffix}" end if ! self.list.respond_to?(attrib) return false end string = self.list.send(attrib).to_s.strip if ! string.empty? prefix = "#{string} " # Only insert prefix if it's not present already. if self.subject.nil? self.subject = string elsif ! self.subject.include?(prefix) self.subject = "#{prefix}#{self.subject}" end end end # Looking for signatures in each part. They are not aggregated into the main part. # We only return the signature if all parts are validly signed by the same key. def signature_multipart_inline fingerprints = parts.map do |part| if part.signature_valid? part.signature.fpr else nil end end if fingerprints.uniq.size == 1 parts.first.signature else nil end end def clutch_anglebrackets(input) Array(input).map do |string| if string.first == '<' string else "<#{string}>" end end.join(' ') end end end schleuder-3.4.1/lib/schleuder/mail/parts_list.rb000066400000000000000000000004211353765016400216630ustar00rootroot00000000000000module Mail class PartsList # Disable sorting of mime-parts completely. # Can't be done during runtime on the body-instance (`body#set_sort_order`) # because MailGpg exchanges the body-instances when encrypting/signing. def sort!(*args) end end end schleuder-3.4.1/lib/schleuder/plugin_runners/000077500000000000000000000000001353765016400213055ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/plugin_runners/base.rb000066400000000000000000000051031353765016400225430ustar00rootroot00000000000000module Schleuder module PluginRunners module Base def run(list, mail) list.logger.debug "Starting #{self}" @list = list @mail = mail setup output = mail.keywords.map do |keyword, arguments| run_plugin(keyword, arguments) end output.flatten.compact end private def run_plugin(keyword, arguments) @list.logger.debug "Running keyword '#{keyword}'" error = check_admin_only(keyword) return error if error command = keyword.gsub('-', '_') if ['list_name', 'listname'].include? (command) return nil elsif ! @plugin_module.respond_to?(command) return I18n.t('plugins.unknown_keyword', keyword: keyword) else response = run_command(command, arguments) if @list.keywords_admin_notify.include?(keyword) notify_admins(keyword, arguments, response) end return response end rescue => exc # Log to system, this information is probably more useful for # system-admins than for list-admins. Schleuder.logger.error(exc.message_with_backtrace) I18n.t("plugins.plugin_failed", keyword: keyword) end def run_command(command, arguments) out = @plugin_module.send(command, arguments, @list, @mail) Array(out).flatten end def check_admin_only(keyword) if @list.admin_only?(keyword) && ! @list.from_admin?(@mail) @list.logger.debug "Error: Keyword is admin-only, sent by non-admin" Schleuder::Errors::KeywordAdminOnly.new(keyword).to_s else false end end def setup check_listname_keyword load_plugin_files @plugin_module = self.name.demodulize.gsub("Runner", "").constantize end def check_listname_keyword return nil if @mail.keywords.blank? listname_kw = @mail.keywords.assoc('list-name') || @mail.keywords.assoc('listname') if listname_kw.blank? @mail.reply_to_signer I18n.t(:missing_listname_keyword_error) exit else listname_args = listname_kw.last if ! [@list.email, @list.request_address].include?(listname_args.first) @mail.reply_to_signer I18n.t(:wrong_listname_keyword_error) exit end end end def load_plugin_files @list.logger.debug "Loading plugins" Dir["#{Schleuder::Conf.plugins_dir}/*.rb"].each do |file| require file end end end end end schleuder-3.4.1/lib/schleuder/plugin_runners/list_plugins_runner.rb000066400000000000000000000013611353765016400257400ustar00rootroot00000000000000module Schleuder module PluginRunners module ListPluginsRunner extend Base def self.notify_admins(keyword, arguments, response) if arguments.blank? msg = I18n.t('plugins.keyword_admin_notify_lists_without_arguments', signer: @mail.signer, keyword: keyword ) else msg = I18n.t('plugins.keyword_admin_notify_lists', signer: @mail.signer, keyword: keyword, arguments: arguments.join(' ') ) end @list.logger.notify_admin(msg, nil, 'Notice') end end end end schleuder-3.4.1/lib/schleuder/plugin_runners/request_plugins_runner.rb000066400000000000000000000015401353765016400264540ustar00rootroot00000000000000module Schleuder module PluginRunners module RequestPluginsRunner extend Base def self.notify_admins(keyword, arguments, response) if arguments.blank? explanation = I18n.t('plugins.keyword_admin_notify_request_without_arguments', signer: @mail.signer, keyword: keyword ) else explanation = I18n.t('plugins.keyword_admin_notify_request', signer: @mail.signer, keyword: keyword, arguments: arguments.join(' ') ) end response = response.join("\n\n") msg = "#{explanation}\n\n#{response}" @list.logger.notify_admin(msg, nil, 'Notice') end end end end schleuder-3.4.1/lib/schleuder/plugins/000077500000000000000000000000001353765016400177145ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/plugins/attach_listkey.rb000066400000000000000000000011521353765016400232500ustar00rootroot00000000000000module Schleuder module ListPlugins def self.attach_listkey(arguments, list, mail) filename = "#{list.fingerprint}.pgpkey" # "Mail" only really converts to multipart if the content-type is blank. mail.content_type = nil mail.add_file({ filename: filename, content: list.export_key }) mail.attachments[filename].content_type = 'application/pgp-keys' mail.attachments[filename].content_description = "OpenPGP public key of #{list.email}" mail.attachments[filename].content_disposition = "attachment; filename=#{filename}" nil end end end schleuder-3.4.1/lib/schleuder/plugins/get_version.rb000066400000000000000000000002041353765016400225610ustar00rootroot00000000000000module Schleuder module RequestPlugins def self.get_version(arguments, list, mail) Schleuder::VERSION end end end schleuder-3.4.1/lib/schleuder/plugins/key_management.rb000066400000000000000000000100641353765016400232260ustar00rootroot00000000000000module Schleuder module RequestPlugins def self.add_key(arguments, list, mail) if mail.has_attachments? results = self.import_keys_from_attachments(list, mail) else results = [self.import_key_from_body(list, mail)] end import_stati = results.compact.collect(&:imports).flatten if import_stati.blank? return I18n.t('plugins.key_management.no_imports') end out = [] import_stati.each do |import_status| if import_status.action == 'error' out << I18n.t("plugins.key_management.key_import_status.error", fingerprint: import_status.fingerprint) else key = list.gpg.find_distinct_key(import_status.fingerprint) if key out << I18n.t("plugins.key_management.key_import_status.#{import_status.action}", key_oneline: key.oneline) end end end out.join("\n\n") end def self.delete_key(arguments, list, mail) if arguments.blank? return I18n.t( "plugins.key_management.delete_key_requires_arguments" ) end arguments.map do |argument| keys = list.keys(argument) case keys.size when 0 I18n.t("errors.no_match_for", input: argument) when 1 begin keys.first.delete! I18n.t('plugins.key_management.deleted', key_string: keys.first.oneline) rescue GPGME::Error::Conflict I18n.t('plugins.key_management.not_deletable', key_string: keys.first.oneline) end else I18n.t('errors.too_many_matching_keys', { input: argument, key_strings: keys.map(&:to_s).join("\n") }) end end.join("\n\n") end def self.list_keys(arguments, list, mail) args = Array(arguments.presence || '') args.map do |argument| # In this case it shall be allowed to match keys by arbitrary # sub-strings, therefore we use `list.gpg` directly to not have the # input filtered. list.gpg.keys(argument).map do |key| key.to_s end end.join("\n\n") end def self.get_key(arguments, list, mail) arguments.map do |argument| keys = list.keys(argument) if keys.blank? I18n.t("errors.no_match_for", input: argument) else result = [I18n.t('plugins.key_management.matching_keys_intro', input: argument)] keys.each do |key| atchm = Mail::Part.new atchm.body = key.armored atchm.content_type = 'application/pgp-keys' atchm.content_disposition = "attachment; filename=#{key.fingerprint}.asc" result << atchm end result.flatten end end end def self.fetch_key(arguments, list, mail) if arguments.blank? return I18n.t( "plugins.key_management.fetch_key_requires_arguments" ) end arguments.map do |argument| list.fetch_keys(argument) end end # helper methods private def self.is_armored_key?(material) return false unless /^-----BEGIN PGP PUBLIC KEY BLOCK-----$/ =~ material return false unless /^-----END PGP PUBLIC KEY BLOCK-----$/ =~ material lines = material.split("\n").reject(&:empty?) # remove header lines.shift # remove tail lines.pop # verify the rest # TODO: verify length except for lasts lines? # headers according to https://tools.ietf.org/html/rfc4880#section-6.2 lines.map do |line| /\A((comment|version|messageid|hash|charset):.*|[0-9a-z\/=+]+)\Z/i =~ line end.all? end def self.import_keys_from_attachments(list, mail) mail.attachments.map do |attachment| material = attachment.body.to_s list.import_key(material) if self.is_armored_key?(material) end end def self.import_key_from_body(list, mail) key_material = mail.first_plaintext_part.body.to_s list.import_key(key_material) if self.is_armored_key?(key_material) end end end schleuder-3.4.1/lib/schleuder/plugins/list_management.rb000066400000000000000000000010121353765016400234020ustar00rootroot00000000000000module Schleuder module RequestPlugins def self.get_logfile(arguments, list, mail) if File.readable?(list.logfile) attachment = Mail::Part.new attachment.body = File.read(list.logfile) attachment.content_disposition = "inline; filename=#{list.email}.log" intro = I18n.t("plugins.list_management.logfile_attached", listname: list.email) [intro, attachment] else I18n.t("plugins.list_management.no_logfile", listname: list.email) end end end end schleuder-3.4.1/lib/schleuder/plugins/resend.rb000066400000000000000000000130471353765016400215260ustar00rootroot00000000000000module Schleuder module ListPlugins def self.resend(arguments, list, mail) resend_it(arguments, mail, false) end def self.resend_enc(arguments, list, mail) resend_encrypted_only(arguments, list, mail) end def self.resend_encrypted_only(arguments, list, mail) resend_it(arguments, mail, true) end def self.resend_cc(arguments, list, mail) resend_it_cc(arguments, mail, false) end def self.resend_cc_enc(arguments, list, mail) resend_cc_encrypted_only(arguments, list, mail) end def self.resend_cc_encrypted_only(arguments, list, mail) resend_it_cc(arguments, mail, true) end def self.resend_unencrypted(arguments, list, mail) do_resend_unencrypted(arguments, list, mail, :to) end def self.resend_cc_unencrypted(arguments, list, mail) do_resend_unencrypted(arguments, list, mail, :cc) end # helper methods private def self.do_resend_unencrypted(arguments, list, mail, target) if ! resend_recipients_valid?(mail, arguments) return false end recip_map = Hash[Array(arguments).map{|email| [email,''] }] if do_resend(mail, recip_map, target, false) mail.add_subject_prefix_out! end end def self.resend_it_cc(arguments, mail, encrypted_only) if ! resend_recipients_valid?(mail, arguments) return false end recip_map = map_with_keys(mail, arguments, encrypted_only) # Only continue if all recipients are still here. if recip_map.size < arguments.size return end if do_resend(mail, recip_map, :cc, encrypted_only) mail.add_subject_prefix_out! end end def self.resend_it(arguments, mail, encrypted_only) if ! resend_recipients_valid?(mail, arguments) return false end recip_map = map_with_keys(mail, arguments, encrypted_only) resent_stati = recip_map.map do |email, key| do_resend(mail, {email => key}, :to, encrypted_only) end if resent_stati.include?(true) # At least one message has been resent mail.add_subject_prefix_out! end end def self.do_resend(mail, recipients_map, to_or_cc, encrypted_only) if recipients_map.empty? return end gpg_opts = make_gpg_opts(mail, recipients_map, encrypted_only) if gpg_opts == false return false end # Compose and send email new = mail.clean_copy new[to_or_cc] = recipients_map.keys new.add_public_footer! new.sender = mail.list.bounce_address # `dup` gpg_opts because `deliver` changes their value and we need them # below to determine encryption! new.gpg gpg_opts.dup if new.deliver add_resent_headers(mail, recipients_map, to_or_cc, gpg_opts[:encrypt]) return true else add_error_header(mail, recipients_map) return false end rescue Net::SMTPFatalError => exc add_error_header(mail, recipients_map) logger.error "Error while sending: #{exc}" return false end def self.map_with_keys(mail, recipients, encrypted_only) Array(recipients).inject({}) do |hash, email| keys = mail.list.keys(email) # Exclude unusable keys. keys.select! { |key| key.usable_for?(:encrypt) } case keys.size when 1 hash[email] = keys.first when 0 if encrypted_only # Don't add the email to the result to exclude it from the # recipients. add_keys_error(mail, email, keys.size) else hash[email] = '' end else # Always report this situation, regardless of sending or not. It's # bad and should be fixed. add_keys_error(mail, email, keys.size) if ! encrypted_only hash[email] = '' end end hash end end def self.make_gpg_opts(mail, recipients_map, encrypted_only) gpg_opts = mail.list.gpg_sign_options # Do all recipients have a key? if recipients_map.values.map(&:class).uniq == [GPGME::Key] gpg_opts.merge!(encrypt: true) elsif encrypted_only false end gpg_opts end def self.add_keys_error(mail, email, keys_size) mail.add_pseudoheader(:error, I18n.t("plugins.resend.not_resent_no_keys", email: email, num_keys: keys_size)) end def self.add_error_header(mail, recipients_map) mail.add_pseudoheader(:error, "Resending to #{recipients_map.keys.join(', ')} failed, please check the logs!") end def self.add_resent_headers(mail, recipients_map, to_or_cc, sent_encrypted) if sent_encrypted prefix = I18n.t('plugins.resend.encrypted_to') str = recipients_map.map do |email, key| "#{email} (#{key.fingerprint})" end.join(', ') else prefix = I18n.t('plugins.resend.unencrypted_to') str = recipients_map.keys.join(', ') end headername = resent_header_name(to_or_cc) mail.add_pseudoheader(headername, "#{prefix} #{str}") end def self.resent_header_name(to_or_cc) if to_or_cc.to_s == 'to' 'resent' else 'resent_cc' end end def self.resend_recipients_valid?(mail, recipients) all_valid = true Array(recipients).each do |address| if ! address.match(Conf::EMAIL_REGEXP) mail.add_pseudoheader(:error, I18n.t("plugins.resend.invalid_recipient", address: address)) all_valid = false end end all_valid end end end schleuder-3.4.1/lib/schleuder/plugins/sign_this.rb000066400000000000000000000026171353765016400222360ustar00rootroot00000000000000module Schleuder module RequestPlugins def self.sign_this(arguments, list, mail) if mail.has_attachments? list.logger.debug "Signing each attachment's body" intro = I18n.t('plugins.signatures_attached') parts = mail.attachments.map do |attachment| make_signature_part(attachment, list) end [intro, parts].flatten else list.logger.debug "Clear-signing first available text/plain part" clearsign(mail.first_plaintext_part) end end # helper methods private def self.make_signature_part(attachment, list) material = attachment.body.to_s return nil if material.strip.blank? file_basename = attachment.filename.presence || Digest::SHA256.hexdigest(material) list.logger.debug "Signing #{file_basename}" filename = "#{file_basename}.sig" part = Mail::Part.new part.body = detachsign(material) part.content_type = 'application/pgp-signature' part.content_disposition = "attachment; filename=#{filename}" part.content_description = "OpenPGP signature for '#{file_basename}'" part end def self.detachsign(thing) crypto.sign(thing, mode: GPGME::SIG_MODE_DETACH).to_s end def self.clearsign(mail) crypto.clearsign(mail.body.to_s).to_s end def self.crypto @crypto ||= GPGME::Crypto.new(armor: true) end end end schleuder-3.4.1/lib/schleuder/plugins/subscription_management.rb000066400000000000000000000134411353765016400251640ustar00rootroot00000000000000module Schleuder module RequestPlugins def self.subscribe(arguments, list, mail) if arguments.blank? return I18n.t( "plugins.subscription_management.subscribe_requires_arguments" ) end email = arguments.shift if arguments.present? # Collect all arguments that look like fingerprint-material fingerprint = '' while arguments.first.present? && arguments.first.match(/^(0x)?[a-f0-9]+$/i) fingerprint << arguments.shift end # Use possibly remaining args as flags. adminflag = arguments.shift deliveryflag = arguments.shift end sub, _ = list.subscribe(email, fingerprint, adminflag, deliveryflag) if sub.persisted? I18n.t( "plugins.subscription_management.subscribed", email: sub.email, fingerprint: sub.fingerprint, admin: sub.admin, delivery_enabled: sub.delivery_enabled ) else I18n.t( "plugins.subscription_management.subscribing_failed", email: sub.email, errors: sub.errors.full_messages.join(".\n") ) end end def self.unsubscribe(arguments, list, mail) # If no address was given we unsubscribe the sender. email = arguments.first.presence || mail.signer.email # Refuse to unsubscribe the last admin. if list.admins.size == 1 && list.admins.first.email == email return I18n.t( "plugins.subscription_management.cannot_unsubscribe_last_admin", email: email ) end # TODO: May signers have multiple UIDs? We don't match those currently. if ! list.from_admin?(mail) && email != mail.signer.email # Only admins may unsubscribe others. return I18n.t( "plugins.subscription_management.forbidden", email: email ) end sub = list.subscriptions.where(email: email).first if sub.blank? return I18n.t( "plugins.subscription_management.is_not_subscribed", email: email ) end if res = sub.delete I18n.t( "plugins.subscription_management.unsubscribed", email: email ) else I18n.t( "plugins.subscription_management.unsubscribing_failed", email: email, error: res.errors.to_a ) end end def self.list_subscriptions(arguments, list, mail) subs = if arguments.blank? list.subscriptions.all.to_a else arguments.map do |argument| list.subscriptions.where("email like ?", "%#{argument}%").to_a end.flatten end if subs.blank? return nil end out = [ I18n.t("plugins.subscription_management.list_of_subscriptions") ] out << subs.map do |subscription| # Fingerprints are at most 40 characters long, and lines shouldn't # exceed 80 characters if possible. s = subscription.email if subscription.fingerprint.present? s << "\t0x#{subscription.fingerprint}" end if ! subscription.delivery_enabled? s << "\tDelivery disabled!" end s end out.join("\n") end def self.set_fingerprint(arguments, list, mail) if arguments.blank? return I18n.t( "plugins.subscription_management.set_fingerprint_requires_arguments" ) end if arguments.first.match(/@/) if arguments.first == mail.signer.email || list.from_admin?(mail) email = arguments.shift else return I18n.t( "plugins.subscription_management.set_fingerprint_only_self" ) end else email = mail.signer.email end sub = list.subscriptions.where(email: email).first if sub.blank? return I18n.t( "plugins.subscription_management.is_not_subscribed", email: email ) end fingerprint = arguments.join unless GPGME::Key.valid_fingerprint?(fingerprint) return I18n.t( "plugins.subscription_management.set_fingerprint_requires_valid_fingerprint", fingerprint: fingerprint ) end sub.fingerprint = fingerprint if sub.save I18n.t( "plugins.subscription_management.fingerprint_set", email: email, fingerprint: sub.fingerprint ) else I18n.t( "plugins.subscription_management.setting_fingerprint_failed", email: email, fingerprint: sub.fingerprint, errors: sub.errors.to_a.join("\n") ) end end def self.unset_fingerprint(arguments, list, mail) if arguments.blank? return I18n.t( "plugins.subscription_management.unset_fingerprint_requires_arguments" ) end email = arguments.first unless email == mail.signer.email || list.from_admin?(mail) return I18n.t( "plugins.subscription_management.unset_fingerprint_only_self" ) end if email == mail.signer.email && list.from_admin?(mail) && arguments.last != 'force' return I18n.t( "plugins.subscription_management.unset_fingerprint_requires_arguments" ) end sub = list.subscriptions.where(email: email).first if sub.blank? return I18n.t( "plugins.subscription_management.is_not_subscribed", email: email ) end sub.fingerprint = '' if sub.save I18n.t( "plugins.subscription_management.fingerprint_unset", email: email ) else I18n.t( "plugins.subscription_management.unsetting_fingerprint_failed", email: email, errors: sub.errors.to_a.join("\n") ) end end end end schleuder-3.4.1/lib/schleuder/runner.rb000066400000000000000000000103451353765016400200740ustar00rootroot00000000000000module Schleuder class Runner def run(msg, recipient) error = setup_list(recipient) return error if error logger.info "Parsing incoming email." @mail = Mail.create_message_to_list(msg, recipient, list) error = run_filters('pre') return error if error begin # This decrypts, verifies, etc. @mail = @mail.setup rescue GPGME::Error::DecryptFailed logger.warn "Decryption of incoming message failed." return Errors::DecryptionFailed.new(list) end error = run_filters('post') return error if error if ! @mail.was_validly_signed? logger.debug "Message was not validly signed, adding subject_prefix_in" @mail.add_subject_prefix_in! end if ! @mail.was_encrypted? logger.debug "Message was not encrypted, skipping plugins" elsif @mail.was_validly_signed? # Plugins logger.debug "Message was encrypted and validly signed" PluginRunners::ListPluginsRunner.run(list, @mail).compact end # Don't send empty messages over the list. if @mail.empty? logger.info "Message found empty, not sending it to list." return Errors::MessageEmpty.new(@list) end logger.debug "Adding subject_prefix" @mail.add_subject_prefix! # Subscriptions logger.debug "Creating clean copy of message" copy = @mail.clean_copy(list.headers_to_meta.any?) list.send_to_subscriptions(copy) nil end private def list @list end def run_filters(filter_type) error = filters_runner(filter_type).run(@mail) if error if list.bounces_notify_admins? text = "#{I18n.t('.bounces_notify_admins')}\n\n#{error}" # TODO: raw_source is mostly blank? logger.notify_admin text, @mail.original_message, I18n.t('notice') end return error end end def filters_runner(filter_type) if filter_type == 'pre' filters_runner_pre_decryption else filters_runner_post_decryption end end def filters_runner_pre_decryption @filters_runner_pre_decryption ||= Filters::Runner.new(list,'pre') end def filters_runner_post_decryption @filters_runner_post_decryption ||= Filters::Runner.new(list,'post') end def logger list.present? && list.logger || Schleuder.logger end def log_and_return(error, reveal_error=false) Schleuder.logger.error(error) if reveal_error error else # Return an unrevealing error, the sender and all bystanders don't need to know these details. Errors::FatalError.new end end def setup_list(recipient) return @list if @list logger.info "Loading list '#{recipient}'" if ! @list = List.by_recipient(recipient) return log_and_return(Errors::ListNotFound.new(recipient), true) end # Check necessary permissions of crucial files. if ! File.exist?(@list.listdir) return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_existing)) elsif ! File.directory?(@list.listdir) return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_a_directory)) elsif ! File.readable?(@list.listdir) return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_readable)) elsif ! File.writable?(@list.listdir) return log_and_return(Errors::ListdirProblem.new(@list.listdir, :not_writable)) else if File.exist?(@list.logfile) && ! File.writable?(@list.logfile) return log_and_return(Errors::ListdirProblem.new(@list.logfile, :not_writable)) end end # Check basic sanity of list. %w[fingerprint key secret_key admins].each do |attrib| if @list.send(attrib).blank? return log_and_return(Errors::ListPropertyMissing.new(@list.listdir, attrib)) end end # Set locale if I18n.available_locales.include?(@list.language.to_sym) I18n.locale = @list.language.to_sym end # This cannot be put in List, as Mail wouldn't know it then. logger.debug "Setting GNUPGHOME to #{@list.listdir}" ENV['GNUPGHOME'] = @list.listdir nil end end end schleuder-3.4.1/lib/schleuder/subscription.rb000066400000000000000000000056621353765016400213150ustar00rootroot00000000000000module Schleuder class Subscription < ActiveRecord::Base belongs_to :list validates :list_id, inclusion: { in: -> (id) { List.pluck(:id) }, message: "must refer to an existing list" } validates :email, presence: true, email: true, uniqueness: {scope: :list_id} validates :fingerprint, allow_blank: true, fingerprint: true validates :delivery_enabled, :admin, boolean: true default_scope { order(:email) } scope :without_fingerprint, -> { where(fingerprint: [nil,'']) } def to_s email end def self.configurable_attributes [:fingerprint, :admin, :delivery_enabled] end def fingerprint=(arg) # Always assign the given value, because it must be possible to overwrite # the previous fingerprint with an empty value. That value should better # be nil instead of a blank string, but currently schleuder-cli (v0.1.0) expects # only strings. write_attribute(:fingerprint, arg.to_s.gsub(/\s*/, '').gsub(/^0x/, '').chomp.upcase) end def key # TODO: make key-related methods a concern, so we don't have to go # through the list and neither re-implement the methods here. # Prefix '0x' to force GnuPG to match only hex-values, not UIDs. list.keys("0x#{self.fingerprint}").first end def send_mail(mail) list.logger.debug "Preparing sending to #{self.inspect}" mail = ensure_headers(mail) gpg_opts = self.list.gpg_sign_options if self.key.blank? if self.list.send_encrypted_only? notify_of_missed_message(:absent) return false else list.logger.warn "Sending plaintext because no key is present!" end elsif ! self.key.usable? if self.list.send_encrypted_only? notify_of_missed_message(key.usability_issue) return false else list.logger.warn "Sending plaintext because assigned key is #{key.usability_issue}!" end else gpg_opts.merge!(encrypt: true, keys: {self.email => "0x#{self.fingerprint}"}) end list.logger.info "Sending message to #{self.email}" mail.gpg gpg_opts mail.deliver end def ensure_headers(mail) mail.to = self.email mail.from = self.list.email mail.sender = self.list.bounce_address mail end def notify_of_missed_message(reason) self.list.logger.warn "Not sending to #{self.email}: key is unusable because it is #{reason} and sending plain text not allowed" mail = ensure_headers(Mail.new) mail.subject = I18n.t('notice') mail.body = I18n.t("missed_message_due_to_unusable_key", list_email: self.list.email) + I18n.t('errors.signoff') mail.gpg self.list.gpg_sign_options mail.deliver end def admin? self.admin == true end def delete_key list.delete_key(self.fingerprint) end end end schleuder-3.4.1/lib/schleuder/validators/000077500000000000000000000000001353765016400204035ustar00rootroot00000000000000schleuder-3.4.1/lib/schleuder/validators/boolean_validator.rb000066400000000000000000000003401353765016400244110ustar00rootroot00000000000000class BooleanValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if ! [true, false].include?(value) record.errors.add(attribute, I18n.t("errors.must_be_boolean")) end end end schleuder-3.4.1/lib/schleuder/validators/email_validator.rb000066400000000000000000000003621353765016400240650ustar00rootroot00000000000000class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value =~ Conf::EMAIL_REGEXP record.errors[attribute] << (options[:message] || I18n.t("errors.invalid_email")) end end end schleuder-3.4.1/lib/schleuder/validators/fingerprint_validator.rb000066400000000000000000000004071353765016400253250ustar00rootroot00000000000000class FingerprintValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless GPGME::Key.valid_fingerprint?(value) record.errors[attribute] << (options[:message] || I18n.t("errors.invalid_fingerprint")) end end end schleuder-3.4.1/lib/schleuder/validators/greater_than_zero_validator.rb000066400000000000000000000003431353765016400264770ustar00rootroot00000000000000class GreaterThanZeroValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.to_i == 0 record.errors.add(attribute, I18n.t("errors.must_be_greater_than_zero")) end end end schleuder-3.4.1/lib/schleuder/validators/no_line_breaks_validator.rb000066400000000000000000000003361353765016400257510ustar00rootroot00000000000000class NoLineBreaksValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.to_s.include?("\n") record.errors.add(attribute, I18n.t("errors.no_linebreaks") ) end end end schleuder-3.4.1/lib/schleuder/version.rb000066400000000000000000000000511353765016400202410ustar00rootroot00000000000000module Schleuder VERSION = '3.4.1' end schleuder-3.4.1/locales/000077500000000000000000000000001353765016400151315ustar00rootroot00000000000000schleuder-3.4.1/locales/de.yml000066400000000000000000000363421353765016400162540ustar00rootroot00000000000000de: errors: attributes: language: inclusion: "muss einem der folgenden Werte entsprechen: en, de" log_level: inclusion: "muss einem der folgenden Werte entsprechen: debug, info, warn, error" openpgp_header_preference: inclusion: "muss einem der folgenden Werte entsprechen: sign, encrypt, signencrypt, unprotected, none" internal_footer: invalid: "enthält nicht druckbare Zeichen" public_footer: invalid: "enthält nicht druckbare Zeichen" invalid_email: "ist keine valide E-Mail-Adresse" invalid_fingerprint: "ist kein valider OpenPGP-Fingerabdruck" list_fingerprint_missing: "Fingerabdruck der Liste ist nicht gesetzt, kann nicht arbeiten! (In `%{listdir}`.)" list_key_missing: "Schlüssel der Liste nicht im Schlüsselring gefunden, kann nicht arbeiten! (In `%{listdir}`.)" list_secret_key_missing: "Geheimer Schlüssel der Liste nicht im Schlüsselring gefunden, kann nicht arbeiten! (In `%{listdir}`.)" list_admins_missing: "List hat keine Admins konfiguriert, kann nicht arbeiten! (In `%{listdir}`.)" fatalerror: | Es ist ein schwerwiegender Fehler aufgetreten. Administratoren wurden benachrichtigt. Bitte versuche es später noch ein Mal. signoff: | Freundliche Grüße, Dein Schleuder-System. decryption_failed: | Deine Email konnnte nicht entschlüsselt werden. Emails an diese Adresse müssen mit diesem Schlüssel verschlüsselt werden: %{key} Um den Schlüssel zugesandt zu bekommen sende eine Email an <%{sendkey_email}>. message_unsigned: Emails an diese Adresse müssen mit einem OpenPGP-Schlüssel signiert sein. message_signature_unknown: | Emails an diese Adresse müssen mit dem OpenPGP-Schlüssel signiert sein, der für dein Abo eingetragen ist. Wenn du nicht weisst, welcher Schlüssel das ist, frage die Administrator/innen. Die erreichst du per Email an <%{owner_email}>. (Vorzugsweise verschlüssele die Email mit dem Schlüssel dieser Adresse: %{list_fingerprint}). message_unencrypted: Emails an diese Adresse müssen OpenPGP-konform verschlüsselt sein. message_unauthenticated: Emails an diese Adresse müssen verschlüsselt und mit einem OpenPGP-Schlüssel signiert sein, der für ein Abo eingetragen ist. message_sender_not_subscribed: Nur Absender mit Abo dürfen Emails an diese Adresse schicken. message_not_from_admin: Nur Admins dürfen Emails an diese Adresse schicken. message_empty: | Deine Email enthielt keinen Text, daher wurde sie nicht über die Liste verteilt. Falls du ausschließlich Schlüsselwörter gesendet hast beachte, dass administrative Schlüsselwörter an die "request"-Adresse (<%{request_address}>) geschickt werden müssen um berücksichtigt zu werden. list_not_found: "Fehler: Keine Liste zu dieser Adresse gefunden: '%{email}'." no_linebreaks: "Darf keine Zeilenumbrüche enthalten" invalid_characters: "enthält ungültige Zeichen" listdir_problem: message: "Problem mit dem Listen-Verzeichnis: '%{dir}' %{problem}." not_existing: existiert nicht not_a_directory: ist kein Verzeichnis not_empty: ist nicht leer not_writable: ist nicht beschreibbar not_readable: ist nicht lesbar keyword_admin_only: Das Schlüsselwort '%{keyword}' darf nur von Listen-Admins verwendet werden. key_generation_failed: Das Erzeugen des OpenPGP-Schlüsselpaares für %{listname} ist aus unbekannten Gründen fehlgeschlagen. Bitte prüfe das Listen-Verzeichnis ('%{listdir}') und die Log-Dateien. key_adduid_failed: "Das Hinzufügen einer User-ID zum OpenPGP-Schlüssel ist mit folgender Meldung fehlgeschlagen:\n%{errmsg}" too_many_keys: "Fehler: In %{listdir} existieren mehrere OpenPGP-Schlüssel für %{listname}. Bitte lösche alle bis auf einen." loading_list_settings_failed: "%{config_file} konnte nicht eingelesen werden, bitte Formatierung auf gültiges YAML prüfen." message_too_big: "Deine Email war zu groß. Erlaubt sind für diese Liste %{allowed_size}KB." must_be_boolean: "muss true oder false sein" must_be_greater_than_zero: "muss größer als null sein" not_pgp_mime: "Deine Email war nicht im pgp/mime-Format verschlüsselt." delivery_error: "Beim Versenden einer Email an %{email} ist der folgende Fehler aufgetreten: %{error}" no_match_for: "Keine Treffer für %{input}" too_many_matching_keys: | Zu viele Schlüssel gefunden für '%{input}': %{key_strings} plugins: unknown_keyword: Unbekanntes Schlüsselwort '%{keyword}'. plugin_failed: Das Schlüsselwort '%{keyword}' verursachte einen unbekannten Fehler. Die System-Administratoren wurden benachrichtigt. keyword_admin_notify_request: "%{signer} schickte das Schlüsselwort '%{keyword}' mit den Werten '%{arguments}' und erhielt dies als Antwort:" keyword_admin_notify_request_without_arguments: "%{signer} schickte das Schlüsselwort '%{keyword}' und erhielt dies als Antwort:" keyword_admin_notify_lists: "%{signer} benutzte das Schlüsselwort '%{keyword}' mit den Werten '%{arguments}' in einer Email an die Liste." keyword_admin_notify_lists_without_arguments: "%{signer} benutzte das Schlüsselwort '%{keyword}' in einer Email an die Liste." key_management: deleted: "Gelöscht: %{key_string}" not_deletable: "Darf nicht gelöscht werden: %{key_string}" no_imports: In deiner Email konnten keine Schlüssel gefunden werden. :( key_import_status: imported: | Dieser Schlüssel wurde neu hinzugefügt: %{key_oneline} updated: | Dieser Schlüssel wurde aktualisiert: %{key_oneline} unchanged: | Dieser Schlüssel wurde nicht verändert: %{key_oneline} error: | Der Schlüssel mit diesem Fingerabdruck konnte aus unbekanntem Grund nicht hinzugefügt werden: %{fingerprint} matching_keys_intro: Alle Schlüssel aus dem Schlüsselring der Liste, die '%{input}' enthalten, sind an diese Email angehängt. delete_key_requires_arguments: | Fehler: Du hast zu dem Schlüsselwort 'DELETE-KEY' keinen Wert angegeben. Ein Wert ist nötig, weitere sind optional. Bspw.: X-DELETE-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Oder, um mehrere Schlüssel auf einmal zu löschen: X-DELETE-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 a-subscription@hostname Die Schlüssel werden nur gelöscht, wenn sie der einzige Treffer für den jeweiligen Wert sind. fetch_key_requires_arguments: | Fehler: Du hast zu dem Schlüsselwort 'FETCH-KEY' keinen Wert angegeben. Ein Wert ist nötig, weitere sind optional. Jeder Wert kann eine URL sein, die via HTTP geladen wird, oder ein Text, mit dem auf den OpenPGP-Schlüsselserver gesucht wird. Bspw., um einen Schlüssel per fingerprint vom Schlüsselserver zu holen: X-FETCH-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Oder, um zwei Schlüssel per Email-Adresse vom Schlüsselserver zu holen: X-FETCH-KEY: a-subscription@hostname anotherone@example.org Oder, um einen Schlüssel per HTTP von einem Server zu laden: X-FETCH-KEY: https://example.org/keys/mykey.asc resend: not_resent_no_keys: Resending an <%{email}> fehlgeschlagen (%{num_keys} Schlüssel gefunden und unverschlüsseltes Senden verboten). encrypted_to: Verschlüsselt an unencrypted_to: Unverschlüsselt an invalid_recipient: "Ungültige Emailadresse für resend: %{address}" subscription_management: forbidden: "Fehler: Du bist nicht berechtigt, das Abo für %{email} zu löschen." is_not_subscribed: Kein Abo für %{email} gefunden. unsubscribed: Abo für %{email} wurde gelöscht. unsubscribing_failed: | Abo für %{email} nicht gelöscht: %{errors} cannot_unsubscribe_last_admin: | %{email} ist das einzige admin-Abo für diese Liste, daher kann es nicht gelöscht werden. subscribed: | Abo für %{email} mit diesen Werten eingetragen: Fingerabdruck: %{fingerprint} Admin? %{admin} Email-Zustellung aktiv? %{delivery_enabled} subscribing_failed: | Abo für %{email} nicht eingetragen: %{errors}. list_of_subscriptions: "Abos:\n" set_fingerprint_only_self: Nur admins dürfen den Fingerabdruck für andere Abos festlegen. fingerprint_set: Fingerabdruck für %{email} auf %{fingerprint} gesetzt. setting_fingerprint_failed: | Fingerabdruck für %{email} konnte nicht auf %{fingerprint} gesetzt werden: %{errors}. set_fingerprint_requires_valid_fingerprint: | Du hast zu dem Schlüsselwort 'SET-FINGERPRINT' keinen gültigen Wert angegeben. Es wurde der folgende Wert erkannt: %{fingerprint} Benötigt werden ein oder zwei Werte, bspw.: X-SET-FINGERPRINT: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 oder (als admin): X-SET-FINGERPRINT: subscription2@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Wobei der Fingerprint in der gesamten Länge (40 Zeichen) angegeben werden muss. Optional mit 0x als Präfix. Um einen Fingerprint zu entfernen kannst du das Schlüsselwort 'UNSET-FINGERPRINT' verwenden. set_fingerprint_requires_arguments: | Du hast zu dem Schlüsselwort 'SET-FINGERPRINT' keinen Wert angegeben. Benötigt werden ein oder zwei Werte, bspw.: X-SET-FINGERPRINT: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 oder (als admin): X-SET-FINGERPRINT: subscription2@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Um einen Fingerprint zu entfernen kannst du das Schlüsselwort 'UNSET-FINGERPRINT' verwenden. unset_fingerprint_only_self: Nur admins dürfen den Fingerabdruck für andere Abos festlegen. fingerprint_unset: Fingerabdruck für %{email} wurde entfernt. unsetting_fingerprint_failed: | Fingerabdruck für %{email} konnte nicht entfernt werden: %{errors}. unset_fingerprint_requires_arguments: | Du hast zu dem Schlüsselwort 'UNSET-FINGERPRINT' keinen Wert angegeben. Benötigt werden ein Wert, bspw.: X-UNSET-FINGERPRINT: subscription2@hostname Als admin musst du um deinen eigenen Fingerabdruck zu entfernen, noch zusätzlich das Argument force mitgeben. bspw.: X-UNSET-FINGERPRINT: adminsubscription2@hostname force subscribe_requires_arguments: | Fehler: Du hast zu dem Schlüsselwort 'SUBSCRIBE' keinen Wert angegeben. Mindestens ein Wert ist nötig, drei weitere sind optional. Bspw.: X-SUBSCRIBE: new-subscription@hostname Oder, um den Schlüssel für das neue Abo zuzuweisen: X-SUBSCRIBE: new-subscription@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Oder, um den Schlüssel zuzuweisen, und das Abo zum admin zu machen: X-SUBSCRIBE: new-subscription@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 true Oder, um den Schlüssel zuzuweisen, das Abo zum admin zu machen, und die Zustellung von Listen-Emails für dieses Abo abzuschalten: X-SUBSCRIBE: new-subscription@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 true false Wenn du die optionalen Werte weglässt hat das Abo keinen Schlüssel zugewiesen, ist nicht admin, und hat die Zustellung von Listen-Email aktiviert. signatures_attached: Die Signaturen hängen an. list_management: no_logfile: Keine Log-Datei für %{listname} gefunden. (Das kann an einem hohem Log-Level und der Abwesenheit von Fehlern liegen.) logfile_attached: Die Log-Datei für %{listname} hängt an. list_public_key_subject: Schlüssel dieser Adresse list_public_key_attached: Der Schlüssel zu dieser Adresse hängt an. no_output_result: Deine Email ergab keinen Ausgabe-Text. owner_forward_prefix: Die folgende Email ging für die Listen-Besitzer/innen ein. no_keywords_error: Deine Email enthielt keine Schlüsselwörter, daher gab es nichts zu tun. missing_listname_keyword_error: Deine Email enthielt nicht das notwendige Schlüsselwort "X-LIST-NAME", daher wurde sie zurückgewiesen. wrong_listname_keyword_error: Deine Email enthielt ein falsches "X-LIST-NAME"-Schlüsselwort. Der Wert dieses Schlüsselworts muss der Emailadresse dieser Liste gleichen. bounces_drop_all: Die angehängte Email hätte zurückgewiesen (bounced) werden sollen, wurde aber stillschweigend fallen gelassen, weil die Konfiguration dieser Liste definiert, dass für diese Liste nie Email zurückgewiesen werden soll. bounces_drop_on_headers: "Die angehängte Email hätte zurückgewiesen (bounce) werden sollen, wurde aber stillschweigend fallen gelassen, weil diese Kopfzeile gefunden wurde: %{key}: %{value}" bounces_notify_admins: "Die angehängte Email wurde mit folgender Nachricht zurückgewiesen:" notice: Hinweis incoming_message: Eingehende Email forward_all_incoming_to_admins: Die angehängte Email ging ein. forward_automated_message_to_admins: Die angehängte Email wurde vermutlich von einer Maschine und nicht von einem Menschen versendet. Daher ist sie nicht über die Liste verteilt sondern an euch Admins weitergeleitet worden. automated_message_subject: Automatische Nachricht empfangen check_keys: Schlüsselprüfung check_keys_intro: "Bitte kümmere dich um die folgenden Schlüssel für Liste %{email}." key_expires: | Dieser Schlüssel läuft in %{days} Tagen ab: %{key_oneline} key_unusable: | Dieser Schlüssel ist %{usability_issue}: %{key_oneline} missed_message_due_to_unusable_key: "Du hast eine Email von %{list_email} verpasst weil mit deinem Abo kein (benutzbarer) OpenPGP-Schlüssel verknüpft ist. Bitte kümmere dich darum." refresh_keys: Schlüsselaktualisierung refresh_keys_intro: "Die Aktualisierung aller Schlüssel des Schlüsselrings für Liste %{email} ergab dies:" pin_keys: Schlüsselpinning pin_keys_intro: "Die Überprüfung aller Abos der Liste %{email} ergab, dass wir für folgende Abos einen Schlüssel zur Verwendung festgelegt haben:" key_updated: | Dieser Schlüssel wurde aktualisiert (%{states}): %{key_oneline} key_fetched: | Dieser Schlüssel wurde geholt (%{states}): %{key_oneline} import_states: unchanged: unverändert new_key: neuer Schlüssel new_uids: neue User-IDs new_subkeys: neue Unterschlüssel new_signatures: neue Signaturen fetch_key: invalid_input: "Ungültige Angabe. Gültig sind: URLs, OpenPGP-Fingerabdrücke, oder Emailadressen." pseudoheaders: stripped_html_from_multialt: Diese Email enthielt einen alternativen HTML-Teil, der PGP-Daten beinhaltete. Der HTML-Teil wurde entfernt, um die Email sauberer analysieren zu können. stripped_html_from_multialt_with_keywords: Diese Email enthielt Schlüsselwörter und einen alternativen HTML-Teil. Der HTML-Teil wurde entfernt, um zu verhindern dass diese Schlüsselwörter Aussenstehenden bekannt werden. signature_states: unknown: "Unbekannte Signatur von unbekanntem Schlüssel 0x%{fingerprint}" unsigned: "Unsigniert" encryption_states: encrypted: "Verschlüsselt" unencrypted: "Unverschlüsselt" activerecord: errors: models: schleuder/subscription: attributes: email: taken: 'ist schon eingetragen' schleuder-3.4.1/locales/en.yml000066400000000000000000000332631353765016400162650ustar00rootroot00000000000000en: errors: attributes: language: inclusion: "must be one of: en, de" log_level: inclusion: "must be one of: debug, info, warn, error" openpgp_header_preference: inclusion: "must be one of: sign, encrypt, signencrypt, unprotected, none" internal_footer: invalid: "includes non-printable characters" public_footer: invalid: "includes non-printable characters" invalid_email: "is not a valid email address" invalid_fingerprint: "is not a valid OpenPGP-fingerprint" list_fingerprint_missing: "List has no fingerprint configured, cannot run! (In `%{listdir}`.)" list_key_missing: "List-key is missing in keyring, cannot run! (In `%{listdir}`.)" list_secret_key_missing: "Secret key of list is missing in keyring, cannot run! (In `%{listdir}`.)" list_admins_missing: "List has no admins configured, cannot run! (In `%{listdir}`.)" fatalerror: | A fatal error happened. Administrators have been notified. Please try again later. signoff: | Kind regards, Your Schleuder system. decryption_failed: | Decrypting your message failed. Messages to this address must be encrypted with the following key: %{key} To receive it send an email to <%{email}>. message_unsigned: Messages to this address must be OpenPGP-signed. message_signature_unknown: | Messages to this address must be OpenPGP-signed by the key that is configured for your subscription. If you don't know which one that is, ask an administrator of this list. You can contact the administrators by sending a message to <%{owner_email}> (preferably encrypt it with this addresses' public key: %{list_fingerprint}). message_unencrypted: Messages to this address must be encrypted conforming to OpenPGP. message_unauthenticated: Messages to this address must be encrypted and signed by the key associated with a subscribed address. message_sender_not_subscribed: Only subscribed addresses may send messages to this address. message_not_from_admin: Only admins may send messages to this address. message_empty: | Your message was found empty and wasn't passed on to the list. In case you only sent keywords please note that administrative keywords must be sent to the "request"-address (<%{request_address}>) in order to be respected. no_linebreaks: "must not include line-breaks" list_not_found: "Error: No list found with this address: '%{email}'." invalid_characters: "contains invalid characters" listdir_problem: message: "There's a problem with the list-directory: '%{dir}' %{problem}." not_existing: does not exist not_a_directory: is not a directory not_empty: is not empty not_writable: is not writable not_readable: is not readable keyword_admin_only: The keyword '%{keyword}' may only be used by list-admin. key_generation_failed: Generating the OpenPGP key pair for %{listname} failed for unknown reasons. Please check the list-directory ('%{listdir}') and the log-files. key_adduid_failed: "Adding a user-ID to the OpenPGP key failed with this message:\n%{errmsg}" too_many_keys: "Error: In %{listdir} there's more than one matching OpenPGP-key for %{listname}. Please delete all but one." loading_list_settings_failed: "%{config_file} could not be parsed, please check its formatting to be valid YAML." message_too_big: "Your message was too big. Allowed are up to %{allowed_size}KB." must_be_boolean: "must be true or false" must_be_greater_than_zero: "must be a number greater than zero" not_pgp_mime: "Message was not encrypted in the pgp/mime-format." delivery_error: "The following error occurred while sending a message to %{email}: %{error}" no_match_for: "No match found for %{input}" too_many_matching_keys: | Too many matching keys for '%{input}': %{key_strings} plugins: unknown_keyword: Unknown keyword '%{keyword}'. plugin_failed: Running keyword '%{keyword}' caused an unknown error. System-admins have been notified. keyword_admin_notify_request: "%{signer} sent the keyword '%{keyword}' with the values '%{arguments}' and received this response:" keyword_admin_notify_request_without_arguments: "%{signer} sent the keyword '%{keyword}' and received this response:" keyword_admin_notify_lists: "%{signer} used the keyword '%{keyword}' with the values '%{arguments}' in a message sent to the list." keyword_admin_notify_lists_without_arguments: "%{signer} used the keyword '%{keyword}' in a message sent to the list." key_management: deleted: | This key was deleted: %{key_string} not_deletable: | This key may not be deleted: %{key_string} no_imports: In the message you sent us, no keys could be found. :( key_import_status: imported: | This key was newly added: %{key_oneline} updated: | This key was updated: %{key_oneline} unchanged: | This key was not changed: %{key_oneline} error: | The key with this fingerprint could not be added due to an unknown error: %{fingerprint} matching_keys_intro: All keys from the list's keyring matching '%{input}' are attached to this message. delete_key_requires_arguments: | Error: You did not send any arguments for the keyword 'DELETE-KEY'. One is required, more are optional, e.g.: X-DELETE-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Or, to delete multiple keys at once: X-DELETE-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 a-subscription@hostname The matching keys will be deleted only if the argument matches them distinctly. fetch_key_requires_arguments: | Error: You did not send any arguments for the keyword 'FETCH-KEY'. One is required, more are optional. Each argument can be an URL to fetch via HTTP, or a string to look up at the OpenPGP-keyservers. E.g., to fetch a key by fingerprint from the keyserver: X-FETCH-KEY: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Or, to fetch two keys by email-address from the keyserver: X-FETCH-KEY: a-subscription@hostname anotherone@example.org Or, to fetch a key keys by URL: X-FETCH-KEY: https://example.org/keys/mykey.asc resend: not_resent_no_keys: Resending to <%{email}> failed (%{num_keys} keys found and unencrypted sending disallowed). encrypted_to: Encrypted to unencrypted_to: Unencrypted to invalid_recipient: "Invalid email-address for resending: %{address}" subscription_management: forbidden: "Error: You're not allowed to unsubscribe %{email}." is_not_subscribed: "%{email} is not subscribed." unsubscribed: "%{email} has been unsubscribed." unsubscribing_failed: | Unsubscribing %{email} failed: %{errors} cannot_unsubscribe_last_admin: | %{email} is the only admin for this list, thus it can not be unsubscribed. subscribed: | %{email} has been subscribed with these attributes: Fingerprint: %{fingerprint} Admin? %{admin} Email-delivery enabled? %{delivery_enabled} subscribing_failed: | Subscribing %{email} failed: %{errors}. list_of_subscriptions: "Subscriptions:\n" set_fingerprint_only_self: Only admins may set fingerprints of subscriptions other than their own. fingerprint_set: Fingerprint for %{email} set to %{fingerprint}. setting_fingerprint_failed: | Setting fingerprint for %{email} to %{fingerprint} failed: %{errors}. set_fingerprint_requires_valid_fingerprint: | You did not send a valid fingerprint for the keyword 'SET-FINGERPRINT' The following value was detected: %{fingerprint} One or two are required, e.g.: X-SET-FINGERPRINT: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 or (as an admin): X-SET-FINGERPRINT: subscription2@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 While the fingerprint must be passed in the full length (40 characters). Optionally prefixed with 0x. To remove a fingerprint you can use the keyword 'UNSET-FINGERPRINT' set_fingerprint_requires_arguments: | Error: You did not send any arguments for the keyword 'SET-FINGERPRINT'. One or two are required, e.g.: X-SET-FINGERPRINT: 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 or (as an admin): X-SET-FINGERPRINT: subscription2@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 To remove a fingerprint you can use the keyword 'UNSET-FINGERPRINT' unset_fingerprint_only_self: Only admins may remove fingerprints of subscriptions other than their own. unset_fingerprint_requires_arguments: | Error: You did not send any arguments for the keyword 'UNSET-FINGERPRINT' One value is required, e.g.: X-UNSET-FINGERPRINT: subscription2@hostname As an admin to unset your own fingerprint you must additionally pass the argument force. E.g.: X-UNSET-FINGERPRINT: adminsubscription2@hostname force fingerprint_unset: Fingerprint for %{email} removed. unsetting_fingerprint_failed: | Removing fingerprint for %{email} failed: %{errors}. subscribe_requires_arguments: | Error: You did not send any arguments for the keyword 'SUBSCRIBE'. At least one argument is required, three more are optional. E.g.: X-SUBSCRIBE: new-subscription@hostname Or, defining the key to use for the new subscription: X-SUBSCRIBE: new-subscription@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 Or, defining the key to use, and setting this subscription to be an admin: X-SUBSCRIBE: new-subscription@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 true Or, defining the key to use, setting this subscription as admin, and disabling the delivery of list-emails to this subscription: X-SUBSCRIBE: new-subscription@hostname 0xB3D190D5235C74E1907EACFE898F2C91E2E6E1F3 true false If you omit the optional arguments, the subscription has no key assigned, is not an admin, and has the delivery of list-emails enabled. signatures_attached: Find the signatures attached. list_management: no_logfile: No logfile could be found for %{listname}. (This might be caused by a high log-level and the absence of errors.) logfile_attached: The logfile for %{listname} is attached. list_public_key_subject: Key for this address list_public_key_attached: Find the key for this address attached. no_output_result: Your message resulted in no output. owner_forward_prefix: The following message was received for the list-owners. no_keywords_error: Your message didn't contain any keywords, thus there was nothing to do. missing_listname_keyword_error: Your message did not contain the required "X-LIST-NAME" keyword and was rejected. wrong_listname_keyword_error: Your message contained an incorrect "X-LIST-NAME" keyword. The keyword argument must match the email address of this list. bounces_drop_all: The attached message should have been bounced but was dropped without further notice because the list's configuration defines that no message should ever be bounced. bounces_drop_on_headers: "The attached message should have been bounced but was dropped without further notice because it matched this header-line: %{key}: %{value}" bounces_notify_admins: "The attached message was bounced with the following notice:" notice: Notice incoming_message: Incoming message forward_all_incoming_to_admins: The attached message was received. forward_automated_message_to_admins: Attached is a message that probably was sent by a machine, not a human. Therefore it has not been passed on to the list, but only to you, the admins. automated_message_subject: Automated message received check_keys: Keys check check_keys_intro: "Please take care of these keys for list %{email}." key_expires: | This key expires in %{days} days: %{key_oneline} key_unusable: | This key is %{usability_issue}: %{key_oneline} missed_message_due_to_unusable_key: "You missed an email from %{list_email} because your subscription isn't associated with a (usable) OpenPGP key. Please fix this." refresh_keys: Keys update refresh_keys_intro: "Refreshing all keys from the keyring of list %{email} resulted in this:" pin_keys: Keys pinning pin_keys_intro: "While checking all subscriptions of list %{email} we were pinning a matching key for the following subscriptions:" key_updated: | This key was updated (%{states}): %{key_oneline} key_fetched: | This key was fetched (%{states}): %{key_oneline} import_states: unchanged: unchanged new_key: new key new_uids: new user-IDs new_subkeys: new subkeys new_signatures: new signatures fetch_key: invalid_input: "Invalid input. Allowed are: URLs, OpenPGP-fingerprints, or email-addresses." pseudoheaders: stripped_html_from_multialt: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly. stripped_html_from_multialt_with_keywords: This message included keywords and an alternating HTML-part. The HTML-part was removed to prevent the disclosure of these keywords to third parties. signature_states: unknown: "Unknown signature by unknown key 0x%{fingerprint}" unsigned: "Unsigned" encryption_states: encrypted: "Encrypted" unencrypted: "Unencrypted" activerecord: errors: models: schleuder/subscription: attributes: email: taken: 'is already subscribed' schleuder-3.4.1/man/000077500000000000000000000000001353765016400142625ustar00rootroot00000000000000schleuder-3.4.1/man/schleuder-api-daemon.8000066400000000000000000000033451353765016400203460ustar00rootroot00000000000000.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "SCHLEUDER\-API\-DAEMON" "8" "January 2017" "" "" . .SH "NAME" \fBschleuder\-api\-daemon\fR \- provides the API of schleuder(8) . .SH "SYNOPSIS" \fBschleuder\-api\-daemon\fR . .SH "DESCRIPTION" schleuder\-api\-daemon provides the HTTP\-API of \fBschleuder(8)\fR to clients\. . .SH "ENVIRONMENT" . .SS "Configuration" \fBschleuder\-api\-daemon\fR reads configuration out of the \fBapi\fR section of \fBschleuder\fR\'s configuration\. Please see \fBschleuder\fR(8) for details about the configuration file\. . .P The available options are: . .TP \fBhost\fR The hostname/IP to listen at\. . .TP \fBport\fR The port to listen at\. Default: 4443\. . .TP \fBtls_cert_file\fR Path to the file that contains the TLS\-certificate to use\. You can generate a new one with \fBschleuder cert generate\fR\. . .TP \fBtls_key_file\fR Path to the file that contains the TLS\-key to use\. . .TP \fBvalid_api_keys\fR List of api_keys to allow access to the API\. . .SS "Clients" Available clients using the API are \fBschleuder\-cli\fR(8) and \fBschleuder\-web\fR\. URLs to their websites are listed below (\fISEE ALSO\fR)\. . .SH "BUGS" Known bugs are listed on the Schleuder bugtracker at \fIhttps://0xacab\.org/schleuder/schleuder\fR . .SH "SEE ALSO" \fBschleuder\fR(8), \fBschleuder\-cli\fR(8) . .TP Website of \fBschleuder\fR \fIhttps://schleuder\.org/\fR . .TP More extensive documentation for \fBschleuder\fR \fIhttps://schleuder\.org/docs/\fR . .TP \fBschleuder\-cli\fR, the command line interface for list\-management \fIhttps://0xacab\.org/schleuder/schleuder\-cli/\fR . .TP \fBschleuder\-web\fR, the web interface for list\-management \fIhttps://0xacab\.org/schleuder/schleuder\-web/\fR schleuder-3.4.1/man/schleuder-api-daemon.8.ron000066400000000000000000000027701353765016400211440ustar00rootroot00000000000000schleuder-api-daemon(8) -- provides the API of schleuder(8) =========================================================== ## SYNOPSIS `schleuder-api-daemon` ## DESCRIPTION schleuder-api-daemon provides the HTTP-API of `schleuder(8)` to clients. ## ENVIRONMENT ### Configuration `schleuder-api-daemon` reads configuration out of the `api` section of `schleuder`'s configuration. Please see `schleuder`(8) for details about the configuration file. The available options are: * `host`: The hostname/IP to listen at. * `port`: The port to listen at. Default: 4443. * `tls_cert_file`: Path to the file that contains the TLS-certificate to use. You can generate a new one with `schleuder cert generate`. * `tls_key_file`: Path to the file that contains the TLS-key to use. * `valid_api_keys`: List of api_keys to allow access to the API. ### Clients Available clients using the API are `schleuder-cli`(8) and `schleuder-web`. URLs to their websites are listed below ([SEE ALSO][]). ## BUGS Known bugs are listed on the Schleuder bugtracker at ## SEE ALSO `schleuder`(8), `schleuder-cli`(8) * Website of `schleuder`: * More extensive documentation for `schleuder`: * `schleuder-cli`, the command line interface for list-management: * `schleuder-web`, the web interface for list-management: schleuder-3.4.1/man/schleuder.8000066400000000000000000000143511353765016400163350ustar00rootroot00000000000000.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "SCHLEUDER" "8" "January 2017" "" "" . .SH "NAME" \fBschleuder\fR \- an email hub for groups . .SH "SYNOPSIS" \fBschleuder\fR work \fIlistaddress\fR < \fIemail\fR . .P \fBschleuder\fR help . .P \fBschleuder\fR \fIother\-command\fR [<\.\.\.>] . .P For descriptions of the other commands see the output of the \fIhelp\fR\-command\. . .P This manual page is written for system administrators\. For other information please read the Schleuder documentation online (\fISEE ALSO\fR)\. . .SH "DESCRIPTION" Schleuder is an email hub for groups\. Subscribers can communicate encryptedly and pseudonymously among themselves, receive emails from non\-subscribers and send emails to non\-subscribers via the list\. . .P Schleuder takes care of all de\- and encryption, stripping of headers, formatting conversions, etc\. Further Schleuder can send out its own public key and receive administrative commands by email\. . .P Email cryptography is handled by using GnuPG\. . .SH "ENVIRONMENT" . .SS "Configuration" Schleuder reads its basic settings from a file that it by default expects at \. To make Schleuder read a different file set the environment variable \fISCHLEUDER_CONFIG\fR to the path to your file when running schleuder\. E\.g\.: . .IP "" 4 . .nf SCHLEUDER_CONFIG=/usr/local/etc/schleuder\.yml /path/to/bin/schleuder \.\.\. . .fi . .IP "" 0 . .P For explanations of the possible settings read the default config file\. . .P The default settings for new lists are read from another config file\. By default Schleuder looks at \. To make Schleuder read a different file set the environment variable \fISCHLEUDER_LIST_DEFAULTS\fR analogous to above\. The possible settings are explained in the default config file\. . .SS "Connect to MTA" Schleuder behaves like an email\-filter: it reads email from standard\-input, and reports errors to standard\-error\. If all goes well Schleuder closes the initial connection to the Mail Transport Agent (MTA) only after it sent out all outgoing emails\. . .P In case of an error the MTA is expected to include Schleuder\'s error message into a bounce\-email that is sent back to the sender (this is default behaviour of most MTAs)\. . .P To connect the MTA with Schleuder it must pipe the incoming message into Schleuder\'s STDIN, and give it two arguments: first one: "work", second one: the full recipients email address\. . .P For more information on how to integrate Schleuder with your existing mail setup, please read the Schleuder documentation online (\fISEE ALSO\fR)\. . .SS "Data storage" The keyrings for each list are standard GnuPG keyrings and sit in the filesystem under \fIlists_dir\fR/\fIhostname\fR/\fIlistname\fR/ (\fIlists_dir\fR is read from schleuder\.yml, by default it is )\. They can be used manually using gpg2\. Please be careful to maintain proper file permissions if you touch the files\. . .P In the list\-directory there’s also a list specific log\-file (might be missing if the log\-level is high and no error occurred yet)\. . .P Other logging is sent to syslog\. Where that ends up depends on the operating system and the system administration\. . .P All other list\-related data is stored in the SQL\-database\. Most data is unserialized, only some values are JSON\-encoded\. . .SH "SPECIAL FEATURES" Schleuder features some special functionality\. For more detailed information read the Schleuder documentation online (\fISEE ALSO\fR)\. . .SS "Getting the public key of a list" Each Schleuder\-list replies with its public key to any email sent to \fIlistname\-sendkey@hostname\fR\. E\.g\. to receive the key for the contact address of the Schleuder project write an email to . .br \fIteam\-sendkey@schleuder\.org\fR\. . .SS "Email commands" Schleuder knows some special keywords that trigger different behaviour\. You can e\.g\. subscribe someone, or resend an email to a non\-subscriber using keywords\. . .P Keywords require that: . .IP "\(bu" 4 they start the line and begin with "x\-", . .IP "\(bu" 4 they are written into the beginning of the \fIfirst text\-part\fR of the email (usually that’s just the normal body of the email), . .IP "\(bu" 4 possible arguments must be written \fIon the same line\fR as the keyword (exceptions are mentioned in the descriptions below), . .IP "\(bu" 4 the email must be \fIencrypted and signed\fR by a list\-member’s key\. . .IP "" 0 . .P Keywords can be repeated within one email at will\. Letter case doesn’t matter\. . .P There are two types of keywords: those to enhance messages sent over the list (“list\-keywords”), and those to request something from Schleuder (“request\-keywords”)\. . .P Find detailed descriptions of all available keywords in the Schleuder documentation online (\fISEE ALSO\fR)\. . .SS "Contact list\-owner" Write to \fIlistname\-owner@hostname\fR to contact the list\-owner(s) even if you don\'t know who they are\. Use the list\'s key to encrypt the email! . .SH "EXIT STATUS" . .TP 0 Incoming email was processed without errors\. . .TP 1 Internal failure in incoming email processing\. . .SH "FILES" . .IP "\(bu" 4 \fB/etc/schleuder/schleuder\.yml\fR: default path of global Schleuder configuration . .IP "\(bu" 4 \fB/etc/schleuder/list\-defaults\.yml\fR: default path of default list settings . .IP "\(bu" 4 \fB/var/lib/schleuder/lists\fR default path of lists_dir . .IP "\(bu" 4 \fB\fR/\fB\fR/`\fIlistname\fR: list internal data . .IP "\(bu" 4 \fB\fR/\fB\fR/\fB\fR/list\.log`: log\-file for list . .IP "" 0 . .P All configuration files are formatted as YAML\. See \fIhttp://www\.yaml\.org/\fR for more details\. . .SH "BUGS" Known bugs are listed on the Schleuder bugtracker at \fIhttps://0xacab\.org/schleuder/schleuder\fR . .SH "SEE ALSO" \fBschleuder\-cli\fR(8), \fBgnupg\fR(7)\. . .TP Website of \fBschleuder\fR \fIhttps://schleuder\.org/\fR . .TP More extensive documentation for \fBschleuder\fR \fIhttps://schleuder\.org/docs/\fR . .TP \fBschleuder\-cli\fR, the command line interface for list\-management \fIhttps://0xacab\.org/schleuder/schleuder\-cli/\fR . .TP \fBschleuder\-web\fR, the web interface for list\-management \fIhttps://0xacab\.org/schleuder/schleuder\-web/\fR schleuder-3.4.1/man/schleuder.8.ron000066400000000000000000000131771353765016400171370ustar00rootroot00000000000000schleuder(8) -- an email hub for groups ======================================= ## SYNOPSIS `schleuder` work < `schleuder` help `schleuder` [<...>] For descriptions of the other commands see the output of the -command. This manual page is written for system administrators. For other information please read the Schleuder documentation online ([SEE ALSO][]). ## DESCRIPTION Schleuder is an email hub for groups. Subscribers can communicate encryptedly and pseudonymously among themselves, receive emails from non-subscribers and send emails to non-subscribers via the list. Schleuder takes care of all de- and encryption, stripping of headers, formatting conversions, etc. Further Schleuder can send out its own public key and receive administrative commands by email. Email cryptography is handled by using GnuPG. ## ENVIRONMENT ### Configuration Schleuder reads its basic settings from a file that it by default expects at . To make Schleuder read a different file set the environment variable to the path to your file when running schleuder. E.g.: SCHLEUDER_CONFIG=/usr/local/etc/schleuder.yml /path/to/bin/schleuder ... For explanations of the possible settings read the default config file. The default settings for new lists are read from another config file. By default Schleuder looks at . To make Schleuder read a different file set the environment variable analogous to above. The possible settings are explained in the default config file. ### Connect to MTA Schleuder behaves like an email-filter: it reads email from standard-input, and reports errors to standard-error. If all goes well Schleuder closes the initial connection to the Mail Transport Agent (MTA) only after it sent out all outgoing emails. In case of an error the MTA is expected to include Schleuder's error message into a bounce-email that is sent back to the sender (this is default behaviour of most MTAs). To connect the MTA with Schleuder it must pipe the incoming message into Schleuder's STDIN, and give it two arguments: first one: "work", second one: the full recipients email address. For more information on how to integrate Schleuder with your existing mail setup, please read the Schleuder documentation online ([SEE ALSO][]). ### Data storage The keyrings for each list are standard GnuPG keyrings and sit in the filesystem under /// ( is read from schleuder.yml, by default it is ). They can be used manually using gpg2. Please be careful to maintain proper file permissions if you touch the files. In the list-directory there’s also a list specific log-file (might be missing if the log-level is high and no error occurred yet). Other logging is sent to syslog. Where that ends up depends on the operating system and the system administration. All other list-related data is stored in the SQL-database. Most data is unserialized, only some values are JSON-encoded. ## SPECIAL FEATURES Schleuder features some special functionality. For more detailed information read the Schleuder documentation online ([SEE ALSO][]). ### Getting the public key of a list Each Schleuder-list replies with its public key to any email sent to . E.g. to receive the key for the contact address of the Schleuder project write an email to
. ### Email commands Schleuder knows some special keywords that trigger different behaviour. You can e.g. subscribe someone, or resend an email to a non-subscriber using keywords. Keywords require that: * they start the line and begin with "x-", * they are written into the beginning of the *first text-part* of the email (usually that’s just the normal body of the email), * possible arguments must be written *on the same line* as the keyword (exceptions are mentioned in the descriptions below), * the email must be *encrypted and signed* by a list-member’s key. Keywords can be repeated within one email at will. Letter case doesn’t matter. There are two types of keywords: those to enhance messages sent over the list (“list-keywords”), and those to request something from Schleuder (“request-keywords”). Find detailed descriptions of all available keywords in the Schleuder documentation online ([SEE ALSO][]). ### Contact list-owner Write to to contact the list-owner(s) even if you don't know who they are. Use the list's key to encrypt the email! ## EXIT STATUS * 0: Incoming email was processed without errors. * 1: Internal failure in incoming email processing. ## FILES * `/etc/schleuder/schleuder.yml`: default path of global Schleuder configuration * `/etc/schleuder/list-defaults.yml`: default path of default list settings * `/var/lib/schleuder/lists` default path of lists_dir * ``/``/`: list internal data * ``/``/``/list.log`: log-file for list All configuration files are formatted as YAML. See for more details. ## BUGS Known bugs are listed on the Schleuder bugtracker at ## SEE ALSO `schleuder-cli`(8), `gnupg`(7). * Website of `schleuder`: * More extensive documentation for `schleuder`: * `schleuder-cli`, the command line interface for list-management: * `schleuder-web`, the web interface for list-management: schleuder-3.4.1/schleuder.gemspec000066400000000000000000000054071353765016400170400ustar00rootroot00000000000000# encoding: utf-8 $: << File.expand_path('../lib', __FILE__) require 'schleuder/version' Gem::Specification.new do |s| s.name = "schleuder" s.version = Schleuder::VERSION s.authors = 'schleuder dev team' s.email = "team@schleuder.org" s.homepage = "https://schleuder.org/" s.summary = "Schleuder is a gpg-enabled mailing list manager with remailing-capabilities." s.description = "Schleuder is a group's email-gateway: subscribers can exchange encrypted emails among themselves, receive emails from non-subscribers and send emails to non-subscribers via the list.\n\n(Please note: For some platforms there's a better way of installing Schleuder than `gem install`. See for details.)" s.files = `git ls-files lib locales etc db README.md Rakefile bin/pinentry-clearpassphrase`.split s.executables = %w[schleuder schleuder-api-daemon] s.platform = Gem::Platform::RUBY s.require_path = 'lib' s.rubyforge_project = '[none]' # TODO: extend/replace expired cert #s.signing_key = "#{ENV['HOME']}/.gem/schleuder-gem-private_key.pem" #s.cert_chain = ['gem-public_cert.pem'] s.license = 'GPL-3.0' s.metadata = { "homepage_uri" => "https://schleuder.org/", "documentation_uri" => "https://schleuder.org/docs/", "changelog_uri" => "https://0xacab.org/schleuder/schleuder/blob/master/CHANGELOG.md", "source_code_uri" => "https://0xacab.org/schleuder/schleuder/", "bug_tracker_uri" => "https://0xacab.org/schleuder/schleuder/issues", "mailing_list_uri" => "https://lists.nadir.org/mailman/listinfo/schleuder-announce/", } s.required_ruby_version = ">= 2.1.0" s.add_runtime_dependency 'gpgme', '~> 2.0', '>= 2.0.13' # Explicitly include to force a version. s.add_runtime_dependency 'mail', '~> 2.7.1' s.add_runtime_dependency 'mail-gpg', '~> 0.3', '>= 0.3.3' s.add_runtime_dependency 'activerecord', '~> 4.2' # TODO: Drop this once we cease to support ruby 2.1, see #310 s.add_runtime_dependency 'rack-test', '~> 0.7.0' s.add_runtime_dependency 'rake', '>= 10.5.0' s.add_runtime_dependency 'sqlite3', '~> 1.3.6' s.add_runtime_dependency 'sinatra', '~> 1' s.add_runtime_dependency 'sinatra-contrib', '~> 1' s.add_runtime_dependency 'thor', '~> 0' s.add_runtime_dependency 'thin', '~> 1' s.add_development_dependency 'rspec', '~> 3.5.0' s.add_development_dependency 'hirb' s.add_development_dependency 'factory_bot' s.add_development_dependency 'database_cleaner' s.add_development_dependency 'simplecov-console' s.post_install_message = " Please consider additionally installing schleuder-cli (allows to configure lists from the command line). To set up Schleuder on this system please run `schleuder install`. " end schleuder-3.4.1/spec/000077500000000000000000000000001353765016400144415ustar00rootroot00000000000000schleuder-3.4.1/spec/factories/000077500000000000000000000000001353765016400164205ustar00rootroot00000000000000schleuder-3.4.1/spec/factories/lists.rb000066400000000000000000000030121353765016400200770ustar00rootroot00000000000000FactoryBot.define do factory :list do sequence(:email) {|n| "list#{n}@example.org" } fingerprint { "59C71FB38AEE22E091C78259D06350440F759BD3" } log_level { "warn" } subject_prefix { nil } subject_prefix_in { nil } subject_prefix_out { nil } openpgp_header_preference { "signencrypt" } internal_footer { nil } public_footer { nil } headers_to_meta { ["from", "to", "cc", "date", "sig", "enc"] } bounces_drop_on_headers { { "x-spam-flag" => true } } keywords_admin_only { ["subscribe", "unsubscribe", "delete-key"] } keywords_admin_notify { ["add-key"] } send_encrypted_only { true } receive_encrypted_only { false } receive_signed_only { false } receive_authenticated_only { false } receive_from_subscribed_emailaddresses_only { false } receive_admin_only { false } keep_msgid { true } bounces_drop_all { false } bounces_notify_admins { true } include_list_headers { true } include_openpgp_header { true } max_message_size_kb { 10240 } language { "en" } forward_all_incoming_to_admins { false } logfiles_to_keep { 2 } after(:build) do |list| FileUtils.mkdir_p(list.listdir) gpghome_upstream = File.join "spec", "gnupg" FileUtils.cp_r Dir["#{gpghome_upstream}/{private*,*.gpg,.*migrated}"], list.listdir end trait :with_one_subscription do after(:build) do |list| create(:subscription) end end factory :list_with_one_subscription, traits: [:with_one_subscription] end end schleuder-3.4.1/spec/factories/subscriptions.rb000066400000000000000000000003561353765016400216600ustar00rootroot00000000000000FactoryBot.define do factory :subscription do list sequence(:email) {|n| "subscription#{n}@example.org" } fingerprint { "129A74AD5317457F9E502844A39C61B32003A8D8" } admin { true } delivery_enabled { true } end end schleuder-3.4.1/spec/fixtures/000077500000000000000000000000001353765016400163125ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/bla_foo_key.txt000066400000000000000000000031741353765016400213310ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBEudENYBCADPG94KbqEUQSv3yKbn7Oh/ky0Wn0QTgeTAB/T+oP5au9I/5CnS /Rgd8M4k4n/g9orPDfZ1kp3G0sMphLs5XFh9rdtk4iZUVDdU20nfB1lHGMZreGfv mhWyYs7GlitFPHGhJUSdQ6kmxR5MjnfE8S+nXYVWkthHxaU21NIkXGyGWcTCc4ML 8BbJAsgZt2QCWE+l4OO04GoLJtttug8a2RqAuzGHit2+yc8Zv9HAwUjexrw+KZhI TnTOiT4aF5XZmVJyPYAaksjKtAXbkR7nWDWi4yTTm6VFEN6Jpajk3CEqBuyFJW+Y +60oXjf8ktwughxiV5IJCljlDoX1BDPJXw7XABEBAAG0EGJsYWJsYSA8YmxhQGZv bz6JATgEEwECACIFAkudENYCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJ EOvb6JklHyQSklQH/iavy1ercHoF7VI71b9iSrRHvlYDEnwED9y3p2ZNtOiR3dBX 6/vFErWoLnaIRSXfsUMnMtJxuxIwu0xCXmGxzzXqX1HFjZk6ofG7eajL/JC+ugAg 0ZNsHHBrBfgaWypyO7QSZyZDlEog7Yk6/Dji1fv0RZsqKo4kF+Fc5HsUyw7yN22K yjCjdu+KCgnMA6D1GLG++AirWnWxQW1CCqykuzXIMy2Z040zu8q9hv/m4m+0IXuS eFCkcrN2taP3bY+Ynn8r2X6ZV9xhMOJ13ylqn7hCebH1QsGxfIN2mfFesrUbuLb4 9KSr0qQBkYG81xOfBemCAJNu5pzQBGebgp9siX65AQ0ES50Q1gEIAMx0UKr+H63b XRhB+R/Ipn6njz3YnEAUhMYUgZueeh5MRwf1CJXXPdJ0avWRdbD689T0qhuwHMw0 iAebRxPOTzdbaUiNzXlS+9kfJu/AEbRFJPo1MgiDJr73lLpMWHUxWHSRC/OSoSYA /AHBriTorxaWpmc7A1rfdC13sVPq87TRX8MLhRXOlEiryD+AeM4rmWvBWZiljPuT Oa7dQBkO5USYkkiamlkfHaBGEfFdiWVBP6XIFiixFDUL1BKsImhpyht59aLb4Bar hIxft17xPmoDNVOGj3k0/xkFJEv6N7btB8I5DfcjdOZ3CYd8Orprm+dZPajnZeaS 8sde5ngjTZcAEQEAAYkBHwQYAQIACQUCS50Q1gIbDAAKCRDr2+iZJR8kEizPB/4x xcvm29isTIoVEtEXNkzs589YHarVKNnB1rbBRyB+p/mhdhjb7zVSIPrGrwss0kzC i00sHYr3a70MbKfM8fAoFr2d9ikoUVfBl1tVxCNH+6FYxp8g1vcNmcTMfsArFnFi 2MQOrnJNkUatAuprAouOLEApiGgTy6QikVpsxYPUT+BXHTP/vKW+fg/rJq87ub2v IuhwdI04saTRDj7jYzQsTWDVmwaTd3zcov8H9EL7DGUudZxi3t4kFfOwqBzbQ/fH fOGFBWOe1xM7EmdS0Gplss1pAnY+MuSicD0/j7/tWn7wJelMuTXxczSHM+4Vs/Yw 86SvVJhKa3G/1UCnzzzp =trkO -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/broken_utf8_uid_key.txt000066400000000000000000000032101353765016400230060ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQGiBD4tUP0RBADyrM6g52fnEgXryP54M2R+2AXcyXo6qEd7ZXzqoh9myckJ3gLS DsS7Og/TOev8w3h5ZHmFvDgWBZjzcqg0DGC17pSyBvOx8Byhz6Wd6U7YibxjpBuS /N+WMm5xEjlJVV1SJvNzMSxD5rVOprA61DgfsbW6ScAMk3VDW7jxLcG05wCg/ynJ j/vdowhlqjPJIlSZK49mQfcD/0IhBgd4M4SxxUWQNUdhm7GMXuC2pxqhqnKoSMM7 zmndGPOlf29Xn7Zc3mW8LUvaz2HmJyztMqk0F3qfNTPXEUu8OvyheN5vFNwysF2O zE8wSv8LcyH4GVxwNUSSy3dxcKfJfIoLOpmJ7TZjJ5rkPkYg5UpOlTohAW8EhPSW KU+RA/sH/h8wVdQclBJ3IxJylZ0sk2OyEbaobmGtJbch8Yqo/cbYhprmDKgE6L3h e4cKan78NTr+g7q4O+enHkkql7PW91aormHpCIDd8q01wcbvcdnjsgS4adESq/Eb p8ZZj5itaOslJWvqvyi4j2X0c1hXXHG0MxMvyOIHPHgU4krnd7Q5QvxuZG5pcyBB bGxlIGdlZ2VuIFJlY2h0cyA8aW5mb0BidWVuZG5pcy1nZWdlbi1yZWNodHMuY2g+ iFgEEBECABgFAj4tUP0ICwMJCAcCAQoCGQEFGwMAAAAACgkQEkL24T2OvkqbBwCg wQ2rPbHxPqVU36HTIOr9pKbU5YgAmgK4sDjlS7naIy7Yg6u0q9UcNRtSuQINBD4t UP0QCAD2Qle3CH8IF3KiutapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz0AfG y0OplK33TGSGSfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRPxfx2 vIPFRzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvNILSd 5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjTNP18F1dDox0Y bN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWak XUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/95aHF6jqhYsbsyFGm7YKeSWFUjy1Hh LJMnrfsY043xZqfwiXyDeNT5xAInN6YibUFIU+xCu5zdtknEfUiI1/nMK3pWkYgQ 3xwyJPTpboidcLucinYQejz4Nx14p5/Lv/jpmYlAesjy/t/QINXzDn/BeoOwyOtY 8AxL++X0M95UcNOqvWaXthTBtsGYAxoCvpzmxaHagqw90FYvjGHL5PymCCOHpZZ3 GfL10lc16nUFvetqhZTP3+hG6I5VQi8sHvcULgodLHSM2gkfu8gvcsYbyNZNRKrS WqLC0/pPhkQGNGPLC3uUlBC2tGMS1iWiIOODiDl3brW61FmEBGP1qFl9iEwEGBEC AAwFAj4tUP0FGwwAAAAACgkQEkL24T2OvkorgQCgkMFb92w+01OAABwrjGCIW97h W0UAn3D/PPbXYP0uo3sDOePOqeOv0ls7 =Q88o -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/default_list_key.txt000066400000000000000000000060121353765016400224010ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- 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 /J2/tBYro55u =lep5 -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/example_key.txt000066400000000000000000000060441353765016400213620ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG/MacGPG2 v2 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 gpCMrbUUdX4= =H2Gw -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/expired_key.txt000066400000000000000000000032201353765016400213600ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBExlilkBCACb2AQyclf7latAIE1kCTfKQ9jmcKyf959ymyhzoeNmBDpKjILC 7MOXtICo/V/xAzhWBK/vT9+56brGUBTugnW3yK+zllQprI3kIYaRS1SrbmKVwVse 9qLVUL1BssohFaEeQqT4MNh62ziJymqCguGEGXpYlEqzEDTmmhTANiPKRBZDrdfq 3FU3OJUMTGzuG34mKmXMRr0azprF228LUujMMKyWhG1hxh3El04C4jPuMSbaVcwN E2rgIg8jaNAQuSyXkaprPZ8/nRG8UFGRtCMEIEh6Ou6KybF1NI9LQKCwcsGcLHKU 2u/8vOCExxdwl9Jjlqmof4FQV7bT++6SC0n3ABEBAAG0EWV4cGlyZWQgPGJsYUBm b28+iQE+BBMBAgAoBQJMZYpZAhsDBQkAAVGABgsJCAcDAgYVCAIJCgsEFgIDAQIe AQIXgAAKCRD3Gj+EEtg4iV6yB/4uDLoN1+TswtGjpUlu2CyjHe7pb05dAU4sWfTV I+fxBuyEo+cf/23nOeoGyltBDR0heSg3TIfXQrbWD4WoVsOXPaT0fq6UEzeadkmn A5NN3PGkv46o3ZSF1ltkY9ybMgnmRLHYCojSu5bSBMRVyurr0ozwNRPtFUTka8Lj wxiwDJ394D5y6PjL56FPkUdKydzFGV2ptSKsqyAJvMBeGlQ4I6TpiBx0Lz2A1Qn+ 4uXgTVPqgalC5YKTTTjOfQcieOOeqtI0LHqDpS/DIPLnwTUCN8OL2TQIeDudm3YI P8FCKvImh840vTbpgFSgQeaJzJFv9UrloNyyvbVtaeoxnxoBuQENBExlilkBCADD Gbf0TEs5HDpbUC78tJetGWipmQRTq8gS9dDoKjG5mvlpFARPTAJvewgI7DICXMtV Y8P8eFbsZDGMETduunadutDvyP9J/wuknYHJkE5jJeZjEKyofrWM1BHxHb2bkNU0 hr8EVNvEhVjAb62cJPj5emi/3UhynLoJrrO+AZSr9Bq2QW38ntSUxTXM7VbGUR1M WZSoJ+gEg/IeR2HJziCyb1mhlvo9pZqJT959bNVQ/xU4NHtYca5cV3X88Ald4Dwz 2HM+PIBbsiz3C3fIMNrRxRvMCU4PgsjSZFoRxcdDRT5OjBxNgQ4UmWQH0sTFw9Tx 8B2OtwuguGwaXIV3FpRDABEBAAGJASUEGAECAA8FAkxlilkCGwwFCQABUYAACgkQ 9xo/hBLYOImqngf/QQt0S6tlJ9OMknmAr2pNg+DQkCqTNaSk/iQj4leGGwkpRVoH 5VFTZ0nkmZjcDTTrCjj5rEDaRo8Q38KsB1po8P25ABoK0b28rHw8I3L2Byl1+IB7 +dNKVyFJVfHAYOQsbI/p/2KdtZZIbpxnRVHY+Vlu2p/fx3JqPmqCiaVMcUFw55Qj SKaI+omfnN0WGyrK1Rub925Lch0vZkxVwmTse7qufg0iwTREMy9VZfMavMhkAtM2 AsiEG8j3mwU9JQfBkXqtWxG2VvtsBJ0rLafh5sR3NgjtR2vbSdzJRCV2xO4z6Drh 3Pug3ReHmcLUFTDPE+vmeH+xpEZ2nhvNRFDhmQ== =Nao0 -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/expired_key_extended.txt000066400000000000000000000033421353765016400232450ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: SKS 1.1.6+ Comment: Hostname: sks.spodhuis.org mQENBExlilkBCACb2AQyclf7latAIE1kCTfKQ9jmcKyf959ymyhzoeNmBDpKjILC7MOXtICo /V/xAzhWBK/vT9+56brGUBTugnW3yK+zllQprI3kIYaRS1SrbmKVwVse9qLVUL1BssohFaEe QqT4MNh62ziJymqCguGEGXpYlEqzEDTmmhTANiPKRBZDrdfq3FU3OJUMTGzuG34mKmXMRr0a zprF228LUujMMKyWhG1hxh3El04C4jPuMSbaVcwNE2rgIg8jaNAQuSyXkaprPZ8/nRG8UFGR tCMEIEh6Ou6KybF1NI9LQKCwcsGcLHKU2u/8vOCExxdwl9Jjlqmof4FQV7bT++6SC0n3ABEB AAG0EWV4cGlyZWQgPGJsYUBmb28+iQFVBBMBAgA/AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIe AQIXgBYhBJh2nooQkfNr2IQD7PcaP4QS2DiJBQJYgOG1BQkMHGNXAAoJEPcaP4QS2DiJxiUH /2xYCT+mldoaWMyJlBleSjx0wtWeGNayMuv0RjU2pIkBmslYp/ZZkt+JC3thpJneBW5pJuRB qeUi7yTigdtOrH47g+FfIDCY89ymTDbYW4vpNnnsV7s+ke8tbEmTtMpjFypoTvbnGYlq8VLz 87eRcsLwADOAJfFBdnDD0tyNCrUY/V+Ti/ZI4bHoFA14t8Hm7MIDkfB6sVfzpnZd1ACj+klv 1rfq+9m56lsavS/dM+BlhwfRORT9cenuBs++AXXWvh1CZW/J06kFECG+ptqU5246nQcjE5GX W8sC+TSq7OXSTQAJDF+aWqjA/JrbpSf/3r2/IU+mGH2Bwi7B5uBN6lG5AQ0ETGWKWQEIAMMZ t/RMSzkcOltQLvy0l60ZaKmZBFOryBL10OgqMbma+WkUBE9MAm97CAjsMgJcy1Vjw/x4Vuxk MYwRN266dp260O/I/0n/C6SdgcmQTmMl5mMQrKh+tYzUEfEdvZuQ1TSGvwRU28SFWMBvrZwk +Pl6aL/dSHKcugmus74BlKv0GrZBbfye1JTFNcztVsZRHUxZlKgn6ASD8h5HYcnOILJvWaGW +j2lmolP3n1s1VD/FTg0e1hxrlxXdfzwCV3gPDPYcz48gFuyLPcLd8gw2tHFG8wJTg+CyNJk WhHFx0NFPk6MHE2BDhSZZAfSxMXD1PHwHY63C6C4bBpchXcWlEMAEQEAAYkBJQQYAQIADwUC TGWKWQIbDAUJAAFRgAAKCRD3Gj+EEtg4iaqeB/9BC3RLq2Un04ySeYCvak2D4NCQKpM1pKT+ JCPiV4YbCSlFWgflUVNnSeSZmNwNNOsKOPmsQNpGjxDfwqwHWmjw/bkAGgrRvbysfDwjcvYH KXX4gHv500pXIUlV8cBg5Cxsj+n/Yp21lkhunGdFUdj5WW7an9/Hcmo+aoKJpUxxQXDnlCNI poj6iZ+c3RYbKsrVG5v3bktyHS9mTFXCZOx7uq5+DSLBNEQzL1Vl8xq8yGQC0zYCyIQbyPeb BT0lB8GReq1bEbZW+2wEnSstp+HmxHc2CO1Ha9tJ3MlEJXbE7jPoOuHc+6DdF4eZwtQVMM8T 6+Z4f7GkRnaeG81EUOGZ =4p8E -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/expiring_key.txt000066400000000000000000000033511353765016400215520ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFmCxIEBCADUiLMwu7I/EuyyNlidrllyVt2GkXVwSa1yIR3nA1KyDqizspih SL4eVaZOvVWyJaGxIFbmG5WHCQi0YuuDv0jcGYT3lmyPumrDYyaU5GHm05gmZ+R4 +oSjJG/v5z7L6au2G/+Iw5pQg819VviJ6po+QwmQeUkHd7xhiniPq7aoVFcoltgE bQF8sPWJ1jyFnTmL36MECgAP9MKPfcSuHzUWJxiDSo8Siqaf7uUY7F2Gz9pBYuiv g6PJqnagtjZGPgxKqQak/IRvPjk6WWIGpaO9fBl1GQ/W3iuhkObBWuHNdErygQX8 0VDc/wRfIeFsMjaPQ/nQOALgzsTGZWpdO/T9ABEBAAG0JmV4cGlyaW5nIGtleSA8 ZXhwaXJpbmdrZXlAZXhhbXBsZS5vcmc+iQFUBBMBCAA+FiEEQh+/cZBkATZ4hZPN num+WSnKzCAFAlmCxIECGwMFCSWYBgAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AA CgkQnum+WSnKzCBnQwgAmCy016/VxvLHSZ45TuYO60dGfrmu6nA3/F2qKjXsszlw UL+7jiFZZzLRQF8GJGnHJnhg6mCz8yQKam8pOqaYCqVvc19GyzPdQ22uiOA0QcuE 0kHd/ncAEtRJ3q+yeb7rcxVlHDHx2VpzNY3d2aRsgSYin+iAWKROUt3efZyTquzJ etOl6SvbwTv+qqDmrEj/zNQmr1Dic+KtLvK8ClyXrfZou5wCGsX14fKk7xkhNPBS f2oCaPjMjGsh9gZ+nZfpv+ppTAbZRPSKHuQrWksFGGxDTMigQORNtwA2SBljmL63 neqajK086P3U3dZm4lD4dPCG/V2VVYgLsooCzJZUYbkBDQRZgsSBAQgAx9bBVzuv AlLbPIqHyXsjurosvoolUKAjdWFaGCK1lZHdy3RwuVVdgWA7qXpr/T6sQ0OzxaK/ HT/1G0haCKd3zC2dD3FUQJHzIPXqE4x36a+PF/qq2vVe43+8Lwz2ceopHprHP7Sx anqnagiV/JQNJna7WTk4RM/61oHiBNWyusEhW2vynTs92ltEL4Esh3BKpyQ6mmV+ DLGalraMSDcuGRzwQMTlRrXygxq2WHc9px3M3PfMBSj86ouhSOkwOPI3eV4XPzB+ uXxwcZTsUxPm8BVVt5OAmrLaaX2K/AWEsZlVdSJpZrFD2WMMo3YRKrwvM8AHhuQO FJKaHPibi+a3zwARAQABiQE8BBgBCAAmFiEEQh+/cZBkATZ4hZPNnum+WSnKzCAF AlmCxIECGwwFCSWYBgAACgkQnum+WSnKzCAwMAf+L5ZFRTgha0AKnmWTR7JA2weg EPPDt33AHZFOCYcxuHm8TPgl4OuSvoxxWovCm56/nIiWnqojNVlWCQxAHHQmC1dA xJ7EDqdqCqVvRyRCzUsONICKeHdAoNA7TWw/DF+JZn2l+ud2w+EiQt9qy3sQXZeG SOG6s5kSPxxuwPmmMmCu5u+QI4elcHaw9RLGFbwlO4VlBS/n0k6P+UDY/4Eqkyi0 L7GCFtw3ZKjRR24MKYdGRbv7hpzvp6JyrbYA9H4wGJir43Dk/EH7Ul+4Y8FcHFk4 FU9yPzi0dW07tKD29F0GxRdqtIs0rWHIbUJVskDwDhJdteIsO/bTuKN4tZvtcQ== =c/Mw -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/filters/000077500000000000000000000000001353765016400177625ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/filters/post_decryption/000077500000000000000000000000001353765016400232075ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/filters/post_decryption/75_post_example.rb000066400000000000000000000001111353765016400265400ustar00rootroot00000000000000module Schleuder::Filters def self.post_example(list, mail) end end schleuder-3.4.1/spec/fixtures/filters/pre_decryption/000077500000000000000000000000001353765016400230105ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/filters/pre_decryption/25_example.rb000066400000000000000000000001041353765016400252710ustar00rootroot00000000000000module Schleuder::Filters def self.example(list, mail) end end schleuder-3.4.1/spec/fixtures/filters/pre_decryption/invalid_filter_file.rb000066400000000000000000000001111353765016400273200ustar00rootroot00000000000000module Schleuder::Filters def self.never_show_up(list, mail) end end schleuder-3.4.1/spec/fixtures/filters_without_pre/000077500000000000000000000000001353765016400224135ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/filters_without_pre/post_decryption/000077500000000000000000000000001353765016400256405ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/filters_without_pre/post_decryption/05_post_example.rb000066400000000000000000000001071353765016400311670ustar00rootroot00000000000000module Schleuder::Filters def self.post_example(list,mail) end end schleuder-3.4.1/spec/fixtures/mails/000077500000000000000000000000001353765016400174175ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/big_first_mime_part.txt000066400000000000000000000643421353765016400241760ustar00rootroot00000000000000Content-Type: multipart/mixed; boundary="NrllqtU3K9aakndDUX4Wq60iXnCpR3rT4"; protected-headers="v1" From: foo To: list1@lists.example.net Message-ID: Subject: My protected subject References: In-Reply-To: --NrllqtU3K9aakndDUX4Wq60iXnCpR3rT4 Content-Type: multipart/alternative; boundary="------------030809040800070509090406" This is a multi-part message in MIME format. --------------030809040800070509090406 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable foobar lg >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> Cc:=20 >>>>>> >>>>>> >>>>>> >>>>>> >> > --------------030809040800070509090406 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable foorbar
lg

Am 23.11.2017 um 11:51 schrieb foobar@lists.example.net:



Am 23.11.2017 um 09:44 schrieb foobar@li= sts.example.net:
=
From: bbaauunn <bbaauunn@=
example.com>

To: foobar@lists.example.net

Cc:=20

true" class=3D"moz-txt-link-rfc2396E" href=3D"mailto:bbaauunn@example.com"=
><bbaauunn@example.com>

Enc: Encrypted



hey,






Am 22.11.2017 um 19:03 schrieb <= a class=3D"moz-txt-link-abbreviated" href=3D"mailto:foobar@lists.example.net" moz-do-not-send=3D"true">foobar@lists.example.net= :
From: bbaauunn <utop=
ia11@example.com>







To: foobar@lists.example.net







Cc:=20














true" class=3D"moz-txt-link-rfc2396E" href=3D"mailto:bbaauunn@example.com"=
><bbaauunn@example.com>







Enc: Encrypted









br>

To: foobar@lists.example.net Cc:=20 Enc: Encrypted

































































































































































From: bbaauunn <=
bbaauunn@example.com>
















              

              
Cc:=20
















              
















              

              
Enc: Encrypted
















              

              

              

              

              

              

              
hey!
















              
















              
















              
















              
















              
















              
lg
















              

              

              

              

              

              

              
To: 
















              
Cc:=20
















              
















              

              
Enc: Encrypted
















              

                

              

                

              

                

              

                

              
















              

                

              
















              

                

              
















              
















              
















              

                

              

              

              

              
Cc:=20
















              
















              

              
Enc: Encrypted
















              

                  

              

                  

              

                  

              
















              
















              
















              

                  

              

                  

              

              

              

              
Cc:=20
















              
0
















              

              
Enc: Encrypted
















              

                    

              

                    

              

                    

              

                    

              
















              
















              
















              

                    

              
















              
















              
















              

                    

              
















              

                    

              
LG
















              

                  

              

              

              

              

            





--------------030809040800070509090406-- --NrllqtU3K9aakndDUX4Wq60iXnCpR3rT4-- schleuder-3.4.1/spec/fixtures/mails/bounce.eml000066400000000000000000000114501353765016400213720ustar00rootroot00000000000000Received: by schleuder.example.org with local (Exim 4.87) id 1dOBwu-0005qF-LI for schleuder@example.org; Fri, 23 Jun 2017 00:00:00 +0200 X-Failed-Recipients: paz@nadir.org Auto-Submitted: auto-replied From: Mail Delivery System To: schleuder@example.org Content-Type: multipart/report; report-type=delivery-status; boundary=1498175744-eximdsn-1404350378 MIME-Version: 1.0 Subject: Mail delivery failed: returning message to sender Message-Id: Date: Fri, 23 Jun 2017 01:55:44 +0200 --1498175744-eximdsn-1404350378 Content-type: text/plain; charset=us-ascii This message was created automatically by mail delivery software. A message that you sent could not be delivered to one or more of its recipients. This is a permanent error. The following address(es) failed: paz@nadir.org host 127.0.0.1 [127.0.0.1] SMTP error from remote mail server after RCPT TO:: 450 4.7.1 : Recipient address rejected: Sorry, your message cannot be delivered to that person because their mailbox is full. If you can contact them another way, you may wish to tell them of this problem: retry timeout exceeded --1498175744-eximdsn-1404350378 Content-type: message/delivery-status Reporting-MTA: dns; schleuder.example.org Action: failed Final-Recipient: rfc822;paz@nadir.org Status: 5.0.0 Remote-MTA: dns; 127.0.0.1 Diagnostic-Code: smtp; 450 4.7.1 : Recipient address rejected: Sorry, your message cannot be delivered to that person because their mailbox is full. If you can contact them another way, you may wish to tell them of this problem: retry timeout exceeded --1498175744-eximdsn-1404350378 Content-type: message/rfc822 Received: from [IPv6:::1] (localhost.localdomain [IPv6:::1]) by flutsch.local (Postfix) with ESMTP id 1DCDC801DD for ; Thu, 29 Dec 2016 15:09:43 +0100 (CET) From: schleuder@example.org To: paz@nadir.org Subject: bounce test Message-ID: Date: Thu, 29 Dec 2016 15:09:29 +0100 MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9" This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) --V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9 Content-Type: application/pgp-encrypted Content-Description: PGP/MIME version identification Version: 1 --V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9 Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message Content-Disposition: inline; filename="encrypted.asc" -----BEGIN PGP MESSAGE----- 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 =eXeI -----END PGP MESSAGE----- --V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9-- --1498175744-eximdsn-1404350378-- schleuder-3.4.1/spec/fixtures/mails/broken_utf8_charset.eml000066400000000000000000000032701353765016400240570ustar00rootroot00000000000000Return-Path: Delivered-To: example@example.org Received: from mail.example.org (mail.example.org [192.168.1.2]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by trebuchet.example.org (Postfix) with ESMTPS id 2C8A1A0403 for ; Sat, 10 Mar 2018 02:52:58 +0100 (CET) Received: from [127.0.0.1] (localhost [127.0.0.1])example.org (mx.example.org [192.168.1.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.example.org (Postfix) with ESMTPS id 7FB4610F for ; Sat, 10 Mar 2018 02:52:57 +0100 (CET) Received: from [127.0.0.1] (localhost [127.0.0.1])ng TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.example.org (Postfix) with ESMTPS id DA6D2880A0 for ; Sat, 10 Mar 2018 02:52:54 +0100 (CET) Received: from [127.0.0.1] (localhost [127.0.0.1])novol420-pc (unknown [192.168.1.3]) by smtp1-g21.free.fr (Postfix) with SMTP id 95E05B0051B for ; Sat, 10 Mar 2018 02:52:51 +0100 (CET) Message-ID: From: "Invitation" To: Subject: =?windows-1258?B?c29pcull?= Date: Sat, 10 Mar 2018 02:52:36 +0100 MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="----=MailPart0000_0010_0A37C499" This is a multi-part message in MIME format. ------=MailPart0000_0010_0A37C499 Content-Type: text/plain; charset="windows-1258" Content-Transfer-Encoding: quoted-printable Hello Goodbye ------=MailPart0000_0010_0A37C499-- schleuder-3.4.1/spec/fixtures/mails/encrypted+signed-inline/000077500000000000000000000000001353765016400241355ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/encrypted+signed-inline/thunderbird.eml000066400000000000000000000072571353765016400271610ustar00rootroot00000000000000Received: from [IPv6:::1] (localhost.localdomain [IPv6:::1]) by flutsch.local (Postfix) with ESMTP id 63585801DD for ; Thu, 29 Dec 2016 15:17:07 +0100 (CET) To: schleuder@example.org From: paz Subject: encrypted+signed-inline test Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: <216c8188-a773-fa94-d2e1-b2db4a309fed@nadir.org> Date: Thu, 29 Dec 2016 15:17:07 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit -----BEGIN PGP MESSAGE----- Charset: utf-8 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 IRNo4dJ6xTzOYXQD0Wo+z5kV =gl5b -----END PGP MESSAGE----- schleuder-3.4.1/spec/fixtures/mails/encrypted+signed-mime/000077500000000000000000000000001353765016400236065ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/encrypted+signed-mime/thunderbird.eml000066400000000000000000000112001353765016400266110ustar00rootroot00000000000000Received: from [IPv6:::1] (localhost.localdomain [IPv6:::1]) by flutsch.local (Postfix) with ESMTP id 1DCDC801DD for ; Thu, 29 Dec 2016 15:09:43 +0100 (CET) To: schleuder@example.org From: paz Subject: encrypted+signed-mime test Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: Date: Thu, 29 Dec 2016 15:09:29 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9" This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) --V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9 Content-Type: application/pgp-encrypted Content-Description: PGP/MIME version identification Version: 1 --V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9 Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message Content-Disposition: inline; filename="encrypted.asc" -----BEGIN PGP MESSAGE----- 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 xdiqE90= =FY19 -----END PGP MESSAGE----- --V4nVnp5nddnPgg4bACDcs1kSu5wBuHpL9-- schleuder-3.4.1/spec/fixtures/mails/encrypted-inline/000077500000000000000000000000001353765016400226705ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/encrypted-inline/thunderbird.eml000066400000000000000000000056141353765016400257070ustar00rootroot00000000000000Received: from [IPv6:::1] (localhost.localdomain [IPv6:::1]) by flutsch.local (Postfix) with ESMTP id C4826801DD for ; Thu, 29 Dec 2016 15:19:35 +0100 (CET) To: schleuder@example.org From: paz Subject: encrypted-inline test Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: Date: Thu, 29 Dec 2016 15:19:35 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit -----BEGIN PGP MESSAGE----- Charset: utf-8 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 gIXCntFc =7wno -----END PGP MESSAGE----- schleuder-3.4.1/spec/fixtures/mails/encrypted-mime/000077500000000000000000000000001353765016400223415ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/encrypted-mime/thunderbird.eml000066400000000000000000000074271353765016400253640ustar00rootroot00000000000000Received: from [IPv6:::1] (localhost.localdomain [IPv6:::1]) by flutsch.local (Postfix) with ESMTP id DAFE5801DD for ; Thu, 29 Dec 2016 15:13:34 +0100 (CET) To: schleuder@example.org From: paz Subject: encrypted-mime test Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: <51db1a44-b9b7-1390-b8ce-a0980101842d@nadir.org> Date: Thu, 29 Dec 2016 15:13:34 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="9JN8w93dvNGWNV5ifxvV0OtHWs5PxOc8A" This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) --9JN8w93dvNGWNV5ifxvV0OtHWs5PxOc8A Content-Type: application/pgp-encrypted Content-Description: PGP/MIME version identification Version: 1 --9JN8w93dvNGWNV5ifxvV0OtHWs5PxOc8A Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message Content-Disposition: inline; filename="encrypted.asc" -----BEGIN PGP MESSAGE----- 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 =jj1e -----END PGP MESSAGE----- --9JN8w93dvNGWNV5ifxvV0OtHWs5PxOc8A-- schleuder-3.4.1/spec/fixtures/mails/exchange.eml000066400000000000000000000051521353765016400217030ustar00rootroot00000000000000From: someone@example.org To: schleuder@example.org Message-ID: <098234lkj098324lkajsdfklj09er09a842d@localhost> Date: Thu, 01 01 2017 12:13:14 +0100 Subject: hotmail mangled message Content-Type: multipart/mixed; boundary="_003_MIME_" MIME-Version: 1.0 X-OriginatorOrg: hotmail.com X-MS-Exchange-CrossTenant-originalarrivaltime: 29 May 2017 00:00:00.0000 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Internet X-MS-Exchange-CrossTenant-id: dajfkajfkljaklfj X-MS-Exchange-Transport-CrossTenantHeadersStamped: HEADERSTAMP --_003_MIME_ Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable --_003_MIME_ Content-Type: application/pgp-encrypted; name="PGPMIME version identification" Content-Description: PGP/MIME version identification Content-Disposition: attachment; filename="PGPMIME version identification"; size=12; creation-date="Mon, 29 May 2017 00:00:00 GMT"; modification-date="Mon, 29 May 2017 00:00:00 GMT" Content-ID: Content-Transfer-Encoding: base64 VmVyc2lvbjogMQoK --_003_MIME_ Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message.asc Content-Disposition: attachment; filename="encrypted.asc"; creation-date="Mon, 29 May 2017 00:00:00 GMT"; modification-date="Mon, 29 May 2017 00:00:00 GMT" Content-ID: Content-Transfer-Encoding: base64 LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQTY5MVg4R2wyTUFyQVEvL1d3SEx1Nlp2 WE15M0VkemlaYUZnaytqQ2NxOHZXS3dTc1FQS25zWTdSRWxKCkpsdENta1JSZ2d0ajVDbDNaWWIv eVhLVEhNUElUd0hnQ3F3MVFhVFpIT2ZCT3FET3lyWVJndExEWHpabXd3RnQKQVdSWmVDWndQenJp OElKbmJaMHdlaUhMRzFiMzR2c1pqc2pIN1E3dHd4clRVbEFmbTJuUXY3TU9naFpibFRKQwpOUlY5 ekdSM0lNa0JEbURhMy9kUkRkam5kaG02dFJxMVFNS1FjdUNYekpScHUwQlNuMFFacnFHcitKdmQz TnN1CjVqTlR4dHQyV3Z6d296eHdHbVltckZ4U3E3L2VXdjdTWnFEMWFicEFQY0RrQmxIdkdobi9a Z29sZUovVGJKb2oKaWpRb1RzUDQ5YnFWUm5HRUtFK3RWVmt5dmpmeFgrTDNIUGpVdjVheFpoRFRp ejNsQnZBWWRTZlZ4cHFDWTVHMApDVWFJNFlxb3lEUlY3a2UyY0FxOFV5ODA2akFmYkUzMi9qUGlQ VkxjUDROem9WRGVyRkZ5NDBVMFp6cGtLL2pCCnpNdUVxZE9nd09xZUtHdkZCMEd5OWFXcVZYUWJZ Y1FoZ0dRV3Jxck9Jb1pTbmt3SnlhSjdqNXIvV0tFMDZuRVUKdEVXcEFEWDlHVlRFL1EyL0ZJMzB6 UCtzMnU2S2FLclB5dXZORTZXMk1abFNZMFUzaVFtY1dwQlFQZUZGOEhhYQpMM1hvMy9SdVFCRlBX S2hWTzh4ZlAxRllVc2lnallYcjJIcGpXaVdBejkxVG00MC95RjRObWN0STZEejFzaExTCkI2aGds Z2hneFZEdkZIcUprREVlRGk2R1lsTTVaaGg0WklpeDBlVWFjcGRybVM3TUZaenhzMjA0T3prQ21n UFMKV1FHbWV6NWlvNmxtbUZLZlhWYXJOajBwVHZHOVUzekJGUUozWldEUUZDZCtidGtrUm5ndWhH K3NiN2taZmkrTAo1U1ZqZDNWRGRReTU5TlNIbzk4Ukl2dEtIZlhnU0l1WU5Lc2pDTHpOcFhMNGM0 WGYzY0U0ZndNWQo9VXU1SAotLS0tLUVORCBQR1AgTUVTU0FHRS0tLS0tCg== --_003_MIME_ schleuder-3.4.1/spec/fixtures/mails/exchange_no_parts.eml000066400000000000000000000030071353765016400236050ustar00rootroot00000000000000From foobar@outlook.com Wed Jul 12 10:40:53 2017 Received: from bla.eurprd02.prod.outlook.com ([fe80::dead:beef]) by bar.eurprd02.prod.outlook.com ([fe80::dead:beef%42]) with mapi id 00.00.0000.00; Wed, 12 Jul 2017 00:00:00 +0000 From: Foo Bla Bar To: "schleuder@example.org" Subject: aSubject Thread-Topic: aSubject Thread-Index: jafkjakfjaklfjkljklj Date: Wed, 12 Jul 2017 00:00:00 +0000 Message-ID: Accept-Language: de-DE, en-US Content-Language: de-DE X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: example.org; dkim=none (message not signed) header.d=none;example.org; dmarc=none action=none header.from=outlook.com; x-incomingtopheadermarker: OriginalChecksum:somechecksum;UpperCasedChecksum:anotherchecksum;SizeAsReceived:6666;Count:23 x-ms-exchange-messagesentrepresentingtype: 1 x-tmn: [akfjkafjkajf+ajkfhajkfhjhkjfahfjk] x-ms-publictraffictype: Email x-incomingheadercount: 23 x-eopattributedmessage: 0 x-exchange-antispam-report-test: UriScan:; spamdiagnosticoutput: 1:99 spamdiagnosticmetadata: NSPM Content-Type: text/plain; charset="us-ascii" Content-ID: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-OriginatorOrg: outlook.com X-MS-Exchange-CrossTenant-originalarrivaltime: 12 Jul 2017 00:00:00.0000 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Internet X-MS-Exchange-CrossTenant-id: aaaa-bbbb-cccc-dddd-aaaaaaaaaaaa X-MS-Exchange-Transport-CrossTenantHeadersStamped: DEADBEEF bla-vla lu du schleuder-3.4.1/spec/fixtures/mails/mail_with_pgp_boundaries_in_body.txt000066400000000000000000000005151353765016400267220ustar00rootroot00000000000000To: list@example.org From: test@example.org Subject: Test Message-ID: <8db04406-e2ab-fd06-d4c5-c19b5765c52b@example.com> Date: Tue, 27 Dec 2016 17:18:57 +0100 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit Test -----BEGIN PGP MESSAGE----- Only to show what is between here... -----END PGP MESSAGE----- schleuder-3.4.1/spec/fixtures/mails/multipart-alternative/000077500000000000000000000000001353765016400237545ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/multipart-alternative/thunderbird-multi-alt-signed.eml000066400000000000000000000052101353765016400321400ustar00rootroot00000000000000To: paz@nadir.org From: paz Subject: multi/alt test signed Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: <8aff12ad-3045-a69c-bdcc-f1638c699623@nadir.org> Date: Sun, 12 Feb 2017 22:26:47 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.7.0 MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="b3ViXpO41teSXWPCLNnj2VCalcqUBwURw" This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --b3ViXpO41teSXWPCLNnj2VCalcqUBwURw Content-Type: multipart/mixed; boundary="wq1LskS7mbBfokmGQFKSgUaVwAn5TCnAA"; protected-headers="v1" From: paz To: paz@nadir.org Message-ID: <8aff12ad-3045-a69c-bdcc-f1638c699623@nadir.org> Subject: multi/alt test signed --wq1LskS7mbBfokmGQFKSgUaVwAn5TCnAA Content-Type: multipart/alternative; boundary="------------9168A8DB20220BC208511980" This is a multi-part message in MIME format. --------------9168A8DB20220BC208511980 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable _*This is a test.*_ --------------9168A8DB20220BC208511980 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable

This is a test.

--------------9168A8DB20220BC208511980-- --wq1LskS7mbBfokmGQFKSgUaVwAn5TCnAA-- --b3ViXpO41teSXWPCLNnj2VCalcqUBwURw Content-Type: application/pgp-signature; name="signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEUlB7AWOo2fAJT/4DsaNvCAaeVd4FAlig0xoACgkQsaNvCAae Vd7QzQ//a3DZKrIJeABLD/RfB4rZVmxat+kJAjsNKSnJk7sfxrtcCsDTlNY2QGGt SEe8f3HtpcOjFMVqH7FVXSaXM+E/emmPP19js2c2uLVTZi9SLkvF+NX/4dOXq0nR 6sVwEokN0/dTu4aV8cdTenKFUdIJzAjpfWgAPFOmyCe2KDSzyLrkmYbtBgSqO6l3 3S5rk4AqVVYrklWp50C+oKxVyE57Rs9848YOn3awRvFT+aocdH+UI74h9PD9Hx8z OhaT9qNEJ/KJGYjjCRv4Ls5zbKHok4RCuFozRU9UEvBDzT/NzWnMLWL90eSh4akV U/MMLGRSaB27+KBLRuxsre/eSQt6cCQWDGZbkf4VLKTEHtIKWnNPRXzSUb9SQ4xa zjHqKDVBZmHW8ftqwWK2fU1EGa9cAOpBqFyIdA/XPRX6w4n0r/Wo+ZVRtVPBiZEv K0SMNpXidfo07MgC9urEvPZi8Bo5EqwZYSpVUZsLQSWKvkw+n/+APzIaXyjP1DNs 4yFc4pqAErtokiftjT2r4P3gCXDTAOJBzHP9e1FzMS4rW9dLw+aa7FyWUvXqiRzd F0X/Ah8f8asr9Tpt6uoDYpRZ3Ny3R4/Gr0U9CzqBFbg0PfN1PJmOl2wGc6giIiAx Qf3wFp+7EmQJzlfLdEKyb4Nl5n2ColDVcD6r1pA4QNuh0vdgwYs= =7nKg -----END PGP SIGNATURE----- --b3ViXpO41teSXWPCLNnj2VCalcqUBwURw-- schleuder-3.4.1/spec/fixtures/mails/multipart-alternative/thunderbird-multi-alt-unsigned.eml000066400000000000000000000024141353765016400325060ustar00rootroot00000000000000Return-Path: X-Original-To: paz@duckdalbe.org Delivered-To: paz@duckdalbe.org Received: from flutsch.local (x4dbbbe7a.dyn.telefonica.de [77.187.190.122]) by duckdalbe.org (Postfix) with ESMTPSA id DC98332097 for ; Sun, 12 Feb 2017 22:27:50 +0100 (CET) From: paz Subject: multi/alt test unsigned To: paz@nadir.org Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: Date: Sun, 12 Feb 2017 22:27:48 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.7.0 MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="------------995F058E0A221863BAC4E3E7" This is a multi-part message in MIME format. --------------995F058E0A221863BAC4E3E7 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit _*This is a test.*_ --------------995F058E0A221863BAC4E3E7 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: 7bit

This is a test.

--------------995F058E0A221863BAC4E3E7-- schleuder-3.4.1/spec/fixtures/mails/plain/000077500000000000000000000000001353765016400205225ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/plain/thunderbird.eml000066400000000000000000000055771353765016400235510ustar00rootroot00000000000000Return-Path: Delivered-To: n.siessegger@zeromail.org Received: from smtpin.nadir.org (smtpin.nadir.org [217.114.68.218]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by snail.zeromail.org (Postfix) with ESMTPS id D7C8EC001D for ; Tue, 27 Dec 2016 17:18:59 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by smtpin.nadir.org (Postfix) with ESMTP id AB2BDE60C1 for ; Tue, 27 Dec 2016 17:18:59 +0100 (CET) Received: from smtpin.nadir.org ([127.0.0.1]) by localhost (smtpin-na.nadir.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id GbNqpozJuesw for ; Tue, 27 Dec 2016 17:18:57 +0100 (CET) Received: from mout.web.de (mout.web.de [212.227.15.4]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtpin.nadir.org (Postfix) with ESMTPS id A91EAE604F for ; Tue, 27 Dec 2016 17:18:57 +0100 (CET) Received: from nna.local ([80.187.107.60]) by smtp.web.de (mrweb004 [213.165.67.108]) with ESMTPSA (Nemesis) id 0McT8i-1c4NZw3xE8-00Hgam for ; Tue, 27 Dec 2016 17:18:57 +0100 To: n.siessegger@zeromail.org From: Nina Siessegger Subject: Test Message-ID: <8db04406-e2ab-fd06-d4c5-c19b5765c52b@web.de> Date: Tue, 27 Dec 2016 17:18:57 +0100 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit X-Provags-ID: V03:K0:q07lv8kyV5vZ2Bkz3y99DthdSlg2NU0SQbMMZtb6PkyKzuO3TqS O2/qixJf4THt+J1OEiMuWOG71sYXBENA7fUZzy25Xy3mpDLgOJj55qE8+KOFuLfWPyb2OeI 39NYZ0+mLzIuJEiq4JnfYnF7s1SN5jv3CadFuO206MV7recIxXOjpxHPDvoyfzLMyxqRb5b jM6SATIl6pRdymPUNFsjQ== X-UI-Out-Filterresults: notjunk:1;V01:K0:9gbmm2iZtw4=:psOpZ/aY5zv6agYRXPOnce fJ6S+HVb03oKnQICo4EZZxwCuxBpd1xUHzJFid+KUGgvVNXPwpi5Z0hg57WQQYwhopWhY2eCm o9nJ6xMVS8CV8uX0lEXsoNM/JRt5oM1OvIIQuuuAzUDw7F1nnmGQPOMleAMAQ7GyvfirSZfx4 VrLOsF0vIZJOwoP5rZusgqrCteOJn5TC+Wy5UDb0w3ogzJQRsNLTN2Q6f5b7mzuT1zEVPdpU2 HMHvoRTVQpMYtfTjoVJlVKfh5Zf6eurPDFa980+/h5xQKf0+W+u1Z1JCe8zI45628TPPzQNN9 VvugURsiPDP3h3zxj/joo6O2l4Vk/30Mxr7k+PVhSLno4GNjS5oL52o+AG0ZRjD3fKWrnArXr j0emey1AI1EZij+FwwWyf0JF7ubRvN2qXRovU+ZYZ3fV1x56S5ZXdiuOqImHzgYcCdKQ1Mbj3 HX+jleuL6Gkgv2hdHcq++zErqiSbBhJKxPfDGAIwRpb/kAf3/sEGGhdLAlzlagXbbNgScVNsa PQIRuXvDim+eTk8Wb0Wh17flIzpgtz/bU8ZA61WLN8yt1v3aGevuwbMbO75dO/QOU4s4Yaaln 2LUsTYwtLKuqOY+reg6J3dZYyRwMTIl8961GcGXI3xHyHlKORKPk36b0/H0kHRi337qNl5mKF Z4WiQdpohcsGg0plEuZRLdxJ7knXQX3kZxO2FlTAhfPdWN1MDuQ+CiWi8MRwApOTQeR0nzRuR 2RAcnGeDuEgC3L/e6jkVJTvg45eulIeiIQPtEmx7eC3xs+wnifTCXqI4Mi9SDWtQxnPmWPPrh QAox8QKjkrSq/JyqvmWV7fCOJur8Wmms+oyr8g/MpOBqTMGbZumCNBa4UaGnSDTFon8Qg8djv c4C7eaiiU7Z6bS9xU/qA== Test schleuder-3.4.1/spec/fixtures/mails/protected-headers-request.eml000066400000000000000000000061451353765016400252140ustar00rootroot00000000000000From - Fri Sep 29 10:33:00 2017 Subject: Encrypted Message From: paz To: schleuder-request@example.org Message-ID: Date: Fri, 29 Sep 2017 10:33:00 +0200 MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi" This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) --NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi Content-Type: application/pgp-encrypted Content-Description: PGP/MIME version identification Version: 1 --NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message Content-Disposition: inline; filename="encrypted.asc" -----BEGIN PGP MESSAGE----- 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 QO+1dOvJpclKws4ZKEkZcAjxKCd+DPjodK4= =msZE -----END PGP MESSAGE----- --NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi-- schleuder-3.4.1/spec/fixtures/mails/protected-headers.eml000066400000000000000000000043021353765016400235170ustar00rootroot00000000000000From - Fri Sep 29 10:33:00 2017 Subject: Encrypted Message From: paz To: schleuder@example.org Message-ID: Date: Fri, 29 Sep 2017 10:33:00 +0200 MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi" This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) --NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi Content-Type: application/pgp-encrypted Content-Description: PGP/MIME version identification Version: 1 --NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message Content-Disposition: inline; filename="encrypted.asc" -----BEGIN PGP MESSAGE----- hQIMA691X8Gl2MArAQ/9EkuJLshPrMOSe38BjyUBe+1L2hUQOxBd0MI0d/C0Yadj 7YtlwveeqTpLGuT7H+ecaVlhMC9KFL4NT8v64D2e4S68wymVHUqO8SezMW8Bp5ut 2psZb+/KqF7oKLkkOu6neKo8PODrDcPK5pQGBBdV9DP2Z7uNfSyTJvVfNsf/3Q7e mpQAgj9g2uooMPSqNRhOOrLPL6uWPIkfcvnt1gb4eG++ltpBD243yRxnUFrUxghq rVH6V1la0d/WO3sLOu3oVAC71NPNq5DilVFfKl+/H5BwK54pmmuvu05JKgZw1QqJ lImWRFv8GPmeyvqu9F0A4E0e4aFIly5/uHrBq0uw7R/gmeUICcwb28/ZftPj2ZEr O9C0flYVwWB3cor5owM0rOtcgbwBI6EvSQ1d51qNA1PnpIu7woptFl10tguBiJ5X HmrK+nOaXy1Rf/clrrmd/BeLtuXheb+N6WpWgiwV1MSZqP8IFBYq7R/STQpWyhxO qmMGx27iJmTHx1nQpfzZybiecws1oOHY/gOc7uI1I4+zfitdRBsOozQDB8K8lEjK 3lsDmQoCu8GUQJj6t6OGn5YlEQ+oSGNc189sDThgGG5KDVGqd5OoI0ckxWam1GeR b40imecyWm5v2aejOaHisK463YsUSO8HsIUDEtCllvl4gF1dxeUvflWKSNqqwrPS wS4BC3VdOj2RY6vxtTBpfWLqLqsMQ970wXV9oKCLSrbobgJSwUleU3I1WGhXegDI hBxRFvYIDr3R6OTDnbGTnB7We//mk5GN5eXAMs5o7Zqcn54Mox471x8Veg1dUoj2 2c6/Vgg5yX1cy+5SaZGSYDeKbyx2PSoMXZZWQHQ+KPe527gLAzpoeUAWN1H+lU6y eORjBBlk/GQF15r5BmytQraTqsJTRG22SpycpYNugzgJPzozdjXGZ5Vdz9UHo3FC Ch6gNXjeDXNCJ8PRQr3LQKrIsT6nE7zmF0N69LwJfMSm5MYIJnkhJ5tzJ/7MmcUr OfgVkorhjM2oz4w8imI+2xFy4LhOKYEr0JHBN8X5QZ5yYQ2CVPDX3MwvpW0qn5PH iQSC99Kabne9rmCByx7Ue+WUCZkuTJq201U0iSf2gNu2e8ebz+Y0ZxJ7JgH1eqyU MLfSxyFry3JQ84zHgIFJV6EPmk4W+JhsdoPGTYghsUYpBaGsn/e1JPhpn3+mQnPV +KH+x2ES9EmSMmHYBV5RY7YQB31hqnazY+omqSCgZotBq18/jHg2fnzEtvzj0fEr PgMyC/zUcSTWknToRhU7dVFzYs19/1vnJJthAQxH7Zbx5kfisYVMcyh2OSEpaPH/ C1d7wjZUT2goLpGaDYK+xQ== =u70c -----END PGP MESSAGE----- --NNqdAeeD0BMoMnLVbGvlJ93USlrgv66Hi-- schleuder-3.4.1/spec/fixtures/mails/qp-encoding-clear.eml000066400000000000000000000012401353765016400234030ustar00rootroot00000000000000Date: Tue, 11 Jul 2017 12:00:40 +0200 From: test@localhost To: schleuder@example.org Subject: Input Test QP encoding broken Message-ID: <20170711100040.GF7104@blabla> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On 17-07-11 11:59:56, schleuder@nadir.org wrote: > From: foo > To: schleuder@example.org > Cc:=20 > Date: Tue, 11 Jul 2017 11:59:49 +0200 > Sig: Good signature from somethingsomething > Enc: Encrypted > foo foo foo > foo foo foo > foo foo foo >=20 > [1] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=3D867031 https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=3D867031 schleuder-3.4.1/spec/fixtures/mails/qp-encoding-encrypted+signed.eml000066400000000000000000000061621353765016400255670ustar00rootroot00000000000000From: test@localhost Subject: something To: schleuder@example.org Message-ID: <381fb765-1e53-4578-bc93-bff7e689cbaa@example.org> Date: Fri, 21 Jul 2017 11:50:35 +0200 MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="OguUqTN7k1TEvsvAHoUI3Hv5qStt1SOH3" This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) --OguUqTN7k1TEvsvAHoUI3Hv5qStt1SOH3 Content-Type: application/pgp-encrypted Content-Description: PGP/MIME version identification Version: 1 --OguUqTN7k1TEvsvAHoUI3Hv5qStt1SOH3 Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message Content-Disposition: inline; filename="encrypted.asc" -----BEGIN PGP MESSAGE----- hQIMA691X8Gl2MArAQ/7BrJwnNXjz+DhuRAyShy4LttCKX0RRG2rV2FegzYdfd40 oB9ZPXtyL8L9fQ3uwUYpiexCqbR1yYNqPRkRZeHBOAVtHiUruZvuL+xq6yBXq3nt 5Cz/TqN79B1G1+YyX+m5DOwURfiV104lBtfVE2g+XIG/+HGBBuviRT3VJrPQw1ld oVaQNf6KQORaUwYUkLxlX+M1MdPxA7MxKmCxVBJpakGtgKTihUcE9u0g5r3kYQke qyXglGu6RaFtztk5+JSrwn+z59Yjp+uD+qdYdtvXCkPHNWABT1CRwy2c7zUExWlh 0sCxOwi6YeNJOBP1U2ceMNEHTe2XOarY5HOL1TW+bH8Ta1lCvGtwoZrwMJuDDpW0 hpnE2YZmjh6M/WV5+ToJpcw6u5K2xBVB1hLWgLf5e8H9tVFhBywvjXt6Q1PKcMk1 MfqAfYN22UYWbpbZ+AABccR+bAyECPspYbEjiZ9rjRDaDyjc5m6Jq4ctqy1ETtCA or/0sf8a+8Cxc9+tG3iaQ1f63LJTm914GJQfBb1Yi1PoEPOPxveUBcC88EVaaNvP PdnRx+L633FDDT2BHiYcK1/dD1BVjvZECtRK/522Y46epJi7E+U7b/RjOYcnfS4z ob344qvOhN+m0RJmEZVbYfr6UhxPMyQcDcX4kzpugVr/nwIk3mh3OBUuHeTovALS 6gHa460vxp16neb5thpp96g60VC3R93KXTCPyfg5XvkziKPw/UM7vmz/Rqb4tOOb eMcsH73E34ONqfcPzX+Lrg7RqXhK9EZ5iuU3bFIEu1Xp0ixlXGPotyY/VR8P/+zu Hd8C2xoE8o7SeqVIf7UIViPV1fzB/DxHwa1Q+5rJMoDvP0CTqgC5YaRw24ab7Zbh GfKAPoAEiufwgAtsVNE9yF7+fb6U0AZ8qh0Jos+ksCA6FGOflnxwqCEYrH9hT7n6 kAeVkoAoWH1YtNTQerMIASOOpjYMHu5AQnJ6RFOKDvYy+SQycTLz0DjkCCGakBPv ToyndsuGZRsnpP7ctO7CgtpCxFq8YpbZ/YzZ8aOf3UjW4K7dB4sgf2RrSCdCevK/ 8tDB/0a/t45KQoZiTVwOmWp3neL+r6rhelgAYKxV7fdIHn5C/ROH9Rm3m0Fua2mb onuelGo5bdndcl1BrkiA2iv03reOR6ID68JFJvp4KvZflu898jB2ZIkWw4vYaHDg Z5TnI9LlnJUqVFXn9SF3x8TkRdJysw6w4el8GFKzwxe7/y0CfH9MCCUTQ3pdpfna WTs00ElvgsWvIE8Nhz2cRnWfleex3/LJQhm/K24iQL45xR3RoGaO93KrbBZkUQp3 0Nu+laHElEyA97YGxs36/3rinT6EMrq6gATrNHaXM94zPQR2g7+ooHTUR/RCvRLM 80V5cpgcGkqT6Yy+wFDsTFaMJdH8ZlrOSdtthlePGqx11gvFOL8AJpGNMOq05OuV xcS7XpirPqgrblLdvgOQFM6D70nJ7g0HlMPyAm45+PyHPg4qVYXj91441mbnZfSC QUTtnJBUEp9ibpzckDn1i5ZYPEZ6QmIWfPc2heO7w3wEznwknTHfh0B2zjNAm9/B riW4xSvfIfpGyZnWrW/fGGYXYegiW0ljh49qBnTzEwSmr01YUoIl4mPA/dDou3RV WD/MgfvN9cYy4mZBMmwC45gwkU1gUELGHMfnOtLSdJ8MbOiW6CdIlvesO4KdrDdC rHrVBxdMXKSk1alcRi3KXzv86yF3q/XWyzC/BGVE9WF3DuG90NadnGDTP+IcGvy8 fyaQhUIZ0AM/XawW/y/TBTbb2mjti6FFMsMy5aTg7OJO9kCgdkjgihQXJK1Y3jwQ Zk/qsvRDZc5u50z9+/Ln3OKoRw1V7dNTYwn30iiQ0jXWN3qfrl61aB3EprJuoCCO N97Jyq+s3aZNwf73VaIqjcKtIt9/ASvA2RczgcBzANG0C9GCeKXianQz14Qr0uvv 8wG+thSAkGJgFlBEMfKXTYaRJnp+K0yjeBAP/JKVyltUYfamXLV9XLHn8sqsGwgL 5lgrPeq0K7vTzCuhdWrP8kbACFvcXzJVi6wJ6q9r9w5sOnPIezjOX+42so6VB/lB j3MMeAdEIkzHuY3JrUseYp3O+/xjQzKw/8ep8IXL53sq+nLHeqJotN5geMVHJlIe fjKBmPwNygUETOV/8xfmjSqbQ5mLiy9BwtwUkRjkPV7iSl5GU4zreMEO79E2dWfL xzaqyAiBk4um86YPQBklXXFJlvyd9z/RJZiiSEdUcuF8NAtQhE0cwrkfK4EGeftT Xe7gviDKl62tMcXuN8d5E0RUeVdCGEaSQQy7 =QAIj -----END PGP MESSAGE----- --OguUqTN7k1TEvsvAHoUI3Hv5qStt1SOH3-- schleuder-3.4.1/spec/fixtures/mails/qp-encoding-encrypted.eml000066400000000000000000000034711353765016400243220ustar00rootroot00000000000000Date: Tue, 11 Jul 2017 12:00:40 +0200 From: test@localhost To: schleuder@example.org Subject: Input Test QP encoding broken encrypted Message-ID: <20170711100040.GF7104@blabla> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="4f28nU6agdXSinmL" Content-Disposition: inline --4f28nU6agdXSinmL Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --4f28nU6agdXSinmL Content-Type: application/octet-stream Content-Disposition: attachment; filename="msg.asc" -----BEGIN PGP MESSAGE----- hQIMA691X8Gl2MArARAAreFTl/Yz+V0YBDfl54Zz+S3rENPDKXEmNJNzcYoLIEuH beoeWMHY9nzD7QkaNaeNL3foK9gr62njvrf6KsQVgnoUE8cQFhk2FJkPoylWGAnT 2zI/dZLpMit/J3WSG1U02C/lOXN7M1kuvx7O/tGiKRtudyCwGO+qrAp/BCEIwBDz sJcEo9HAU4odV1oPFGDDw+y+EfOmTj+afplsSft0hDmI+hWnkjaWKCxhmR5BEWr2 fBkaAzH4rMls8g22llZrL3Rens4UT/htOMiDXoxBQ4oE3P+sb8WesmpSJwU77y3J 7DBdwBfI0q4kI3EjOBMvOQPLCYZl5+3LckQRl5QGPaBC+BaeAsXS2dEPRl5Xw1Ae ZzxTgusV7V0hDhNG3z3SzmczfUZ7+6OQ7HsOhHAVa3KmuWpaw8Dr5s2n6AhzZNGs A8HmWQETa9DxGQTKrcijB5LyIFTo8d5Hc144En9demO0ApBd3Jg5lhkAr+vAL/z0 OkL35vosRWMncHx2GB6ido7IoMz4zXiKW34ezXJWvH5SwRdjOuy6WMvOzGToxJcn fK55f/xoq63N/uhHuVy8BH56AjI5OAfEowZ1jb/pGGRGM36JwrdwInbwSMU/X34r 6ERESKrDqRvk27u0C10iAMFMfIdQVlfJN+kKXxyKDxOaukbSexnaFBNyi07b7DzS wLUBqDDjF8zXjCQNvOlqcBrVht3e0H4YqUFPfXy+fuGYL6FWobjuUDN9ZYbcuDwu w6ioBRoFmUwvOzTI9S40+JDR8RcFPbZuYYzBEZnqNJapsy7XrvXQUXlmOB7klm0s 4m/d5edXlp93KqbO+oJ05Bse88cXvfDIrRKkV+YSWOB9VxAES3SK79QQRYlJlEdn zzcmSt8ODewc6WfO93u0gG/2xCftEHpi/iHFlgrzSVuZMa0Vn5AXva819aJ3t9D7 OYKEp2IARQHSB30W7iIOK/cyS6ymE27137uETNQ7FinES9929E9WCd+3vk/lSZLr DnhSuGSNRV36srVsN7SkbbrvfF6iipJDC3Y1GQVmngpu6nU1r0/koqGkeIJr7Sao mGIgpVBBZYbhS3LBw6h87gtYEbKirwNLuP+WHY2uQkLIBCv7/ugrATHn503nwwEp GNFu+Bhh6FQZhMfgIlSHpV5jsHeMQFqy/xgW5VgnWhqZAEEKW1AA =IB9S -----END PGP MESSAGE----- --4f28nU6agdXSinmL-- schleuder-3.4.1/spec/fixtures/mails/signed-inline/000077500000000000000000000000001353765016400221445ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/signed-inline/thunderbird.eml000066400000000000000000000027501353765016400251610ustar00rootroot00000000000000Received: from [IPv6:::1] (localhost.localdomain [IPv6:::1]) by flutsch.local (Postfix) with ESMTP id 777D3801DD for ; Thu, 29 Dec 2016 15:21:42 +0100 (CET) To: schleuder@example.org From: paz Subject: signed-inline Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: Date: Thu, 29 Dec 2016 15:21:42 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 This is a test. Umlauts: öäß. ♥★ -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEUlB7AWOo2fAJT/4DsaNvCAaeVd4FAlhlG/YACgkQsaNvCAae Vd4g5A/+Joc1sst26EGHhYL/MrYICBljcvFHWYHo81sr6ED1ek18eWiHtHGFhWvO HyJ0Ccu1ktdcyay7K70gpRNrjvs2d3l5sAAfgMyDPsvBrvWHKYUpiypcVG/7FRrD 1RVsQroh4xTo2wzWOjn47dT0EMhVZtg4jITSQn25dj8/D9viUeE3fiGFLtG5KLhU B1l+3A5cYrzaSRy9p6VoCPSEOCToZ34LJd9RkG/QKMD9JT/HcZN89sRVL0e4qI8e wQWsI6Oi7WNGiUA+p8Q5un90KNCqqumjkVxIiiFk5wSB4egba6JIBW43pKYrklRz wIXrNCk/RWcPJMTqIH9n+2IrDlKeYO/u+h/uh1y0SOTnImkmpfcgYsG3ggPnVask T2J8CCQ5Xy0HecyEFt5GO9pYG73vG9bX/YQ3cGN6yUJxmXzlQ02iS6UVx5g8Ypy2 NuZwQKFUGUeFta2mRlkKn8VZfqXrWv3VtKoaa1Zm0gZydTDA/37W5A2xrGk9IvjZ CUGx4jLqXbOFJ+zOthI9JJmaPP4sc7hUmegAjV1PJP6AaIZ+6TLwn+gY1z7SaRd2 59bA6Bnm4S7p8nV7oPwW/WaOUu3mF7y5w4Yd8DAS5BKC5UiBexrhY6mH71jXb+J9 VZOIwSEI9QvBKQM/7wULApVGq1kPR3K6XKAMvWiez3ayqsYUflk= =TSUJ -----END PGP SIGNATURE----- schleuder-3.4.1/spec/fixtures/mails/signed-mime/000077500000000000000000000000001353765016400216155ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/mails/signed-mime/thunderbird.eml000066400000000000000000000043651353765016400246360ustar00rootroot00000000000000Received: from [IPv6:::1] (localhost.localdomain [IPv6:::1]) by flutsch.local (Postfix) with ESMTP id E276680329 for ; Thu, 29 Dec 2016 15:14:42 +0100 (CET) To: schleuder@example.org From: paz Subject: signed-mime test Openpgp: id=52507B0163A8D9F0094FFE03B1A36F08069E55DE Message-ID: Date: Thu, 29 Dec 2016 15:14:42 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="DnUG3PvpPDcqa5kwpXHvXR1RjOgf17M3b" This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --DnUG3PvpPDcqa5kwpXHvXR1RjOgf17M3b Content-Type: multipart/mixed; boundary="L98FCUdRgUPMMRg5CUqPXDjjv0kGAXPf0"; protected-headers="v1" From: paz To: schleuder@example.org Message-ID: Subject: signed-mime test --L98FCUdRgUPMMRg5CUqPXDjjv0kGAXPf0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable This is a test Umlauts: =C3=B6=C3=A4=C3=9F =E2=99=A5=E2=98=85 --L98FCUdRgUPMMRg5CUqPXDjjv0kGAXPf0-- --DnUG3PvpPDcqa5kwpXHvXR1RjOgf17M3b Content-Type: application/pgp-signature; name="signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEUlB7AWOo2fAJT/4DsaNvCAaeVd4FAlhlGlIACgkQsaNvCAae Vd5VQQ/7BfL7kQA30QfQMoOLqnww/jX2aCeOIAisuPE0CU1oqxn00ntSIl7vMYIl Gu16gfwXrftbNxLfEwlLN6xZbzPJg+JefumOZglAqP9zHONxuwhO3QkaN0+K2Gfl ECDVAOxVZX52APoFkkgJjkUcHmCa2GeFROhgmDtCxeGjBTQlRg6WNu+xeJmR9GUG MQSaaMxHHSFxTmXQonRa5C1ATw9ZXekDOHK8LEO5hq04WEus0zio0a6fi+/xgTUS rJb+9el+pSOa9pZ5ZFfWWqFYuG5d4GNQpNeZQZjnUKKt0PvSw8g6Sqe0UvAeyjyg HRjDAsCijMaMql4+w6Ko7T8vl/C2By0bBeJfpZ1YkR+wa28+v2u3G8l2Mk7azkzT a8QHMzyTITv3D3MunpXSrp6Blh9X2u5PmkD9BkxHduuz/J7kgvufini8oqc3WP4U zZ8ZfU3ruon2le4UCRE/kWtDgCEK8+ppYqF/GSJZ6j1lO+tOfuWjwhotdtdG88/x bRS2GXecp7Jy8aosC0c0MHqZEMRUCKfN9lQLFf3oi+7f1AhDZbTzVnI+KGduZU3h /gk3kwLHFUd2epIjiWOygQyfpMQCqNvaskz8md3uH9P2F1mfHi1CaUB5Nq1gLegP GNmgErDQLDCoBnEW3aNsEM/BzPy2JRMi9k/sPXQ0GsTZhH62MV0= =CSzt -----END PGP SIGNATURE----- --DnUG3PvpPDcqa5kwpXHvXR1RjOgf17M3b-- schleuder-3.4.1/spec/fixtures/more_filters/000077500000000000000000000000001353765016400210045ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/more_filters/pre_decryption/000077500000000000000000000000001353765016400240325ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/more_filters/pre_decryption/166_late_example.rb000066400000000000000000000001111353765016400274040ustar00rootroot00000000000000module Schleuder::Filters def self.late_example(list, mail) end end schleuder-3.4.1/spec/fixtures/more_filters/pre_decryption/25_example.rb000066400000000000000000000001041353765016400263130ustar00rootroot00000000000000module Schleuder::Filters def self.example(list, mail) end end schleuder-3.4.1/spec/fixtures/more_filters/pre_decryption/6_early_example.rb000066400000000000000000000001121353765016400274250ustar00rootroot00000000000000module Schleuder::Filters def self.early_example(list, mail) end end schleuder-3.4.1/spec/fixtures/mutt_protected_headers.txt000066400000000000000000000033621353765016400236140ustar00rootroot00000000000000From schleuder@example.org Thu Jun 13 15:19:33 2019 Received: from 127.0.0.1 (helo=localhost.localdomain) by mail.example.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92) id 1hbPdc-0007GN-6b for schleuder@example.org; Thu, 13 Jun 2019 15:19:32 +0200 Date: Thu, 13 Jun 2019 15:19:30 +0200 From: dev To: schleuder@example.org Subject: ... Message-ID: <20190613131930.ABC@xyz> MIME-Version: 1.0 Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="z6Eq5LdranGa6ru8" Content-Disposition: inline --z6Eq5LdranGa6ru8 Content-Type: application/pgp-encrypted Content-Disposition: attachment Version: 1 --z6Eq5LdranGa6ru8 Content-Type: application/octet-stream Content-Disposition: attachment; filename="msg.asc" -----BEGIN PGP MESSAGE----- hQIMA691X8Gl2MArAQ//SFZyc/TD/9PYMddJcUIp4F85wsoCUZUaVLpKBzUZdrLv rln9bgaou4MiUXF8ZTSqq2ET6A3X7+wpDjs79KiDJnILUmguGDT2KTkyD8lxP9nd oIKtqKdf95AYGmItYkaQqdZf1No2q4ZBQNWXp8+LZgxINn5AW+9wuOo8F9w+tyZJ 8r9jlj5TJ0YnVp5FieKMMyxiSOCGX8lAaqi4TbML35OWrnL8Decsz5tTX4jfqr8L cvNuIpa863WkbZxMxLEEn4/yC6upmOnU3eSZ9M/UoXiqgBsd01KEoOvmIIPOgGce IaCxO4zuoPvtcQsuinlLCI2oX9mpex6iTMGmD1J0G9FNGI3OHkwZcahw+4/3dv9K jfUjm6XwndtYi6ifAPAf8M8RT84hFlZKqR7IpGmpqWnLZx6BcFV0RDu8GCIPD6Fr UeLu1hGLD3SMbKy9zSR4lDSkMRvCUumXAebtEvfp7dfQ9Z8I866J5/9EZIDH88M1 Rb9agaBlwwr8Oy0hzC3rwvLyqXi1KD79f+YmGL0yatYPTm37qCE+QdfXCkesN6jg SV3zjtpBalP0KMCtAhouFf6xDz615nWvC5NRh2yzYOhSVfmZEVrB9Zz7GZx8rsMi 2U0ALYJIc6EI0uc/sLZ9dYu6hBa72VmSe90zS5IE2ZYB24GnzXV95iMsvH35/4vS igFWNQmHxWc9GfwgXN2/P2k4zokCxywLLhoa5xoy+SGmipz2pU8IS4chGT6H5edU /CdBAAdHw7eNgXkuctH9eAM7WE/yu+1tmAPUvzr3ojFyXF+L8d8gcCbOJA4scHb6 /L8B0J+CZG/dkMDxU/xjyc7zz8not13HTr6xklMUIam/OzM95YFmleAN4g== =l547 -----END PGP MESSAGE----- --z6Eq5LdranGa6ru8-- schleuder-3.4.1/spec/fixtures/olduid_key.txt000066400000000000000000000033051353765016400212040ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFjWeYkBCADfzR16dDh1o+kYN1OzxDrKw+/DYvxpwggfgZ0nAZkdwM+oHpLB lRfKgfs1lghuMpff/DbTM0xiFUtqPACDrpf9sQ1VS0Z2zCfERy0SITZisBVAtwmg 551MSCBY/g2PWy455r20hcSJSuFLt+Dw8u/y5WNeDpnA7seXvPXs7JrXwwJEi/Ql XZK+1d9AgzQAlFhL2fvvOrAV4NM6JvMy7kpRScTt+szMiGLf+kfiYYlj58xdkhPJ AqU9g0qDnp6Ia7sgaU0Hgs4IWymM6sLrOjtQJI+2/MOx++FK0SpfLW84q++ANw6F dHmYAcXZ3C1iL6YZzkKTLJhwdpupekLxBdynABEBAAG0GW9sZCB1aWQgPG9sZEBl eGFtcGxlLm9yZz6JAU4EEwEKADgCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AW IQRu5R14/Qsz3mXM9p0hBOIOIIifZgUCWNZ5vwAKCRAhBOIOIIifZj5DCACizhev n/X/oloddPeYfAElO+KBf2Dx9XYRuEoJNb+w5pkwAbcvOKoSVQGPSwUw1IFHr7w4 WjfNRzDm8iBqPUc6jBEwAH9s+LN0rIcr2rDYtXkMZpCgfczPmVoDmMoFn7abnNZc XLMqtk3raRaWeYfzggveyGkUAdPx34N0R6MykZtIQaqnTxel1Htj/XeERfNwmIhX CrBaKsVWYncllPzulupv6xe1hBHlENXc2XEoUqphxJtQEkDGwPPXe5Rb/K7XyJh6 uuXgFWL5cT8OxYt6O8XRZIhdT80kLqLf9WkHQYBHYvMlstzcQYG33f16NIMuj1Ra ern2KdEzIwBCyMsxuQENBFjWeYkBCAD4HsM1gZ8UPtEOFWaUrFnEJLopS+B1iTBS ISdXV0tjQEnplr8Os8LPd2Y6niLYRt7+r0E2CFrZvZ6D9ZVnvK4ex10MnmEqLfgW eK6rssUONptCA46AzCYyGKLXjPzl2R+jtEfJIw3PugN8rSKx4ZFA1h6sPP7NCve6 taICeFtPB4NYT9WLdegoBCVMaRNBqatZv9t11Oq6j874H41ppQnyZ69C3PygGHUe rwx6sREis74QFHVuW10sm44buuGBnAV1vmiYK15s/ByZpcZdIaTljF31vZtIEJbb 9tKDvuSWKLlji3Ao72hn3bWdfOoZPenB8b0VPdteUAgDg4aXxeIjABEBAAGJATYE GAEKACACGwwWIQRu5R14/Qsz3mXM9p0hBOIOIIifZgUCWNZ50gAKCRAhBOIOIIif Zk1JCADbemG0mChBv3TVWZAMFuoqK7vXbDZ3bsJyxUFNeDs49BYO/h3iBbT8nCny bnCzIP0EcCMaZr5629b50fblQhEgndzvgvDOv3RfxUNcbw2SunpS4u2Q3c8TD4ps av1LzfzV+9ha2m1vNJHsKzcoWALNFz2a9KcKuvjvY/8NalsueVbMKgSjVaSLwvEj 9HldIjeeHy2OFMqIjk+DomdVrmcJ4J3ERGrIrq32O7zwWM2iDwx+EXMI2XEvqbbr WfHgGnmNBGJJC4Rs+kQB+PsMeaANXVLvdnX4aC/OAfZBB1UCxI2UFtKtxp3B+eJN TL561iNgOgEG8UAfKyReEwOUM81S =LUmS -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/olduid_key_with_newuid.txt000066400000000000000000000042651353765016400236200ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- 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 Ug== =8V6x -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/openpgp-keys/000077500000000000000000000000001353765016400207335ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/openpgp-keys/public-key-with-third-party-signature.txt000066400000000000000000000045741353765016400307670ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- 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 QKfPPOk= =EqIC -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/openpgpkey_52507B0163A8D9F0094FFE03B1A36F08069E55DE.asc000066400000000000000000000061131353765016400257010ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- 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 nyRAUuvGT7Lx =wHia -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/partially_expired_key.txt000066400000000000000000000041271353765016400234500ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- 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 =xAyq -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/revoked_key.txt000066400000000000000000000035641353765016400213720ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQGiBEjVO7oRBADQvT6wtD2IzzIiK0NbrcilCKCp4MWb8cYXTXguwPQI6y0Nerz4 3YLDaGdb7idJAybemG/BHNVUG90XnCE5XhKKzcjFfklCly+RzRDhJqfBn7z8eH5e dsK6J0X1Vgeo02tqA4xd3EDK8rdqL2yZfl/2egH8+85R3gDk+kqkfEp4pwCgp6VO aEGVKE6G3fX1R6bSUNJybI0D/jhUEj/HXCs44AF+zIKKSzTCwzfdvjHjjs2uoF7t UJa89W7f0EIhFa10RizIfCsivmPxXPxtCvIYGPrssZdFTSgrmy9jU/9pivueyFWY TM8iKelPanlmwP5tzXJt4LRzU3OpsneUSyu60Tpad1trK/2QeqFRNYFVr7OFnGWB rME5BADOU8Zz+Xc65uKcBtjQXd2razdSuil4fgjHicmIwNYnD2aj6lnc+fnfuAYx NPVxUoWfCS4OaSq09OANwFzyWHRGyS/IetaP1kIaxwkOe1j6r0IiF6ZFpZt1DFYF ikgnJBCYDvG95j/8M6Ta05KERkxbjq2QSW4F4s0ax/Au1tztc4hnBCARCAAnBQJM ikhtIB0BUGxlYXNlIHVzZSAweDA2OUU1NURFIGluc3RlYWQuAAoJEAmKyDpMACjp D3sAnjJSaWwbbbPzYug/Jkg+G4Qy+NlKAJ4geMqNPlyPzCci1vqGpTMZwbPm8rQT cGF6IDxwYXpAbmFkaXIub3JnPohiBBMRAgAiAhsDAh4BAheABQJKAbofBwsJCAcD AgEEFQoIAwUWAgMBAAAKCRAJisg6TAAo6TZkAKCVGY5i5Vty1AHB+Oxw74ek33cP lACgjiUAdpcJ0Nb0xjQQT1JPW79LIe6IYAQTEQIAIQUCSNU7ugIbAwcLCQgHAwIB BBUCCAMEFgIDAQIeAQIXgAAKCRAJisg6TAAo6X7NAJiXMEyWnG743jgn7kapIylO wcAQAJ4742pWNW+u2pyrA2tSTnB5B7g9ILkCDQRI1Tu6EAgA0LZj6Cy3t6vCvbyg 8tNXEBA3fED2nAFnmsMmQhlOhy/yqpa3WFnxZhcqi8v81E9bCQrhh3hGIi/aLdJT 6ENxAvDs2vdNAL3Tz3hB/bTGtSLLbgU64xVuMFD7nNeT17GRmabm8utEnKgf6o/c n4+nS1YPQq64b3CpId7Va67nF58fxBGuNgFy7uNIN6zqblHgPWD/cr2hR5hPX9kk xMgS42PFBqoJZ0I+uaTZRf6pGlsEW9OPIfojK1ttRJKLiykwzxZHRW2qVdl+SiWS HH4TtcxG8HNWShdybimBy4fGM2bIk3P6d/t3gQL5/Q5LEBScoDNOYqqUCXcpdGiA Ji+CEwADBggAjuxoMm6Mc2ST4B0RkiA3PE3rCFhoc4gzjksTNyYuZj9w9JSADa3V l73Su4D4YByCOYxVpdde703dFIlLf4/Gbzdx6ejj5g9Nk3ARR3LtEJsno97HIYQK uM0iyTW1cajAJR7gI+K+YQ07G8vFV81t4DlPD9dHJPV6blgy/HgapnthWJBB5L6b MaAwk+inqbkisyaxE4Ov6mOULqhzyDkGZXlgeaa0wUcyg28WRulPGOG/xICpuM6a FNRKvuBbrX2RH2AiLXwAfclrIe7x7a2RgQcb1tcvfxoQIP7gwAGtsP98dPc1pkma 0GMMcA57HTRKe/T/ZrAVqF3aXdBL48Nc3IhJBBgRAgAJBQJI1Tu6AhsMAAoJEAmK yDpMACjpNwsAnRkZCkPV+RkZmOtoc/UgmZ7AtI3mAJ4vDlEFfTV315Uv73jwVJzi 9PDUAQ== =Latx -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/schleuder_at_example_public_key.txt000066400000000000000000000060121353765016400254350ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- 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 /J2/tBYro55u =lep5 -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/signonly_key.txt000066400000000000000000000016651353765016400215750ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFmC2g4BCADDRv16LnKRbVT9uyjtNkReqM0abvWUKZOBGYr1JV8t8hEeMKwv BpPwh1AZyw+Ihhn/BybwyBDfqmInKWE81bGUl0ZRTaX2i/5JP7uTIcn/7DY3O4rN Hx/h/Xworqq34MhdoxJCsxFlaEyPaNg9bNNVknoKDRpPTMWBCOhLlvJTr2Lr6gbk bDt62fRR6qrwA7Um28jz2j0Dn0bFKv7mDFlQ4zVL89z93oa5G5JRztogWv4FyawM slSrvLhMt+Jw8HOL2w0vyPuZTcITgy8mcyajF2/NnP6aW5p6ujXIw4od3gJtDUOI Uec+ZMFSDvD1jzv42ksHz5D4SkuWWrtffwfxABEBAAG0H3NpZ25vbmx5IDxzaWdu b25seUBleGFtcGxlLm9yZz6JAU4EEwEKADgWIQSxzYuxXCZzxr/Y+ktwss8p4BrV PgUCWYLaDgIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRBwss8p4BrVPgj+ B/9hs+NMmdjOM/3KGrL65qVKwAe/zR0xVhmnT+kg9XkCuH1dKT2vae5zkzvU883R DTwk5nFpKFuklYDyTtpOxZkmNXzYIBihFdqWLdg9SO/ydXwFB5dcsinAadWYTNg6 NS6hA5hG7Uc3zush8DzvUvR9JPdKTXLZ4zKNZLJ/SdLQO+e3lgDkfkJ9RH4VNFja Fys4nXVLOAnj39Yngcz6e1xngvqM4453/XgHtxg9AnDj4w09LgdoVfonPwKiLiZ7 f7eFeaHdQr+OWI/0A4Nghzd1SHnBSvlDS7XRiuiu1SYkDWK6M8c7p+It3wHAUeG3 tXmKTndc5oY20DAh14ez7kt6 =SgbM -----END PGP PUBLIC KEY BLOCK----- schleuder-3.4.1/spec/fixtures/v2list/000077500000000000000000000000001353765016400175355ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/v2list/list.conf000066400000000000000000000021241353765016400213560ustar00rootroot00000000000000--- headers_to_meta: - :from - :to - :cc - :date receive_from_member_emailaddresses_only: false keep_msgid: true log_level: WARN log_syslog: false keywords_admin_only: - ADD-MEMBER - DELETE-MEMBER - DELETE-KEY - SAVE-MEMBERS - DEL-KEY myname: v2 List default_mime: MIME receive_authenticated_only: false receive_admin_only: false bounces_drop_all: false include_list_headers: true myaddr: v2list@example.org key_fingerprint: 0392CF72B345256BB730049789226FD6A42B2A7A log_file: list.log archive: false prefix_out: "[out]" log_io: false openpgp_header_preference: signencrypt prefix: "[v2]" bounces_drop_on_headers: x-spam-flag: "yes" receive_encrypted_only: false bounces_notify_admin: true max_message_size: 10240 gpg_password: te=65k:3N4*leKo[js:=[+p[DGFd%6ui send_encrypted_only: false receive_signed_only: false public_footer: "-- \nfooter" dump_incoming_mail: false include_openpgp_header: true admins: - key_fingerprint: C4D60F8833789C7CAA44496FD3FFA6613AB10ECE mime: MIME email: schleuder2@example.org encrypted_only: false prefix_in: "[in]" keywords_admin_notify: - ADD-KEY - UNSUBSCRIBE schleuder-3.4.1/spec/fixtures/v2list/list.log000066400000000000000000000000001353765016400212010ustar00rootroot00000000000000schleuder-3.4.1/spec/fixtures/v2list/members.conf000066400000000000000000000007351353765016400220430ustar00rootroot00000000000000--- - email: anotherone@example.org # 2 matching keys - email: anyone@example.org # pinned fingerprint key_fingerprint: C4D60F8833789C7CAA44496FD3FFA6613AB10ECE mime: MIME encrypted_only: false - email: bla@foo # 2 matching keys, 1 of which is expired - email: old@example.org # has matching key in keyring - email: schleuder2@example.org # also set as admin - email: someone@example.org # no matching key encrypted_only: true schleuder-3.4.1/spec/fixtures/v2list/pubring.gpg000066400000000000000000000277211353765016400217130ustar00rootroot00000000000000 XuDo)?jEFfńpH䆐mmH>RSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f-v2 List (schleuder list) ;%/  XuD "o֤+*zLyB`A8(rP5r7ɥ͆\0#ځ mfQ'.WN8P:ԁ<_J%/t`J.胔ؿ)eW)&'CY,JcCts'k \Z!n [d*f09KR}_s"h}FGp%YUJ8'G$8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2Y ~a-S aw"H Dq}( *XF n ;QjCeOLj4$rL ى5R?UB }G9KF1wH|r)1D#tv?{alzBaza6?گ_eʓbc)q;_okFStTt"gAp*0maXucvjm}3P|4ͫm)Another Testuser 8"XN   a:zd 9~l^|55/J';,s V~YAɧ^zh{C %~okIY\?Xtngb#]J%D+#^<D,Nف LnBGMr2;q {vJk؅un澹` kIWu1Iޫx|Ve8Jܪ2PY[bzޑG)>x+ʹg!foFX` "VIݝNXE~b~>"%r1adZTr1]d"(RQϟ&x~<,;#EMnhP\7QAJ҂V̵TS=-id?2'v z+}~QM"*;Du"wa_p&՟eε,!nReЋ|=VVMlȻl^+GfC`gԺP4bxB޾N~ a5w{ ]G-r XC{ 9 bY)b/@h-s#.8.sFeq 6R BPf8?)3:޲|: x,;zM1+bZRU] GKG6U@.ZWǑ=p\ XN a:-z_3&Q)>b%eYJœIn`}wVXLܳ5puIVHZL,EYc6WNgyCRQ;q<`*ٓ$<3exn&LB/t3'ԉmbt5+jTZ[o3\* BeGy].4,}R㸂!_s&@ .@~T|DQevRy>"~1ʫ'LYׂ;۫Y dkOUzhdp|Lf&cH͍xIY_/wݔHf-V26CP2И\ "_K} MaJL*`BC?glԈOP1\sC&mwuD1\_k`8|ЏJv%+yK@!'&YנK{52G 1̛h@wWj)5nu~ Xyzt8u7S:bi'Ϩʁ5n263LbKj< UKFv'G-!6b@ LH X [.9潴ĉJKc^ǗD%]@4XK::&2JQÏbGac]=Jk iM[):;P$ñJ*_-o87ty-b/B,pvzBܧold uid N 8   !nx 3e! fXy ! f>CZt|%;`vJ 50/8UK0ԁG8Z7G0 j=G:0lt+ڰصy f}ϙZ\\*Miy i߃tG2HAO{cwEpW Z*Vbw%oq(RaěP@{[Șzbq?ŋz;d]O$.iAGb%Az4.TZz)3#B1 Xy5>fY$)Ku0R!'WWKc@I閿wf:"FA6Zٽg] a*-x6B&2׌G# Ϻ|"@< x[OXOՋu(%LiAYu꺏i gBu z"un[],ၜuh+^l]!]H҃(cp(hgݵ|==^P#6  !nx 3e! fXy ! fMIza(AtY *+l6wnrAMx;8)np p#fzB οt_C\o zRljKZmo4+7(X= c j[.yV*U#y]"7-ʈOgUg DjȮ;X͢ ~sq/YybI lD y ]Rvuh/AUčҭƝMLz#`:@+$^3R Y=?c$c<.0`=Cv0D欚OJ2Sny>s䘚P ^Yŗø']B(Z`_zds 6 %MKn$UetH>pJ~u2a!$~`)Z]yˣ8*4t*<$s͖hB08yxLjUFC?H794AxH!ERW8"Y=?   DYKQM=sMR%j+nĔB^Y#OzNe-7U,J"<9 z l Dgr6(^cZ,S{A볡++E7^ϑ-S{t~&(lW){ߎ㚤ZrM5c vu*GR~<v.ݦg~-#RqSQO=m봺Ŭ^e aJG)fxζ}{zU_'&JD5M~~|ƞ2c䍧 ^Y}~Wk97`DS3‚ Y=p,Iv_w!')0 =7-rΛx+*iKp-;7C8"Y=    y'MUĞcr+_d!m@-rpC: lΊV v_O$FSrjf5G];,p"2xwP/ߍ.$ bHU)hs}4ULcʯ/ a0_V07͖5,>W ,__;F`%^q!Sf UUvۊ7N"Osk>يL8r,S3,ʕ-?)W Y=oB=G 4 Q&/`+=̥WPؙv?_7_7GiQ@x1 =#oX sR*mKV:ʪ8@)vbJDO.跑o`abTu4Ex>t!j_7c@_깷+0pyڭUb,9딳K- Y=  y'M+y0٘zGH{(u{`ʆʜUs%/+I'/EWo,b+'$bfc<uzbfPN~z>g8{*CK=˛ž|RZvAP4&UN5LP\r2n(m/ %I"MKJs+kXr J d'> 5V LeY2rW@ Md 7Cpr(sf:J×_8VO߹PuȯT)!KTnb[PA!B0z8jzXJ46#DCU78 Ll~&*eFΚo R0maėN31&U j"#h,k=?PQ# Hz:ɱu4K@r,rpcPW Iexpired >(LeY Q   ?8^. ѣIn,oN]N,Y#섣m9[A !y(7LBV×=~7vIM񤿎ݔ[dcܛ2 D һUҌ5Dk >rOGJ]" ^T8#t/= MSꁩB傓M8}"x㞪4,z/ 57Ë4x;v?B*&46TȂoJܲmi1 LeYLK9:[P.hS*1iOLo{2\UcxVd17nvI ɐNc%c~4TąXo$zhHr AmԔ5VQLY'Ga oY=O}lP84{Xq\Wu ]<3s><[, w0 NdZCE>NMd l\wC%LeY  Q ?8A tKe'ӌyjMА*5$#W )EZQSgI䙘 4 8@F¬Zh ѽ|<#r)u{JW!IU`,lbHngEQYnڟrj>jLqAp#H*nKr/fLUd{~ "4D3/Ued6Ȅ=%z[Vl+-w6GkID%v3:0xFvDPᙰ K nA+Ȧ-DZ?)$ u)9\X}d&TT7TIYGkxgbƖ+E)HNtΉ>ٙRr=ʴۑX5$ӛEމ!*%o(^7.bW X3_blabla 8"K   %$T&WpzR;տbJGV|ܷfMW.vE%߱C'2q0LB^a5_Qō:y ѓlpk[*r;g&CJ :8E**$\{7m0v ZuAmB 53-Ӎ3ʽo!{xPrvm+~Wa0u)jByB|v^Ҥngl~ KtP]AȦ~=؜@zLG=tju4GO7[iHyR&E$52&LXu1Xt &$g;Zt-wS_ ΔH?x+kY9@DHYF]eA?(5 "hiy_^>j5Sy4$K79 #tw |:kY=e^x#M K %$,1جL6LX(ֶG ~v5R Ư ,L‹M,k l()(QW[U#GXƟ ~+qbrMFk,@)hˤ"ZlŃOW3~&;"pt8>c4,M`՛w|ܢB e.ub$C|c;gRjeiv>2p=?Z~%L5s430TJkq@<schleuder-3.4.1/spec/fixtures/v2list/random_seed000066400000000000000000000011301353765016400217330ustar00rootroot00000000000000Dw3!"\޴t^2e8+\l`~ucԇc{mPχQ#a[ qYز[,KR %M )94n2LdT  fdE}H1c:ߗ2T=ʙL6"k"h/!&JS8)=EH-&D]f TTf+Lc j`Y$Q/H/O/xz7q]8{ltST{ۢdy15`O斢/g-N,GdF'pYa!\feX,RM>K]ehH;r#$(S OHschleuder-3.4.1/spec/fixtures/v2list/secring.gpg000066400000000000000000000150361353765016400216730ustar00rootroot00000000000000=XuDo)?jEFfńpH䆐mmH>RSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/.y:ʦ)`?^Jx n|S2;oQ'j}|Xrp~&1OwARI\5P D'-Ag9;wK9rVvlwi }UI# Fq- SSך-Rԉl*#+n2HoXBR ®" 2]ZBLJL#+ᰝo n,?eTʩEz2oO_5ioǽ=ԋ~4qFfכ> ^[ˉ&( )nTa9ק~9n>rRѳ:ͽчPXvȿάY'aAFTFuv"㸇, z,E1px]dG@c DTJ_[bY;k "{i3[z ifʌ6σF|-ڲkķ&^&c~PNyW|I;B*x첵Z7,,0!SMύEbNfKsrw[WVn]Z|o%3)Ub`Sgela§CgOi7hZƬH3t}H<5pԈYd\Hukް;bR}>[˖Yb*PfA#.RVD# 8] v?BIRSwqΧ02݁pt<K^V̶ Dj6KW0$CI޹S5L|z N.kEJlzF¶#аU@H&L[fk; u[MaAO/qchfn-rΪҘչ&KoGE ڕ{64`pn/U@wn2&xb2}pߌ5vky/$ ۛaqnL'v^x#Sܑ~qUt/.ɩ͇GH.+o[f g荝KGxNA~&v{:"B&z8"XuD/   "o֤+*zBb7L^ 9iH,@. Ae>.*d%Cv VnOc::|{'[[ڢz\ lH'0jF̘m15{QaX 1Ҭ/Xs;9;w1l"dXPªQ ;/@e&)S{KTТYl'NGC+\y+bsOD@?&<2}d_ᛥݓ/|ktB1 9n5g DFC@TgD3<;MYV.>XXoeА9c#Z1r>Mu_Iid%s$ϛN`~T89 LL?%M;0];`ڐ/pOQ6t r8p`(eP^Td9XCJfFm:5v2 List (schleuder list) 8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f>XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\.y:ʦ)`tlv90GGQFax_5p!XE,>XI**.ַ%oB0”{PmP wCBߡ%hVa1 rC`@۳U#)a.\ܦii>`C$vT:*l m/9KWپ,*#]ҍ%"p`ՏgS*fǯ"x{Ը}:5C80D`0$  mPD7 cel-(!L?eb[Ơ/'!L2ϵށ1w6cۓ_ nD$ 3v+&(TY&iu t砥)ȓw=Iaʷ$8Ѳ38ȇDGT{~:O^rWnNUAa[9${ '~w?ـkT oa`\9V tmS>}um4-J a>콉!L %sL7Z%ߚ>W;oݽq@` e3xa'w8tѰ)GO`SfSaUb%pT9kwU'b џd~bSpe|}vg6oRQ] ܙ;{B鯨܉8d`x-.8%C>7*z*gj(}fKcU LN&O-VH׫KOXt˥V,6M1|;d^P"cR_/Ŗrqae)?sMb$g'4̇!4  4A '.! 㯙g`0CT0:s,aܥ¸(#a` O|0 1xW h,/ b ԝ,Nw nx(o>_۰%:)h]&PDBUtt]aF> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2YRSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f-v2 List (schleuder list) ;%/  XuD "o֤+*zLyB`A8(rP5r7ɥ͆\0#ځ mfQ'.WN8P:ԁ<_J%/t`J.胔ؿ)eW)&'CY,JcCts'k \Z!n [d*f09KR}_s"h}FGp%YUJ8'G$8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2Y ~a-S aw"H Dq}( *XF n ;QjCeOLj4$rL ى5R?UB }G9KF1wH|r)1D#tv?{alzBaza6?گ_eʓbc)q;_okFStTt"gAp*0maXucvjm}3P|4ͫm)Another Testuser 8"XN   a:zd 9~l^|55/J';,s V~YAɧ^zh{C %~okIY\?Xtngb#]J%D+#^<D,Nف LnBGMr2;q {vJk؅un澹` kIWu1Iޫx|Ve8Jܪ2PY[bzޑG)>x+ʹg!foFX` "VIݝNXE~b~>"%r1adZTr1]d"(RQϟ&x~<,;#EMnhP\7QAJ҂V̵TS=-id?2'v z+}~QM"*;Du"wa_p&՟eε,!nReЋ|=VVMlȻl^+GfC`gԺP4bxB޾N~ a5w{ ]G-r XC{ 9 bY)b/@h-s#.8.sFeq 6R BPf8?)3:޲|: x,;zM1+bZRU] GKG6U@.ZWǑ=p\ XN a:-z_3&Q)>b%eYJœIn`}wVXLܳ5puIVHZL,EYc6WNgyCRQ;q<`*ٓ$<3exn&LB/t3'ԉmbt5+jTZ[o3\* BeGy].4,}R㸂!_s&@ .@~T|DQevRy>"~1ʫ'LYׂ;۫Y dkOUzhdp|Lf&cH͍xIY_/wݔHf-V26CP2И\ "_K} MaJL*`BC?glԈOP1\sC&mwuD1\_k`8|ЏJv%+yK@!'&YנK{52G 1̛h@wWj)5nu~schleuder-3.4.1/spec/fixtures/v2list_admin_non_delivery/random_seed000066400000000000000000000011301353765016400256600ustar00rootroot00000000000000Dw3!"\޴t^2e8+\l`~ucԇc{mPχQ#a[ qYز[,KR %M )94n2LdT  fdE}H1c:ߗ2T=ʙL6"k"h/!&JS8)=EH-&D]f TTf+Lc j`Y$Q/H/O/xz7q]8{ltST{ۢdy15`O斢/g-N,GdF'pYa!\feX,RM>K]ehH;r#$(S OHschleuder-3.4.1/spec/fixtures/v2list_admin_non_delivery/secring.gpg000066400000000000000000000150361353765016400256200ustar00rootroot00000000000000=XuDo)?jEFfńpH䆐mmH>RSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/.y:ʦ)`?^Jx n|S2;oQ'j}|Xrp~&1OwARI\5P D'-Ag9;wK9rVvlwi }UI# Fq- SSך-Rԉl*#+n2HoXBR ®" 2]ZBLJL#+ᰝo n,?eTʩEz2oO_5ioǽ=ԋ~4qFfכ> ^[ˉ&( )nTa9ק~9n>rRѳ:ͽчPXvȿάY'aAFTFuv"㸇, z,E1px]dG@c DTJ_[bY;k "{i3[z ifʌ6σF|-ڲkķ&^&c~PNyW|I;B*x첵Z7,,0!SMύEbNfKsrw[WVn]Z|o%3)Ub`Sgela§CgOi7hZƬH3t}H<5pԈYd\Hukް;bR}>[˖Yb*PfA#.RVD# 8] v?BIRSwqΧ02݁pt<K^V̶ Dj6KW0$CI޹S5L|z N.kEJlzF¶#аU@H&L[fk; u[MaAO/qchfn-rΪҘչ&KoGE ڕ{64`pn/U@wn2&xb2}pߌ5vky/$ ۛaqnL'v^x#Sܑ~qUt/.ɩ͇GH.+o[f g荝KGxNA~&v{:"B&z8"XuD/   "o֤+*zBb7L^ 9iH,@. Ae>.*d%Cv VnOc::|{'[[ڢz\ lH'0jF̘m15{QaX 1Ҭ/Xs;9;w1l"dXPªQ ;/@e&)S{KTТYl'NGC+\y+bsOD@?&<2}d_ᛥݓ/|ktB1 9n5g DFC@TgD3<;MYV.>XXoeА9c#Z1r>Mu_Iid%s$ϛN`~T89 LL?%M;0];`ڐ/pOQ6t r8p`(eP^Td9XCJfFm:5v2 List (schleuder list) 8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f>XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\.y:ʦ)`tlv90GGQFax_5p!XE,>XI**.ַ%oB0”{PmP wCBߡ%hVa1 rC`@۳U#)a.\ܦii>`C$vT:*l m/9KWپ,*#]ҍ%"p`ՏgS*fǯ"x{Ը}:5C80D`0$  mPD7 cel-(!L?eb[Ơ/'!L2ϵށ1w6cۓ_ nD$ 3v+&(TY&iu t砥)ȓw=Iaʷ$8Ѳ38ȇDGT{~:O^rWnNUAa[9${ '~w?ـkT oa`\9V tmS>}um4-J a>콉!L %sL7Z%ߚ>W;oݽq@` e3xa'w8tѰ)GO`SfSaUb%pT9kwU'b џd~bSpe|}vg6oRQ] ܙ;{B鯨܉8d`x-.8%C>7*z*gj(}fKcU LN&O-VH׫KOXt˥V,6M1|;d^P"cR_/Ŗrqae)?sMb$g'4̇!4  4A '.! 㯙g`0CT0:s,aܥ¸(#a` O|0 1xW h,/ b ԝ,Nw nx(o>_۰%:)h]&PDBUtt]aF> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2YRSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f-v2 List (schleuder list) ;%/  XuD "o֤+*zLyB`A8(rP5r7ɥ͆\0#ځ mfQ'.WN8P:ԁ<_J%/t`J.胔ؿ)eW)&'CY,JcCts'k \Z!n [d*f09KR}_s"h}FGp%YUJ8'G$8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2Y ~a-S aw"H Dq}( *XF n ;QjCeOLj4$rL ى5R?UB }G9KF1wH|r)1D#tv?{alzBaza6?گ_eʓbc)q;_okFStTt"gAp*0maXucvjm}3P|4ͫm)Another Testuser 8"XN   a:zd 9~l^|55/J';,s V~YAɧ^zh{C %~okIY\?Xtngb#]J%D+#^<D,Nف LnBGMr2;q {vJk؅un澹` kIWu1Iޫx|Ve8Jܪ2PY[bzޑG)>x+ʹg!foFX` "VIݝNXE~b~>"%r1adZTr1]d"(RQϟ&x~<,;#EMnhP\7QAJ҂V̵TS=-id?2'v z+}~QM"*;Du"wa_p&՟eε,!nReЋ|=VVMlȻl^+GfC`gԺP4bxB޾N~ a5w{ ]G-r XC{ 9 bY)b/@h-s#.8.sFeq 6R BPf8?)3:޲|: x,;zM1+bZRU] GKG6U@.ZWǑ=p\ XN a:-z_3&Q)>b%eYJœIn`}wVXLܳ5puIVHZL,EYc6WNgyCRQ;q<`*ٓ$<3exn&LB/t3'ԉmbt5+jTZ[o3\* BeGy].4,}R㸂!_s&@ .@~T|DQevRy>"~1ʫ'LYׂ;۫Y dkOUzhdp|Lf&cH͍xIY_/wݔHf-V26CP2И\ "_K} MaJL*`BC?glԈOP1\sC&mwuD1\_k`8|ЏJv%+yK@!'&YנK{52G 1̛h@wWj)5nu~schleuder-3.4.1/spec/fixtures/v2list_admin_without_key/random_seed000066400000000000000000000011301353765016400255360ustar00rootroot00000000000000Dw3!"\޴t^2e8+\l`~ucԇc{mPχQ#a[ qYز[,KR %M )94n2LdT  fdE}H1c:ߗ2T=ʙL6"k"h/!&JS8)=EH-&D]f TTf+Lc j`Y$Q/H/O/xz7q]8{ltST{ۢdy15`O斢/g-N,GdF'pYa!\feX,RM>K]ehH;r#$(S OHschleuder-3.4.1/spec/fixtures/v2list_admin_without_key/secring.gpg000066400000000000000000000150361353765016400254760ustar00rootroot00000000000000=XuDo)?jEFfńpH䆐mmH>RSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/.y:ʦ)`?^Jx n|S2;oQ'j}|Xrp~&1OwARI\5P D'-Ag9;wK9rVvlwi }UI# Fq- SSך-Rԉl*#+n2HoXBR ®" 2]ZBLJL#+ᰝo n,?eTʩEz2oO_5ioǽ=ԋ~4qFfכ> ^[ˉ&( )nTa9ק~9n>rRѳ:ͽчPXvȿάY'aAFTFuv"㸇, z,E1px]dG@c DTJ_[bY;k "{i3[z ifʌ6σF|-ڲkķ&^&c~PNyW|I;B*x첵Z7,,0!SMύEbNfKsrw[WVn]Z|o%3)Ub`Sgela§CgOi7hZƬH3t}H<5pԈYd\Hukް;bR}>[˖Yb*PfA#.RVD# 8] v?BIRSwqΧ02݁pt<K^V̶ Dj6KW0$CI޹S5L|z N.kEJlzF¶#аU@H&L[fk; u[MaAO/qchfn-rΪҘչ&KoGE ڕ{64`pn/U@wn2&xb2}pߌ5vky/$ ۛaqnL'v^x#Sܑ~qUt/.ɩ͇GH.+o[f g荝KGxNA~&v{:"B&z8"XuD/   "o֤+*zBb7L^ 9iH,@. Ae>.*d%Cv VnOc::|{'[[ڢz\ lH'0jF̘m15{QaX 1Ҭ/Xs;9;w1l"dXPªQ ;/@e&)S{KTТYl'NGC+\y+bsOD@?&<2}d_ᛥݓ/|ktB1 9n5g DFC@TgD3<;MYV.>XXoeА9c#Z1r>Mu_Iid%s$ϛN`~T89 LL?%M;0];`ڐ/pOQ6t r8p`(eP^Td9XCJfFm:5v2 List (schleuder list) 8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f>XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\.y:ʦ)`tlv90GGQFax_5p!XE,>XI**.ַ%oB0”{PmP wCBߡ%hVa1 rC`@۳U#)a.\ܦii>`C$vT:*l m/9KWپ,*#]ҍ%"p`ՏgS*fǯ"x{Ը}:5C80D`0$  mPD7 cel-(!L?eb[Ơ/'!L2ϵށ1w6cۓ_ nD$ 3v+&(TY&iu t砥)ȓw=Iaʷ$8Ѳ38ȇDGT{~:O^rWnNUAa[9${ '~w?ـkT oa`\9V tmS>}um4-J a>콉!L %sL7Z%ߚ>W;oݽq@` e3xa'w8tѰ)GO`SfSaUb%pT9kwU'b џd~bSpe|}vg6oRQ] ܙ;{B鯨܉8d`x-.8%C>7*z*gj(}fKcU LN&O-VH׫KOXt˥V,6M1|;d^P"cR_/Ŗrqae)?sMb$g'4̇!4  4A '.! 㯙g`0CT0:s,aܥ¸(#a` O|0 1xW h,/ b ԝ,Nw nx(o>_۰%:)h]&PDBUtt]aF> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2YRSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f-v2 List (schleuder list) ;%/  XuD "o֤+*zLyB`A8(rP5r7ɥ͆\0#ځ mfQ'.WN8P:ԁ<_J%/t`J.胔ؿ)eW)&'CY,JcCts'k \Z!n [d*f09KR}_s"h}FGp%YUJ8'G$8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2Y ~a-S aw"H Dq}( *XF n ;QjCeOLj4$rL ى5R?UB }G9KF1wH|r)1D#tv?{alzBaza6?گ_eʓbc)q;_okFStTt"gAp*0maXucvjm}3P|4ͫm)Another Testuser 8"XN   a:zd 9~l^|55/J';,s V~YAɧ^zh{C %~okIY\?Xtngb#]J%D+#^<D,Nف LnBGMr2;q {vJk؅un澹` kIWu1Iޫx|Ve8Jܪ2PY[bzޑG)>x+ʹg!foFX` "VIݝNXE~b~>"%r1adZTr1]d"(RQϟ&x~<,;#EMnhP\7QAJ҂V̵TS=-id?2'v z+}~QM"*;Du"wa_p&՟eε,!nReЋ|=VVMlȻl^+GfC`gԺP4bxB޾N~ a5w{ ]G-r XC{ 9 bY)b/@h-s#.8.sFeq 6R BPf8?)3:޲|: x,;zM1+bZRU] GKG6U@.ZWǑ=p\ XN a:-z_3&Q)>b%eYJœIn`}wVXLܳ5puIVHZL,EYc6WNgyCRQ;q<`*ٓ$<3exn&LB/t3'ԉmbt5+jTZ[o3\* BeGy].4,}R㸂!_s&@ .@~T|DQevRy>"~1ʫ'LYׂ;۫Y dkOUzhdp|Lf&cH͍xIY_/wݔHf-V26CP2И\ "_K} MaJL*`BC?glԈOP1\sC&mwuD1\_k`8|ЏJv%+yK@!'&YנK{52G 1̛h@wWj)5nu~schleuder-3.4.1/spec/fixtures/v2list_duplicate_members/random_seed000066400000000000000000000011301353765016400254770ustar00rootroot00000000000000Dw3!"\޴t^2e8+\l`~ucԇc{mPχQ#a[ qYز[,KR %M )94n2LdT  fdE}H1c:ߗ2T=ʙL6"k"h/!&JS8)=EH-&D]f TTf+Lc j`Y$Q/H/O/xz7q]8{ltST{ۢdy15`O斢/g-N,GdF'pYa!\feX,RM>K]ehH;r#$(S OHschleuder-3.4.1/spec/fixtures/v2list_duplicate_members/secring.gpg000066400000000000000000000150361353765016400254370ustar00rootroot00000000000000=XuDo)?jEFfńpH䆐mmH>RSUMbMG֜,k*'wǣ& ȅ^ncZo+őC?;5Uv*y~>`1luU{H>]XnY]bcA $Ӯn S_vXf :D(+^R 'p{u⽮er o¤15Lev%)5c1fj/[7/`€]j,^,<7SB'>A6dxއu <Ԗ9 pEpvqm!t Bf>q2nYXVg5_-zK'?::v;n;ﻄo>7Hs΅:Ӥ>1-]u`HU-qHV,@w})c sDW|i @vg(!d"'JzJ8/.y:ʦ)`?^Jx n|S2;oQ'j}|Xrp~&1OwARI\5P D'-Ag9;wK9rVvlwi }UI# Fq- SSך-Rԉl*#+n2HoXBR ®" 2]ZBLJL#+ᰝo n,?eTʩEz2oO_5ioǽ=ԋ~4qFfכ> ^[ˉ&( )nTa9ק~9n>rRѳ:ͽчPXvȿάY'aAFTFuv"㸇, z,E1px]dG@c DTJ_[bY;k "{i3[z ifʌ6σF|-ڲkķ&^&c~PNyW|I;B*x첵Z7,,0!SMύEbNfKsrw[WVn]Z|o%3)Ub`Sgela§CgOi7hZƬH3t}H<5pԈYd\Hukް;bR}>[˖Yb*PfA#.RVD# 8] v?BIRSwqΧ02݁pt<K^V̶ Dj6KW0$CI޹S5L|z N.kEJlzF¶#аU@H&L[fk; u[MaAO/qchfn-rΪҘչ&KoGE ڕ{64`pn/U@wn2&xb2}pߌ5vky/$ ۛaqnL'v^x#Sܑ~qUt/.ɩ͇GH.+o[f g荝KGxNA~&v{:"B&z8"XuD/   "o֤+*zBb7L^ 9iH,@. Ae>.*d%Cv VnOc::|{'[[ڢz\ lH'0jF̘m15{QaX 1Ҭ/Xs;9;w1l"dXPªQ ;/@e&)S{KTТYl'NGC+\y+bsOD@?&<2}d_ᛥݓ/|ktB1 9n5g DFC@TgD3<;MYV.>XXoeА9c#Z1r>Mu_Iid%s$ϛN`~T89 LL?%M;0];`ڐ/pOQ6t r8p`(eP^Td9XCJfFm:5v2 List (schleuder list) 8"XuD/   "o֤+*zk8o''%Oih|/d~*Xh˃rW3:Nhnk7#M"|~'2yZx6J~ӵyq$(LqP}6~K?@!}zW%I8 WQ5wWrzdQÅxH%Z&K^3w^rJsudV-S^e?s? gbDr=ғ:"r)qQЬgcS[asK԰]X%'@B8#1[=OZŽN>|~M9[\Nt"Gyr<;)|Iqܥ>BڬeOr!y(ܾ9)C3v2 List (schleuder list) 8"XuD/   "o֤+*zOhudX[07{-y,ry|1R*q$VQtٓO"eQ:Ǐ5?PJ`"Mq$ӳ}fD%Ezaֱ" ZęŤ-'WtWo. ȕ36Nq!X6S[u[Ӥq_yl v8hfpoQ4'<>8GhcĤPՐOP}9NZ&U\>} [m֏V*'RIK2dv&d z~*(rEWPmo-+)d *7utq!o4ׯ0 ' ,8)gb>*2>kLɒctU"wdCnPx1tG֙}@UAT<;NLl5FI-qU"c|b댷VuґkbIV4f>XuD ]LD-zN LR,B t(_ w[ǂP&!P^}7O6` 0gTM8لRB *P4I8W;~ Y'J_o.Kf ଇZL 3az'o-`{%q)qY-08Ä'H˪gX,kJX|ɒWI)D/kJ m+{d^|H-H!( Ϋa1~,D?҂(4*-j?*doyif)ݵPʞ7AwS=VKtr,*7bKqer 9?>^7y-ࠝaKZ O#o6kqL^~-chwhA)`S&7tܕ΅"֔&+`l/[k·VZ>Ts/G` QlTڀay^l2N>E(^gHĤ5#P-dKzP`i6P\.y:ʦ)`tlv90GGQFax_5p!XE,>XI**.ַ%oB0”{PmP wCBߡ%hVa1 rC`@۳U#)a.\ܦii>`C$vT:*l m/9KWپ,*#]ҍ%"p`ՏgS*fǯ"x{Ը}:5C80D`0$  mPD7 cel-(!L?eb[Ơ/'!L2ϵށ1w6cۓ_ nD$ 3v+&(TY&iu t砥)ȓw=Iaʷ$8Ѳ38ȇDGT{~:O^rWnNUAa[9${ '~w?ـkT oa`\9V tmS>}um4-J a>콉!L %sL7Z%ߚ>W;oݽq@` e3xa'w8tѰ)GO`SfSaUb%pT9kwU'b џd~bSpe|}vg6oRQ] ܙ;{B鯨܉8d`x-.8%C>7*z*gj(}fKcU LN&O-VH׫KOXt˥V,6M1|;d^P"cR_/Ŗrqae)?sMb$g'4̇!4  4A '.! 㯙g`0CT0:s,aܥ¸(#a` O|0 1xW h,/ b ԝ,Nw nx(o>_۰%:)h]&PDBUtt]aF> XuD.) "o֤+*z] XuD ŷ'B!o1g^"۝rV4)%l n24%#RKJcմ}o} :<nN)!X{1߽5LGXg(R3'7MV4Lmd+Kݱm^W/JM~cy%Beѷ\䇞rB֧p> 8ܯ+Yv8Llaj]Խ_ h'gS/$[g%5݇UEȵiF4*i Ubrp2YHߍ,R:lBuele,- zGJ2X h2 nB_!$T#|nQ\htz֫3"B,w)(1:e3:)(1:d512:S҂3LaZT)+!:fSG$$;$:[ ;_u\nnhfu>tl*Z{}:w,tdĘz,rGx6+nGaiB{"ؗm fo "ݯC;BKqп4* v 5lp I{L'˜v SM!cWs4ǜFL.;άF NW8E2J@yWP*gh}H~L._$(S"µ˜;irͣ:UX1KdQEQ(? է>T)bӐ_aG;"vihI/ QЀ 22-aii ~(х&M$7xs_|7Y}mC>Ay[_slٞDgRJvrhAjF2H14xs&A<Ϟ4RS)(1:p257:x*]nR؁FCʩ}CU~6iJk#YDŽJ4Be. } bV}|W),qJyM߂:Y@!W/?0CDgCgd Cnװ|߁oO3*)"g8=e-Tt{1C%}r#zsy zge{;fa/ 瘑@?9'pi갗ݓ|6)(1:q257:B3"7GM>sec)CsR`fGt55|D YקՇu`0ygR=$8RhIa(mS(}yNf [ cHM] Awj?k"R!R5Ɲ 4i.3MTx`^*S;LS [5M12>G&T 礙c#\}Kix*Ib]zxmĊAԁr@E҅RGj@K)(1:u257:ºhJg!>Rx TfГ¿5y\"b|D{zpn"ŒG+;!# I1%$L(z=75sIid*r~K$YWP-$"SFz cҜ]l2 m6N9DT*EOF l dmNJ+j炇nW0bb|D{zpn"ŒG+;!# I1%$L(z=75sIid*r~K$YWP-$"SFz cҜ]l2 m6N9DT*EOF l dmNJ+j炇nW0b94w!_ڱ D0?ކS7Z6锄NvX|]ct1H,=~]u %cy5D6d]Yp1قx?gI(R7Y!)j^6 6뫺,,8)θWge?,9 W8K;}KZ?0IdUmseh8).QmNEy2NkI=:i*w\xjWhD&E[p^gN1pYbk{h Y6v`0ߚgL3? %"/ό1:_257:cUZM,O&h1^iX"G7VjA+S+6ȸp)9@</d`{x{p)"9BH9wrBLVRّGJx /ISiW<8fg=Ls61Q9t%UI>xLE_{:x2sHcȴo~~u_@1Or8^=ֳ׵3G5IeYb) NFdVо/5@r"n3g#_A[1:_257:*͌KKBo*䎳k.`%$Zd[\T۷p ˨U zqqh]2}FͤZ N:2 #Z\-l'R}{W}R>Ng~UFJ#jQpt/`axKHuRU/= Chx*:BVPQ@E:ZyV*Ѣ!"ZEqփרVCkY篘I*ހIHߍ,R:lBuele,- zGJ2X h2 nB_!$T#|nQ\htz֫3"B,w*Schleuder TestUser 8"XF=   cPDuC(%_v I¸gٰd6v h+R卞]cnV2ِ7vk&@Xp)+'ƣ2N` 19GmR6NVwl]5HNUZ(Rѐ?[iZE}lZܜfA gS!'Rl)ÐdwAG%fq- goz+GxEȉN)~é[}qXh= qŊŻ1ӱt9V n2]ٖ6Q%ޛl'sqh \d_ kx妆- ?e61ƢTCv36/?[!1qDYq[X_ϱ#"ZXu-!L|ְ XF=0̖WuŊ|Cj`5T(LV4px \z|fR:ByGE=ls"7DzM֞,@~5dt2i2Y>b|D{zpn"ŒG+;!# I1%$L(z=75sIid*r~K$YWP-$"SFz cҜ]l2 m6N9DT*EOF l dmNJ+j炇nW0b0%.!8!I ,7>MJûr( G.O3\ \$-I͜mՀ h]4O@PyZqWqAkCH)C}_YLtj_̵!El.nlz/0+!$r^VkY }+ ;E/iVA%:@m:`Q;dӾ]Ѩjk}+p d#c: ߗ' a:9'\QOfs$yB32d# y'uOnBug1@rΒ4zxj> B٧?׹C\{2gG^ U ~:?Ⓙ-{ TmQKM")*]|e g.a:+nschleuder-3.4.1/spec/gnupg/random_seed000066400000000000000000000011301353765016400177570ustar00rootroot00000000000000&~)|ie/^wFQ|8$٭lad>"hȪf -d`!Qcwvc rm zi e T} W n,ʮi>riD?5b󢾭hlD0K'<5sccxYMȺA.)ƅ`6/&Y@1M)dC.Ã~~=W=,/V u)w9d%1QX JA "FA)]lMtaXL,`OXK=+3+B:|Dko^k9Ű_n, (|&r8Hߍ,R:lBuele,- zGJ2X h2 nB_!$T#|nQ\htz֫3"B,wS҂3LaZT)+!:fSG$$;$:[ ;_u\nnhfu>tl*Z{}:w,tdĘz,rGx6+nGaiB{"ؗm fo "ݯC;BKqп4* v 5lp I{L'˜v SM!cWs4ǜFL.;άF NW8E2J@yWP*gh}H~L._$(S"µ˜;irͣ:UX1KdQEQ(? է>T)bӐ_aG;"vihI/ QЀ 22-aii ~(х&M$7xs_|7Y}mC>Ay[_slٞDgRJvrhAjF2H14xs&A<Ϟ4RSx*]nR؁FCʩ}CU~6iJk#YDŽJ4Be. } bV}|W),qJyM߂:Y@!W/?0CDgCgd Cnװ|߁oO3*)"g8=e-Tt{1C%}r#zsy zge{;fa/ 瘑@?9'pi갗ݓ|6B3"7GM>sec)CsR`fGt55|D YקՇu`0ygR=$8RhIa(mS(}yNf [ cHM] Awj?k"R!R5Ɲ 4i.3MTx`^*S;LS [5M12>G&T 礙c#\}Kix*Ib]zxmĊAԁr@E҅RGj@KºhJg!>Rx TfГ¿5y\"8"XF=   cPDuC(%_v I¸gٰd6v h+R卞]cnV2ِ7vk&@Xp)+'ƣ2N` 19GmR6NVwl]5HNUZ(Rѐ?[iZE}lZܜfA gS!'Rl)ÐdwAG%fq- goz+GxEȉN)~é[}qXh= qŊŻ1ӱt9V n2]ٖ6Q%ޛl'sqh \d_ kx妆- ?e61ƢTCv36/?[!1qDYq[X_ϱ#"ZXu-!L|ְXF=0̖WuŊ|Cj`5T(LV4px \z|fR:ByGE=ls"7DzM֞,@~5dt2i2Y>b|D{zpn"ŒG+;!# I1%$L(z=75sIid*r~K$YWP-$"SFz cҜ]l2 m6N9DT*EOF l dmNJ+j炇nW0b94w!_ڱ D0?ކS7Z6锄NvX|]ct1H,=~]u %cy5D6d]Yp1قx?gI(R7Y!)j^6 6뫺,,8)θWge?,9 W8K;}KZ?0IdUmseh8).QmNEy2NkI=:i*w\xjWhD&E[p^gN1pYbk{h Y6v`0ߚgL3? %"/όcUZM,O&h1^iX"G7VjA+S+6ȸp)9@</d`{x{p)"9BH9wrBLVRّGJx /ISiW<8fg=Ls61Q9t%UI>xLE_{:x2sHcȴo~~u_@1Or8^=ֳ׵3G5IeYb) NFdVо/5@r"n3g#_A[*͌KKBo*䎳k.`%$Zd[\T۷p ˨U zqqh]2}FͤZ N:2 #Z\-l'R}{W}R>Ng~UFJ#jQpt/`axKHuRU/= Chx*:BVPQ@E:ZyV*Ѣ!"ZEqփרVCkY篘I*ހI0%.!8!I ,7>MJûr( G.O3\ \$-I͜mՀ h]4O@PyZqWqAkCH)C}_YLtj_̵!El.nlz/0+!$r^VkY }+ ;E/iVA%:@m:`Q;dӾ]Ѩjk}+p d#c: ߗ' a:9'\QOfs$yB32d# y'uOnBug1@rΒ4zxj> B٧?׹C\{2gG^ U ~:?Ⓙ-{ TmQKM")*]|e g.a:+nschleuder-3.4.1/spec/gnupg/trustdb.gpg000066400000000000000000000025201353765016400177460ustar00rootroot00000000000000gpgXFH  5^*/H_n[ `mEGρ7-xt Y"ǂYcPDu! KM'L \ 9[܇schleuder-3.4.1/spec/helpers/000077500000000000000000000000001353765016400161035ustar00rootroot00000000000000schleuder-3.4.1/spec/helpers/api_daemon_spec_helper.rb000066400000000000000000000005041353765016400230740ustar00rootroot00000000000000ENV['RACK_ENV'] = 'test' require 'spec_helper' require 'rack/test' require 'schleuder-api-daemon' module RSpecMixin include Rack::Test::Methods def app() SchleuderApiDaemon end end RSpec.configure { |c| c.include RSpecMixin } # For RSpec 2.x and 3.x def authorize! basic_authorize 'schleuder', 'test_api_key' end schleuder-3.4.1/spec/migrations/000077500000000000000000000000001353765016400166155ustar00rootroot00000000000000schleuder-3.4.1/spec/migrations/add_sig_enc_to_headers_to_meta_defaults_spec.rb000066400000000000000000000060251353765016400301720ustar00rootroot00000000000000require 'spec_helper' describe 'AddSigEncToHeadersToMetaDefaults' do let(:migrations_paths) { 'db/migrate' } let(:migration_under_test) { 20180110203100 } let(:previous_migration) { 20170713215059 } describe 'up' do it 'sets the column defaults' do ActiveRecord::Migrator.migrate(migrations_paths, previous_migration) list_klass = create_list_klass list_klass.reset_column_information ActiveRecord::Migrator.migrate(migrations_paths, migration_under_test) list_klass.reset_column_information expect(list_klass.column_defaults['headers_to_meta']).to eql(["from", "to", "cc", "date", "sig", "enc"]) end it 'adds sig and enc to headers_to_meta for lists wihtout the attributes' do ActiveRecord::Migrator.migrate(migrations_paths, previous_migration) list_klass = create_list_klass list = create(:list, headers_to_meta: list_klass.column_defaults['headers_to_meta']) expect(list.headers_to_meta).not_to include('enc', 'sig') ActiveRecord::Migrator.migrate(migrations_paths, migration_under_test) list_klass.reset_column_information list.reload expect(list.headers_to_meta).to include('enc', 'sig') end it 'does not add sig and enc to headers to meta if the attributes already exist' do headers_to_meta_including_sig_and_enc = ["from", "to", "cc", "date", "sig", "enc"] ActiveRecord::Migrator.migrate(migrations_paths, previous_migration) list_klass = create_list_klass list = create(:list, headers_to_meta: headers_to_meta_including_sig_and_enc) expect(list.headers_to_meta).to eql headers_to_meta_including_sig_and_enc ActiveRecord::Migrator.migrate(migrations_paths, migration_under_test) list_klass.reset_column_information list.reload expect(list.headers_to_meta).to eql headers_to_meta_including_sig_and_enc end end describe 'down' do it 'sets the column defaults' do ActiveRecord::Migrator.migrate(migrations_paths, migration_under_test) list_klass = create_list_klass list_klass.reset_column_information ActiveRecord::Migrator.migrate(migrations_paths, previous_migration) list_klass.reset_column_information expect(list_klass.column_defaults['headers_to_meta']).to eql(["from", "to", "cc", "date"]) end it 'removes sig and enc from headers_to_meta from existing lists' do ActiveRecord::Migrator.migrate(migrations_paths, migration_under_test) list_klass = create_list_klass list_klass.reset_column_information list = create(:list, headers_to_meta: list_klass.column_defaults['headers_to_meta']) expect(list.headers_to_meta).to include('enc', 'sig') ActiveRecord::Migrator.migrate(migrations_paths, previous_migration) list_klass.reset_column_information list.reload expect(list.headers_to_meta).not_to include('enc', 'sig') end end def create_list_klass Class.new(ActiveRecord::Base) do self.table_name = 'lists' self.serialize :headers_to_meta, JSON end end end schleuder-3.4.1/spec/schleuder-api-daemon/000077500000000000000000000000001353765016400204275ustar00rootroot00000000000000schleuder-3.4.1/spec/schleuder-api-daemon/requests/000077500000000000000000000000001353765016400223025ustar00rootroot00000000000000schleuder-3.4.1/spec/schleuder-api-daemon/requests/authorization_spec.rb000066400000000000000000000006701353765016400265440ustar00rootroot00000000000000require 'helpers/api_daemon_spec_helper' describe 'authorization via api' do it 'allows un-authorized access to /status.json' do get '/status.json' expect(last_response).to be_ok end it 'blocks un-authorized access to other URLs' do get '/lists.json' expect(last_response.status).to be(401) end it 'allows authorized access' do authorize! get '/status.json' expect(last_response).to be_ok end end schleuder-3.4.1/spec/schleuder-api-daemon/requests/keys_spec.rb000066400000000000000000000111531353765016400246150ustar00rootroot00000000000000require 'helpers/api_daemon_spec_helper' describe 'keys via api' do before :each do @list = List.last || create(:list) end context 'list' do it 'doesn\'t list keys without authorization' do get "/keys.json?list_id=#{@list.id}" expect(last_response.status).to be 401 end it 'does list keys with authorization' do authorize! get "/keys.json?list_id=#{@list.id}" expect(last_response.status).to be 200 expect(JSON.parse(last_response.body).length).to be 1 end end context 'check' do it 'doesn\'t check keys without authorization' do get "/keys/check_keys.json?list_id=#{@list.id}" expect(last_response.status).to be 401 end it 'does check keys with authorization' do @list.import_key(File.read("spec/fixtures/revoked_key.txt")) @list.import_key(File.read("spec/fixtures/signonly_key.txt")) @list.import_key(File.read("spec/fixtures/expired_key.txt")) authorize! get "/keys/check_keys.json?list_id=#{@list.id}" expect(last_response.status).to be 200 result = JSON.parse(last_response.body)['result'] expect(result).to include("This key is expired:\n0x98769E8A1091F36BD88403ECF71A3F8412D83889") expect(result).to include("This key is revoked:\n0x7E783CDE6D1EFE6D2409739C098AC83A4C0028E9") expect(result).to include("This key is not capable of encryption:\n0xB1CD8BB15C2673C6BFD8FA4B70B2CF29E01AD53E") @list.delete_key('0x70B2CF29E01AD53E') @list.delete_key('0x098AC83A4C0028E9') @list.delete_key('0x70B2CF29E01AD53E') end end context 'export' do it 'doesn\'t export keys without authorization' do get "/keys.json?list_id=#{@list.id}" expect(last_response.status).to be 401 end it 'does list keys with authorization' do authorize! get "/keys.json?list_id=#{@list.id}" expect(last_response.status).to be 200 expect(JSON.parse(last_response.body).length).to be 1 end end context 'import' do it 'doesn\'t import keys without authorization' do parameters = {'list_id' => @list.id, 'keymaterial' => File.read('spec/fixtures/bla_foo_key.txt') } expect { post '/keys.json', parameters.to_json expect(last_response.status).to be 401 }.to change{ @list.keys.length }.by 0 end it 'does list keys with authorization' do authorize! parameters = {'list_id' => @list.id, 'keymaterial' => File.read('spec/fixtures/bla_foo_key.txt') } expect { post '/keys.json', parameters.to_json expect(last_response.status).to be 200 }.to change{ @list.keys.length }.by 1 @list.delete_key('0xEBDBE899251F2412') end end context 'delete' do before(:each) do @list.import_key(File.read("spec/fixtures/bla_foo_key.txt")) end it 'doesn\'t delete keys without authorization' do expect { delete "/keys/0xEBDBE899251F2412.json?list_id=#{@list.id}" expect(last_response.status).to be 401 }.to change{ @list.keys.length }.by 0 @list.delete_key('0xEBDBE899251F2412') end it 'does delete keys with authorization' do authorize! expect { delete "/keys/0xEBDBE899251F2412.json?list_id=#{@list.id}" expect(last_response.status).to be 200 }.to change{ @list.keys.length }.by(-1) end end context 'a key with broken utf8 in uid' do context 'already imported' do before(:each) do @list.import_key(File.read("spec/fixtures/broken_utf8_uid_key.txt")) end after(:each) do @list.delete_key('0x1242F6E13D8EBE4A') end it 'does list this key' do authorize! get "/keys.json?list_id=#{@list.id}" expect(last_response.status).to be 200 expect(JSON.parse(last_response.body).length).to be 2 end it 'does get key' do authorize! get "/keys/0x1242F6E13D8EBE4A.json?list_id=#{@list.id}" expect(last_response.status).to be 200 expect(JSON.parse(last_response.body)['fingerprint']).to eq("3102B29989BEE703AE5ED62E1242F6E13D8EBE4A") end it 'does delete key' do authorize! expect { delete "/keys/0x1242F6E13D8EBE4A.json?list_id=#{@list.id}" expect(last_response.status).to be 200 }.to change{ @list.keys.length }.by(-1) end end it 'does add key' do authorize! parameters = {'list_id' => @list.id, 'keymaterial' => File.read('spec/fixtures/broken_utf8_uid_key.txt') } expect { post '/keys.json', parameters.to_json expect(last_response.status).to be 200 }.to change{ @list.keys.length }.by(1) end end end schleuder-3.4.1/spec/schleuder-api-daemon/requests/list_spec.rb000066400000000000000000000016001353765016400246110ustar00rootroot00000000000000require 'helpers/api_daemon_spec_helper' describe 'lists via api' do it 'creates a list' do authorize! list = create(:list) parameters = { email: 'new_testlist@example.com', fingerprint: list.fingerprint } expect { post '/lists.json', parameters.to_json expect(last_response.status).to be 200 }.to change { List.count }.by 1 end it 'shows a list' do authorize! list = create(:list) get "lists/#{list.id}.json" expect(last_response.status).to be 200 expect(JSON.parse(last_response.body)['email']).to eq list.email end it 'correctly finds a list by email-address that starts with a number' do authorize! list = create(:list, email: "9list@hostname") get "lists/#{list.email}.json" expect(last_response.status).to be 200 expect(JSON.parse(last_response.body)['email']).to eq list.email end end schleuder-3.4.1/spec/schleuder-api-daemon/requests/status_spec.rb000066400000000000000000000002751353765016400251700ustar00rootroot00000000000000require 'helpers/api_daemon_spec_helper' describe 'status' do it 'returns status code 200' do authorize! get '/status.json' expect(last_response.status).to be 200 end end schleuder-3.4.1/spec/schleuder-api-daemon/requests/subscription_spec.rb000066400000000000000000000045451353765016400263750ustar00rootroot00000000000000require 'helpers/api_daemon_spec_helper' describe 'subscription via api' do before :each do @list = List.last || create(:list) @email = create(:subscription).email end it 'doesn\'t subscribe new member without authorization' do parameters = {'list_id' => @list.id, :email => @email} expect { post '/subscriptions.json', parameters.to_json expect(last_response.status).to be 401 }.to change { Subscription.count }.by 0 end it 'subscribes new member to a list' do authorize! parameters = {'list_id' => @list.id, :email => @email} expect { post '/subscriptions.json', parameters.to_json expect(last_response.status).to be 201 }.to change { Subscription.count }.by 1 expect(Subscription.where(:email => @email).first.admin?).to be false expect(Subscription.where(:email => @email).first.delivery_enabled).to be true end it 'subscribes an admin user' do authorize! parameters = {'list_id' => @list.id, :email => @email, :admin => true} expect { post '/subscriptions.json', parameters.to_json expect(last_response.status).to be 201 }.to change { Subscription.count }.by 1 expect(Subscription.where(:email => @email).first.admin?).to be true end it 'subscribes an admin user with a truthy value' do authorize! parameters = {'list_id' => @list.id, :email => @email, :admin => '1'} expect { post '/subscriptions.json', parameters.to_json expect(last_response.status).to be 201 }.to change { Subscription.count }.by 1 expect(Subscription.where(:email => @email).first.admin?).to be true end it 'subscribes an user and unsets delivery flag' do authorize! parameters = {'list_id' => @list.id, :email => @email, :delivery_enabled => false} expect { post '/subscriptions.json', parameters.to_json expect(last_response.status).to be 201 }.to change { Subscription.count }.by 1 expect(Subscription.where(:email => @email).first.delivery_enabled).to be false end it 'unsubscribes members' do authorize! subscription = create(:subscription, :list_id => @list.id) parameters = {'list_id' => @list.id, :email => @email, :delivery_enabled => false} expect { delete "/subscriptions/#{subscription.id}.json" expect(last_response.status).to be 200 }.to change { Subscription.count }.by -1 end end schleuder-3.4.1/spec/schleuder-api-daemon/requests/version_spec.rb000066400000000000000000000004341353765016400253270ustar00rootroot00000000000000require 'helpers/api_daemon_spec_helper' describe 'version' do it 'returns the current schleuder version' do authorize! get '/version.json' expect(last_response.status).to be 200 expect(last_response.body).to eq "{\"version\":\"#{Schleuder::VERSION}\"}" end end schleuder-3.4.1/spec/schleuder-certificate.pem000066400000000000000000000023611353765016400214040ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDeDCCAmCgAwIBAgIBADANBgkqhkiG9w0BAQsFADA1MQswCQYDVQQGEwJNVzES MBAGA1UECgwJU2NobGV1ZGVyMRIwEAYDVQQLDAlzY2hsZXVkZXIwHhcNMTcwNjA3 MTU1NDU2WhcNMjcwNjA1MTU1NDU2WjA1MQswCQYDVQQGEwJNVzESMBAGA1UECgwJ U2NobGV1ZGVyMRIwEAYDVQQLDAlzY2hsZXVkZXIwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQCkEknthf/N325Yo2Vi+7JAXyewBmgAN+fQapB8KAba8BrN 8TIKi7otJ4GA2TNyYtSlXnEUslXu9YHSevDgrJpnzE0pnOmbDA21yyAZk1kG4NMO fnxw0pjyLIcOF+Do2PCI6K5ipKUZParGiBcdaJpJhRkePw5ILpGu5B3VwgLlQ+H/ vl0dC2+dLrEIVrTs5aUw5EFx/CS5i0KnYDo0K/jvDeFN2xa2tlfVOcQ9ZtVwe+Pl Rl2Kue/RcyIwfE/0M9ijys9xC7lrfmJdeBZ0i0sRoz/eTH5BAUwAGQeMAX4C58Yz qHg9WxIPUsFCvHGqIqg8IWjvfX8s2u6HEpk5HWcrAgMBAAGjgZIwgY8wDwYDVR0T AQH/BAUwAwEB/zAdBgNVHQ4EFgQUB/5P+h1owyWXwRKYnnsFrUyyYEswXQYDVR0j BFYwVIAUB/5P+h1owyWXwRKYnnsFrUyyYEuhOaQ3MDUxCzAJBgNVBAYTAk1XMRIw EAYDVQQKDAlTY2hsZXVkZXIxEjAQBgNVBAsMCXNjaGxldWRlcoIBADANBgkqhkiG 9w0BAQsFAAOCAQEAHRz4AmJWrOe9ieIh8I+qAqUsXtX9Hgh/2RvlNTNs/NvSHb8d xgrncM2ULzWeq+y7Egj61+85x2xXSplgruUFW04Rql01fzBAFafJqk2SvHEzdLIx 1mzHth/d7mFFMg0HYT4iiwhiklH3jrDJHT7EUunRQQEhgunWdXw57BiAppG2bW0U J9LHcGMsxjR6JxbgwQoCZRUBwiGAKd5P4dLc7sELx+wdjU+ChHNf2c7kzVi//Oa2 Ky7jgEuQ0TWmCzAKtx8L1WDu8x0I1QoGrUV0mLVkoCt8nPjDhbxM4hQq/QgkNZ/C wK+nz/RKC99U6r8eaofY2MJA8lr1+69JfzkelQ== -----END CERTIFICATE----- schleuder-3.4.1/spec/schleuder-private-key.pem000066400000000000000000000032171353765016400213630ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEApBJJ7YX/zd9uWKNlYvuyQF8nsAZoADfn0GqQfCgG2vAazfEy Cou6LSeBgNkzcmLUpV5xFLJV7vWB0nrw4KyaZ8xNKZzpmwwNtcsgGZNZBuDTDn58 cNKY8iyHDhfg6NjwiOiuYqSlGT2qxogXHWiaSYUZHj8OSC6RruQd1cIC5UPh/75d HQtvnS6xCFa07OWlMORBcfwkuYtCp2A6NCv47w3hTdsWtrZX1TnEPWbVcHvj5UZd irnv0XMiMHxP9DPYo8rPcQu5a35iXXgWdItLEaM/3kx+QQFMABkHjAF+AufGM6h4 PVsSD1LBQrxxqiKoPCFo731/LNruhxKZOR1nKwIDAQABAoIBAQCYF5wQYzdOUOCp qk5CA7Cpm4ve0RF3oltyCFcHwNMaAZnXbs9El2JumUCjgLUARD17TqDk3qxqZ4uA 4haJL3ey4OBmwt6KrBHJhBKtornUdnUv6nDQ5WiClmRb3CbRssjHIWsGZjnlvBSj FWTYDi94F7nBIBLNNt41kaFWlhK5E0fsPwd5efvNbZpJSwDPZ/iG1dwyZhCGmfu2 HeziDRDxBNDRvPFdwj2l4Oty55vqLXJuQ4nHIHk+DkPd4RtLn8xLFLdm1iFbFSFd k7/0bqpzF3+8o0lXIY3jTKH/Z1C57NAW3enjVADRuqKNZjzIsBKKCF9u/ZkfCLMC EvPlyhYBAoGBANdtJ2O4pgBho6r4Pe/iyeSTS8knr6AU+ViQMfti6qHbBH918wCa ohj5tlEovZedwaKTB58hNy6U7CT5uVK5L8LKXqBYul97ONWlSumj2J++cAsnPCyx p+zYjUm3KI/jUx/vGccOsd1eMGvhYXWBkRU9JG4qL+12W2SgE7Q+XEWxAoGBAML5 CpYt2JGE27HkQBnyaApQAARKShQhUrsHohApZpyZGkwbIqmmjEaCY1nqk70s1LLg nU1vmlnRXNetIhUcj+JtnHKJDqMEvZM/eFxQIXKdsVcD6WQf2u9ZU31TW8uH5G88 eadEHCRrTe/xvEyHlDlIXUkEJwmJDF+36QZQ/sWbAoGBAMNwA4w0uGUgL5usGoTG +uKjvt1/Y5WXcZ8nMjEeTD8Ks8nu98ZUgzqlUQHQNDCYrlMPkJqNR8K62IGzDK4/ 01Skw7Q0yuBUqfspOg082AoUexGjRrRFeFMnIwb9Y48mbQNLp9cvPa3XBZbZodE4 +qaKEcLgAxsrhT6E+1tKN+wBAoGAD5SBEREmzjIUsDlyGeCyCajs52rcUpF7H/Dz NWFpjrf5Tv2YHoBtkzDWKZhCKArOEGE8kLSLXAQL7Dwsjg1TPh/OMaTcI5C8aWjY AGBy28rYIgDxBIw7HYdA0bH4kuIQEgd+HSynJw3gE314s5Dd+lnbAnuvduaZs4hp uZR9V2MCgYB8mmk+KEiD2I3hgkBZSk6eKf1NJOrYO4xdwCb42XArERO4PtiQBdP2 VfsXhPnkkrZSDJ3/vZGw+uiI7u5UD8fl3ATIRMeYYqyiTeFM4ELPToF6hDhtcf/q l0fkQB4Je9KT8zqCDkvytvDjTTXGrxDEPTEPL1OymX4SiIRJaRSEQw== -----END RSA PRIVATE KEY----- schleuder-3.4.1/spec/schleuder.yml000066400000000000000000000006751353765016400171520ustar00rootroot00000000000000database: test: adapter: sqlite3 database: <%= ENV["SCHLEUDER_DB_PATH"] || 'db/test.sqlite3' %> lists_dir: <%= ENV["SCHLEUDER_TMP_DIR"] || '/tmp/schleuder-test/' %> listlogs_dir: <%= ENV["SCHLEUDER_TMP_DIR"] || '/tmp/schleuder-test/' %> smtp_settings: port: 2523 keyserver: hkp://127.0.0.1:9999 api: tls_cert_file: 'spec/schleuder-certificate.pem' tls_key_file: 'spec/schleuder-private-key.pem' valid_api_keys: 'test_api_key' schleuder-3.4.1/spec/schleuder/000077500000000000000000000000001353765016400164175ustar00rootroot00000000000000schleuder-3.4.1/spec/schleuder/errors_spec.rb000066400000000000000000000066041353765016400213000ustar00rootroot00000000000000require 'spec_helper' describe "Errors" do def signoff t("errors.signoff") end it "::MessageNotFromAdmin shows sensible string in response to to_s()" do expect(Errors::MessageNotFromAdmin.new.to_s).to eql(t('errors.message_not_from_admin') + signoff) end it "::MessageSenderNotSubscribed shows sensible string in response to to_s()" do expect(Errors::MessageSenderNotSubscribed.new.to_s).to eql(t('errors.message_sender_not_subscribed') + signoff) end it "::MessageUnauthenticated shows sensible string in response to to_s()" do expect(Errors::MessageUnauthenticated.new.to_s).to eql(t('errors.message_unauthenticated') + signoff) end it "::MessageUnencrypted shows sensible string in response to to_s()" do expect(Errors::MessageUnencrypted.new.to_s).to eql(t('errors.message_unencrypted') + signoff) end it "::MessageUnsigned shows sensible string in response to to_s()" do expect(Errors::MessageUnsigned.new.to_s).to eql(t('errors.message_unsigned') + signoff) end it "::LoadingListSettingsFailed shows sensible string in response to to_s()" do expect(Errors::LoadingListSettingsFailed.new.to_s).to eql(t('errors.loading_list_settings_failed', config_file: ENV['SCHLEUDER_LIST_DEFAULTS']) + signoff) end it "::DecryptionFailed shows sensible string in response to to_s()" do list = create(:list) expect(Errors::DecryptionFailed.new(list).to_s).to eql(t("errors.decryption_failed", { key: list.key.to_s, email: list.sendkey_address }) + signoff) end it "::KeyAdduidFailed shows sensible string in response to to_s()" do expect(Errors::KeyAdduidFailed.new('bla').to_s).to eql(t('errors.key_adduid_failed', { errmsg: 'bla' }) + signoff) end it "::KeyGenerationFailed shows sensible string in response to to_s()" do list = create(:list) expect(Errors::KeyGenerationFailed.new(list.listdir, list.email).to_s).to eql(t('errors.key_generation_failed', {listdir: list.listdir, listname: list.email}) + signoff) end it "::KeywordAdminOnly shows sensible string in response to to_s()" do expect(Errors::KeywordAdminOnly.new('bla').to_s).to eql(t('errors.keyword_admin_only', keyword: 'bla') + signoff) end it "::ListNotFound shows sensible string in response to to_s()" do list = create(:list) expect(Errors::ListNotFound.new(list.email).to_s).to eql(t('errors.list_not_found', email: list.email) + signoff) end it "::ListdirProblem shows sensible string in response to to_s()" do list = create(:list) expect(Errors::ListdirProblem.new(list.listdir, 'not_empty').to_s).to eql(t('errors.listdir_problem.message', dir: list.listdir, problem: t("errors.listdir_problem.not_empty")) + signoff) end it "::MessageEmpty shows sensible string in response to to_s()" do list = create(:list) expect(Errors::MessageEmpty.new(list).to_s).to eql(t('errors.message_empty', { request_address: list.request_address }) + signoff) end it "::MessageTooBig shows sensible string in response to to_s()" do list = create(:list) expect(Errors::MessageTooBig.new(list).to_s).to eql(t('errors.message_too_big', { allowed_size: list.max_message_size_kb }) + signoff) end it "::TooManyKeys shows sensible string in response to to_s()" do list = create(:list) expect(Errors::TooManyKeys.new(list.listdir, list.email).to_s).to eql(t('errors.too_many_keys', {listdir: list.listdir, listname: list.email}) + signoff) end end schleuder-3.4.1/spec/schleuder/integration/000077500000000000000000000000001353765016400207425ustar00rootroot00000000000000schleuder-3.4.1/spec/schleuder/integration/cli_spec.rb000066400000000000000000000324121353765016400230520ustar00rootroot00000000000000require "spec_helper" describe 'cli' do context "migrates a v2-list to v3.0" do it 'creates the list' do v2list_path = 'spec/fixtures/v2list' output = run_cli("migrate #{v2list_path}") list = Schleuder::List.by_recipient('v2list@example.org') expect(output).to be_present expect(list).to be_present end it "imports the public keys" do v2list_path = 'spec/fixtures/v2list' output = run_cli("migrate #{v2list_path}") list = Schleuder::List.by_recipient('v2list@example.org') expect(output).not_to match('Error:') keys = list.keys.map(&:fingerprint) expect(list.key.fingerprint).to eq '0392CF72B345256BB730049789226FD6A42B2A7A' expect(keys).to include 'C4D60F8833789C7CAA44496FD3FFA6613AB10ECE' end it "imports the secret key" do v2list_path = 'spec/fixtures/v2list' output = run_cli("migrate #{v2list_path}") list = Schleuder::List.by_recipient('v2list@example.org') expect(output).not_to match('Error:') expect(list.secret_key).to be_present expect(list.secret_key.fingerprint).to eq '0392CF72B345256BB730049789226FD6A42B2A7A' signed = GPGME::Crypto.new(:armor => true).clearsign('lala').read expect(signed).to match(/^-----BEGIN PGP SIGNED MESSAGE-----\n.*\n\nlala\n-----BEGIN PGP SIGNATURE-----\n.*\n-----END PGP SIGNATURE-----\n$/m) end it "imports the config" do v2list_path = 'spec/fixtures/v2list' output = run_cli("migrate #{v2list_path}") list = Schleuder::List.by_recipient('v2list@example.org') expect(output).not_to match('Error:') expect(list.to_s).to eq 'v2list@example.org' expect(list.log_level).to eq 'warn' expect(list.fingerprint).to eq '0392CF72B345256BB730049789226FD6A42B2A7A' expect(list.keywords_admin_only).to eq %w[subscribe unsubscribe delete-key] expect(list.keywords_admin_notify).to eq %w[add-key unsubscribe] expect(list.send_encrypted_only).to eq false expect(list.receive_encrypted_only).to eq false expect(list.receive_signed_only).to eq false expect(list.receive_authenticated_only).to eq false expect(list.receive_from_subscribed_emailaddresses_only).to eq false expect(list.receive_admin_only).to eq false expect(list.keep_msgid).to eq true expect(list.bounces_drop_all).to eq false expect(list.bounces_notify_admins).to eq true expect(list.include_list_headers).to eq true expect(list.include_openpgp_header).to eq true expect(list.openpgp_header_preference).to eq 'signencrypt' expect(list.headers_to_meta).to eq %w[from to cc date] expect(list.bounces_drop_on_headers).to eq({'x-spam-flag' => "yes"}) expect(list.subject_prefix).to eq '[v2]' expect(list.subject_prefix_in).to eq '[in]' expect(list.subject_prefix_out).to eq '[out]' expect(list.max_message_size_kb).to eq 10240 expect(list.public_footer).to eq "-- \nfooter" expect(list.internal_footer).to be_nil end it "imports the subscriptions" do v2list_path = 'spec/fixtures/v2list' output = run_cli("migrate #{v2list_path}") list = Schleuder::List.by_recipient('v2list@example.org') admins_emails = list.admins.map(&:email) subscription_emails = list.subscriptions.map(&:email) expect(output).not_to match('Error:') expect(admins_emails).to eql(["schleuder2@example.org"]) expect(subscription_emails).to eql(["anotherone@example.org", "anyone@example.org", "bla@foo", "old@example.org", "schleuder2@example.org", "someone@example.org"]) expect(list.subscriptions.where(email: "anotherone@example.org").first.fingerprint).to eql('') expect(list.subscriptions.where(email: "anyone@example.org").first.fingerprint).to eql("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(list.subscriptions.where(email: "bla@foo").first.fingerprint).to eql("87E65ED2081AE3D16BE4F0A5EBDBE899251F2412") expect(list.subscriptions.where(email: "old@example.org").first.fingerprint).to eql("6EE51D78FD0B33DE65CCF69D2104E20E20889F66") expect(list.subscriptions.where(email: "schleuder2@example.org").first.fingerprint).to eql("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(list.subscriptions.where(email: "someone@example.org").first.fingerprint).to eql('') end it "does not fail on duplicated v2 subscriptions" do v2list_path = 'spec/fixtures/v2list_duplicate_members' output = run_cli("migrate #{v2list_path}") expect(output).not_to match('Error:') list = Schleuder::List.by_recipient('v2list@example.org') subscription_emails = list.subscriptions.map(&:email) expect(subscription_emails).to eq ['schleuder2@example.org'] end it "respects non delivery status of admins" do v2list_path = 'spec/fixtures/v2list_admin_non_delivery' output = run_cli("migrate #{v2list_path}") expect(output).not_to match('Error:') list = Schleuder::List.by_recipient('v2list@example.org') subscriptions = list.subscriptions expect(subscriptions.find{|s| s.email == 'schleuder2@example.org' }.delivery_enabled).to eq false subscription_emails = subscriptions.map(&:email) expect(subscription_emails.sort).to eq(['schleuder2@example.org', 'schleuder2-member@example.org'].sort) end it "does not fail on admin without key" do v2list_path = 'spec/fixtures/v2list_admin_without_key' output = run_cli("migrate #{v2list_path}") expect(output).not_to match('Error:') list = Schleuder::List.by_recipient('v2list@example.org') admin_emails = list.admins.map(&:email) expect(admin_emails.sort).to eq( ['schleuder2@example.org', 'schleuder2-nokey@example.org' ].sort) end it "warns about file system permissions if it was run as root" do expect(Process).to receive(:euid).and_return(0) v2list_path = 'spec/fixtures/v2list' orig_stdout = $stdout $stdout = StringIO.new Cli.new.migrate_v2_list(v2list_path) output = $stdout.string $stdout = orig_stdout expect(output).to include("Warning: this process was run as root") end end context '#refresh_keys' do it 'updates keys from the keyserver' do list = create(:list) list.subscribe("admin@example.org", nil, true) list.import_key(File.read("spec/fixtures/expired_key.txt")) list.import_key(File.read("spec/fixtures/olduid_key.txt")) with_sks_mock do Cli.new.refresh_keys dirmngr_pid = `pgrep -a dirmngr | grep #{list.listdir}`.split(' ',2).first expect(dirmngr_pid).to be_nil end mail = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } b = mail.first_plaintext_part.body.to_s expect(b).to match(/Refreshing all keys from the keyring of list #{list.email} resulted in this:\n\n/) expect(b).to match(/\nThis key was updated \(new signatures\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]\n/) expect(b).to match(/\nThis key was updated \(new user-IDs and new signatures\):\n0x6EE51D78FD0B33DE65CCF69D2104E20E20889F66 new@example.org \d{4}-\d{2}-\d{2}\n/) teardown_list_and_mailer(list) end it 'updates keys from the keyserver for only a specific list' do list1 = create(:list) list2 = create(:list) [list1,list2].each do |list| list.subscribe("admin@example.org", nil, true) list.import_key(File.read("spec/fixtures/expired_key.txt")) list.import_key(File.read("spec/fixtures/olduid_key.txt")) end with_sks_mock do Cli.new.refresh_keys list1.email end mail = Mail::TestMailer.deliveries.find { |message| message.to == [list1.admins.first.email] } b = mail.first_plaintext_part.body.to_s expect(b).to match(/Refreshing all keys from the keyring of list #{list1.email} resulted in this:\n\n/) expect(b).to match(/\nThis key was updated \(new signatures\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]\n/) expect(b).to match(/\nThis key was updated \(new user-IDs and new signatures\):\n0x6EE51D78FD0B33DE65CCF69D2104E20E20889F66 new@example.org \d{4}-\d{2}-\d{2}\n/) teardown_list_and_mailer(list1) teardown_list_and_mailer(list2) end it 'reports errors from refreshing keys' do list = create(:list) list.subscribe("admin@example.org", nil, true) list.import_key(File.read("spec/fixtures/expired_key.txt")) Cli.new.refresh_keys mail = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } expect(mail.to_s).to include("Refreshing all keys from the keyring of list #{list.email} resulted in this") if GPGME::Ctx.sufficient_gpg_version?('2.1') expect(mail.to_s).to include("keyserver refresh failed: No keyserver available") else # The wording differs slightly among versions. expect(mail.to_s).to match(/gpgkeys: .* error .* connect/) end teardown_list_and_mailer(list) end it "warns about file system permissions if it was run as root" do expect(Process).to receive(:euid).and_return(0) list = create(:list) orig_stdout = $stdout $stdout = StringIO.new Cli.new.refresh_keys(list.email) output = $stdout.string $stdout = orig_stdout expect(output).to include("Warning: this process was run as root") end end context '#pin_keys' do it 'pins fingerprints on not yet set keys' do list = create(:list) list.subscribe("admin@example.org", nil, true) list.subscribe("schleuder2@example.org", nil, false) list.import_key(File.read('spec/fixtures/example_key.txt')) expect(list.subscriptions_without_fingerprint.size).to eq 2 Cli.new.pin_keys expect(list.subscriptions_without_fingerprint.size).to eq 1 expect(list.subscriptions_without_fingerprint.collect(&:email)).to eq ['admin@example.org'] mail = Mail::TestMailer.deliveries.first expect(Mail::TestMailer.deliveries.length).to eq 1 expect(mail.first_plaintext_part.body.to_s).to eql("While checking all subscriptions of list #{list.email} we were pinning a matching key for the following subscriptions:\n\nschleuder2@example.org: C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") teardown_list_and_mailer(list) end it 'only works on the specific list' do list1 = create(:list) list2 = create(:list) [list1,list2].each do |list| list.subscribe("admin@example.org", nil, true) list.subscribe("schleuder2@example.org", nil, false) list.import_key(File.read('spec/fixtures/example_key.txt')) expect(list.subscriptions_without_fingerprint.size).to eq 2 end Cli.new.pin_keys list1.email expect(list1.subscriptions_without_fingerprint.size).to eq 1 expect(list1.subscriptions_without_fingerprint.collect(&:email)).to eq ['admin@example.org'] expect(list2.subscriptions_without_fingerprint.size).to eq 2 mail = Mail::TestMailer.deliveries.first expect(Mail::TestMailer.deliveries.length).to eq 1 expect(mail.first_plaintext_part.body.to_s).to eql("While checking all subscriptions of list #{list1.email} we were pinning a matching key for the following subscriptions:\n\nschleuder2@example.org: C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") teardown_list_and_mailer(list1) teardown_list_and_mailer(list2) end it 'does not report anything if nothing was done' do list = create(:list) list.subscribe("admin@example.org", nil, true) list.subscribe("schleuder2@example.org", nil, false) expect(list.subscriptions_without_fingerprint.size).to eq 2 Cli.new.pin_keys expect(list.subscriptions_without_fingerprint.size).to eq 2 expect(Mail::TestMailer.deliveries.empty?).to eq true teardown_list_and_mailer(list) end end context '#install' do it 'exits if a shell-process failed' do dbfile = Conf.database["database"] tmp_filename = "#{dbfile}.tmp" File.rename(dbfile, tmp_filename) FileUtils.touch dbfile begin Cli.new.install rescue SystemExit => exc end expect(exc).to be_present expect(exc.status).to eql(1) File.rename(tmp_filename, dbfile) end it "warns about file system permissions if it was run as root" do expect(Process).to receive(:euid).and_return(0) orig_stdout = $stdout $stdout = StringIO.new Cli.new.install output = $stdout.string $stdout = orig_stdout expect(output).to include("Warning: this process was run as root") end end context '#commands' do it 'exits with a status code of 1 in case the command is not implemented' do run_cli('not-implemented') expect($?.exitstatus).to eq(1) end end context '#check_keys' do it "warns about file system permissions if it was run as root" do expect(Process).to receive(:euid).and_return(0) orig_stdout = $stdout $stdout = StringIO.new Cli.new.check_keys output = $stdout.string $stdout = orig_stdout expect(output).to include("Warning: this process was run as root") end end end schleuder-3.4.1/spec/schleuder/integration/dash_addresses_spec.rb000066400000000000000000000124511353765016400252600ustar00rootroot00000000000000require "spec_helper" describe 'someone sends an email to a listname-dash-address' do it "sends the list's key as reply to -sendkey" do list = create(:list) list.subscribe("schleuder@example.org", nil, true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.sendkey_address mail.from = 'outside@example.org' mail.body = 'The key, please!' mail.subject = 'key' mail.deliver mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(mail.to_s, list.sendkey_address) expect(output).to be_nil message = Mail::TestMailer.deliveries.first expect(message.to).to eql(['outside@example.org']) signed_message_parts = message.parts[0].parts expect(signed_message_parts.first.body.to_s).to eql('Find the key for this address attached.') expect(message.parts[0].attachments.first.body.to_s).to include(list.fingerprint) expect(message.parts[0].attachments.first.body.to_s).to include('-----BEGIN PGP PUBLIC KEY BLOCK-----') expect(message.in_reply_to).to eql(mail.message_id) end it "forwards the message to the admins if extension is -owner" do list = create(:list) # owner needs a key so they get email list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.subscribe("admin@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.owner_address mail.from = 'outside@example.org' mail.body = 'Please contact me directly!' mail.subject = 'help' mail.deliver mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(mail.to_s, list.owner_address) expect(output).to be_nil raw_msgs = Mail::TestMailer.deliveries raw_msgs.sort_by { |msg| msg.to.first } # reparse the messages so we decrypt and remove all the craft # for easier parsing afterwards message1, message2 = raw_msgs[0..1].collect{|m| Mail.create_message_to_list(m.to_s, list.email, list).setup } expect(message1.to).to eql(['admin@example.org']) expect(message1.subject).to eql('help') expect(message1.parts.first.body.to_s).to include('From: outside@example.org') expect(message1.parts.first.body.to_s).to include('Note: The following message was received for the list-owners.') expect(message1.parts.last.body.to_s).to eql('Please contact me directly!') expect(message2.to).to eql(['schleuder@example.org']) expect(message2.subject).to eql('help') expect(message2.parts.first.body.to_s).to include('From: outside@example.org') expect(message2.parts.first.body.to_s).to include('Note: The following message was received for the list-owners.') expect(message2.parts.last.body.to_s).to eql('Please contact me directly!') end it "forwards the message to the admins if extension is -bounce" do list = create(:list) list.subscribe("admin@example.org", nil, true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.bounce_address mail.from = 'mailer-daemon@example.org' mail.body = 'delivery failure' mail.subject = 'something' mail.deliver mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(mail.to_s, list.bounce_address) expect(output).to be_nil message = Mail::TestMailer.deliveries.first expect(message.to).to eql(['admin@example.org']) expect(message.subject).to eql(I18n.t('automated_message_subject')) signed_message_parts = message.parts[0].parts expect(signed_message_parts.first.body.to_s).to eql(I18n.t('forward_automated_message_to_admins')) expect(signed_message_parts.last.mime_type).to eql('message/rfc822') expect(signed_message_parts.last.body.to_s).to include('From: mailer-daemon@example.org') expect(signed_message_parts.last.body.to_s).to include(mail.message_id) expect(signed_message_parts.last.body.to_s).to include("Subject: something") expect(signed_message_parts.last.body.to_s).to include("delivery failure") end it "forwards the message to the admins if extension is -bounce and it's a real bounce mail" do list = create(:list) list.subscribe("admin@example.org", nil, true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new(File.read('spec/fixtures/mails/bounce.eml')) mail.to = list.owner_address mail.deliver mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(mail.to_s, list.bounce_address) expect(output).to be_nil message = Mail::TestMailer.deliveries.first expect(message.to).to eql(['admin@example.org']) expect(message.subject).to eql(I18n.t('automated_message_subject')) signed_message_parts = message.parts[0].parts expect(signed_message_parts.first.body.to_s).to eql(I18n.t('forward_automated_message_to_admins')) expect(signed_message_parts.last.mime_type).to eql('message/rfc822') expect(signed_message_parts.last.body.to_s).to include('Mailer-Daemon@schleuder.example.org') expect(signed_message_parts.last.body.to_s).to include(mail.message_id) expect(signed_message_parts.last.body.to_s).to include("Subject: bounce test") expect(signed_message_parts.last.body.to_s).to include("mailbox is full") end end schleuder-3.4.1/spec/schleuder/integration/filters_spec.rb000066400000000000000000000117611353765016400237570ustar00rootroot00000000000000require "spec_helper" describe "running filters" do context '.max_message_size' do it "bounces to big mails" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = '+' * (1024 * list.max_message_size_kb) mail.deliver big_email = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(big_email.to_s, list.email) expect(output.message).to include(I18n.t('errors.message_too_big', { allowed_size: list.max_message_size_kb })) teardown_list_and_mailer(list) end end context '.fix_exchange_messages!' do it "accepts an invalid pgp/mime Exchange message" do list = create(:list) list.subscribe("admin@example.org", nil, true) # so we can easily parse the outgoing mail list.send_encrypted_only = false list.save start_smtp_daemon message_path = 'spec/fixtures/mails/exchange.eml' error = run_schleuder(:work, list.email, message_path) mails = Dir.glob("#{smtp_daemon_outputdir}/mail-*") expect(error).to be_empty expect(mails.size).to eq 1 exchange = Mail.read(mails.first) expect(exchange.to).to eql(["admin@example.org"]) expect(exchange.body.to_s).to include("foo\n") stop_smtp_daemon end it "accepts a valid plain-text message" do list = create(:list) list.subscribe("admin@example.org", nil, true) # so we can easily parse the outgoing mail list.send_encrypted_only = false list.save start_smtp_daemon message_path = 'spec/fixtures/mails/exchange_no_parts.eml' error = run_schleuder(:work, list.email, message_path) mails = Dir.glob("#{smtp_daemon_outputdir}/mail-*") expect(error).to be_empty expect(mails.size).to eq 1 exchange = Mail.read(mails.first) expect(exchange.to).to eql(["admin@example.org"]) expect(exchange.body.to_s).to include("bla-vla") stop_smtp_daemon end end context '.strip_html_from_alternative!' do it "strips HTML-part from multipart/alternative-message that contains ascii-armored PGP-data" do list = create(:list) list.subscribe("admin@example.org", nil, true) # so we can easily parse the outgoing mail list.send_encrypted_only = false list.save start_smtp_daemon mail = Mail.new mail.to = list.email mail.from = 'outside@example.org' content = encrypt_string(list, "blabla") mail.text_part = content mail.html_part = "

#{content}

" mail.subject = "test" error = nil with_tmpfile(mail.to_s) do |fn| error = run_schleuder(:work, list.email, fn) end mails = Dir.glob("#{smtp_daemon_outputdir}/mail-*") expect(error).to be_empty expect(mails.size).to eq 1 htmlmail = Mail.read(mails.first) expect(htmlmail.to).to eql(["admin@example.org"]) signed_parts = htmlmail.parts[0].parts expect(signed_parts[0].body.to_s).to include("Note: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.") # why is this double wrapped? expect(signed_parts[1].parts[0][:content_type].content_type).to eql("text/plain") expect(signed_parts[1].parts[0].body.to_s).to eql("blabla\n") stop_smtp_daemon end it "does NOT strip HTML-part from multipart/alternative-message that does NOT contain ascii-armored PGP-data" do list = create(:list) list.subscribe("admin@example.org", nil, true) # so we can easily parse the outgoing mail list.send_encrypted_only = false list.save start_smtp_daemon mail = Mail.new mail.to = list.email mail.from = 'outside@example.org' content = "blabla" mail.text_part = content mail.html_part = "

#{content}

" mail.subject = "test" error = nil with_tmpfile(mail.to_s) do |fn| error = run_schleuder(:work, list.email, fn) end mails = Dir.glob("#{smtp_daemon_outputdir}/mail-*") expect(error).to be_empty expect(mails.size).to eq 1 htmlmail = Mail.read(mails.first) expect(htmlmail.to).to eql(["admin@example.org"]) # this is double wrapped signed_parts = htmlmail.parts[0].parts[1].parts expect(signed_parts[0][:content_type].content_type).to eql("text/plain") expect(signed_parts[0].body.to_s).to eql("blabla") expect(signed_parts[1][:content_type].content_type).to eql("text/html") expect(signed_parts[1].body.to_s).to eql("

blabla

") stop_smtp_daemon end end end schleuder-3.4.1/spec/schleuder/integration/keywords_spec.rb000066400000000000000000003347121353765016400241620ustar00rootroot00000000000000# coding: utf-8 require "spec_helper" describe "user sends keyword" do it "x-subscribe without attributes" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been subscribed") expect(message.to_s).to match(/Fingerprint:\s*$/) expect(message.to_s).to include("Admin? false") expect(message.to_s).to include("Email-delivery enabled? true") expect(subscription).to be_present expect(subscription.fingerprint).to be_blank expect(subscription.admin).to eql(false) expect(subscription.delivery_enabled).to eql(true) teardown_list_and_mailer(list) end it "x-subscribe with attributes" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org 0x#{list.fingerprint} true false" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been subscribed") expect(message.to_s).to match(/Fingerprint:\s+#{list.fingerprint}/) expect(message.to_s).to include("Admin? true") expect(message.to_s).to include("Email-delivery enabled? false") expect(subscription).to be_present expect(subscription.fingerprint).to eql(list.fingerprint) expect(subscription.admin).to eql(true) expect(subscription.delivery_enabled).to eql(false) teardown_list_and_mailer(list) end it "x-subscribe with one attribute and spaces-separated fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org 0x#{list.fingerprint.dup.insert(4, ' ')} true" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been subscribed") expect(message.to_s).to match(/Fingerprint:\s+#{list.fingerprint}/) expect(message.to_s).to include("Admin? true") expect(message.to_s).to include("Email-delivery enabled? true") expect(subscription).to be_present expect(subscription.fingerprint).to eql(list.fingerprint) expect(subscription.admin).to eql(true) expect(subscription.delivery_enabled).to eql(true) teardown_list_and_mailer(list) end it "x-subscribe without attributes, but with spaces-separated fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org 0x#{list.fingerprint.dup.insert(4, ' ')}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been subscribed") expect(message.to_s).to match(/Fingerprint:\s+#{list.fingerprint}/) expect(message.to_s).to include("Admin? false") expect(message.to_s).to include("Email-delivery enabled? true") expect(subscription).to be_present expect(subscription.fingerprint).to eql(list.fingerprint) expect(subscription.admin).to eql(false) expect(subscription.delivery_enabled).to eql(true) teardown_list_and_mailer(list) end it "x-subscribe with attributes and spaces-separated fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org 0x#{list.fingerprint.dup.insert(4, ' ')} true false" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been subscribed") expect(message.to_s).to match(/Fingerprint:\s+#{list.fingerprint}/) expect(message.to_s).to include("Admin? true") expect(message.to_s).to include("Email-delivery enabled? false") expect(subscription).to be_present expect(subscription.fingerprint).to eql(list.fingerprint) expect(subscription.admin).to eql(true) expect(subscription.delivery_enabled).to eql(false) teardown_list_and_mailer(list) end it "x-subscribe with attributes (first one 'false') and spaces-separated fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org 0x#{list.fingerprint.dup.insert(4, ' ')} false false" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been subscribed") expect(message.to_s).to match(/Fingerprint:\s+#{list.fingerprint}/) expect(message.to_s).to include("Admin? false") expect(message.to_s).to include("Email-delivery enabled? false") expect(subscription).to be_present expect(subscription.fingerprint).to eql(list.fingerprint) expect(subscription.admin).to eql(false) expect(subscription.delivery_enabled).to eql(false) teardown_list_and_mailer(list) end it "x-subscribe with attributes (last one 'true') and spaces-separated fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE: test@example.org 0x#{list.fingerprint.dup.insert(4, ' ')} false true" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been subscribed") expect(message.to_s).to match(/Fingerprint:\s+#{list.fingerprint}/) expect(message.to_s).to include("Admin? false") expect(message.to_s).to include("Email-delivery enabled? true") expect(subscription).to be_present expect(subscription.fingerprint).to eql(list.fingerprint) expect(subscription.admin).to eql(false) expect(subscription.delivery_enabled).to eql(true) teardown_list_and_mailer(list) end it "x-subscribe without arguments" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-SUBSCRIBE:" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).not_to include("translation missing") expect(message.first_plaintext_part.body.to_s).to eql(I18n.t("plugins.subscription_management.subscribe_requires_arguments")) expect(subscription).to be_blank teardown_list_and_mailer(list) end it "x-unsubscribe without argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list.subscribe("admin@example.org", 'C4D60F8833789C7CAA44496FD3FFA6613AB10ECE', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = 'schleuder@example.org' gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: '59C71FB38AEE22E091C78259D06350440F759BD3' } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-UNSUBSCRIBE:" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("schleuder@example.org has been unsubscribed") expect(subscription).to be_blank teardown_list_and_mailer(list) end it "x-unsubscribe with invalid argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-UNSUBSCRIBE: test@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org is not subscribed") teardown_list_and_mailer(list) end it "x-unsubscribe" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.subscribe("test@example.org") ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-UNSUBSCRIBE: test@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("test@example.org has been unsubscribed") expect(subscription).to be_blank teardown_list_and_mailer(list) end it "x-unsubscribe doesn't unsubscribe last admin" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.subscribe("test@example.org") ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-UNSUBSCRIBE: schleuder@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to eql(I18n.t('plugins.subscription_management.cannot_unsubscribe_last_admin', email: 'schleuder@example.org')) expect(list.subscriptions.size).to be(2) teardown_list_and_mailer(list) end it "x-set-fingerprint with own email-address and valid fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: schleuder@example.org C4D60F8833789C7CAA44496FD3FFA6613AB10ECE" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Fingerprint for schleuder@example.org set to C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(subscription).to be_present expect(subscription.fingerprint).to eql('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') teardown_list_and_mailer(list) end it "x-set-fingerprint with own email-address and valid, spaces-separated fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: schleuder@example.org C4D6 0F88 3378 9C7C AA44 496F D3FF A661 3AB1 0ECE" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Fingerprint for schleuder@example.org set to C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(subscription).to be_present expect(subscription.fingerprint).to eql('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') teardown_list_and_mailer(list) end it "x-set-fingerprint without email-address and with valid fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: C4D60F8833789C7CAA44496FD3FFA6613AB10ECE" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Fingerprint for schleuder@example.org set to C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(subscription).to be_present expect(subscription.fingerprint).to eql('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') teardown_list_and_mailer(list) end it "x-set-fingerprint with other email-address and valid fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.subscribe('test@example.org') list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: test@example.org C4D60F8833789C7CAA44496FD3FFA6613AB10ECE" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Fingerprint for test@example.org set to C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(subscription).to be_present expect(subscription.fingerprint).to eql('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') teardown_list_and_mailer(list) end it "x-set-fingerprint with other email-address and valid fingerprint as non-admin" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3') list.subscribe("test@example.org", 'C4D60F8833789C7CAA44496FD3FFA6613AB10ECE', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = 'schleuder@example.org' gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: '59C71FB38AEE22E091C78259D06350440F759BD3' } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: test@example.org 59C71FB38AEE22E091C78259D06350440F759BD3" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Only admins may set fingerprints of subscriptions other than their own") expect(subscription).to be_present expect(subscription.fingerprint).to eql('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') teardown_list_and_mailer(list) end it "x-set-fingerprint with email-address but without fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: schleuder@example.org " mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to eql(I18n.t( "plugins.subscription_management.set_fingerprint_requires_valid_fingerprint", fingerprint: '' )) expect(subscription).to be_present expect(subscription.fingerprint).to eql('59C71FB38AEE22E091C78259D06350440F759BD3') teardown_list_and_mailer(list) end it "x-set-fingerprint with email-address but without valid fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: schleuder@example.org 59C71FB38AEE22E091C78259D0" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) # arguments are downcased when parsed expect(message.first_plaintext_part.body.to_s).to eql(I18n.t( "plugins.subscription_management.set_fingerprint_requires_valid_fingerprint", fingerprint: '59C71FB38AEE22E091C78259D0'.downcase )) expect(subscription).to be_present expect(subscription.fingerprint).to eql('59C71FB38AEE22E091C78259D06350440F759BD3') teardown_list_and_mailer(list) end it "x-set-fingerprint without email-address and with invalid fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: blaBLA" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) # arguments are downcased when parsed expect(message.first_plaintext_part.body.to_s).to eql(I18n.t( "plugins.subscription_management.set_fingerprint_requires_valid_fingerprint", fingerprint: 'blaBLA'.downcase )) expect(subscription.fingerprint).to eql('59C71FB38AEE22E091C78259D06350440F759BD3') teardown_list_and_mailer(list) end it "x-set-fingerprint with not-subscribed email-address and valid fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: bla@example.org C4D60F8833789C7CAA44496FD3FFA6613AB10ECE" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("bla@example.org is not subscribed") teardown_list_and_mailer(list) end it "x-set-fingerprint without argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-set-fingerprint: " mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to eql(I18n.t("plugins.subscription_management.set_fingerprint_requires_arguments")) teardown_list_and_mailer(list) end it "x-unset-fingerprint without argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-unset-fingerprint: " mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to eql(I18n.t("plugins.subscription_management.unset_fingerprint_requires_arguments")) teardown_list_and_mailer(list) end it "x-unset-fingerprint with other email-address as admin" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.subscribe('test@example.org','C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-unset-fingerprint: test@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Fingerprint for test@example.org removed.") expect(subscription).to be_present expect(subscription.fingerprint.blank?).to be_truthy teardown_list_and_mailer(list) end it "x-unset-fingerprint with own email-address as admin but without force" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-unset-fingerprint: schleuder@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to eql(I18n.t("plugins.subscription_management.unset_fingerprint_requires_arguments")) expect(subscription).to be_present expect(subscription.fingerprint).to eql('59C71FB38AEE22E091C78259D06350440F759BD3') teardown_list_and_mailer(list) end it "x-unset-fingerprint with own email-address as admin and force" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-unset-fingerprint: schleuder@example.org force" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'schleuder@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Fingerprint for schleuder@example.org removed.") expect(subscription).to be_present expect(subscription.fingerprint.blank?).to be_truthy teardown_list_and_mailer(list) end it "x-unset-fingerprint with not-subscribed email-address" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-unset-fingerprint: bla@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("bla@example.org is not subscribed") teardown_list_and_mailer(list) end it "x-unset-fingerprint with other email-address as non-admin" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3') list.subscribe("test@example.org", 'C4D60F8833789C7CAA44496FD3FFA6613AB10ECE', true) list.import_key(File.read('spec/fixtures/example_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = 'schleuder@example.org' gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: '59C71FB38AEE22E091C78259D06350440F759BD3' } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-unset-fingerprint: test@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup subscription = list.subscriptions.where(email: 'test@example.org').first expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Only admins may remove fingerprints of subscriptions other than their own") expect(subscription).to be_present expect(subscription.fingerprint).to eql('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') teardown_list_and_mailer(list) end it "x-list-subscriptions without arguments" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-subscriptions:" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s.lines.size).to eql(3) expect(message.to_s).to include("schleuder@example.org 0x59C71FB38AEE22E091C78259D06350440F759BD3") teardown_list_and_mailer(list) end it "x-list-subscriptions without arguments but with admin-notification" do list = create(:list, keywords_admin_notify: ['list-subscriptions']) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.subscribe("user@example.org") ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-subscriptions:" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first notification = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup raw = Mail::TestMailer.deliveries[1] response = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expected_text = "Subscriptions:\n\nschleuder@example.org\t0x59C71FB38AEE22E091C78259D06350440F759BD3\nuser@example.org" expect(Mail::TestMailer.deliveries.size).to eql(2) expect(notification.to).to eql(['schleuder@example.org']) expect(notification.first_plaintext_part.body.to_s).to eql("schleuder@example.org sent the keyword 'list-subscriptions' and received this response:\n\n#{expected_text}") expect(response.to).to eql(['schleuder@example.org']) expect(response.first_plaintext_part.body.to_s).to eql(expected_text) teardown_list_and_mailer(list) end it "x-list-subscriptions with matching argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-subscriptions: example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s.lines.size).to eql(3) expect(message.to_s).to include("schleuder@example.org 0x59C71FB38AEE22E091C78259D06350440F759BD3") teardown_list_and_mailer(list) end it "x-list-subscriptions with non-matching argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-subscriptions: blabla" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s.lines.size).to eql(1) expect(message.to_s).to include("Your message resulted in no output") teardown_list_and_mailer(list) end it "x-add-key with inline key-material" do list = create(:list, keywords_admin_notify: []) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) keymaterial = File.read('spec/fixtures/example_key.txt') mail.body = "x-list-name: #{list.email}\nX-ADD-KEY:\n#{keymaterial}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num + 1) expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to match(/^This key was newly added:\n0xC4D60F8833789C7CAA44496FD3FFA6613AB10ECE schleuder2@example.org \d{4}-\d{2}-\d{2}\n$/) teardown_list_and_mailer(list) end it "x-add-key with attached key-material" do list = create(:list, keywords_admin_notify: []) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-ADD-KEY:" mail.add_file('spec/fixtures/example_key.txt') mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num + 1) expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to match(/^This key was newly added:\n0xC4D60F8833789C7CAA44496FD3FFA6613AB10ECE schleuder2@example.org \d{4}-\d{2}-\d{2}\n$/) teardown_list_and_mailer(list) end it "x-add-key to update a key" do list = create(:list, keywords_admin_notify: []) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read("spec/fixtures/expired_key.txt")) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-ADD-KEY:" mail.add_file("spec/fixtures/expired_key_extended.txt") mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to match(/^This key was updated:\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]\n$/) teardown_list_and_mailer(list) end it "x-add-key with garbage as key-material" do list = create(:list, keywords_admin_notify: []) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-ADD-KEY:\nlakdsjflaksjdflakjsdflkajsdf" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to eql("In the message you sent us, no keys could be found. :(") teardown_list_and_mailer(list) end it "x-fetch-key with invalid input" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: lala!" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to_s).to include("Invalid input.") teardown_list_and_mailer(list) end it "x-fetch-key with email address" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: admin@example.org" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num + 1) expect(message.first_plaintext_part.body.to_s).to match(/This key was fetched \(new key\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]\n/) teardown_list_and_mailer(list) end it "x-fetch-key with unknown email-address" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: something@localhost" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to_s).to include("Fetching something@localhost did not succeed") teardown_list_and_mailer(list) end it "x-fetch-key with URL" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: http://127.0.0.1:9999/keys/example.asc" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num + 1) expect(message.first_plaintext_part.body.to_s).to match(/This key was fetched \(new key\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]\n/) teardown_list_and_mailer(list) end it "x-fetch-key with invalid URL" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) url = "http://127.0.0.1:9999/foo" mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: #{url}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to_s).to include("Fetching #{url} did not succeed") teardown_list_and_mailer(list) end it "x-fetch-key with unknown fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: 0x0000000000000000000000000000000000000000" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to_s).to include("Fetching 0x0000000000000000000000000000000000000000 did not succeed") teardown_list_and_mailer(list) end it "x-fetch-key with fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: 0x98769E8A1091F36BD88403ECF71A3F8412D83889" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num + 1) expect(message.first_plaintext_part.body.to_s).to match(/This key was fetched \(new key\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]\n/) teardown_list_and_mailer(list) end it "x-fetch-key with fingerprint of unchanged key" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: 0x59C71FB38AEE22E091C78259D06350440F759BD3" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.first_plaintext_part.body.to_s).to match(/This key was fetched \(unchanged\):\n0x59C71FB38AEE22E091C78259D06350440F759BD3 schleuder@example.org \d{4}-\d{2}-\d{2}/) teardown_list_and_mailer(list) end it "x-fetch-key without arguments" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-fetch-KEY: " mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear with_sks_mock do begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end end raw = Mail::TestMailer.deliveries.find { |message| message.to == [list.admins.first.email] } message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to_s).not_to include("translation missing") expect(message.first_plaintext_part.body.to_s).to eql(I18n.t("plugins.key_management.fetch_key_requires_arguments")) teardown_list_and_mailer(list) end it "x-resend" do list = create(:list, public_footer: "-- \nblablabla") list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: #{list.email}\nX-resend: someone@example.org\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first resent_message = raw.verify resent_message_body = resent_message.parts.map { |p| p.body.to_s }.join raw = Mail::TestMailer.deliveries.last message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Resent: Unencrypted to someone@example.org") expect(resent_message.to).to include("someone@example.org") expect(resent_message.to_s).not_to include("Resent: Unencrypted to someone@example.org") expect(resent_message_body).to eql(content_body + list.public_footer.to_s) teardown_list_and_mailer(list) end it "does not parse keywords once the mail body started" do list = create(:list, public_footer: "-- \nblablabla") list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = < list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: #{list.email}\nX-resend: someone@example.org\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first resent_message = raw.verify resent_message_body = resent_message.parts.map { |p| p.body.to_s }.join expect(resent_message_body).not_to include(list.internal_footer) expect(resent_message_body).to eql(content_body + list.public_footer.to_s) teardown_list_and_mailer(list) end it "x-resend with iso-8859-1 body" do list = create(:list, public_footer: "-- \nblablabla") list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again! ¡Hola!\r\n" mail.charset = 'iso-8859-1' mail.body = "x-list-name: #{list.email}\nX-resend: someone@example.org\n#{content_body}".encode('iso-8859-1') mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first resent_message = raw.verify raw = Mail::TestMailer.deliveries.last message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Resent: Unencrypted to someone@example.org") expect(message.parts[1].body.to_s.force_encoding(message.parts[1].charset)).to eql(content_body.encode(message.parts[1].charset)) expect(resent_message.to).to include("someone@example.org") expect(resent_message.to_s).not_to include("Resent: Unencrypted to someone@example.org") expect(resent_message.parts[0].body.to_s).to eql(content_body.encode(resent_message.parts[0].charset)) expect(resent_message.parts[1].body.to_s).to eql(list.public_footer.to_s) teardown_list_and_mailer(list) end it "x-resend with utf-8 body and umlauts" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "This is a test\r\nAnd here are some umlauts:ÄäÖöÜüß" mail.charset = 'utf-8' mail.body = "x-list-name: #{list.email}\nX-resend: someone@example.org\n#{content_body}".encode('utf-8') mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first resent_message = raw.verify raw = Mail::TestMailer.deliveries.last message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Resent: Unencrypted to someone@example.org") expect(message.parts[1].body.to_s.force_encoding(message.parts[1].charset)).to eql(content_body.encode(message.parts[1].charset)) expect(resent_message.to).to include("someone@example.org") expect(resent_message.to_s).not_to include("Resent: Unencrypted to someone@example.org") expect(resent_message.parts[0].body.to_s).to eql(content_body.encode(resent_message.parts[0].charset)) teardown_list_and_mailer(list) end it "x-resend with admin-notification" do list = create(:list, keywords_admin_notify: ['resend']) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: #{list.email}\nX-resend: someone@example.org\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries[1] notification = Mail.create_message_to_list(raw.to_s, list.email, list).setup raw = Mail::TestMailer.deliveries[2] message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(Mail::TestMailer.deliveries.size).to eql(3) expect(notification.to).to eql(['schleuder@example.org']) expect(notification.first_plaintext_part.body.to_s).to eql("schleuder@example.org used the keyword 'resend' with the values 'someone@example.org' in a message sent to the list.") expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Resent: Unencrypted to someone@example.org") teardown_list_and_mailer(list) end it "x-resend with admin-notification and admin has delivery disabled" do list = create(:list, keywords_admin_notify: ['resend']) list.subscribe("user@example.org", "59C71FB38AEE22E091C78259D06350440F759BD3") list.subscribe("admin@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true, false) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: #{list.email}\nX-resend: someone@example.org\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries[1] notification = Mail.create_message_to_list(raw.to_s, list.email, list).setup raw = Mail::TestMailer.deliveries[2] message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(Mail::TestMailer.deliveries.size).to eql(3) expect(notification.to).to eql(['admin@example.org']) expect(notification.first_plaintext_part.body.to_s).to eql("admin@example.org used the keyword 'resend' with the values 'someone@example.org' in a message sent to the list.") expect(message.to).to eql(['user@example.org']) expect(message.to_s).to include("Resent: Unencrypted to someone@example.org") teardown_list_and_mailer(list) end it "x-resend without x-list-name" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "X-resend: someone@example.org\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).not_to include("Resent: Unencrypted to someone@example.org") expect(message.to_s).to include(%[Your message did not contain the required "X-LIST-NAME" keyword and was rejected.]) teardown_list_and_mailer(list) end it "x-resend with two matching keys, one of which is expired" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir list.import_key(File.read("spec/fixtures/expired_key.txt")) list.import_key(File.read("spec/fixtures/bla_foo_key.txt")) mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: #{list.email}\nX-resend-encrypted-only: bla@foo\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end resent_message = Mail::TestMailer.deliveries.first raw = Mail::TestMailer.deliveries.last message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(list.keys('bla@foo').size).to eql(2) expect(resent_message.to).to eql(['bla@foo']) expect(resent_message.content_type).to match(/^multipart\/encrypted.*application\/pgp-encrypted/) expect(message.first_plaintext_part.body.to_s).to include("Resent: Encrypted to bla@foo (87E65ED2081AE3D16BE4F0A5EBDBE899251F2412)") teardown_list_and_mailer(list) end it "x-resend-unencrypted with matching key" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir list.import_key(File.read("spec/fixtures/bla_foo_key.txt")) mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: #{list.email}\nX-resend-unencrypted: bla@foo\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end resent_message = Mail::TestMailer.deliveries.first raw = Mail::TestMailer.deliveries.last message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(list.keys('bla@foo').size).to eql(1) expect(resent_message.to).to eql(['bla@foo']) expect(resent_message.content_type).to_not match(/^multipart\/encrypted.*application\/pgp-encrypted/) expect(resent_message.first_plaintext_part.body.to_s).to include('Hello again!') expect(message.first_plaintext_part.body.to_s).to include("Resent: Unencrypted to bla@foo") teardown_list_and_mailer(list) end it "x-resend with expired key" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir list.import_key(File.read("spec/fixtures/expired_key.txt")) mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: #{list.email}\nX-resend-encrypted-only: bla@foo\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(list.keys('bla@foo').size).to eql(1) expect(message.first_plaintext_part.to_s).to include("Resending to failed (0 keys found") teardown_list_and_mailer(list) end it "x-resend with wrong x-list-name" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" mail.body = "x-list-name: somethingelse@example.org\nX-resend: someone@example.org\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).not_to include("Resent: Unencrypted to someone@example.org") expect(message.to_s).to include(%[Your message contained an incorrect "X-LIST-NAME" keyword. The keyword argument must match the email address of this list.]) teardown_list_and_mailer(list) end it "x-resend with invalid recipient" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "Hello again!\n" invalid_recipient = '`ls`bla' mail.body = "x-list-name: #{list.email}\nX-resend: #{invalid_recipient}\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end delivered_emails = Mail::TestMailer.deliveries raw = delivered_emails.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(delivered_emails.size).to eql(1) expect(message.to_s).not_to include("Resent: Unencrypted to someone@example.org") expect(message.to_s).to include("Error: Invalid email-address for resending: #{invalid_recipient}") teardown_list_and_mailer(list) end it "x-sign-this with inline text" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) signed_text = "signed\nsigned\nsigned\n\n" mail.body = "x-list-name: #{list.email}\nx-sign-this:\n#{signed_text}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(message.to_s.gsub("\r", '')).to match(/BEGIN PGP SIGNED MESSAGE-----\nHash: SHA(256|512)\n\n#{signed_text}-----BEGIN PGP SIGNATURE/) teardown_list_and_mailer(list) end it "x-sign-this with attachments" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) keywords = Mail::Part.new keywords.body = "\n\nx-list-name: #{list.email}\nx-sign-this:" mail.parts << keywords signed_content = File.read('spec/fixtures/example_key.txt') mail.attachments['example_key.txt'] = { mime_type: 'application/pgp-key', content: signed_content } mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup signature = message.attachments.first.body.to_s # list.gpg.verify() results in a "Bad Signature". The sign-this plugin # also uses GPGME::Crypto, apparently that makes a difference. crypto = GPGME::Crypto.new verification_string = '' crypto.verify(signature, {signed_text: signed_content}) do |sig| verification_string = sig.to_s end expect(message.to_s).to include("Find the signatures attached.") expect(message.attachments.size).to eql(1) expect(message.attachments.first.filename).to eql("example_key.txt.sig") expect(verification_string).to include("Good signature from D06350440F759BD3") teardown_list_and_mailer(list) end it "x-list-key with arbitrary email-sub-string" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-KEYs: der@ex" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to match(/pub 4096R\/59C71FB38AEE22E091C78259D06350440F759BD3 \d{4}-\d{2}-\d{2}/) expect(message.to_s.scan(/^pub /).size).to eql(1) teardown_list_and_mailer(list) end it "x-list-key with correctly prefixed email-sub-string" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-KEYs: @schleuder" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to match(/pub 4096R\/59C71FB38AEE22E091C78259D06350440F759BD3 \d{4}-\d{2}-\d{2}/) expect(message.to_s.scan(/^pub /).size).to eql(1) teardown_list_and_mailer(list) end it "x-list-key with prefixed fingerprint" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-KEYs: 0x59C71FB38AEE22E091C78259D06350440F759BD3" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to match(/pub 4096R\/59C71FB38AEE22E091C78259D06350440F759BD3 \d{4}-\d{2}-\d{2}/) expect(message.to_s.scan(/^pub /).size).to eql(1) teardown_list_and_mailer(list) end it "x-get-key with valid argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-GET-KEY: 0x59C71FB38AEE22E091C78259D06350440F759BD3" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to match(/pub 4096R\/59C71FB38AEE22E091C78259D06350440F759BD3 \d{4}-\d{2}-\d{2}/) expect(message.to_s).to include("-----BEGIN PGP PUBLIC KEY") teardown_list_and_mailer(list) end it "x-get-key with invalid argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-get-KEY: blabla" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("No match found for") expect(message.to_s).not_to include("-----BEGIN PGP PUBLIC KEY") teardown_list_and_mailer(list) end it "x-get-key with empty argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-get-KEY:" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Your message resulted in no output") expect(message.to_s).not_to include("-----BEGIN PGP PUBLIC KEY") teardown_list_and_mailer(list) end it "x-delete-key with valid argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-delete-KEY: C4D60F8833789C7CAA44496FD3FFA6613AB10ECE" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num - 1) expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to match(/^This key was deleted:\n0xC4D60F8833789C7CAA44496FD3FFA6613AB10ECE schleuder2@example.org \d{4}-\d{2}-\d{2}\n$/) teardown_list_and_mailer(list) end it "x-delete-key with invalid argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-delete-KEY: lala" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("No match found for") expect(message.to_s).not_to include("This key was deleted:") teardown_list_and_mailer(list) end it "x-delete-key with not distinctly matching argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-delete-KEY: schleuder" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to).to eql(['schleuder@example.org']) expect(message.to_s).to include("Too many matching keys for ") expect(message.to_s).not_to include("Deleted") teardown_list_and_mailer(list) end it "x-delete-key without argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-delete-KEY:" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num) expect(message.to_s).not_to include("translation missing") expect(message.first_plaintext_part.body.to_s).to eql(I18n.t("plugins.key_management.delete_key_requires_arguments")) teardown_list_and_mailer(list) end it "x-get-logfile with debug level sends non-empty logfile" do list = create(:list) list.update_attribute(:log_level, 'debug') list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-get-logfile" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.parts.last.body.to_s.lines.size).to be > 1 expect(message.parts.last.body.to_s).to include("Logfile created on") expect(message.parts.last.body.to_s).to include("DEBUG") teardown_list_and_mailer(list) end it "x-get-logfile with error-level sends empty logfile" do list = create(:list) list.update_attribute(:log_level, 'error') list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-get-logfile" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s.lines.size).to eql(1) expect(message.body.to_s).to include("Logfile created on") teardown_list_and_mailer(list) end it "x-attach-listkey" do list = create(:list, log_level: 'debug') list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.email mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.email => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) content_body = "something something list-key" mail.body = "x-list-name: #{list.email}\nX-attach-listkey\n#{content_body}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup expect(message.parts.length).to eql(2) expect(message.parts.last.parts.length).to eql(2) expect(message.parts.last.parts.first.body.to_s).to eql(content_body) expect(message.parts.last.parts.last.content_type.to_s).to eql("application/pgp-keys") expect(message.parts.last.parts.last.body.decoded).to match(/pub 4096R\/59C71FB38AEE22E091C78259D06350440F759BD3 \d{4}-\d{2}-\d{2}/) expect(message.parts.last.parts.last.body.decoded).to include("-----BEGIN PGP PUBLIC KEY BLOCK-----") expect(message.parts.last.parts.last.body.decoded).to include("mQINBFhGvz0BEADXbbTWo/PStyTznAo/f1UobY0EiVPNKNERvYua2Pnq8BwOQ5bS") teardown_list_and_mailer(list) end it "x-get-version" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-get-version" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s.lines.size).to eql(1) expect(message.first_plaintext_part.body.to_s).to eql(Schleuder::VERSION) teardown_list_and_mailer(list) end it "x-get-version with deprecated x-listname keyword" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-listname: #{list.email}\nX-get-version" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s.lines.size).to eql(1) expect(message.first_plaintext_part.body.to_s).to eql(Schleuder::VERSION) teardown_list_and_mailer(list) end it "x-get-version with delivery disabled" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true, false) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-listname: #{list.email}\nX-get-version" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s.lines.size).to eql(1) expect(message.first_plaintext_part.body.to_s).to eql(Schleuder::VERSION) teardown_list_and_mailer(list) end it "x-list-keys without arguments" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list.import_key(File.read('spec/fixtures/bla_foo_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-keys" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.first_plaintext_part.body.to_s.lines.size).to eql(16) expect(message.first_plaintext_part.body.to_s).to include("59C71FB38AEE22E091C78259D06350440F759BD3") expect(message.first_plaintext_part.body.to_s).to include("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(message.first_plaintext_part.body.to_s).to include("87E65ED2081AE3D16BE4F0A5EBDBE899251F2412") teardown_list_and_mailer(list) end it "x-list-keys with one argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list.import_key(File.read('spec/fixtures/bla_foo_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-keys schleuder2" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.first_plaintext_part.body.to_s.lines.size).to eql(4) expect(message.first_plaintext_part.body.to_s).not_to include("59C71FB38AEE22E091C78259D06350440F759BD3") expect(message.first_plaintext_part.body.to_s).to include("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(message.first_plaintext_part.body.to_s).not_to include("87E65ED2081AE3D16BE4F0A5EBDBE899251F2412") teardown_list_and_mailer(list) end it "x-list-keys with two arguments" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list.import_key(File.read('spec/fixtures/bla_foo_key.txt')) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-keys schleuder2 bla" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.first_plaintext_part.body.to_s.lines.size).to eql(10) expect(message.first_plaintext_part.body.to_s).not_to include("59C71FB38AEE22E091C78259D06350440F759BD3") expect(message.first_plaintext_part.body.to_s).to include("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(message.first_plaintext_part.body.to_s).to include("87E65ED2081AE3D16BE4F0A5EBDBE899251F2412") teardown_list_and_mailer(list) end context "with broken utf8 in key" do it "x-list-keys works" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read('spec/fixtures/example_key.txt')) list.import_key(File.read("spec/fixtures/broken_utf8_uid_key.txt")) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-list-keys" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.first_plaintext_part.body.to_s.lines.size).to eql(16) expect(message.first_plaintext_part.body.to_s).to include("59C71FB38AEE22E091C78259D06350440F759BD3") expect(message.first_plaintext_part.body.to_s).to include("3102B29989BEE703AE5ED62E1242F6E13D8EBE4A") teardown_list_and_mailer(list) end it "x-add-key with inline key-material" do list = create(:list, keywords_admin_notify: []) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list_keys_num = list.keys.size ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) keymaterial = File.read("spec/fixtures/broken_utf8_uid_key.txt") mail.body = "x-list-name: #{list.email}\nX-ADD-KEY:\n#{keymaterial}" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(list.keys.size).to eql(list_keys_num + 1) expect(message.to).to eql(['schleuder@example.org']) expect(message.first_plaintext_part.body.to_s).to match(/This key was newly added:\n0x3102B29989BEE703AE5ED62E1242F6E13D8EBE4A info@buendnis-gegen-rechts.ch \d{4}-\d{2}-\d{2}\n/) teardown_list_and_mailer(list) end it "x-get-key with valid argument" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) list.import_key(File.read("spec/fixtures/broken_utf8_uid_key.txt")) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nX-GET-KEY: 0x3102B29989BEE703AE5ED62E1242F6E13D8EBE4A" mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.to_s).to match(/pub 1024D\/3102B29989BEE703AE5ED62E1242F6E13D8EBE4A \d{4}-\d{2}-\d{2}/) expect(message.to_s).to include("-----BEGIN PGP PUBLIC KEY") teardown_list_and_mailer(list) end end end schleuder-3.4.1/spec/schleuder/integration/protected_headers_spec.rb000066400000000000000000000116601353765016400257710ustar00rootroot00000000000000require "spec_helper" describe "protected subject" do it "is not leaked" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) mail = Mail.read("spec/fixtures/mails/protected-headers.eml") mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first expect(raw.subject).to eql('Encrypted Message') teardown_list_and_mailer(list) end it "is included in mime-headers" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) mail = Mail.read("spec/fixtures/mails/protected-headers.eml") mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(raw.to_s).not_to match('Re: the real subject') expect(message.subject).to eql("Re: the real subject") expect(message.content_type_parameters['protected-headers']).to eql("v1") teardown_list_and_mailer(list) end it "is included as mime-part in body" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) mail = Mail.read("spec/fixtures/mails/protected-headers.eml") mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.parts[1].body.to_s).to eql("Subject: Re: the real subject\n") teardown_list_and_mailer(list) end it "don't block request-messages" do list = create(:list, email: 'something@example.org') list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) mail = Mail.read("spec/fixtures/mails/protected-headers-request.eml") mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.body.to_s).to include('59C71FB38AEE22E091C78259D06350440F759BD3') teardown_list_and_mailer(list) end it "works with mutt protected headers" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) mail = Mail.read("spec/fixtures/mutt_protected_headers.txt") mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.email) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.parts[1].body.to_s).to eql("Subject: x\n") expect(message.parts[2].body.to_s).to eql("test\n") teardown_list_and_mailer(list) end it "recognizes keywords in mails with protected headers and empty subject" do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = "x-list-name: #{list.email}\nx-list-keys" protected_headers = Mail::Part.new do body "Subject: protected" content_type "text/rfc822-headers; protected-headers=v1" end mail.add_part protected_headers mail.deliver encrypted_mail = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear begin Schleuder::Runner.new().run(encrypted_mail.to_s, list.request_address) rescue SystemExit end raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.request_address, list).setup expect(message.first_plaintext_part.body.to_s).to include("59C71FB38AEE22E091C78259D06350440F759BD3") expect(message.first_plaintext_part.body.to_s).to_not include("Your message didn't contain any keywords, thus there was nothing to do.") teardown_list_and_mailer(list) end end schleuder-3.4.1/spec/schleuder/integration/receive_bounce.rb000066400000000000000000000007111353765016400242430ustar00rootroot00000000000000require "spec_helper" describe "a bounce message is received" do it "from bounce example" do start_smtp_daemon list = create(:list) list.subscribe("admin@example.org", nil, true) message_path = 'spec/fixtures/mails/bounce.eml' error = run_schleuder(:work, list.email, message_path) mails = Dir.glob("#{smtp_daemon_outputdir}/mail-*") expect(error).to be_empty expect(mails.size).to eq 1 stop_smtp_daemon end end schleuder-3.4.1/spec/schleuder/integration/send_encrypted_spec.rb000066400000000000000000000011701353765016400253060ustar00rootroot00000000000000require "spec_helper" describe "user sends an encrypted message" do [ 'encrypted-inline', 'encrypted-mime', 'encrypted+signed-inline', 'encrypted+signed-mime', ].each do |t| it "from thunderbird being #{t}" do start_smtp_daemon list = create(:list) list.subscribe("admin@example.org", nil, true) message_path = "spec/fixtures/mails/#{t}/thunderbird.eml" error = run_schleuder(:work, list.email, message_path) mails = Dir.glob("#{smtp_daemon_outputdir}/mail-*") expect(error).to be_empty expect(mails.size).to eq 1 stop_smtp_daemon end end end schleuder-3.4.1/spec/schleuder/integration/send_plain_spec.rb000066400000000000000000000015341353765016400244200ustar00rootroot00000000000000require "spec_helper" describe "user sends a plain text message" do [ 'plain', 'signed-inline', 'signed-mime', ].each do |t| it "from thunderbird being #{t}" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) list.import_key(File.read('spec/fixtures/openpgpkey_52507B0163A8D9F0094FFE03B1A36F08069E55DE.asc')) mail = Mail.read("spec/fixtures/mails/#{t}/thunderbird.eml") error = nil begin Schleuder::Runner.new().run(mail.to_s, list.email) rescue SystemExit => exc error = exc end mails = Mail::TestMailer.deliveries expect(error).to be_nil expect(mails.size).to eq 1 content = mails.first.parts[0].parts[1].to_s expect(content).not_to include("-----BEGIN PGP SIGNATURE-----") end end end schleuder-3.4.1/spec/schleuder/runner_spec.rb000066400000000000000000000443361353765016400213010ustar00rootroot00000000000000require "spec_helper" describe Schleuder::Runner do describe "#run" do context "with a plain text message" do it "delivers the incoming message" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") error = Schleuder::Runner.new().run(mail, list.email) expect(Mail::TestMailer.deliveries.length).to eq 1 expect(error).to be_blank teardown_list_and_mailer(list) end it "has the correct headerlines" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.to).to eq ["admin@example.org"] expect(message.header.to_s.scan("admin@example.org").size).to eq 1 expect(message.from).to eq [list.email] teardown_list_and_mailer(list) end it "contains the specified pseudoheaders in the correct order" do list = create(:list, send_encrypted_only: false, headers_to_meta: ["from", "sig"]) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first content_part = message.parts.first pseudoheaders = "From: Nina Siessegger \nSig: Unsigned" expect(content_part.parts.first.body).to include(pseudoheaders) expect(content_part.parts.first.body).not_to include('To:') expect(content_part.parts.first.body).not_to include('Enc:') expect(content_part.parts.first.body).not_to include('Date:') teardown_list_and_mailer(list) end it "doesn't have unwanted headerlines from the original message" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.to).to eq ["admin@example.org"] expect(message.header.to_s.scan("zeromail").size).to eq 0 expect(message.header.to_s.scan("nna.local").size).to eq 0 expect(message.header.to_s.scan("80.187.107.60").size).to eq 0 expect(message.header.to_s.scan("User-Agent:").size).to eq 0 teardown_list_and_mailer(list) end it "doesn't leak the Message-Id as configured" do list = create(:list, send_encrypted_only: false, keep_msgid: false) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.header.to_s.scan("8db04406-e2ab-fd06-d4c5-c19b5765c52b@web.de").size).to eq 0 teardown_list_and_mailer(list) end it "does keep the Message-Id as configured" do list = create(:list, send_encrypted_only: false, keep_msgid: true) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.header.to_s.scan("8db04406-e2ab-fd06-d4c5-c19b5765c52b@web.de").size).to eq 1 teardown_list_and_mailer(list) end it "contains the list headers if include_list_headers is set to true" do list = create( :list, email: "superlist@example.org", send_encrypted_only: false, include_list_headers: true, ) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.header["List-Id"].to_s).to eq "" expect(message.header["List-Owner"].to_s).to eq " (Use list's public key)" expect(message.header["List-Help"].to_s).to eq "" teardown_list_and_mailer(list) end it "contains the open pgp header if include_openpgp_header is set to true" do list = create( :list, send_encrypted_only: false, include_openpgp_header: true, ) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.header["Openpgp"].to_s).to include list.fingerprint teardown_list_and_mailer(list) end it "does not deliver content if send_encrypted_only is set to true" do list = create(:list, send_encrypted_only: true) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.body).to eq "" expect(message.parts.first.body.to_s).to include "You missed an email from "\ "#{list.email} because your subscription isn't associated with a "\ "(usable) OpenPGP key. Please fix this." teardown_list_and_mailer(list) end it "includes the internal_footer" do list = create( :list, send_encrypted_only: false, internal_footer: "-- \nfor our eyes only!" ) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.parts.first.parts.last.body.to_s).to eql(list.internal_footer) teardown_list_and_mailer(list) end it "does not include the public_footer" do public_footer = "-- \nsomething public blabla" list = create( :list, send_encrypted_only: false, internal_footer: "-- \nfor our eyes only!", public_footer: public_footer ) list.subscribe("admin@example.org", nil, true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.parts.first.to_s).not_to include(list.public_footer) teardown_list_and_mailer(list) end end it "delivers a signed error message if a subscription's key is expired on a encrypted-only list" do list = create(:list, send_encrypted_only: true) list.subscribe("admin@example.org", nil, true, false) list.subscribe("expired@example.org", '98769E8A1091F36BD88403ECF71A3F8412D83889') key = File.read("spec/fixtures/expired_key.txt") list.import_key(key) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first verified = message.verify signature_fingerprints = verified.signatures.map(&:fpr) expect(Mail::TestMailer.deliveries.size).to eq 1 expect(message.to).to include('expired@example.org') expect(message.to_s).to include("You missed an email from ") expect(signature_fingerprints).to eq([list.fingerprint]) teardown_list_and_mailer(list) end it "delivers a signed error message if a subscription's key is not available on a encrypted-only list" do list = create(:list, send_encrypted_only: true) list.subscribe("admin@example.org", 'AAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDEEEEEE', true) mail = File.read("spec/fixtures/mails/plain/thunderbird.eml") Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first expect(message.to).to eq ['admin@example.org'] expect(message.to_s).to include("You missed an email from #{list.email} ") teardown_list_and_mailer(list) end it "injects pseudoheaders appropriately into an unsigned thunderbird-multipart/alternative-message" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read('spec/fixtures/mails/multipart-alternative/thunderbird-multi-alt-unsigned.eml') Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first content_part = message.parts.first expect(message.to).to eq ['admin@example.org'] expect(content_part.mime_type).to eql('multipart/mixed') expect(content_part.body).to be_blank expect(content_part.parts.size).to eql(2) expect(content_part.parts.first.mime_type).to eql('text/plain') expect(content_part.parts.first.body).to include('From: paz ') expect(content_part.parts.last.mime_type).to eql('multipart/alternative') teardown_list_and_mailer(list) end it "injects pseudoheaders appropriately into a signed multipart/alternative-message (thunderbird+enigmail-1.9) " do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read('spec/fixtures/mails/multipart-alternative/thunderbird-multi-alt-signed.eml') Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first content_part = message.parts.first expect(message.to).to eq ['admin@example.org'] expect(content_part.mime_type).to eql('multipart/mixed') expect(content_part.body).to be_blank expect(content_part.parts.size).to eql(2) expect(content_part.parts.first.mime_type).to eql('text/plain') expect(content_part.parts.first.body).to include('From: paz ') expect(content_part.parts.last.mime_type).to eql('multipart/mixed') expect(content_part.parts.last.parts.size).to eql(1) expect(content_part.parts.last.parts.first.mime_type).to eql('multipart/alternative') teardown_list_and_mailer(list) end context "Quoted-Printable encoding" do it "is handled properly in cleartext emails" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read('spec/fixtures/mails/qp-encoding-clear.eml') Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first content_part = message.parts.first expect(content_part.parts.last.content_transfer_encoding).to eql('quoted-printable') expect(content_part.parts.last.body.encoded).to include('=3D86') expect(content_part.parts.last.body.encoded).not_to include('=86') teardown_list_and_mailer(list) end it "is handled properly in encrypted+signed emails" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", "59C71FB38AEE22E091C78259D06350440F759BD3", true) mail = File.read('spec/fixtures/mails/qp-encoding-encrypted+signed.eml') Schleuder::Runner.new().run(mail, list.email) raw = Mail::TestMailer.deliveries.first message = Mail.create_message_to_list(raw.to_s, list.email, list).setup content_part = message.parts.last.first_plaintext_part expect(content_part.decoded).to include('bug=86') teardown_list_and_mailer(list) end it "is handled properly in encrypted emails" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read('spec/fixtures/mails/qp-encoding-encrypted.eml') Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first content_part = message.parts.first expect(content_part.parts.last.content_transfer_encoding).to eql('quoted-printable') expect(content_part.parts.last.body.encoded).to include('=3D86') expect(content_part.parts.last.body.encoded).not_to include('=86') teardown_list_and_mailer(list) end end it 'does not throw an error on emails with large first mime-part' do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = File.read('spec/fixtures/mails/big_first_mime_part.txt') mail.deliver message = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(message.to_s, list.email) expect(output).to be nil teardown_list_and_mailer(list) end it 'does not throw an error on emails that contain other gpg keywords' do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = File.read('spec/fixtures/mails/mail_with_pgp_boundaries_in_body.txt') mail.deliver message = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(message.to_s, list.email) expect(output).to be nil teardown_list_and_mailer(list) end it 'does not throw an error on emails with an attached pgp key as application/octet-stream' do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: true, sign_as: list.admins.first.fingerprint } mail.gpg(gpg_opts) mail.body = 'See attachment' mail.attachments['251F2412.asc'] = { :content_type => '"application/octet-stream"; name="251F2412.asc"', :content_transfer_encoding => '7bit', :content => File.read('spec/fixtures/bla_foo_key.txt') } mail.deliver message = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(message.to_s, list.email) expect(output).to be nil teardown_list_and_mailer(list) end it 'does not throw an error on encrypted but unsigned emails that contain a forwarded encrypted email' do list = create(:list) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) ENV['GNUPGHOME'] = list.listdir mail = Mail.new mail.to = list.request_address mail.from = list.admins.first.email gpg_opts = { encrypt: true, keys: {list.request_address => list.fingerprint}, sign: false, } mail.gpg(gpg_opts) mail.body = "Hi\n\nI'll forward you this email, have a look at it!\n\n#{File.read('spec/fixtures/mails/encrypted-mime/thunderbird.eml')}" mail.deliver message = Mail::TestMailer.deliveries.first Mail::TestMailer.deliveries.clear output = process_mail(message.to_s, list.email) expect(output).to be nil teardown_list_and_mailer(list) end it 'does not throw an error on emails with broken utf-8' do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) mail = File.read('spec/fixtures/mails/broken_utf8_charset.eml') # From mail 2.7.0 this is handled correctly # See #334 for background if Gem::Version.new(Mail::VERSION.version) < Gem::Version.new('2.7.0') expect{ Schleuder::Runner.new().run(mail, list.email) }.to raise_error(ArgumentError) else Schleuder::Runner.new().run(mail, list.email) message = Mail::TestMailer.deliveries.first output = process_mail(message.to_s, list.email) expect(output).to be nil end teardown_list_and_mailer(list) end end context 'after keyword parsing' do it 'falls back to default charset per RFC if none is set' do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", "59C71FB38AEE22E091C78259D06350440F759BD3", true) # manually build a specific mail structure that comes without a charset mail = Mail.new mail.from = "admin@example.org" mail.to = list.request_address ENV['GNUPGHOME'] = list.listdir cipher_data = GPGME::Data.new GPGME::Ctx.new({armor: true}) do |ctx| ctx.add_signer(*GPGME::Key.find(:secret, "59C71FB38AEE22E091C78259D06350440F759BD3", :sign)) ctx.encrypt_sign( GPGME::Key.find(:public,list.fingerprint, :encrypt), GPGME::Data.new("Content-Type: text/plain\n\nNur ein test\n"), cipher_data, 0 ) cipher_data.seek(0) end mail.content_type "multipart/encrypted; boundary=\"#{mail.boundary}\"; protocol=\"application/pgp-encrypted\"" ver_part = Mail::Part.new do body "Version: 1" content_type "application/pgp-encrypted" end mail.add_part ver_part enc_part = Mail::Part.new do body cipher_data.to_s content_type "application/octet-stream" end mail.add_part enc_part output = process_mail(mail.to_s, list.email) expect(output).to be nil teardown_list_and_mailer(list) end end end schleuder-3.4.1/spec/schleuder/unit/000077500000000000000000000000001353765016400173765ustar00rootroot00000000000000schleuder-3.4.1/spec/schleuder/unit/conf_spec.rb000066400000000000000000000017111353765016400216620ustar00rootroot00000000000000require "spec_helper" describe Schleuder::Conf do it "reads ERB code in config files" do # Suppress warnings about already defined constants # if using "load" further below verbose_orig = $VERBOSE $VERBOSE = nil # Define constants val_old = "val_old" # Check if env var is set if not ENV["SCHLEUDER_DB_PATH"].nil? val_old = ENV["SCHLEUDER_DB_PATH"] end # Set env var, reload the config and check whether the correct value # is returned val_test = "SCHLEUDER_ERB_TEST" ENV["SCHLEUDER_DB_PATH"] = val_test load "schleuder/conf.rb" expect(Schleuder::Conf.database["database"]).to eql(val_test) # Reset the env var ENV["SCHLEUDER_DB_PATH"] = nil # Set the env var to the original value if val_old != "val_old" ENV["SCHLEUDER_DB_PATH"] = val_old end load "schleuder/conf.rb" # Set verbose level to original value $VERBOSE = $verbose_orig end end schleuder-3.4.1/spec/schleuder/unit/filters_runner_spec.rb000066400000000000000000000126731353765016400240070ustar00rootroot00000000000000require "spec_helper" module Schleuder::Filters def self.dummy(list,mail) nil end def self.stop(list,mail) nil end end describe Schleuder::Filters::Runner do let(:list) do # setup the list with an admin that can be notified list = create(:list, send_encrypted_only: false) list.subscribe("schleuder@example.org", nil, true) list end let(:pre_filters) { Schleuder::Filters::Runner.new(list,'pre') } let(:post_filters){ Schleuder::Filters::Runner.new(list,'post') } it { expect(pre_filters).to respond_to :run } context '#run' do it 'runs the filters' do mail = Mail.new expect(Schleuder::Filters).to receive(:dummy).once expect(Schleuder::Filters).to_not receive(:stop) expect(pre_filters).to receive(:filters).and_return(['dummy']) expect(pre_filters.run(mail)).to be_nil end it 'stops on a StandardError and returns error' do mail = Mail.new error = StandardError.new expect(Schleuder::Filters).to_not receive(:dummy) expect(Schleuder::Filters).to receive(:stop).once { error } expect(pre_filters).to receive(:filters).and_return(['stop','dummy']) expect(pre_filters.run(mail)).to eql(error) expect(Mail::TestMailer.deliveries.first).to be_nil end it 'stops on a StandardError and will notify admins' do mail = Mail.new error = StandardError.new pre_filters.list.bounces_drop_all = true expect(Schleuder::Filters).to_not receive(:dummy) expect(Schleuder::Filters).to receive(:stop).once { error } expect(pre_filters).to receive(:filters).and_return(['stop','dummy']) expect(pre_filters.run(mail)).to be_nil expect(Mail::TestMailer.deliveries.first).to_not be_nil end it 'stops on a StandardError and will notify on headers match' do mail = Mail.new error = StandardError.new mail['X-SPAM-FLAG'] = 'TRUE' expect(Schleuder::Filters).to_not receive(:dummy) expect(Schleuder::Filters).to receive(:stop).once { error } expect(pre_filters).to receive(:filters).and_return(['stop','dummy']) expect(pre_filters.run(mail)).to be_nil expect(Mail::TestMailer.deliveries.first).to_not be_nil end end context 'loading filters' do it 'loads filters from built-in filters_dir sorts them' do Schleuder::Conf.instance.config['filters_dir'] = File.join(Dir.pwd,'spec/fixtures/no_filters') expect(pre_filters.filters).to eq [ 'forward_bounce_to_admins', 'forward_all_incoming_to_admins', 'send_key', 'fix_exchange_messages', 'strip_html_from_alternative' ] expect(post_filters.filters).to eq [ 'request', 'max_message_size', 'forward_to_owner', 'receive_admin_only', 'receive_authenticated_only', 'receive_signed_only', 'receive_encrypted_only', 'receive_from_subscribed_emailaddresses_only', 'strip_html_from_alternative_if_keywords_present' ] end it 'loads custom filters from filters_dir and sorts them in, ignores filter not following convention' do Schleuder::Conf.instance.config['filters_dir'] = File.join(Dir.pwd,'spec/fixtures/filters') expect(pre_filters.filters).to eq [ 'forward_bounce_to_admins', 'forward_all_incoming_to_admins', 'example', 'send_key', 'fix_exchange_messages', 'strip_html_from_alternative' ] expect(post_filters.filters).to eq [ 'request', 'max_message_size', 'forward_to_owner', 'receive_admin_only', 'receive_authenticated_only', 'receive_signed_only', 'receive_encrypted_only', 'post_example', 'receive_from_subscribed_emailaddresses_only', 'strip_html_from_alternative_if_keywords_present' ] end it 'loads custom filters from filters_dir and sorts them in with missing dir' do Schleuder::Conf.instance.config['filters_dir'] = File.join(Dir.pwd,'spec/fixtures/filters_without_pre') expect(pre_filters.filters).to eq [ 'forward_bounce_to_admins', 'forward_all_incoming_to_admins', 'send_key', 'fix_exchange_messages', 'strip_html_from_alternative' ] expect(post_filters.filters).to eq [ 'post_example', 'request', 'max_message_size', 'forward_to_owner', 'receive_admin_only', 'receive_authenticated_only', 'receive_signed_only', 'receive_encrypted_only', 'receive_from_subscribed_emailaddresses_only', 'strip_html_from_alternative_if_keywords_present' ] end it 'loads custom filters from filters_dir even with non-2-digit priority' do Schleuder::Conf.instance.config['filters_dir'] = File.join(Dir.pwd,'spec/fixtures/more_filters') expect(pre_filters.filters).to eq [ 'early_example', 'forward_bounce_to_admins', 'forward_all_incoming_to_admins', 'example', 'send_key', 'fix_exchange_messages', 'strip_html_from_alternative', 'late_example' ] expect(post_filters.filters).to eq [ 'request', 'max_message_size', 'forward_to_owner', 'receive_admin_only', 'receive_authenticated_only', 'receive_signed_only', 'receive_encrypted_only', 'receive_from_subscribed_emailaddresses_only', 'strip_html_from_alternative_if_keywords_present', ] end end end schleuder-3.4.1/spec/schleuder/unit/filters_spec.rb000066400000000000000000000126441353765016400224140ustar00rootroot00000000000000require "spec_helper" describe Schleuder::Filters do before do # Make sure we have the filters loaded, as they will be loaded lazily within the code. list = create(:list) Schleuder::Filters::Runner.new(list, 'pre').filters Schleuder::Filters::Runner.new(list, 'post').filters end context '.fix_exchange_messages' do it "fixes pgp/mime-messages that were mangled by Exchange" do message = Mail.read("spec/fixtures/mails/exchange.eml") Schleuder::Filters.fix_exchange_messages(nil, message) expect(message[:content_type].content_type).to eql("multipart/encrypted") end it "works with a text/plain message" do message = Mail.read("spec/fixtures/mails/exchange_no_parts.eml") Schleuder::Filters.fix_exchange_messages(nil, message) expect(message[:content_type].content_type).to eql("text/plain") end end context '.strip_html_from_alternative' do it "strips HTML-part from multipart/alternative-message that contains ascii-armored PGP-data" do list = create(:list) mail = Mail.new mail.to = list.email mail.from = 'outside@example.org' content = encrypt_string(list, "blabla") mail.text_part = content mail.html_part = "

#{content}

" mail.subject = "test" Schleuder::Filters.strip_html_from_alternative(list, mail) expect(mail[:content_type].content_type).to eql("multipart/mixed") expect(mail.parts.size).to be(1) expect(mail.parts.first[:content_type].content_type).to eql("text/plain") expect(mail.dynamic_pseudoheaders).to include("Note: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.") end it "does NOT strip HTML-part from multipart/alternative-message that does NOT contain ascii-armored PGP-data" do mail = Mail.new mail.to = 'schleuder@example.org' mail.from = 'outside@example.org' content = "blabla" mail.text_part = content mail.html_part = "

#{content}

" mail.subject = "test" Schleuder::Filters.strip_html_from_alternative(nil, mail) expect(mail[:content_type].content_type).to eql("multipart/alternative") expect(mail.parts.size).to be(2) expect(mail.parts.first[:content_type].content_type).to eql("text/plain") expect(mail.parts.last[:content_type].content_type).to eql("text/html") expect(mail.dynamic_pseudoheaders).not_to include("Note: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.") end it "does not choke on nor change a message without Content-Type-header" do mail = Mail.new mail.to = 'schleuder@example.org' mail.from = 'outside@example.org' mail.body = "blabla" mail.subject = "test" Schleuder::Filters.strip_html_from_alternative(nil, mail) expect(mail[:content_type]).to be_nil expect(mail.parts.size).to be(0) expect(mail.dynamic_pseudoheaders).not_to include("Note: This message included an alternating HTML-part that contained PGP-data. The HTML-part was removed to enable parsing the message more properly.") end end context '.strip_html_from_alternative_if_keywords_present' do it 'strips HTML-part from multipart/alternative-message that contains keywords' do list = create(:list) mail = Mail.new mail.to = list.email mail.from = 'outside@example.org' mail.text_part = content = 'x-resend: someone@example.org\n\nblabla' mail.html_part = '

x-resend: someone@example.org

blabla

' mail.subject = 'test' mail.to_s Schleuder::Filters.strip_html_from_alternative_if_keywords_present(list, mail) expect(mail[:content_type].content_type).to eql('multipart/mixed') expect(mail.parts.size).to be(1) expect(mail.parts.first[:content_type].content_type).to eql('text/plain') expect(mail.dynamic_pseudoheaders).to include('Note: This message included keywords and an alternating HTML-part. The HTML-part was removed to prevent the disclosure of these keywords to third parties.') end it 'does NOT strip HTML-part from multipart/alternative-message that does NOT contain keywords' do list = create(:list) mail = Mail.new mail.to = 'schleuder@example.org' mail.from = 'outside@example.org' mail.text_part = content = 'Hello someone@example.org,\n\nblabla' mail.html_part = '

Hello someone@example.org,

blabla

' mail.subject = 'test' Schleuder::Filters.strip_html_from_alternative_if_keywords_present(list, mail) expect(mail[:content_type].content_type).to eql('multipart/alternative') expect(mail.parts.size).to be(2) expect(mail.parts.first[:content_type].content_type).to eql('text/plain') expect(mail.parts.last[:content_type].content_type).to eql('text/html') expect(mail.dynamic_pseudoheaders).to be_blank end it 'does not choke on nor change a message without Content-Type-header' do mail = Mail.new mail.to = 'schleuder@example.org' mail.from = 'outside@example.org' mail.body = 'blabla' mail.subject = 'test' Schleuder::Filters.strip_html_from_alternative_if_keywords_present(nil, mail) expect(mail[:content_type]).to be_nil expect(mail.parts.size).to be(0) expect(mail.dynamic_pseudoheaders).to be_blank end end end schleuder-3.4.1/spec/schleuder/unit/gpgme_ctx.rb000066400000000000000000000207511353765016400217050ustar00rootroot00000000000000require "spec_helper" describe GPGME::Ctx do it "#keyimport" do list = create(:list) keymaterial = File.read('spec/fixtures/example_key.txt') expect(list.gpg.keys.map(&:fingerprint)).not_to include('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') expect { list.gpg.keyimport(keymaterial) }.to change{ list.gpg.keys.size }.by 1 expect(list.gpg.keys.map(&:fingerprint)).to include('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') end it "#keyimport with unusable data" do list = create(:list) keymaterial = "blabla" expect(list.gpg.keys.map(&:fingerprint)).not_to include('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') expect { list.gpg.keyimport(keymaterial) }.to change{ list.gpg.keys.size }.by 0 expect(list.gpg.keys.map(&:fingerprint)).not_to include('C4D60F8833789C7CAA44496FD3FFA6613AB10ECE') end it "#find_keys with prefixed fingerprint" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('0x59C71FB38AEE22E091C78259D06350440F759BD3') expect(keys.size).to eql(1) end it "#find_keys with un-prefixed fingerprint" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('59C71FB38AEE22E091C78259D06350440F759BD3') expect(keys.size).to eql(1) end it "#find_keys with bracketed email-address" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('schleuder ') expect(keys.size).to eql(1) end it "#find_keys with bracketed wrong email-address" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('blabla ') expect(keys.size).to eql(0) end it "#find_keys with un-bracketed email-address" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('schleuder@example.org') expect(keys.size).to eql(1) end it "#find_keys with un-bracketed wrong email-address" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('blabla@example.org') expect(keys.size).to eql(0) end it "#find_keys with correctly marked sub-string" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('@schleuder') expect(keys.size).to eql(2) end it "#find_keys with correctly marked narrower sub-string" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('@schleuder@') expect(keys.size).to eql(1) end it "#find_keys with un-marked sub-string" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys('schleuder') expect(keys.size).to eql(2) end it "#find_keys without argument" do list = create(:list) list.import_key(File.read("spec/fixtures/example_key.txt")) keys = list.gpg.find_keys() expect(keys.size).to eql(2) end it "#clean_and_classify_input with prefixed fingerprint" do list = create(:list) kind, input = list.gpg.clean_and_classify_input('0x59C71FB38AEE22E091C78259D06350440F759BD3') expect(kind).to eql(:fingerprint) expect(input).to eql('0x59C71FB38AEE22E091C78259D06350440F759BD3') end it "#clean_and_classify_input with un-prefixed fingerprint" do list = create(:list) kind, input = list.gpg.clean_and_classify_input('59C71FB38AEE22E091C78259D06350440F759BD3') expect(kind).to eql(:fingerprint) expect(input).to eql('0x59C71FB38AEE22E091C78259D06350440F759BD3') end it "#clean_and_classify_input with bracketed email-address" do list = create(:list) kind, input = list.gpg.clean_and_classify_input('bla ') expect(kind).to eql(:email) expect(input).to eql('') end it "#clean_and_classify_input with un-bracketed email-address" do list = create(:list) kind, input = list.gpg.clean_and_classify_input('bla@foo') expect(kind).to eql(:email) expect(input).to eql('') end it "#clean_and_classify_input with URL" do list = create(:list) kind, input = list.gpg.clean_and_classify_input('http://example.org/foo') expect(kind).to eql(:url) expect(input).to eql('http://example.org/foo') end it "#clean_and_classify_input with some string" do list = create(:list) kind, input = list.gpg.clean_and_classify_input('lala') expect(kind).to eql(nil) expect(input).to eql('lala') end it "#gpgcli returns correct data types" do list = create(:list) err, out, exitcode = list.gpg.class.gpgcli('--list-keys') expect(err.class).to eql(Array) expect(out.class).to eql(Array) expect(exitcode.class).to eql(Integer) end it "#gpgcli_expect returns correct data types" do list = create(:list) err, out, exitcode = list.gpg.class.gpgcli_expect('--list-keys') { nil } expect(err.class).to eql(Array) expect(out.class).to eql(NilClass) expect(exitcode.class).to eql(Integer) end context "#keyserver_arg" do it "returns keyserver-args if a keyserver is configured" do list = create(:list) keyserver_args = list.gpg.send(:keyserver_arg) expect(keyserver_args).to eql("--keyserver #{Conf.keyserver}") end it "returns a blank string if the keyserver-option is set to a blank value" do oldval = Conf.instance.config['keyserver'] Conf.instance.config['keyserver'] = '' list = create(:list) keyserver_args = list.gpg.send(:keyserver_arg) expect(keyserver_args).to eql("") Conf.instance.config['keyserver'] = oldval end end context '#refresh_keys' do it 'updates keys from the keyserver' do list = create(:list) list.subscribe("admin@example.org", nil, true) list.import_key(File.read("spec/fixtures/expired_key.txt")) list.import_key(File.read("spec/fixtures/olduid_key.txt")) res = '' with_sks_mock do res = list.gpg.refresh_keys(list.keys) end expect(res).to match(/This key was updated \(new signatures\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]/) expect(res).to match(/This key was updated \(new user-IDs and new signatures\):\n0x6EE51D78FD0B33DE65CCF69D2104E20E20889F66 new@example.org \d{4}-\d{2}-\d{2}/) if GPGME::Ctx.sufficient_gpg_version?('2.1') dirmngr_pid = `pgrep -a dirmngr | grep #{list.listdir}`.split(' ',2).first # no error occurred expect(dirmngr_pid).not_to be_nil end end it 'reports errors from refreshing keys' do list = create(:list) list.subscribe("admin@example.org", nil, true) list.import_key(File.read("spec/fixtures/expired_key.txt")) res = list.gpg.refresh_keys(list.keys) expect(res).to include("Refreshing all keys from the keyring of list #{list.email} resulted in this") if GPGME::Ctx.sufficient_gpg_version?('2.1') expect(mail.to_s).to include("keyserver refresh failed: No keyserver available") dirmngr_pid = `pgrep -a dirmngr | grep #{list.listdir}`.split(' ',2).first expect(dirmngr_pid).not_to be_nil else # The wording differs slightly among versions. expect(mail.to_s).to match(/gpgkeys: .* error .* connect/) end end it 'does not import non-self-signatures if gpg >= 2.1.15; or else sends a warning' do list = create(:list) list.delete_key('87E65ED2081AE3D16BE4F0A5EBDBE899251F2412') list.subscribe('admin@example.org', nil, true) list.import_key(File.read('spec/fixtures/bla_foo_key.txt')) res = '' with_sks_mock do res = list.gpg.refresh_keys(list.keys) end # GPGME apparently does not show signatures correctly in some cases, so we better use gpgcli. signature_output = list.gpg.class.gpgcli(['--list-sigs', '87E65ED2081AE3D16BE4F0A5EBDBE899251F2412'])[1].grep(/0F759BD3.*schleuder@example.org/) if GPGME::Ctx.sufficient_gpg_version?('2.1.15') expect(res).to be_empty expect(signature_output).to be_empty else message = Mail::TestMailer.deliveries.first expect(message.to).to eql([Conf.superadmin]) expect(message.subject).to eql('Schleuder installation problem') expect(res).not_to be_empty expect(signature_output).not_to be_empty end end end end schleuder-3.4.1/spec/schleuder/unit/gpgme_key.rb000066400000000000000000000054541353765016400217020ustar00rootroot00000000000000require "spec_helper" describe GPGME::Key do describe "#oneline" do it "displays the expected basic attributes" do list = create(:list) key = list.key expect(key.oneline).to match(/0x59C71FB38AEE22E091C78259D06350440F759BD3 schleuder@example.org \d{4}-\d{2}-\d{2}/) end it "displays the expected attributes for an expiring key" do list = create(:list) list.import_key(File.read("spec/fixtures/expiring_key.txt")) key = list.key("421FBF7190640136788593CD9EE9BE5929CACC20") expect(key.oneline).to match(/0x421FBF7190640136788593CD9EE9BE5929CACC20 expiringkey@example.org \d{4}-\d{2}-\d{2} \[expires: \d{4}-\d{2}-\d{2}\]/) end it "displays the expected attributes for an expired key" do list = create(:list) list.import_key(File.read("spec/fixtures/expired_key.txt")) key = list.key("98769E8A1091F36BD88403ECF71A3F8412D83889") expect(key.oneline).to match(/0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]/) end it "displays the expected attributes for a revoked key" do list = create(:list) list.import_key(File.read("spec/fixtures/revoked_key.txt")) key = list.key("7E783CDE6D1EFE6D2409739C098AC83A4C0028E9") expect(key.oneline).to match(/0x7E783CDE6D1EFE6D2409739C098AC83A4C0028E9 paz@nadir.org \d{4}-\d{2}-\d{2} \[revoked\]/) end # gpgme.rb doesn't report missing encryption-capability properly yet. it "displays the expected attributes for a key that's not capable of encryption" do list = create(:list) list.import_key(File.read("spec/fixtures/signonly_key.txt")) key = list.key("B1CD8BB15C2673C6BFD8FA4B70B2CF29E01AD53E") expect(key.oneline).to match(/0xB1CD8BB15C2673C6BFD8FA4B70B2CF29E01AD53E signonly@example.org \d{4}-\d{2}-\d{2} \[not capable of encryption\]/) end end describe '.valid_fingerprint?' do context 'valid fingerprints' do ['59C71FB38AEE22E091C78259D06350440F759BD3', '0x59C71FB38AEE22E091C78259D06350440F759BD3', '59C71FB38AEE22E091C78259D0635044', '0x59C71FB38AEE22E091C78259D0635044', ].each do |fp| it "accepts #{fp} as a valid fingerprint" do expect(described_class.valid_fingerprint?(fp)).to be_truthy end end end context 'invalid fingerprints' do ['Z9C71FB38AEE22E091C78259D06350440F759BD3', '59C71FB38AEE22E091C78259D06350440F759BD3A', '59C71FB38AEE22E091C78259D06350440F759BD', '0x59C71FB38AEE22E091C78259D06350440F759B', 'Z9C71FB38AEE22E091C78259D0635044', 'Z9C71FB38AEE22E091C78259D0635044', ].each do |fp| it "rejects #{fp} as an invalid fingerprint" do expect(described_class.valid_fingerprint?(fp)).to be_falsey end end end end end schleuder-3.4.1/spec/schleuder/unit/list_builder_spec.rb000066400000000000000000000067271353765016400234320ustar00rootroot00000000000000require "spec_helper" describe Schleuder::ListBuilder do it "creates a new, valid list" do listname = "list-#{rand}@example.org" adminaddress = 'schleuder2@example.org' adminkey = File.read('spec/fixtures/example_key.txt') list, messages = ListBuilder.new({email: listname, fingerprint: nil}, adminaddress, nil, adminkey).run expect(list).to be_an_instance_of Schleuder::List expect(list).to be_valid expect(messages).to be_blank end it "returns an error-message if given an invalid email-address" do listname = "list-#{rand}" adminaddress = 'schleuder2@example.org' adminkey = File.read('spec/fixtures/example_key.txt') list, messages = ListBuilder.new({email: listname, fingerprint: nil}, adminaddress, nil, adminkey).run expect(list).to be_nil expect(messages).to be_an_instance_of Hash expect(messages.keys).to eq ['email'] expect(messages.values).to be_present end it "creates a listdir for the list" do listname = "list-#{rand}@example.org" adminaddress = 'schleuder2@example.org' adminkey = File.read('spec/fixtures/example_key.txt') list, _ = ListBuilder.new({email: listname, fingerprint: nil}, adminaddress, nil, adminkey).run expect(File.directory?(list.listdir)).to be true end it "creates a list-key with all required UIDs" do listname = "list-#{rand}@example.org" adminaddress = 'schleuder2@example.org' adminkey = File.read('spec/fixtures/example_key.txt') list, _ = ListBuilder.new({email: listname, fingerprint: nil}, adminaddress, nil, adminkey).run uids = list.key.uids.map(&:email) expect(uids).to include(list.email) expect(uids).to include(list.request_address) expect(uids).to include(list.owner_address) end it "subscribes the adminaddress and imports the adminkey" do listname = "list-#{rand}@example.org" adminaddress = 'schleuder2@example.org' adminkey = File.read('spec/fixtures/example_key.txt') list, _ = ListBuilder.new({email: listname, fingerprint: nil}, adminaddress, nil, adminkey).run subscription_emails = list.subscriptions.map(&:email) keys_fingerprints = list.keys.map(&:fingerprint) expect(subscription_emails).to eq [adminaddress] expect(keys_fingerprints).to include("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") end it "subscribes the adminaddress and respects the given adminfingerprint" do listname = "list-#{rand}@example.org" adminaddress = 'schleuder2@example.org' list, _ = ListBuilder.new({email: listname, fingerprint: nil}, adminaddress, "59C71FB38AEE22E091C78259D06350440F759BD3").run subscription_emails = list.subscriptions.map(&:email) admin_subscription = list.admins.first expect(subscription_emails).to eq [adminaddress] expect(admin_subscription.fingerprint).to eql("59C71FB38AEE22E091C78259D06350440F759BD3") end it "subscribes the adminaddress and ignores the adminfingerprint if an adminkey was given" do listname = "list-#{rand}@example.org" adminaddress = 'schleuder2@example.org' adminkey = File.read('spec/fixtures/example_key.txt') list, _ = ListBuilder.new({email: listname, fingerprint: nil}, adminaddress, "59C71FB38AEE22E091C78259D06350440F759BD3", adminkey).run subscription_emails = list.subscriptions.map(&:email) subscription_fingerprints = list.subscriptions.map(&:fingerprint) expect(subscription_emails).to eq [adminaddress] expect(subscription_fingerprints).to eq ["C4D60F8833789C7CAA44496FD3FFA6613AB10ECE"] end end schleuder-3.4.1/spec/schleuder/unit/list_spec.rb000066400000000000000000000677141353765016400217270ustar00rootroot00000000000000require "spec_helper" describe Schleuder::List do BOOLEAN_LIST_ATTRIBUTES = [ :send_encrypted_only, :receive_encrypted_only, :receive_signed_only, :receive_authenticated_only, :receive_from_subscribed_emailaddresses_only, :receive_admin_only, :keep_msgid, :bounces_drop_all, :bounces_notify_admins, :include_list_headers, :include_list_headers, :include_openpgp_header, :forward_all_incoming_to_admins ].freeze it "has a valid factory" do list = create(:list_with_one_subscription) expect(list).to be_valid end it { is_expected.to respond_to :subscriptions } it { is_expected.to respond_to :email } it { is_expected.to respond_to :fingerprint } it { is_expected.to respond_to :log_level } it { is_expected.to respond_to :subject_prefix } it { is_expected.to respond_to :subject_prefix_in } it { is_expected.to respond_to :subject_prefix_out } it { is_expected.to respond_to :openpgp_header_preference } it { is_expected.to respond_to :internal_footer } it { is_expected.to respond_to :public_footer } it { is_expected.to respond_to :headers_to_meta } it { is_expected.to respond_to :bounces_drop_on_headers } it { is_expected.to respond_to :keywords_admin_only } it { is_expected.to respond_to :keywords_admin_notify } it { is_expected.to respond_to :send_encrypted_only } it { is_expected.to respond_to :receive_encrypted_only } it { is_expected.to respond_to :receive_signed_only } it { is_expected.to respond_to :receive_authenticated_only } it { is_expected.to respond_to :receive_from_subscribed_emailaddresses_only } it { is_expected.to respond_to :receive_admin_only } it { is_expected.to respond_to :keep_msgid } it { is_expected.to respond_to :bounces_drop_all } it { is_expected.to respond_to :bounces_notify_admins } it { is_expected.to respond_to :include_list_headers } it { is_expected.to respond_to :include_openpgp_header } it { is_expected.to respond_to :max_message_size_kb } it { is_expected.to respond_to :language } it { is_expected.to respond_to :forward_all_incoming_to_admins } it { is_expected.to respond_to :logfiles_to_keep } it "is invalid when email is nil" do # Don't use factory here because we'd run into List.listdir expecting email to not be nil. list = Schleuder::List.new(email: nil) expect(list).not_to be_valid expect(list.errors.messages[:email]).to include("can't be blank") end it "is invalid when email is blank" do list = build(:list, email: "") expect(list).not_to be_valid expect(list.errors.messages[:email]).to include("can't be blank") end it "is invalid when email does not contain an @" do list = build(:list, email: "fooatbar.org") expect(list).not_to be_valid expect(list.errors.messages[:email]).to include("is not a valid email address") end it "is invalid when fingerprint is blank" do list = build(:list, fingerprint: "") expect(list).not_to be_valid expect(list.errors.messages[:fingerprint]).to include("can't be blank") end it "is invalid when fingerprint is nil" do list = build(:list, fingerprint: nil) expect(list).not_to be_valid expect(list.errors.messages[:fingerprint]).to include("can't be blank") end it "is invalid when fingerprint contains invalid characters" do list = build(:list, fingerprint: "&$$$$67923AAA") expect(list).not_to be_valid expect(list.errors.messages[:fingerprint]).to include("is not a valid OpenPGP-fingerprint") end BOOLEAN_LIST_ATTRIBUTES.each do |list_attribute| it "is invalid if #{list_attribute} is nil" do list = build(:list) list[list_attribute] = nil expect(list).not_to be_valid expect(list.errors.messages[list_attribute]).to include("must be true or false") end it "is invalid if #{list_attribute} is blank" do list = build(:list) list[list_attribute] = "" expect(list).not_to be_valid expect(list.errors.messages[list_attribute]).to include("must be true or false") end end [:headers_to_meta, :keywords_admin_only, :keywords_admin_notify].each do |list_attribute| it "is invalid if #{list_attribute} contains special characters" do list = build(:list) list[list_attribute] =["$from", "to", "date", "cc"] expect(list).not_to be_valid expect(list.errors.messages[list_attribute]).to include("contains invalid characters") end it "is valid if #{list_attribute} does not contain special characters" do list = build(:list) list[list_attribute] = ["foobar"] expect(list).to be_valid end end it "is invalid if bounces_drop_on_headers contains special characters" do list = build(:list, bounces_drop_on_headers: {"$" => "%"}) expect(list).not_to be_valid expect(list.errors.messages[:bounces_drop_on_headers]).to include("contains invalid characters") end [:subject_prefix, :subject_prefix_in, :subject_prefix_out].each do |list_attribute| it "is invalid if #{list_attribute} contains a linebreak" do list = build(:list) list[list_attribute] = "Foo\nbar" expect(list).not_to be_valid expect(list.errors.messages[list_attribute]).to include("must not include line-breaks") end it "is valid if #{list_attribute} is nil" do list = build(:list) list[list_attribute] = nil expect(list).to be_valid end end it "is invalid if openpgp_header_preference is foobar" do list = build(:list, openpgp_header_preference: "foobar") expect(list).not_to be_valid expect(list.errors.messages[:openpgp_header_preference]).to include("must be one of: sign, encrypt, signencrypt, unprotected, none") end [:max_message_size_kb, :logfiles_to_keep].each do |list_attribute| it "is invalid if #{list_attribute} is 0" do list = build(:list) list[list_attribute] = 0 expect(list).not_to be_valid expect(list.errors.messages[list_attribute]).to include("must be a number greater than zero") end end it "is invalid if log_level is foobar" do list = build(:list, log_level: "foobar") expect(list).not_to be_valid expect(list.errors.messages[:log_level]).to include("must be one of: debug, info, warn, error") end it "is invalid if language is jp" do list = build(:list, language: "jp") expect(list).not_to be_valid expect(list.errors.messages[:language]).to include("must be one of: en, de") end it "is invalid if internal_footer includes a non-printable character" do list = build(:list, internal_footer: "\a") expect(list).not_to be_valid expect(list.errors.messages[:internal_footer]).to include("includes non-printable characters") end it "is invalid if public_footer includes a non-printable character" do list = build(:list, public_footer: "\a") expect(list).not_to be_valid expect(list.errors.messages[:public_footer]).to include("includes non-printable characters") end describe ".configurable_attributes" do it "returns an array that contains the configurable attributes" do expect(Schleuder::List.configurable_attributes).to eq [ :bounces_drop_all, :bounces_drop_on_headers, :bounces_notify_admins, :forward_all_incoming_to_admins, :headers_to_meta, :include_list_headers, :include_openpgp_header, :internal_footer, :keep_msgid, :keywords_admin_notify, :keywords_admin_only, :language, :log_level, :logfiles_to_keep, :max_message_size_kb, :openpgp_header_preference, :public_footer, :receive_admin_only, :receive_authenticated_only, :receive_encrypted_only, :receive_from_subscribed_emailaddresses_only, :receive_signed_only, :send_encrypted_only, :subject_prefix, :subject_prefix_in, :subject_prefix_out, ] end it "does not contain the attributes email and fingerprint" do expect(Schleuder::List.configurable_attributes).to_not include(:email) expect(Schleuder::List.configurable_attributes).to_not include(:fingerprint) end end describe "#fingerprint" do it "transforms the fingerprint to upper case" do list = Schleuder::List.new(email: "example@example.org", fingerprint: "c4d60f8833789c7caa44496fd3ffa6613ab10ece") expect(list.fingerprint).to eq("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") end it "removes whitespaces and 0x from the fingerprint" do fingerprint = "0x 99 991 1000 10" list = build(:list, fingerprint: fingerprint) expect(list.fingerprint).to eq "99991100010" end end describe "#logfile" do it "returns the logfile path" do list = create(:list, email: "foo@bar.org") expect(list.logfile).to eq File.join(Schleuder::Conf.listlogs_dir, "bar.org/foo/list.log") end end describe "#logger" do it "calls the ListLogger" do list = create(:list) expect(Listlogger).to receive(:new).with(list) list.logger end end describe "#to_s" do it "returns the email" do list = create(:list, email: "foo@bar.org") expect(list.email).to eq "foo@bar.org" end end describe "#admins" do it "returns subscriptions of admin users" do list = create(:list) admin_subscription = create( :subscription, email: "admin@foo.org", admin: true, list_id: list.id, ) _user_subscription = create( :subscription, email: "user@foo.org", admin: false, list_id: list.id, ) expect(list.admins).to eq [admin_subscription] end end describe "#key" do it "returns the key with the fingerprint of the list" do list = create( :list, fingerprint: "59C7 1FB3 8AEE 22E0 91C7 8259 D063 5044 0F75 9BD3" ) expect(list.key.fingerprint()).to eq "59C71FB38AEE22E091C78259D06350440F759BD3" end end describe "#secret_key" do it "returns the secret key with the fingerprint of the list" do list = create( :list, fingerprint: "59C71FB38AEE22E091C78259D06350440F759BD3" ) expect(list.secret_key.secret?).to eq true expect(list.secret_key.fingerprint).to eq "59C71FB38AEE22E091C78259D06350440F759BD3" end end describe "#keys" do it "it returns an array with the keys of the list" do list = create(:list) expect(list.keys).to be_kind_of Array expect(list.keys.length).to eq 1 end it "returns an array of keys matching the given fingerprint" do list = create( :list, fingerprint: "59C71FB38AEE22E091C78259D06350440F759BD3" ) expect(list.keys).to be_kind_of Array expect(list.keys.first.fingerprint).to eq "59C71FB38AEE22E091C78259D06350440F759BD3" end it "returns an array with the keys matching the given email address" do list = create(:list, email: "schleuder@example.org") expect(list.keys("schleuder@example.org").length).to eq 1 expect( list.keys("schleuder@example.org").first.fingerprint ).to eq "59C71FB38AEE22E091C78259D06350440F759BD3" end it "returns an array with the keys matching the given bracketed email address" do list = create(:list, email: "schleuder@example.org") expect( list.keys("bla ").first.fingerprint ).to eq "59C71FB38AEE22E091C78259D06350440F759BD3" end end describe "#import_key" do it "imports a given key" do list = create(:list) key = File.read("spec/fixtures/example_key.txt") expect { list.import_key(key) }.to change { list.keys.count }.by(1) list.delete_key("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") end end describe "#delete_key" do it "deletes the key with the given fingerprint" do list = create(:list) key = File.read("spec/fixtures/example_key.txt") list.import_key(key) expect do list.delete_key("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") end.to change { list.keys.count }.by(-1) end it "returns false if no key with the fingerprint was found" do list = create(:list) expect(list.delete_key("A4C60C8833789C7CAA44496FD3FFA6611AB10CEC")).to eq false end end describe "#export_key" do it "exports the key with the fingerprint of the list if no argument is given" do list = create(:list, email: "schleuder@example.org") expected_public_key = File.read("spec/fixtures/schleuder_at_example_public_key.txt") # Get rid of the first, opening line, so we don't compare against optional comments in the output, etc. expected_public_key = expected_public_key.split("\n").slice(1..-1).join("\n") expect(list.export_key()).to include expected_public_key end end it "exports the key with the given fingerprint" do list = create(:list, email: "schleuder@example.org") expected_public_key = File.read("spec/fixtures/schleuder_at_example_public_key.txt") # Get rid of the first, opening line, so we don't compare against optional comments in the output, etc. expected_public_key = expected_public_key.split("\n").slice(1..-1).join("\n") expect( list.export_key("59C71FB38AEE22E091C78259D06350440F759BD3") ).to include expected_public_key end describe "#check_keys" do it "adds a message if a key expires in two weeks or less" do list = create(:list) key = double("key") generation_time = Time.now - 1.year expiry_time = Time.now + 7.days allow_any_instance_of(GPGME::Key).to receive(:subkeys).and_return(key) allow(key).to receive(:first).and_return(key) allow(key).to receive(:timestamp).and_return(generation_time) allow(key).to receive(:expires?).and_return(true) allow(key).to receive(:expired?).and_return(false) allow(key).to receive(:expired).and_return(false) allow(key).to receive(:any?).and_return(false) allow(key).to receive(:expires).and_return(expiry_time) allow(key).to receive(:fingerprint).and_return("59C71FB38AEE22E091C78259D06350440F759BD3") datefmt = "%Y-%m-%d" generation_date = generation_time.strftime(datefmt) expiry_date = expiry_time.strftime(datefmt) expect(list.check_keys).to eq "This key expires in 6 days:\n0x59C71FB38AEE22E091C78259D06350440F759BD3 schleuder@example.org #{generation_date} [expires: #{expiry_date}]\n\n" end it "adds a message if a key is revoked" do list = create(:list) allow_any_instance_of(GPGME::Key).to receive(:trust).and_return(:revoked) expect(list.check_keys).to match(/This key is revoked:\n0x59C71FB38AEE22E091C78259D06350440F759BD3 schleuder@example.org \d{4}-\d{2}-\d{2} \[revoked\]\n\n/) end it "adds a message if a key is disabled" do list = create(:list) allow_any_instance_of(GPGME::Key).to receive(:trust).and_return(:disabled) expect(list.check_keys).to match(/This key is disabled:\n0x59C71FB38AEE22E091C78259D06350440F759BD3 schleuder@example.org \d{4}-\d{2}-\d{2} \[disabled\]\n\n/) end it "adds a message if a key is invalid" do list = create(:list) allow_any_instance_of(GPGME::Key).to receive(:trust).and_return(:invalid) expect(list.check_keys).to match(/This key is invalid:\n0x59C71FB38AEE22E091C78259D06350440F759BD3 schleuder@example.org \d{4}-\d{2}-\d{2} \[invalid\]\n\n/) end end describe ".by_recipient" do it "returns the list for a given address" do list = create(:list, email: "list@example.org") expect(Schleuder::List.by_recipient("list-owner@example.org")).to eq list end end describe "#sendkey_address" do it "adds the sendkey keyword to the email address" do list = create(:list, email: "list@example.org") expect(list.sendkey_address).to eq "list-sendkey@example.org" end end describe "#request_address" do it "adds the request keyword to the email address" do list = create(:list, email: "list@example.org") expect(list.request_address).to eq "list-request@example.org" end end describe "#owner_address" do it "adds the owner keyword to the email address" do list = create(:list, email: "list@example.org") expect(list.owner_address).to eq "list-owner@example.org" end end describe "#bounce_address" do it "adds the bounce keyword to the email address" do list = create(:list, email: "list@example.org") expect(list.bounce_address).to eq "list-bounce@example.org" end end describe "#gpg" do it "returns an instance of GPGME::Ctx" do list = create(:list) expect(list.gpg).to be_an_instance_of GPGME::Ctx end it "sets the GNUPGHOME environment variable to the listdir" do list = create(:list) list.gpg expect(ENV["GNUPGHOME"]).to eq list.listdir end end context '#fetch_keys' do it 'fetches one key by fingerprint' do list = create(:list) list.subscribe("admin@example.org", nil, true) output = '' with_sks_mock do output = list.fetch_keys('98769E8A1091F36BD88403ECF71A3F8412D83889') end expect(output).to match(/This key was fetched \(new key\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]/) teardown_list_and_mailer(list) end it 'fetches one key by URL' do list = create(:list) list.subscribe("admin@example.org", nil, true) output = '' with_sks_mock do output = list.fetch_keys('http://127.0.0.1:9999/keys/example.asc') end expect(output).to match(/This key was fetched \(new key\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]/) teardown_list_and_mailer(list) end it 'fetches one key by email address' do list = create(:list) list.subscribe("admin@example.org", nil, true) output = '' with_sks_mock do output = list.fetch_keys('admin@example.org') end expect(output).to match(/This key was fetched \(new key\):\n0x98769E8A1091F36BD88403ECF71A3F8412D83889 bla@foo \d{4}-\d{2}-\d{2} \[expired: \d{4}-\d{2}-\d{2}\]/) teardown_list_and_mailer(list) end it 'does not import non-self-signatures if gpg >= 2.1.15; or else sends a warning' do list = create(:list) list.delete_key('87E65ED2081AE3D16BE4F0A5EBDBE899251F2412') list.subscribe('admin@example.org', nil, true) output = '' with_sks_mock do output = list.fetch_keys('87E65ED2081AE3D16BE4F0A5EBDBE899251F2412') end # GPGME apparently does not show signatures correctly in some cases, so we better use gpgcli. signature_output = list.gpg.class.gpgcli(['--list-sigs', '87E65ED2081AE3D16BE4F0A5EBDBE899251F2412'])[1].grep(/0F759BD3.*schleuder@example.org/) expect(output).to include("This key was fetched (new key):\n0x87E65ED2081AE3D16BE4F0A5EBDBE899251F2412 bla@foo") if GPGME::Ctx.gpg_knows_import_filter? expect(signature_output).to be_empty else message = Mail::TestMailer.deliveries.first expect(message.to).to eql([Conf.superadmin]) expect(message.subject).to eql('Schleuder installation problem') expect(signature_output).not_to be_empty end teardown_list_and_mailer(list) end end describe "send_list_key_to_subscriptions" do it "sends its key to all subscriptions" do list = create(:list, send_encrypted_only: false) list.subscribe("admin@example.org", nil, true) list.send_list_key_to_subscriptions raw = Mail::TestMailer.deliveries.first expect(raw.parts.first.parts.first.body.to_s).to eql("Find the key for this address attached.") expect(raw.parts.first.parts.last.body.to_s).to include("4096R/59C71FB38AEE22E091C78259D06350440F759BD3") expect(raw.parts.first.parts.last.body.to_s).to include("-----BEGIN PGP PUBLIC KEY BLOCK-----") end end describe "#subscribe" do it "subscribes and ignores nil-values for admin and delivery_enabled" do list = create(:list) sub, _ = list.subscribe("admin@example.org", nil, nil, nil) expect(sub.admin?).to be(false) expect(sub.delivery_enabled?).to be(true) end it "subscribes and sets the fingerprint from key material that contains exactly one key" do list = create(:list) key_material = File.read("spec/fixtures/example_key.txt") sub, msgs = list.subscribe("admin@example.org", "", true, true, key_material) expect(msgs).to be(nil) expect(sub.fingerprint).to eql("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(list.subscriptions.size).to be(1) expect(list.subscriptions.first.fingerprint).to eql("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") expect(list.keys.size).to be(2) expect(list.keys.map(&:fingerprint)).to eql(["59C71FB38AEE22E091C78259D06350440F759BD3", "C4D60F8833789C7CAA44496FD3FFA6613AB10ECE"]) end it "subscribes and does not set the fingerprint from key material containing multiple keys" do list = create(:list) key_material = File.read("spec/fixtures/example_key.txt") key_material << File.read("spec/fixtures/olduid_key.txt") sub, msgs = list.subscribe("admin@example.org", "", true, true, key_material) expect(msgs).to eql("The given key material contained more than one key, could not determine which fingerprint to use. Please set it manually!") expect(sub.fingerprint).to be_blank expect(list.subscriptions.size).to be(1) expect(list.subscriptions.first.fingerprint).to be_blank expect(list.keys.size).to be(3) expect(list.keys.map(&:fingerprint)).to eql(["59C71FB38AEE22E091C78259D06350440F759BD3", "C4D60F8833789C7CAA44496FD3FFA6613AB10ECE", "6EE51D78FD0B33DE65CCF69D2104E20E20889F66"]) end it "subscribes and does not set the fingerprint from key material containing no keys" do list = create(:list) key_material = "blabla" sub, msgs = list.subscribe("admin@example.org", "", true, true, key_material) expect(msgs).to eql("The given key material did not contain any keys!") expect(sub.fingerprint).to be_blank expect(list.subscriptions.size).to be(1) expect(list.subscriptions.first.fingerprint).to be_blank expect(list.keys.size).to be(1) expect(list.keys.map(&:fingerprint)).to eql(["59C71FB38AEE22E091C78259D06350440F759BD3"]) end it "subscribes and ignores a given fingerprint if key material is given, too" do list = create(:list) key_material = "blabla" sub, msgs = list.subscribe("admin@example.org", "C4D60F8833789C7CAA44496FD3FFA6613AB10ECE", true, true, key_material) expect(msgs).to eql("The given key material did not contain any keys!") expect(sub.fingerprint).to be_blank expect(list.subscriptions.size).to be(1) expect(list.subscriptions.first.fingerprint).to be_blank expect(list.keys.size).to be(1) expect(list.keys.map(&:fingerprint)).to eql(["59C71FB38AEE22E091C78259D06350440F759BD3"]) end end describe "#send_to_subscriptions" do it "sends the message to all subscribers" do list = create(:list, send_encrypted_only: false) sub, msgs = list.subscribe("admin@example.org", nil, true) sub, msgs = list.subscribe("user1@example.org") sub, msgs = list.subscribe("user2@example.org") mail = Mail.new mail.to = list.email mail.from = 'something@localhost' mail.subject = 'Something' mail.body = "Some content" Schleuder::Runner.new().run(mail, list.email) messages = Mail::TestMailer.deliveries recipients = messages.map { |m| m.to.first }.sort expect(messages.size).to be(3) expect(recipients).to eql(['admin@example.org', 'user1@example.org', 'user2@example.org']) expect(messages[0].parts.first.parts.last.body.to_s).to eql("Some content") expect(messages[0].subject).to eql("Something") expect(messages[1].parts.first.parts.last.body.to_s).to eql("Some content") expect(messages[1].subject).to eql("Something") expect(messages[2].parts.first.parts.last.body.to_s).to eql("Some content") expect(messages[2].subject).to eql("Something") teardown_list_and_mailer(list) end it "sends the message to all subscribers, in the clear if one's key is unusable, if send_encrypted_only is false" do list = create(:list, send_encrypted_only: false) sub, msgs = list.subscribe("admin@example.org", nil, true) key_material = File.read("spec/fixtures/expired_key.txt") sub, msgs = list.subscribe("user1@example.org", nil, false, true, key_material) sub, msgs = list.subscribe("user2@example.org") mail = Mail.new mail.to = list.email mail.from = 'something@localhost' mail.subject = 'Something' mail.body = "Some content" Schleuder::Runner.new().run(mail, list.email) messages = Mail::TestMailer.deliveries recipients = messages.map { |m| m.to.first }.sort expect(messages.size).to be(3) expect(recipients).to eql(['admin@example.org', 'user1@example.org', 'user2@example.org']) expect(messages[0].parts.first.parts.last.body.to_s).to eql("Some content") expect(messages[0].subject).to eql("Something") expect(messages[1].parts.first.parts.last.body.to_s).to eql("Some content") expect(messages[1].subject).to eql("Something") expect(messages[2].parts.first.parts.last.body.to_s).to eql("Some content") expect(messages[2].subject).to eql("Something") teardown_list_and_mailer(list) end it "sends the message only to subscribers with available keys if send_encrypted_only is true, and a notification to the other subscribers" do list = create(:list, send_encrypted_only: true) sub, msgs = list.subscribe("admin@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) sub, msgs = list.subscribe("user1@example.org") sub, msgs = list.subscribe("user2@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3') mail = Mail.new mail.to = list.email mail.from = 'something@localhost' mail.subject = 'Something' mail.body = "Some content" Schleuder::Runner.new().run(mail, list.email) messages = Mail::TestMailer.deliveries recipients = messages.map { |m| m.to.first }.sort expect(messages.size).to be(3) expect(recipients).to eql(['admin@example.org', 'user1@example.org', 'user2@example.org']) expect(messages[0].parts.last.body.to_s).to include("-----BEGIN PGP MESSAGE-----") expect(messages[0].subject).to eql("Something") expect(messages[1].parts.first.body.to_s).to include("You missed an email") expect(messages[1].subject).to eql("Notice") expect(messages[2].parts.last.body.to_s).to include("-----BEGIN PGP MESSAGE-----") expect(messages[2].subject).to eql("Something") teardown_list_and_mailer(list) end it "sends the message only to subscribers with usable keys if send_encrypted_only is true, and a notification to the other subscribers" do list = create(:list, send_encrypted_only: true) key_material = File.read("spec/fixtures/partially_expired_key.txt") sub, msgs = list.subscribe("admin@example.org", nil, true, true, key_material) key_material = File.read("spec/fixtures/expired_key.txt") sub, msgs = list.subscribe("user1@example.org", nil, false, true, key_material) sub, msgs = list.subscribe("user2@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3') mail = Mail.new mail.to = list.email mail.from = 'something@localhost' mail.subject = 'Something' mail.body = "Some content" Schleuder::Runner.new().run(mail, list.email) messages = Mail::TestMailer.deliveries recipients = messages.map { |m| m.to.first }.sort expect(messages.size).to be(3) expect(recipients).to eql(['admin@example.org', 'user1@example.org', 'user2@example.org']) expect(messages[0].parts.first.body.to_s).to include("You missed an email") expect(messages[0].subject).to eql("Notice") expect(messages[1].parts.first.body.to_s).to include("You missed an email") expect(messages[1].subject).to eql("Notice") expect(messages[2].parts.last.body.to_s).to include("-----BEGIN PGP MESSAGE-----") expect(messages[2].subject).to eql("Something") teardown_list_and_mailer(list) end end end schleuder-3.4.1/spec/schleuder/unit/logger_notifications_spec.rb000066400000000000000000000077751353765016400251650ustar00rootroot00000000000000require "spec_helper" describe Schleuder::LoggerNotifications do context 'return path' do it 'sets default superadmin' do list = create(:list, send_encrypted_only: false) list.subscribe("schleuder@example.org", nil, true) list.logger.notify_admin("Something", nil, I18n.t('notice')) message = Mail::TestMailer.deliveries.first expect(message.sender).to eql('root@localhost') expect(message[:Errors_To].to_s).to eql('root@localhost') end it 'sets superadmin' do oldval = Conf.instance.config['superadmin'] Conf.instance.config['superadmin'] = 'schleuder-admin@example.org' list = create(:list, send_encrypted_only: false) list.subscribe("schleuder@example.org", nil, true) list.logger.notify_admin("Something", nil, I18n.t('notice')) message = Mail::TestMailer.deliveries.first expect(message.sender).to eql('schleuder-admin@example.org') expect(message[:Errors_To].to_s).to eql('schleuder-admin@example.org') Conf.instance.config['superadmin'] = oldval end end it "notifies admins of simple text-message" do list = create(:list, send_encrypted_only: false) list.subscribe("schleuder@example.org", nil, true) list.logger.notify_admin("Something", nil, I18n.t('notice')) message = Mail::TestMailer.deliveries.first expect(message.to).to eql(['schleuder@example.org']) expect(message.subject).to eql(I18n.t('notice')) expect(message.first_plaintext_part.body.to_s).to eql("Something") end it "notifies admins of multiple text-messages" do list = create(:list, send_encrypted_only: false) list.subscribe("schleuder@example.org", nil, true) list.logger.notify_admin(["Something", "anotherthing"], nil, I18n.t('notice')) message = Mail::TestMailer.deliveries.first expect(message.to).to eql(['schleuder@example.org']) expect(message.subject).to eql(I18n.t('notice')) expect(message.parts.first.parts.first.body.to_s).to eql("Something") expect(message.parts.first.parts.last.body.to_s).to eql("anotherthing") end it "notifies admins of multiple text-messages and the original message" do list = create(:list, send_encrypted_only: false) list.subscribe("schleuder@example.org", nil, true) mail = Mail.new mail.subject = "A subject" list.logger.notify_admin(["Something", "anotherthing"], mail.to_s, I18n.t('notice')) message = Mail::TestMailer.deliveries.first expect(message.to).to eql(['schleuder@example.org']) expect(message.subject).to eql(I18n.t('notice')) expect(message.parts.first.parts.first.body.to_s).to eql("Something") expect(message.parts.first.parts[1].body.to_s).to eql("anotherthing") expect(message.parts.first.parts[2].body.to_s).to include("Subject: A subject") expect(message.parts.first.parts[2][:content_type].content_type).to eql("message/rfc822") end it "notifies admins encryptedly if their key is usable" do list = create(:list, send_encrypted_only: false) list.subscribe("schleuder@example.org", '59C71FB38AEE22E091C78259D06350440F759BD3', true) mail = Mail.new mail.subject = "A subject" list.logger.notify_admin(["Something", "anotherthing"], mail.to_s, I18n.t('notice')) message = Mail::TestMailer.deliveries.first expect(message.subject).to eql('Notice') expect(message.parts.size).to be(2) expect(message.parts.last.body.to_s).to include('-----BEGIN PGP MESSAGE-----') end it "notifies admins in the clear if their key is unusable" do list = create(:list, send_encrypted_only: false) key_material = File.read("spec/fixtures/partially_expired_key.txt") list.subscribe("schleuder@example.org", nil, true, true, key_material) mail = Mail.new mail.subject = "A subject" list.logger.notify_admin("Something", mail.to_s, I18n.t('notice')) message = Mail::TestMailer.deliveries.first expect(message.subject).to eql('Notice') expect(message.parts.size).to be(2) expect(message.parts.first.parts.first.body.to_s).to eql('Something') end end schleuder-3.4.1/spec/schleuder/unit/message_spec.rb000066400000000000000000000077061353765016400223730ustar00rootroot00000000000000require "spec_helper" describe Mail::Message do it "doesn't change the order of mime-parts" do text_part = Mail::Part.new text_part.body = "This is text" image_part = Mail::Part.new image_part.content_type = 'image/png' image_part.content_disposition = 'attachment; filename=spec.png' message = Mail.new message.parts << image_part message.parts << text_part # This triggers the sorting. message.to_s expect(message.parts.first.mime_type).to eql('image/png') expect(message.parts.last.mime_type).to eql('text/plain') end # TODO: test message with "null" address ("<>") as Return-Path. I couldn't # bring Mail to generate such a message, yet. it "recognizes a message sent to listname-bounce@hostname as automated message" do list = create(:list) mail = Mail.new # Trigger the setting of mandatory headers. mail.to_s mail = Mail.create_message_to_list(mail.to_s, 'something-bounce@localhost', list).setup expect(mail.automated_message?).to be(true) end it "recognizes a message with 'Auto-Submitted'-header as automated message" do list = create(:list) mail = Mail.new mail.header['Auto-Submitted'] = 'yes' # Trigger the setting of mandatory headers. mail.to_s mail = Mail.create_message_to_list(mail.to_s, 'something@localhost', list).setup expect(mail.automated_message?).to be(true) end it "recognizes a cron message with 'Auto-Submitted'-header NOT as automated message" do list = create(:list) mail = Mail.new mail.header['Auto-Submitted'] = 'yes' mail.header['X-Cron-Env'] = '' # Trigger the setting of mandatory headers. mail.to_s mail = Mail.create_message_to_list(mail.to_s, 'something@localhost', list).setup expect(mail.automated_message?).to be(false) end context '#add_subject_prefix!' do it 'adds a configured subject prefix' do list = create(:list) list.subject_prefix = '[prefix]' list.subscribe('admin@example.org',nil,true) mail = Mail.new mail.from 'someone@example.org' mail.to list.email mail.text_part = 'blabla' mail.subject = 'test' message = Mail.create_message_to_list(mail.to_s, list.email, list).setup message.add_subject_prefix! expect(message.subject).to eql('[prefix] test') end it 'adds a configured subject prefix without subject' do list = create(:list) list.subject_prefix = '[prefix]' list.subscribe('admin@example.org',nil,true) mail = Mail.new mail.from 'someone@example.org' mail.to list.email mail.text_part = 'blabla' message = Mail.create_message_to_list(mail.to_s, list.email, list).setup message.add_subject_prefix! expect(message.subject).to eql('[prefix]') end it 'does not add a subject prefix if already present' do list = create(:list) list.subject_prefix = '[prefix]' list.subscribe('admin@example.org',nil,true) mail = Mail.new mail.from 'someone@example.org' mail.to list.email mail.text_part = 'blabla' mail.subject = 'Re: [prefix] test' message = Mail.create_message_to_list(mail.to_s, list.email, list).setup message.add_subject_prefix! expect(message.subject).to eql('Re: [prefix] test') end end it "adds list#public_footer as last mime-part without changing its value" do footer = "\n\n-- \nblabla\n blabla\n " list = create(:list) list.public_footer = footer mail = Mail.new mail.body = 'blabla' mail.list = list mail.add_public_footer! expect(mail.parts.last.body.to_s).to eql(footer) end it "adds list#internal_footer as last mime-part without changing its value" do footer = "\n\n-- \nblabla\n blabla\n " list = create(:list) list.internal_footer = footer mail = Mail.new mail.body = 'blabla' mail.list = list mail.add_internal_footer! expect(mail.parts.last.body.to_s).to eql(footer) end end schleuder-3.4.1/spec/schleuder/unit/subscription_spec.rb000066400000000000000000000102741353765016400234650ustar00rootroot00000000000000require "spec_helper" describe Schleuder::Subscription do BOOLEAN_SUBSCRIPTION_ATTRIBUTES = [ :delivery_enabled, :admin ].freeze it "has a valid factory" do subscription = create(:subscription) expect(subscription).to be_valid end it { is_expected.to respond_to :list_id } it { is_expected.to respond_to :email } it { is_expected.to respond_to :fingerprint } it { is_expected.to respond_to :admin } it { is_expected.to respond_to :delivery_enabled } it "is invalid when list_id is blank" do subscription = build(:subscription, list_id: "") expect(subscription).not_to be_valid expect(subscription.errors.messages[:list_id]).to be_present end it "is invalid when email is nil" do list = create(:list) subscription = build(:subscription, list_id: list.id, email: nil) expect(subscription).not_to be_valid expect(subscription.errors.messages[:email]).to include("can't be blank") end it "is invalid when email is blank" do list = create(:list) subscription = build(:subscription, list_id: list.id, email: "") expect(subscription).not_to be_valid expect(subscription.errors.messages[:email]).to include("can't be blank") end it "is invalid when email does not contain an @" do list = create(:list) subscription = build(:subscription, list_id: list.id, email: "fooatbar.org") expect(subscription).not_to be_valid expect(subscription.errors.messages[:email]).to include("is not a valid email address") end it "is valid when fingerprint is empty" do list = create(:list) subscription = build(:subscription, list_id: list.id, fingerprint: "") expect(subscription).to be_valid expect(subscription.errors.messages[:fingerprint]).to be_blank end it "is valid when fingerprint is nil" do list = create(:list) subscription = build(:subscription, list_id: list.id, fingerprint: nil) expect(subscription).to be_valid expect(subscription.errors.messages[:fingerprint]).to be_blank end it "is invalid when fingerprint contains invalid characters" do list = create(:list) subscription = build(:subscription, list_id: list.id, fingerprint: "&$$$$123AAA") expect(subscription).not_to be_valid expect(subscription.errors.messages[:fingerprint]).to include("is not a valid OpenPGP-fingerprint") end BOOLEAN_SUBSCRIPTION_ATTRIBUTES.each do |subscription_attribute| it "is invalid if #{subscription_attribute} is nil" do list = create(:list) subscription = build(:subscription, list_id: list.id) subscription[subscription_attribute] = nil expect(subscription).not_to be_valid expect(subscription.errors.messages[subscription_attribute]).to include("must be true or false") end it "is invalid if #{subscription_attribute} is blank" do list = create(:list) subscription = build(:subscription, list_id: list.id) subscription[subscription_attribute] = "" expect(subscription).not_to be_valid expect(subscription.errors.messages[subscription_attribute]).to include("must be true or false") end end it "is invalid if the given email is already subscribed for the list" do list1 = create(:list) list2 = create(:list) subscription1 = create(:subscription, list_id: list1.id) subscription2 = create(:subscription, list_id: list2.id, email: subscription1.email) subscription3 = build(:subscription, email: subscription1.email, list_id: subscription1.list_id) expect(subscription1).to be_valid expect(subscription2).to be_valid expect(subscription3).not_to be_valid expect(subscription3.errors[:email]).to eql(["is already subscribed"]) end describe "#fingerprint" do it "transforms the fingerprint to upper case" do subscription = Schleuder::Subscription.new(email: "example@example.org", fingerprint: "c4d60f8833789c7caa44496fd3ffa6613ab10ece") expect(subscription.fingerprint).to eq("C4D60F8833789C7CAA44496FD3FFA6613AB10ECE") end end it "removes whitespaces and 0x from the fingerprint" do fingerprint = "0x 99 991 1000 10" subscription = build(:subscription, fingerprint: fingerprint) expect(subscription.fingerprint).to eq "99991100010" end end schleuder-3.4.1/spec/sks-mock.rb000077500000000000000000000016321353765016400165220ustar00rootroot00000000000000#!/usr/bin/env ruby require 'sinatra/base' class SksMock < Sinatra::Base set :environment, :production set :port, 9999 set :bind, '127.0.0.1' get '/status' do 'ok' end get '/keys/example.asc' do File.read('spec/fixtures/expired_key_extended.txt') end get '/pks/lookup' do case params['search'] when '0x98769E8A1091F36BD88403ECF71A3F8412D83889', 'admin@example.org' File.read('spec/fixtures/expired_key_extended.txt') when '0x6EE51D78FD0B33DE65CCF69D2104E20E20889F66', 'old@example.org' File.read('spec/fixtures/olduid_key_with_newuid.txt') when '0x59C71FB38AEE22E091C78259D06350440F759BD3' File.read('spec/fixtures/default_list_key.txt') when '0x87E65ED2081AE3D16BE4F0A5EBDBE899251F2412' File.read('spec/fixtures/openpgp-keys/public-key-with-third-party-signature.txt') else 404 end end # Run this class as application run! end schleuder-3.4.1/spec/smtp-daemon.rb000077500000000000000000000032311353765016400172140ustar00rootroot00000000000000#!/usr/bin/env ruby # # This script is a very simple SMTP-daemon, that dumps every incoming email # into the given directory. It's meant to capture messages from schleuder-lists # during test-runs. require 'socket' require 'open3' trap ("INT") { exit 0 } def usage puts "Usage: #{File.basename(__FILE__)} portnum output-directory" exit 1 end # get args if ARGV.first.to_s.match('(-h|--help|help)') || ARGV.empty? usage end port = ARGV.first.to_i if port == 0 usage end outputdir = ARGV[1].to_s if outputdir.empty? usage elsif ! File.directory?(outputdir) puts "Not a directory: #{outputdir}" exit 1 end begin # run the server server = TCPServer.new("127.0.0.1", port) # receive input while (connection = server.accept) input = '' recipient = '' connection.puts "220 localhost SMTP" begin while line = connection.gets line.chomp! case line[0..3].downcase when 'ehlo', 'helo' connection.puts "250 localhost" when 'mail', 'rset' connection.puts "250 ok" when 'rcpt' recipient = line.split(':').last.gsub(/[<>\s]*/, '') connection.puts "250 ok" when 'data' connection.puts "354 go ahead" when 'quit' connection.puts "221 localhost" when '.' filename = File.join(outputdir, "mail-#{Time.now.to_f}") # puts "New message to #{recipient} written to #{filename}" IO.write(filename, input) connection.puts "250 ok" else input << line + "\n" end end rescue IOError end connection.close end rescue => exc $stderr.puts exc exit 1 end schleuder-3.4.1/spec/spec_helper.rb000066400000000000000000000111141353765016400172550ustar00rootroot00000000000000ENV['SCHLEUDER_ENV'] ||= 'test' ENV['SCHLEUDER_CONFIG'] = 'spec/schleuder.yml' ENV["SCHLEUDER_LIST_DEFAULTS"] = "etc/list-defaults.yml" if ENV['USE_BUNDLER'] != 'false' require 'bundler/setup' Bundler.setup end # We need to do this before requiring any other code # Check env if we want to run code coverage analysis if ENV['CHECK_CODE_COVERAGE'] == 'true' require 'simplecov' require 'simplecov-console' SimpleCov::Formatter::Console.table_options = {max_width: 400} SimpleCov.formatter = SimpleCov::Formatter::Console SimpleCov.start do add_filter %r{^/vendor/} add_filter %r{^/spec/} end end require 'schleuder' require 'schleuder/cli' require 'database_cleaner' require 'factory_bot' require 'net/http' require 'fileutils' RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.order = :random config.include FactoryBot::Syntax::Methods config.before(:suite) do FactoryBot.find_definitions end config.before(:suite) do DatabaseCleaner.strategy = :deletion DatabaseCleaner.clean_with(:truncation) end config.around(:each) do |example| Mail::TestMailer.deliveries.clear DatabaseCleaner.cleaning do example.run end end config.after(:each) do |example| FileUtils.rm_rf(Dir["spec/gnupg/pubring.gpg~"]) `gpgconf --kill dirmngr > /dev/null 2>&1` `gpgconf --kill gpg-agent > /dev/null 2>&1` end config.after(:suite) do cleanup_gnupg_home stop_smtp_daemon end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end Mail.defaults do delivery_method :test end def cleanup_gnupg_home ENV["GNUPGHOME"] = nil FileUtils.rm_rf Schleuder::Conf.lists_dir end def smtp_daemon_outputdir File.join(Conf.lists_dir, 'smtp-daemon-output') end def with_sks_mock pid = Process.spawn('spec/sks-mock.rb', [:out, :err] => ["/tmp/sks-mock.log", 'w']) uri = URI.parse("http://127.0.0.1:9999/status") attempts = 25 # Use the following env var to increase the time to sleep between # each attempt, for example if building the Debian package ENV['SKS_MOCK_SLEEP'] ||= '1' begin sleep ENV['SKS_MOCK_SLEEP'].to_i Net::HTTP.get(uri) rescue Errno::ECONNREFUSED => exc attempts -= 1 if attempts > 0 retry else raise "sks-mock.rb failed to start, cannot continue: #{exc}" end end yield Process.kill 'TERM', pid Process.wait pid end def start_smtp_daemon if File.directory?(smtp_daemon_outputdir) # Try to kill it, in case it's still around (this occurred on some # systems). stop_smtp_daemon end if ! File.directory?(smtp_daemon_outputdir) FileUtils.mkdir_p(smtp_daemon_outputdir) end daemon = File.join('spec', 'smtp-daemon.rb') pid = Process.spawn(daemon, '2523', smtp_daemon_outputdir) pidfile = File.join(smtp_daemon_outputdir, 'pid') IO.write(pidfile, pid) end def stop_smtp_daemon pidfile = File.join(smtp_daemon_outputdir, 'pid') if File.exist?(pidfile) pid = File.read(pidfile).to_i Process.kill(15, pid) FileUtils.rm_rf smtp_daemon_outputdir end end def run_schleuder(command, email, message_path) `SCHLEUDER_ENV=test SCHLEUDER_CONFIG=spec/schleuder.yml bin/schleuder #{command} #{email} < #{message_path} 2>&1` end def run_cli(command) `SCHLEUDER_ENV=test SCHLEUDER_CONFIG=spec/schleuder.yml bin/schleuder #{command} 2>&1` end def process_mail(msg, recipient) output = nil begin output = Schleuder::Runner.new.run(msg, recipient) rescue SystemExit end output end def teardown_list_and_mailer(list) FileUtils.rm_rf(list.listdir) Mail::TestMailer.deliveries.clear end def encrypt_string(list, str) _, ciphertext, _ = list.gpg.class.gpgcli("--recipient #{list.fingerprint} --encrypt") do |stdin, stdout, stderr| stdin.puts str # Apparently it differs between ruby-version if we have to close the stream manually. stdin.close if ! stdin.closed? stdout.readlines end ciphertext.reject { |line| line.match(/^\[GNUPG:\]/) }.join end def with_tmpfile(content,&blk) file = Tempfile.new('temporary-file',Conf.lists_dir) begin file.write(content) file.close yield file.path ensure file.unlink end end def t(*args) I18n.t(*args) end end