Browse Source

Initial commit for gpg-mailgate-web addition.

pull/33/head
perennate 9 years ago
parent
commit
98b4e341cf
  1. 57
      GnuPG/__init__.py
  2. 30
      gpg-mailgate-web/README
  3. 57
      gpg-mailgate-web/cron.py
  4. 93
      gpg-mailgate-web/public_html/config.sample.php
  5. 40
      gpg-mailgate-web/public_html/confirm.php
  6. 257
      gpg-mailgate-web/public_html/include/common.php
  7. 76
      gpg-mailgate-web/public_html/include/dbconnect.php
  8. 124
      gpg-mailgate-web/public_html/include/lock.php
  9. 88
      gpg-mailgate-web/public_html/include/pgp.php
  10. 40
      gpg-mailgate-web/public_html/index.php
  11. 52
      gpg-mailgate-web/public_html/theme/home.php
  12. 0
      gpg-mailgate-web/public_html/theme/index.html
  13. 10
      gpg-mailgate-web/schema.sql
  14. 9
      gpg-mailgate.conf.sample
  15. 8
      gpg-mailgate.py

57
GnuPG/__init__.py

@ -1,5 +1,9 @@
import os
import os.path
import subprocess
import shutil
import random
import string
def public_keys( keyhome ):
cmd = '/usr/bin/gpg --homedir %s --list-keys --with-colons' % keyhome
@ -15,12 +19,57 @@ def public_keys( keyhome ):
keys.append(key)
return keys
# confirms a key has a given email address
def confirm_key( content, email ):
tmpkeyhome = ''
while True:
tmpkeyhome = '/tmp/' + ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(12))
if not os.path.exists(tmpkeyhome):
break
os.mkdir(tmpkeyhome)
p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', tmpkeyhome, '--import', '--batch'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE )
result = p.communicate(input=content)[1]
confirmed = False
for line in result.split("\n"):
if 'imported' in line and '<' in line and '>' in line:
if line.split('<')[1].split('>')[0].lower() == email.lower():
confirmed = True
break
else:
break # confirmation failed
# cleanup
shutil.rmtree(tmpkeyhome)
return confirmed
# adds a key and ensures it has the given email address
def add_key( keyhome, content ):
p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', keyhome, '--import', '--batch'], stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE )
p.communicate(input=content)
p.wait()
def delete_key( keyhome, email ):
from email.utils import parseaddr
result = parseaddr(email)
if result[1]:
# delete all keys matching this email address
p = subprocess.Popen( ['/usr/bin/gpg', '--homedir', keyhome, '--delete-key', '--batch', '--yes', result[1]], stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
p.wait()
return True
return False
class GPGEncryptor:
def __init__(self, keyhome, recipients = None, charset = None):
self._keyhome = keyhome
self._message = ''
self._recipients = list()
self._charset = charset
self._charset = charset
if recipients != None:
self._recipients.extend(recipients)
@ -34,15 +83,15 @@ class GPGEncryptor:
def _command(self):
cmd = ["/usr/bin/gpg", "--trust-model", "always", "--homedir", self._keyhome, "--batch", "--yes", "--pgp7", "--no-secmem-warning", "-a", "-e"]
# add recipients
for recipient in self._recipients:
cmd.append("-r")
cmd.append(recipient)
# add on the charset, if set
if self._charset:
cmd.append("--comment")
cmd.append('Charset: ' + self._charset)
return cmd

30
gpg-mailgate-web/README

@ -0,0 +1,30 @@
gpg-mailgate-web
----------------
gpg-mailgate-web is a web interface designed to allow any web user
to upload their PGP public key and then have all mail sent from
your mail server be encrypted. (Note: this is not meant for email
authentication, only encryption.)
After submitting their key to a web form, the user will be required
to confirm their email address. A cron script will register the
public key with gpg-mailgate (keyhome_only must be set to no
currently, which is the default) after email confirmation. From
then on, email to the specified address will be encrypted with
the public key.
Installation instructions:
1) Install gpg-mailgate.
2) Create a MySQL database for gpg-mailgate.
a) Schema file is located in schema.sql
b) Database name and account goes in /etc/gpg-mailgate.conf (and set enabled = yes)
3) Copy the contents of public_html to your web directory.
4) Move config.sample.php to config.php and edit the configuration file.
5) Copy cron.py to /usr/local/bin/gpgmw-cron.py and set up a cron job
a) Create /etc/cron.d/gpgmw with the contents:
*/3 * * * * gpgmap /usr/bin/python /usr/local/bin/gpgmw-cron.py > /dev/null
(replace gpgmap with the owner of the gpg-mailgate GPG home directory)
6) Ensure that cron is working and test your new gpg-mailgate-web installation!
Any issues should be reported to https://github.com/uakfdotb/gpg-mailgate

57
gpg-mailgate-web/cron.py

@ -0,0 +1,57 @@
#!/usr/bin/python
from ConfigParser import RawConfigParser
import GnuPG
import MySQLdb
def appendLog(msg):
if cfg.has_key('logging') and cfg['logging'].has_key('file'):
log = open(cfg['logging']['file'], 'a')
log.write(msg + "\n")
log.close()
# Read configuration from /etc/gpg-mailgate.conf
_cfg = RawConfigParser()
_cfg.read('/etc/gpg-mailgate.conf')
cfg = dict()
for sect in _cfg.sections():
cfg[sect] = dict()
for (name, value) in _cfg.items(sect):
cfg[sect][name] = value
if cfg.has_key('database') and cfg['database'].has_key('enabled') and cfg['database']['enabled'] == 'yes' and cfg['database'].has_key('name') and cfg['database'].has_key('host') and cfg['database'].has_key('username') and cfg['database'].has_key('password'):
connection = MySQLdb.connect(host = cfg['database']['host'], user = cfg['database']['username'], passwd = cfg['database']['password'], db = cfg['database']['name'], port = 3306)
cursor = connection.cursor()
# import keys
cursor.execute("SELECT publickey, id, email FROM gpgmw_keys WHERE status = 0 AND confirm = '' LIMIT 100")
result_set = cursor.fetchall()
for row in result_set:
# delete any other public keys associated with this confirmed email address
cursor.execute("DELETE FROM gpgmw_keys WHERE email = %s AND id != %s", (row[2], row[1],))
GnuPG.delete_key(cfg['gpg']['keyhome'], row[2])
appendLog('Deleted key for <' + row[2] + '> via import request')
if row[0].strip(): # we have this so that user can submit blank key to remove any encryption
if GnuPG.confirm_key(row[0], row[2]):
GnuPG.add_key(cfg['gpg']['keyhome'], row[0]) # import the key to gpg
cursor.execute("UPDATE gpgmw_keys SET status = 1 WHERE id = %s", (row[1],)) # mark key as accepted
appendLog('Imported key from <' + row[2] + '>')
else:
cursor.execute("DELETE FROM gpgmw_keys WHERE id = %s", (row[1],)) # delete key
appendLog('Import confirmation failed for <' + row[2] + '>')
connection.commit()
# delete keys
cursor.execute("SELECT email, id FROM gpgmw_keys WHERE status = 2 LIMIT 100")
result_set = cursor.fetchall()
for row in result_set:
GnuPG.delete_key(cfg['gpg']['keyhome'], row[0])
cursor.execute("DELETE FROM gpgmw_keys WHERE id = %s", (row[1],))
appendLog('Deleted key for <' + row[0] + '>')
connection.commit()
else:
print "Warning: doing nothing since database settings are not configured!"

93
gpg-mailgate-web/public_html/config.sample.php

@ -0,0 +1,93 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
$config = array();
//
// GENERAL SITE SETTINGS
//
//web team contact
// this email address will be displayed if there is a database error
$config['email_web'] = 'admin@example.com';
//address to send emails from
$config['email_from'] = 'gpg-mailgate-web@example.com';
//this will be used as the subject when a user requests to add a PGP key
$config['email_subject_requestpgp'] = 'Confirm your email address';
//site URL, without trailing slash
$config['site_url'] = 'http://example.com/gpgmw';
//title of the website (displayed on home page)
$config['site_title'] = 'PGP key management';
//
// MAIL SETTINGS
//
//whether to send mail through SMTP (instead of PHP mail function)
$config['mail_smtp'] = false;
//SMTP settings, if mail_smtp is enabled
$config['mail_smtp_host'] = 'localhost';
$config['mail_smtp_port'] = 25;
$config['mail_smtp_username'] = 'gpgmw';
$config['mail_smtp_password'] = '';
//
// DATABASE SETTINGS
//
//database name (MySQL only); or see include/dbconnect.php
$config['db_name'] = 'gpgmw';
//database host
$config['db_host'] = 'localhost';
//database username
$config['db_username'] = 'gpgmw';
//database password
$config['db_password'] = '';
//
// LOCK SETTINGS
//
//the time in seconds a user must wait before trying again; otherwise they get locked out (count not increased)
$config['lock_time_initial'] = array('requestpgp' => 10);
//the number of tries a user has (that passes the lock_time_initial test) before being locked by overload (extended duration)
$config['lock_count_overload'] = array('requestpgp' => 3);
//the time that overloads last
$config['lock_time_overload'] = array('requestpgp' => 900);
//time after which locks no longer apply, assuming the lock isn't active
$config['lock_time_reset'] = 300;
//max time to store locks in the database; this way we can clear old locks with one function
$config['lock_time_max'] = 3600;
?>

40
gpg-mailgate-web/public_html/confirm.php

@ -0,0 +1,40 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
require_once("config.php");
require_once("include/common.php");
require_once("include/dbconnect.php");
require_once("include/pgp.php");
if(isset($_REQUEST['email']) && isset($_REQUEST['confirm'])) {
$result = confirmPGP($_REQUEST['email'], $_REQUEST['confirm']);
if($result === true) {
get_page("home", array('message' => 'Your email address has been confirmed successfully. Within a few minutes, emails from our mail server to you should be encrypted with your PGP public key.'));
} else {
get_page("home", array('message' => 'Error: failed to confirm any email address. You may have already confirmed the address, or you may have the wrong confirmation key.'));
}
} else {
get_page("home");
}
?>

257
gpg-mailgate-web/public_html/include/common.php

@ -0,0 +1,257 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
function string_begins_with($string, $search)
{
return (strncmp($string, $search, strlen($search)) == 0);
}
function boolToString($bool) {
return $bool ? 'true' : 'false';
}
//returns an absolute path to the include directory
function includePath() {
$self = __FILE__;
$lastSlash = strrpos($self, "/");
return substr($self, 0, $lastSlash + 1);
}
//returns a relative path to the gpg-mailgate-web web root directory, without trailing slash
function basePath() {
$commonPath = __FILE__;
$requestPath = $_SERVER['SCRIPT_FILENAME'];
//count the number of slashes
// number of .. needed for include level is numslashes(request) - numslashes(common)
// then add one more to get to base
$commonSlashes = substr_count($commonPath, '/');
$requestSlashes = substr_count($requestPath, '/');
$numParent = $requestSlashes - $commonSlashes + 1;
$basePath = ".";
for($i = 0; $i < $numParent; $i++) {
$basePath .= "/..";
}
return $basePath;
}
function uid($length) {
$characters = "0123456789abcdefghijklmnopqrstuvwxyz";
$string = "";
for ($p = 0; $p < $length; $p++) {
$string .= $characters[secure_random() % strlen($characters)];
}
return $string;
}
function get_page($page, $args = array()) {
//let pages use some variables
extract($args);
$config = $GLOBALS['config'];
$basePath = basePath();
$themePath = $basePath . "/theme";
$themePageInclude = "$themePath/$page.php";
if(file_exists("$themePath/header.php")) {
include("$themePath/header.php");
}
if(file_exists($themePageInclude)) {
include($themePageInclude);
}
if(file_exists("$themePath/footer.php")) {
include("$themePath/footer.php");
}
}
function isAscii($str) {
return 0 == preg_match('/[^\x00-\x7F]/', $str);
}
//returns random number from 0 to 2^24
function secure_random() {
return hexdec(bin2hex(secure_random_bytes(3)));
}
function gpgmw_mail($subject, $body, $to) { //returns true=ok, false=notok
$config = $GLOBALS['config'];
$from = filter_var($config['email_from'], FILTER_SANITIZE_EMAIL);
$to = filter_var($to, FILTER_SANITIZE_EMAIL);
if($to === false || $from === false) {
return false;
}
if(isset($config['mail_smtp']) && $config['mail_smtp']) {
require_once "Mail.php";
$host = $config['mail_smtp_host'];
$port = $config['mail_smtp_port'];
$username = $config['mail_smtp_username'];
$password = $config['mail_smtp_password'];
$headers = array ('From' => $from,
'To' => $to,
'Subject' => $subject,
'Content-Type' => 'text/plain');
$smtp = Mail::factory('smtp',
array ('host' => $host,
'port' => $port,
'auth' => true,
'username' => $username,
'password' => $password));
$mail = $smtp->send($to, $headers, $body);
if (PEAR::isError($mail)) {
return false;
} else {
return true;
}
} else {
$headers = "From: $from\r\n";
$headers .= "Content-type: text/plain\r\n";
return mail($to, $subject, $body, $headers);
}
}
//secure_random_bytes from https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP
/*
* The function is providing, at least at the systems tested :),
* $len bytes of entropy under any PHP installation or operating system.
* The execution time should be at most 10-20 ms in any system.
*/
function secure_random_bytes($len = 10) {
/*
* Our primary choice for a cryptographic strong randomness function is
* openssl_random_pseudo_bytes.
*/
$SSLstr = '4'; // http://xkcd.com/221/
if (function_exists('openssl_random_pseudo_bytes') &&
(version_compare(PHP_VERSION, '5.3.4') >= 0 ||
substr(PHP_OS, 0, 3) !== 'WIN'))
{
$SSLstr = openssl_random_pseudo_bytes($len, $strong);
if ($strong)
return $SSLstr;
}
/*
* If mcrypt extension is available then we use it to gather entropy from
* the operating system's PRNG. This is better than reading /dev/urandom
* directly since it avoids reading larger blocks of data than needed.
* Older versions of mcrypt_create_iv may be broken or take too much time
* to finish so we only use this function with PHP 5.3 and above.
*/
if (function_exists('mcrypt_create_iv') &&
(version_compare(PHP_VERSION, '5.3.0') >= 0 ||
substr(PHP_OS, 0, 3) !== 'WIN'))
{
$str = mcrypt_create_iv($len, MCRYPT_DEV_URANDOM);
if ($str !== false)
return $str;
}
/*
* No build-in crypto randomness function found. We collect any entropy
* available in the PHP core PRNGs along with some filesystem info and memory
* stats. To make this data cryptographically strong we add data either from
* /dev/urandom or if its unavailable, we gather entropy by measuring the
* time needed to compute a number of SHA-1 hashes.
*/
$str = '';
$bits_per_round = 2; // bits of entropy collected in each clock drift round
$msec_per_round = 400; // expected running time of each round in microseconds
$hash_len = 20; // SHA-1 Hash length
$total = $len; // total bytes of entropy to collect
$handle = @fopen('/dev/urandom', 'rb');
if ($handle && function_exists('stream_set_read_buffer'))
@stream_set_read_buffer($handle, 0);
do
{
$bytes = ($total > $hash_len)? $hash_len : $total;
$total -= $bytes;
//collect any entropy available from the PHP system and filesystem
$entropy = rand() . uniqid(mt_rand(), true) . $SSLstr;
$entropy .= implode('', @fstat(@fopen( __FILE__, 'r')));
$entropy .= memory_get_usage();
if ($handle)
{
$entropy .= @fread($handle, $bytes);
}
else
{
// Measure the time that the operations will take on average
for ($i = 0; $i < 3; $i ++)
{
$c1 = microtime(true);
$var = sha1(mt_rand());
for ($j = 0; $j < 50; $j++)
{
$var = sha1($var);
}
$c2 = microtime(true);
$entropy .= $c1 . $c2;
}
// Based on the above measurement determine the total rounds
// in order to bound the total running time.
$rounds = (int)($msec_per_round*50 / (int)(($c2-$c1)*1000000));
// Take the additional measurements. On average we can expect
// at least $bits_per_round bits of entropy from each measurement.
$iter = $bytes*(int)(ceil(8 / $bits_per_round));
for ($i = 0; $i < $iter; $i ++)
{
$c1 = microtime();
$var = sha1(mt_rand());
for ($j = 0; $j < $rounds; $j++)
{
$var = sha1($var);
}
$c2 = microtime();
$entropy .= $c1 . $c2;
}
}
// We assume sha1 is a deterministic extractor for the $entropy variable.
$str .= sha1($entropy, true);
} while ($len > strlen($str));
if ($handle)
@fclose($handle);
return substr($str, 0, $len);
}
?>

76
gpg-mailgate-web/public_html/include/dbconnect.php

@ -0,0 +1,76 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
function dieDatabaseError($ex = NULL) {
global $config;
if($ex == NULL) {
$pre = "Encountered database error.";
} else {
$pre = "Encountered database error: " . $ex->getMessage() . ".";
}
die($pre . " If this is unexpected, consider <a href=\"mailto:{$config['email_web']}\">reporting it to our web team</a>. Otherwise, <a href=\"/\">click here to return to the home page.</a>");
}
try {
$database = new PDO('mysql:host=' . $config['db_host'] . ';dbname=' . $config['db_name'], $config['db_username'], $config['db_password'], array(PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
} catch(PDOException $ex) {
dieDatabaseError($ex);
}
function databaseQuery($command, $array = array(), $assoc = false) {
global $database;
if(!is_array($array)) {
dieDatabaseError();
}
try {
$query = $database->prepare($command);
if(!$query) {
print_r($database->errorInfo());
dieDatabaseError();
}
//set fetch mode depending on parameter
if($assoc) {
$query->setFetchMode(PDO::FETCH_ASSOC);
} else {
$query->setFetchMode(PDO::FETCH_NUM);
}
$success = $query->execute($array);
if(!$success) {
print_r($query->errorInfo());
dieDatabaseError();
}
return $query;
} catch(PDOException $ex) {
dieDatabaseError($ex);
}
}
?>

124
gpg-mailgate-web/public_html/include/lock.php

@ -0,0 +1,124 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
//lock.php is basic spam-submit prevention
//lock_time_initial, lock_time_overload, lock_count_overload, lock_time_reset, and lock_time_max should be defined in $config
//returns boolean: true=proceed, false=lock up; the difference between this and lockAction is that this can be used for repeated tasks, like admin
// then, only if action was unsuccessful would lockAction be called
function checkLock($action) {
global $config;
$lock_time_initial = $config['lock_time_initial'];
$lock_time_overload = $config['lock_time_overload'];
$lock_count_overload = $config['lock_count_overload'];
$lock_time_reset = $config['lock_time_reset'];
$lock_time_max = $config['lock_time_max'];
if(!isset($lock_time_initial[$action])) {
return true; //well we can't do anything...
}
$ip = $_SERVER['REMOTE_ADDR'];
$result = databaseQuery("SELECT id, time, num FROM gpgmw_locks WHERE ip = ? AND action = ?", array($ip, $action), true);
if($row = $result->fetch()) {
$id = $row['id'];
$time = $row['time'];
$count = $row['num']; //>=0 count means it's a regular initial lock; -1 count means overload lock
if($count >= 0) {
if(time() <= $time + $lock_time_initial[$action]) {
return false;
}
} else {
if(time() <= $time + $lock_time_overload[$action]) {
return false;
}
}
}
return true;
}
//returns boolean: true=proceed, false=lock up
function lockAction($action) {
global $config;
$lock_time_initial = $config['lock_time_initial'];
$lock_time_overload = $config['lock_time_overload'];
$lock_count_overload = $config['lock_count_overload'];
$lock_time_reset = $config['lock_time_reset'];
$lock_time_max = $config['lock_time_max'];
if(!isset($lock_time_initial[$action])) {
return true; //well we can't do anything...
}
$ip = $_SERVER['REMOTE_ADDR'];
$replace_id = -1;
//first find records with ip/action
$result = databaseQuery("SELECT id, time, num FROM gpgmw_locks WHERE ip = ? AND action = ?", array($ip, $action), true);
if($row = $result->fetch()) {
$id = $row['id'];
$time = $row['time'];
$count = $row['num']; //>=0 count means it's a regular initial lock; -1 count means overload lock
if($count >= 0) {
if(time() <= $time + $lock_time_initial[$action]) {
return false;
} else if(time() > $time + $lock_time_reset) {
//this entry is old, but use it to replace
$replace_id = $id;
} else {
//increase the count; maybe initiate an OVERLOAD
$count = $count + 1;
if($count >= $lock_count_overload[$action]) {
databaseQuery("UPDATE gpgmw_locks SET num = '-1', time = ? WHERE ip = ?", array(time(), $ip));
return false;
} else {
databaseQuery("UPDATE gpgmw_locks SET num = ?, time = ? WHERE ip = ?", array($count, time(), $ip));
}
}
} else {
if(time() <= $time + $lock_time_overload[$action]) {
return false;
} else {
//their overload is over, so this entry is old
$replace_id = $id;
}
}
} else {
databaseQuery("INSERT INTO gpgmw_locks (ip, time, action, num) VALUES (?, ?, ?, '1')", array($ip, time(), $action));
}
if($replace_id != -1) {
databaseQuery("UPDATE gpgmw_locks SET num = '1', time = ? WHERE id = ?", array(time(), $replace_id));
}
//some housekeeping
$delete_time = time() - $lock_time_max;
databaseQuery("DELETE FROM gpgmw_locks WHERE time <= ?", array($delete_time));
return true;
}
?>

88
gpg-mailgate-web/public_html/include/pgp.php

@ -0,0 +1,88 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
//returns true on success or error message on failure
function requestPGP($email, $key) {
require_once(includePath() . "/lock.php");
global $config;
if(!checkLock('requestpgp')) {
return "please wait a bit before trying again";
}
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return "invalid email address";
}
if(strlen($email) > 256 || strlen($key) > 1024 * 32) {
return "email address or key too long";
}
if(!isAscii($key)) {
return "only keys encoded with ASCII armor are accepted (gpg --armor)";
}
//housekeeping
databaseQuery("DELETE FROM gpgmw_keys WHERE time < DATE_SUB(NOW(), INTERVAL 48 HOUR) AND confirm != '' AND status = 0");
//if we already have an unaccepted key for this user, only replace if one day has elapsed since the last request
// this may prevent spam
$result = databaseQuery("SELECT HOUR(TIMEDIFF(time, NOW())), id FROM gpgmw_keys WHERE email = ? AND status = 0", array($email));
if($row = $result->fetch()) {
if($row[0] < 24) {
return "there is already a key in the queue for this email address; please wait twenty-four hours between submitting keys, or confirm the previous key and then resubmit";
} else {
databaseQuery('DELETE FROM gpgmw_keys WHERE id = ?', array($row[1]));
}
}
//well, it looks good, let's submit it
lockAction('requestpgp');
$confirm = uid(32);
$result = gpgmw_mail($config['email_subject_requestpgp'], "Please confirm your email address to complete the submission process. You can do so by clicking the link below\n\n{$config['site_url']}/confirm.php?email=" . urlencode($email) . "&confirm=$confirm\n\nThanks,\ngpg-mailgate-web", $email);
if(!$result) {
return "failed to send email";
}
databaseQuery("INSERT INTO gpgmw_keys (email, publickey, confirm) VALUES (?, ?, ?)", array($email, $key, $confirm));
return true;
}
//returns false on failure or true on success
function confirmPGP($email, $confirm) {
if(!lockAction('confirmpgp')) {
return "try again later";
}
$result = databaseQuery("SELECT id FROM gpgmw_keys WHERE confirm = ? AND email = ?", array($confirm, $email));
if($row = $result->fetch()) {
databaseQuery("UPDATE gpgmw_keys SET confirm = '' WHERE id = ?", array($row[0]));
return true;
}
return false;
}
?>

40
gpg-mailgate-web/public_html/index.php

@ -0,0 +1,40 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
require_once("config.php");
require_once("include/common.php");
require_once("include/dbconnect.php");
require_once("include/pgp.php");
if(isset($_POST['email']) && isset($_POST['key'])) {
$result = requestPGP($_POST['email'], $_POST['key']);
if($result === true) {
get_page("home", array('message' => 'Key submission successful. Please check your email to confirm your email address.'));
} else {
get_page("home", array('message' => 'Error: ' . $result . '.'));
}
} else {
get_page("home");
}
?>

52
gpg-mailgate-web/public_html/theme/home.php

@ -0,0 +1,52 @@
<?php
/*
gpg-mailgate
This file is part of the gpg-mailgate source code.
gpg-mailgate is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gpg-mailgate source code 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with gpg-mailgate source code. If not, see <http://www.gnu.org/licenses/>.
*/
?>
<html>
<head>
<title>gpg-mailgate-web</title>
</head>
<body>
<h1><?= $config['site_title'] ?></h1>
<? if(!empty($message)) { ?>
<p><b><i><?= htmlspecialchars($message) ?></i></b></p>
<? } ?>
<p>Use the form below to submit an ASCII-armored PGP public key. After submission, you will receive an email asking you to confirm your email address. Note that this is not a keyserver.</p>
<form method="POST">
<table>
<tr>
<td>Your email address (must match key)</td>
<td><input type="text" name="email" /></td>
</tr>
<tr>
<td>ASCII-armored PGP public key</td>
<td><textarea name="key" rows="10" cols="80"></textarea></td>
</tr>
</table>
<input type="submit" value="Submit key" />
</form>
</body>
</html>

0
gpg-mailgate-web/public_html/theme/index.html

10
gpg-mailgate-web/schema.sql

@ -0,0 +1,10 @@
-- confirm is empty once an email address has been confirmed, and otherwise is the confirmation key
-- status
-- initializes to 0
-- is set to 1 after a public key with (confirm='', status=0) has been imported
-- is set to 2 if a key should be deleted (will be deleted based on email address)
-- publickey is the ASCII-armored PGP public key; can be cleared to save space if status > 0
CREATE TABLE gpgmw_keys (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, email VARCHAR(256), publickey TEXT, confirm VARCHAR(32), status INT NOT NULL DEFAULT 0, time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
-- see include/lock.php for documentation
CREATE TABLE gpgmw_locks (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, ip VARCHAR(16), time INT, action VARCHAR(16), num INT);

9
gpg-mailgate.conf.sample

@ -24,6 +24,15 @@ file = /tmp/gpg-mailgate.log
host = 127.0.0.1
port = 10028
[database]
# uncomment the settings below if you want
# to read keys from a gpg-mailgate-web database
#enabled = yes
#name = gpgmw
#host = localhost
#username = gpgmw
#password =
[keymap]
# You can find these by running the following command:
# gpg --list-keys --keyid-format long user@example.com

8
gpg-mailgate.py

@ -45,15 +45,15 @@ def encrypt_payload( payload, gpg_to_cmdline ):
gpg = GnuPG.GPGEncryptor( cfg['gpg']['keyhome'], gpg_to_cmdline, payload.get_content_charset() )
gpg.update( raw_payload )
payload.set_payload( gpg.encrypt() )
isAttachment = payload.get_param( 'attachment', None, 'Content-Disposition' ) is not None
if isAttachment:
filename = payload.get_filename()
if filename:
pgpFilename = filename + ".pgp"
if payload.get('Content-Disposition') is not None:
payload.set_param( 'filename', pgpFilename, 'Content-Disposition' )
if payload.get('Content-Type') is not None:

Loading…
Cancel
Save