wez Sun Apr 1 16:06:14 2001 EDT
Modified files:
/php4/ext/openssl CREDITS README openssl.c php_openssl.h
Log:
Added whole bunch of PKCS7 (S/MIME) functions and made the key/cert params
a bit more friendly to use. See the README for more info.
Index: php4/ext/openssl/CREDITS
diff -u php4/ext/openssl/CREDITS:1.2 php4/ext/openssl/CREDITS:1.3
--- php4/ext/openssl/CREDITS:1.2 Thu Nov 23 11:30:37 2000
+++ php4/ext/openssl/CREDITS Sun Apr 1 16:06:14 2001
@@ -1,2 +1,2 @@
OpenSSL
-Stig Venaas
+Stig Venaas, Wez Furlong
Index: php4/ext/openssl/README
diff -u php4/ext/openssl/README:1.1 php4/ext/openssl/README:1.2
--- php4/ext/openssl/README:1.1 Thu Nov 23 11:30:37 2000
+++ php4/ext/openssl/README Sun Apr 1 16:06:14 2001
@@ -1,53 +1,210 @@
OpenSSL extension for PHP4
-$Id: README,v 1.1 2000/11/23 19:30:37 venaas Exp $
+$Id: README,v 1.2 2001/04/01 23:06:14 wez Exp $
-The functions implemented so far make it possible to seal and open data,
-and also create and verify signatures. To enable the extension, configure
-PHP with --with-openssl.
+The functions implemented so far make it possible to seal and open data, and
+also create and verify signatures.
+NEW: support for S/MIME encrypt/decrypt/sign/verify, as well as more
+flexibility for specifying certificates/keys.
-Functions:
+To enable the extension, configure PHP with --with-openssl.
-int openssl_get_privatekey(string key [, string passphrase])
+Specifying keys/certificates
+----------------------------
+Most of the functions require a key or a certificate as a parameter; to make
+things easy for you to use openssl, this extension allows you
+to specify certificates in the following way:
+
+1. As an X.509 resource returned from openssl_x509_read
+2. As a string in the format file://filename, where filename is the path to the
+ certificate file (it will be opened and read automatically)
+3. As a string containing the data from the certificate file
+
+Similarly, you can use the following methods of specifying a public key:
+
+1. As a key resource returned from openssl_get_publickey
+2. An X509 resource - public key only
+3. As a string in the format file://filename
+4. As a string containing the data from the key file
+
+Additionally, for a private key, when the openssl extension function does not
+allow you to enter the passphrase as a parameter you may use the syntax
+array($key, "passphrase") where $key can be a key specified using one of the
+methods listed above.
+
+Certificate Verification
+------------------------
+When calling a function that will verify a signature/certificate, the cainfo
+parameter is an array containing file and directory names that specifiy the
+locations of trusted CA files. If a directory is specified, then it must be a
+correctly hashed directory.
+
+Misc:
+-----
+
+mixed openssl_error_string()
+
+returns the message from the last error that the OpenSSL library encountered
+and moves it's internal error pointer to the next message. If there are no
+more error messages, returns false.
+
+General Key/Cert Functions:
+---------------------------
+
+resource openssl_get_privatekey(mixed key [, string passphrase])
+
Parses the key data and returns a key resource identifier. If the key is
encrypted a passphrase is needed. This can be supplied as second argument.
-int openssl_get_publickey(string cert)
+resource openssl_get_publickey(mixed cert)
Extracts the public key from the given certificate and returns a key
resource identifier.
-void openssl_free_key(int key)
+void openssl_free_key(resource key)
Frees the resource given by the key resource identifier.
+Note that this function does not accept the extended key specification
+syntax mentioned above, as it doesn't make sense in this case!
+
+array openssl_x509_parse(mixed x509[, bool shortnames=true])
+
+Parses the certificate data and returns an array containing information
+about the certificate, it's intended purposes, subject, issuer, validity
+etc. etc. If shortnames is true (the default) then the fields will be
+keyed by the shortname forms eg: CN as opposed to commonName (shortnames
+= false).
+
+
+bool openssl_x509_checkpurpose(mixed x509cert, int purpose,
+ array cainfo[, string untrustedfile])
+
+Verifies if the certificate can be used for a specific purpose.
+Purpose can be one of the following values:
+ X509_PURPOSE_SSL_CLIENT
+ X509_PURPOSE_SSL_SERVER
+ X509_PURPOSE_NS_SSL_SERVER
+ X509_PURPOSE_SMIME_SIGN
+ X509_PURPOSE_SMIME_ENCRYPT
+ X509_PURPOSE_CRL_SIGN
+ X509_PURPOSE_ANY
+
+cainfo is an array of CA information (as mentioned above).
+untrusted file specifies a file containing a bunch of certs that
+are not trusted but may be useful in validating the certificate.
+
+
+resource openssl_read_x509(mixed cert)
+
+Parses the cert and returns a resource that can be used with the
+other openssl functions
+
+
+void openssl_free_x509(resource x509)
+
+Frees the resource given by the x509 resource identifier.
+Note that this function does not accept the extended cert specification
+syntax mentioned above, as it doesn't make sense in this case!
+
+
+PKCS7 (S/MIME) Sign/Verify/Encrypt/Decrypt Functions:
+-----------------------------------------------------
+
+These functions allow you to manipulate S/MIME messages!
+
+They are based on apps/smime.c from the openssl dist, so for information,
+see the documentation for openssl.
+
+You may pass in some flags that affect how these functions work using
+and array containing the following values:
+"detached", "nodetached", "text", "nointern", "noverify", "nochain",
+"nocerts", "noattr", "binary", "nosigs".
+The options correspond to the options of the same name for the
+"openssl smime" command (smime(1)).
+
+
+bool openssl_pkcs7_verify(string filename, array flags[, string signerscerts][,
+ array cainfo])
+
+Verifies that the signature on the MIME message contained in the file
+named by filename is valid. If signerscerts is passed in, it holds the
+name of a file into which the certificates of those that signed the
+message will be stored.
+cainfo and flags are CA information and flag information as described
+above.
+
+
+bool openssl_pkcs7_encrypt(string infile, string outfile, array recipcerts,
+ array headers[, array flags])
+
+Encrypts the MIME message contained in the file named by infile using
+the certificates held in recipcerts. The result is place in the file
+named outfile.
+recipcerts is an array of certificate identifiers representing the certs
+of the intended recipients of the message.
+headers is an array of headers to prepend to the message: they will
+not be included in the encoded section.
+flags is flag information as described above.
+Hint: you will want to put "To", "From", and "Subject" headers in headers.
+Headers can be either an assoc array keyed by header named, or can be
+and indexed array containing a single header line per value.
+The message will be encoded using a RC2-40 bit cipher.
+TODO: allow user to specify cipher.
+
+bool openssl_pkcs7_sign(string infile, string outfile, mixed signcert, mixed
+ signkey, array headers[, array flags][, string extracertsfilename])
+
+Signs the MIME message contained in the file named by infile using the
+certificate and key pair identified by signcert/signkey.
+Signkey must be the private key corresponding to signcert.
+The result is placed in the file named by outfile.
+Headers and flags have the same effects as mentioned above.
+extracertsfilename names a file containing a bunch of additional certificates
+to include in the signature, in order to aid the recipient in verifying the
+message.
+
+
+bool openssl_pkcs7_decrypt(string infilename, string outfilename, mixed
+ recipcert, mixed recipkey)
+
+Decrypts the MIME message contained in the file named by infilename
+using the certificate and private key pair recipcert/recipkey.
+The descrypted result is placed in outfilename.
+TODO: add flags parameter, if needed?
+
+EVP Sign/Verify/Encrypt/Decrypt Functions:
+------------------------------------------
-bool openssl_sign(string data, string signature, int key)
+bool openssl_sign(string data, &string signature, mixed key)
Uses key to create signature for data, returns true on success and false
-on failure.
+on failure. signature is passed by reference and contains the newly created
+signature on success.
-int openssl_verify(string data, string signature, int key)
+int openssl_verify(string data, string signature, mixed key)
Uses key to verify that the signature is correct for the given data.
Returns 1 if correct, 0 if incorrect, and -1 on error.
-int openssl_seal(string data, string sealdata, array ekeys, array pubkeys)
+int openssl_seal(string data, &string sealdata, &array ekeys, array pubkeys)
Encrypts data using pubkeys, so that only owners of the respective private
keys and ekeys can decrypt and read the data. Returns the length of the
-sealed data on success, else false.
+sealed data on success, else false. On success, sealdata and ekeys hold
+the sealed data and envelope keys.
-bool openssl_open(string data, string opendata, string ekey, int privkey)
+bool openssl_open(string data, &string opendata, string ekey, int privkey)
Opens (decrypts) sealed data using a private key and the corresponding
-envelope key. Returns true on success and false on failure.
+envelope key. Returns true on success and false on failure. On success,
+opendata will hold the descypted data.
See below for more details on usage. Also feel free to mail me at
Index: php4/ext/openssl/openssl.c
diff -u php4/ext/openssl/openssl.c:1.9 php4/ext/openssl/openssl.c:1.10
--- php4/ext/openssl/openssl.c:1.9 Sun Feb 25 22:07:10 2001
+++ php4/ext/openssl/openssl.c Sun Apr 1 16:06:14 2001
@@ -13,11 +13,12 @@
| [EMAIL PROTECTED] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Stig Venaas <[EMAIL PROTECTED]> |
+ | Wez Furlong <[EMAIL PROTECTED]> |
+----------------------------------------------------------------------+
*/
-
-/* $Id: openssl.c,v 1.9 2001/02/26 06:07:10 andi Exp $ */
+/* $Id: openssl.c,v 1.10 2001/04/01 23:06:14 wez Exp $ */
+
#include "php.h"
#include "php_openssl.h"
@@ -28,7 +29,10 @@
/* OpenSSL includes */
#include <openssl/evp.h>
#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/crypto.h>
#include <openssl/pem.h>
+#include <openssl/err.h>
static unsigned char arg2of3_force_ref[] =
{ 3, BYREF_NONE, BYREF_FORCE, BYREF_NONE };
@@ -37,20 +41,29 @@
static unsigned char arg2and3of4_force_ref[] =
{ 4, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_NONE };
-
function_entry openssl_functions[] = {
PHP_FE(openssl_get_privatekey, NULL)
PHP_FE(openssl_get_publickey, NULL)
PHP_FE(openssl_free_key, NULL)
-#if 0
- PHP_FE(openssl_read_publickey, NULL)
- PHP_FE(openssl_read_x509, NULL)
- PHP_FE(openssl_free_x509, NULL)
-#endif
+
+ PHP_FE(openssl_x509_read, NULL)
+ PHP_FE(openssl_x509_free, NULL)
+
+ PHP_FE(openssl_x509_parse, NULL)
+ PHP_FE(openssl_x509_checkpurpose, NULL)
+
PHP_FE(openssl_sign, arg2of3_force_ref)
PHP_FE(openssl_verify, NULL)
PHP_FE(openssl_seal, arg2and3of4_force_ref)
PHP_FE(openssl_open, arg2of4_force_ref)
+/* for S/MIME handling */
+ PHP_FE(openssl_pkcs7_verify, NULL)
+ PHP_FE(openssl_pkcs7_decrypt, NULL)
+ PHP_FE(openssl_pkcs7_sign, NULL)
+ PHP_FE(openssl_pkcs7_encrypt, NULL)
+
+/* useful to figure out whats going on! */
+ PHP_FE(openssl_error_string, NULL)
{NULL, NULL, NULL}
};
@@ -58,8 +71,8 @@
"openssl",
openssl_functions,
PHP_MINIT(openssl),
+ NULL,
NULL,
- NULL,
NULL,
PHP_MINFO(openssl),
STANDARD_MODULE_PROPERTIES
@@ -72,22 +85,39 @@
static void _php_pkey_free(zend_rsrc_list_entry *rsrc);
static int le_key;
-#if 0
static void _php_x509_free(zend_rsrc_list_entry *rsrc);
static int le_x509;
-#endif
+static X509 * php_openssl_x509_from_zval(zval ** val, int makeresource, long *
+resourceval);
+static EVP_PKEY * php_openssl_evp_from_zval(zval ** val, int public_key, char *
+passphrase, int makeresource, long * resourceval);
+static unsigned long php_openssl_parse_pkcs7_flags(zval ** val, unsigned long def);
+static X509_STORE * setup_verify(zval * calist);
+static STACK_OF(X509) * load_all_certs_from_file(char *certfile);
+
+
PHP_MINIT_FUNCTION(openssl)
{
- le_key = zend_register_list_destructors_ex(_php_pkey_free, NULL,
- "OpenSSL key",
- module_number);
-#if 0
- le_x509 = zend_register_list_destructors_ex(_php_x509_free, NULL,
- "OpenSSL X.509",
- module_number);
-#endif
+ le_key = zend_register_list_destructors_ex(_php_pkey_free, NULL, "OpenSSL
+key", module_number);
+ le_x509 = zend_register_list_destructors_ex(_php_x509_free, NULL, "OpenSSL
+X.509", module_number);
+
OpenSSL_add_all_ciphers();
+
+/*
+ SSL_load_error_strings();
+*/
+ ERR_load_ERR_strings();
+ ERR_load_crypto_strings();
+ ERR_load_EVP_strings();
+
+ /* purposes for cert purpose checking */
+ REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_CLIENT", X509_PURPOSE_SSL_CLIENT,
+CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_SERVER", X509_PURPOSE_SSL_SERVER,
+CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("X509_PURPOSE_NS_SSL_SERVER",
+X509_PURPOSE_NS_SSL_SERVER, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_SIGN", X509_PURPOSE_SMIME_SIGN,
+CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("X509_PURPOSE_SMIME_ENCRYPT",
+X509_PURPOSE_SMIME_ENCRYPT, CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("X509_PURPOSE_CRL_SIGN", X509_PURPOSE_CRL_SIGN,
+CONST_CS|CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("X509_PURPOSE_ANY", X509_PURPOSE_ANY,
+CONST_CS|CONST_PERSISTENT);
+
return SUCCESS;
}
@@ -105,84 +135,557 @@
return SUCCESS;
}
+
+/*
+ Given a zval, coerce it into an X509 object.
+ The zval can be:
+ . X509 resource created using openssl_read_x509()
+ . if it starts with file:// then it will be interpreted as the path to
+that cert
+ . it will be interpreted as the cert data
+ If you supply makeresource, the result will be registered as an x509 resource
+and
+ it's value returned in makeresource.
+*/
+static X509 * php_openssl_x509_from_zval(zval ** val, int makeresource, long *
+resourceval)
+{
+ X509 *cert = NULL;
+
+ if (resourceval)
+ *resourceval = -1;
+
+ if ((*val)->type == IS_RESOURCE) {
+ /* is it an x509 resource ? */
+ void * what;
+ int type;
+
+ what = zend_fetch_resource(val, -1, "OpenSSL X.509", &type, 1,
+le_x509);
+ if (!what)
+ return NULL;
+
+ /* this is so callers can decide if they should free the X509 */
+ if (resourceval)
+ *resourceval = Z_LVAL_PP(val);
+
+ if (type == le_x509)
+ return (X509*)what;
+
+ /* other types could be used here - eg: file pointers and read in the
+data from them */
+
+ return NULL;
+ }
+ /* force it to be a string and check if it refers to a file */
+ convert_to_string_ex(val);
+
+ if (Z_STRLEN_PP(val) > 7 && memcmp(Z_STRVAL_PP(val), "file://", 7) == 0)
+ {
+ /* read cert from the named file */
+ BIO *in;
+
+ in = BIO_new_file(Z_STRVAL_PP(val) + 7, "r");
+ if (in == NULL)
+ return NULL;
+ cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
+ BIO_free(in);
+
+ }
+ else {
+ BIO *in;
+
+ in = BIO_new_mem_buf(Z_STRVAL_PP(val), Z_STRLEN_PP(val));
+ if (in == NULL)
+ return NULL;
+
+ cert = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509,
+ PEM_STRING_X509, in,
+ NULL, NULL, NULL);
+ BIO_free(in);
+ }
+
+ if (cert && makeresource && resourceval) {
+ *resourceval = zend_list_insert(cert, le_x509);
+ }
+ return cert;
+}
+
+
+
+/* Given a zval, coerce it into a EVP_PKEY object.
+ It can be:
+ 1. private key resource from openssl_get_privatekey()
+ 2. X509 resource -> private key will be extracted from it
+ 3. if it starts with file:// interpreted as path to key file
+ 4. interpreted as the data from the cert/key file and interpreted in
+same way as openssl_get_privatekey()
+ 5. an array(0 => [items 2..4], 1 => passphrase)
+ NOTE: If you are requesting a private key but have not specified a passphrase,
+you should use an
+ empty string rather than NULL for the passphrase - NULL causes a passphrase
+prompt to be emitted in
+ the Apache error log!
+*/
+static EVP_PKEY * php_openssl_evp_from_zval(zval ** val, int public_key, char *
+passphrase, int makeresource, long * resourceval)
+{
+ EVP_PKEY * key = NULL;
+ X509 * cert = NULL;
+ int free_cert = 0;
+ long cert_res = -1;
+ char * filename = NULL;
+
+ if (resourceval)
+ *resourceval = -1;
+
+ if ((*val)->type == IS_ARRAY) {
+ zval ** zphrase;
+
+ /* get passphrase */
+
+ if (zend_hash_index_find(HASH_OF(*val), 1, (void **)&zphrase) ==
+FAILURE) {
+ zend_error(E_ERROR, "%s(): key array must be of the form
+array(0 => key, 1 => phrase)", get_active_function_name());
+ return NULL;
+ }
+ convert_to_string_ex(zphrase);
+ passphrase = Z_STRVAL_PP(zphrase);
+
+ /* now set val to be the key param and continue */
+ if (zend_hash_index_find(HASH_OF(*val), 0, (void **)&val) == FAILURE)
+ {
+ zend_error(E_ERROR, "%s(): key array must be of the form
+array(0 => key, 1 => phrase)", get_active_function_name());
+ return NULL;
+ }
+ }
+
+ if ((*val)->type == IS_RESOURCE) {
+ void * what;
+ int type;
+
+ what = zend_fetch_resource(val, -1, "OpenSSL X.509/key", &type, 2,
+le_x509, le_key);
+ if (!what)
+ return NULL;
+
+ if (resourceval)
+ *resourceval = Z_LVAL_PP(val);
+
+ if (type == le_x509) {
+ /* extract key from cert, depending on public_key param */
+ cert = (X509*)what;
+ free_cert = 0;
+ }
+ else if (type == le_key) {
+ /* got the key - return it */
+ return (EVP_PKEY*)what;
+ }
+
+ /* other types could be used here - eg: file pointers and read in the
+data from them */
+
+ return NULL;
+ }
+ else {
+ /* force it to be a string and check if it refers to a file */
+ convert_to_string_ex(val);
+
+ if (Z_STRLEN_PP(val) > 7 && memcmp(Z_STRVAL_PP(val), "file://", 7) ==
+0)
+ filename = Z_STRVAL_PP(val) + 7;
+
+ /* it's an X509 file/cert of some kind, and we need to extract the
+data from that */
+ if (public_key) {
+ cert = php_openssl_x509_from_zval(val, 0, &cert_res);
+ free_cert = (cert_res == -1);
+ /* actual extraction done later */
+ }
+ else {
+ /* we want the private key */
+ if (filename) {
+ BIO *in = BIO_new_file(filename, "r");
+ if (in == NULL)
+ return NULL;
+ key = PEM_read_bio_PrivateKey(in, NULL,NULL,
+passphrase);
+ BIO_free(in);
+ }
+ else {
+ BIO * b = BIO_new_mem_buf(Z_STRVAL_PP(val),
+Z_STRLEN_PP(val));
+ if (b == NULL)
+ return NULL;
+
+ key = (EVP_PKEY *) PEM_ASN1_read_bio((char
+*(*)())d2i_PrivateKey,
+ PEM_STRING_EVP_PKEY, b,
+ NULL, NULL, passphrase);
+ BIO_free(b);
+ }
+ }
+ }
+
+ if (public_key && cert && key == NULL) {
+ /* extract public key from X509 cert */
+ key = (EVP_PKEY *) X509_get_pubkey(cert);
+ }
+
+ if (free_cert && cert)
+ X509_free(cert);
+
+ if (key && makeresource && resourceval) {
+ *resourceval = zend_list_insert(key, le_key);
+ }
+ return key;
+}
+
+
/* {{{ proto int openssl_get_privatekey(string key [, string passphrase])
Get private key */
PHP_FUNCTION(openssl_get_privatekey)
{
zval **key, **passphrase;
- BIO *b;
EVP_PKEY *pkey;
int argc;
-
+
argc = ZEND_NUM_ARGS();
if (argc < 1 || argc > 2 ||
- zend_get_parameters_ex(argc, &key, &passphrase) == FAILURE) {
+ zend_get_parameters_ex(argc, &key, &passphrase) == FAILURE) {
WRONG_PARAM_COUNT;
}
convert_to_string_ex(key);
if (argc == 2) {
- convert_to_string_ex(passphrase);
- }
+ convert_to_string_ex(passphrase);
+ }
+
+ return_value->type = IS_RESOURCE;
+ pkey = php_openssl_evp_from_zval(key, 0, argc == 2 ? Z_STRVAL_PP(passphrase) :
+"", 1, &(return_value->value.lval));
- b = BIO_new_mem_buf(Z_STRVAL_PP(key), Z_STRLEN_PP(key));
- if (b == NULL) {
+ if (pkey == NULL) {
+ zend_error(E_ERROR, "%s(): unable to coerce arg to a private key",
+get_active_function_name());
RETURN_FALSE;
}
+}
+/* }}} */
- pkey = (EVP_PKEY *) PEM_ASN1_read_bio((char *(*)())d2i_PrivateKey,
- PEM_STRING_EVP_PKEY, b,
- NULL, NULL, argc == 2 ?
- Z_STRVAL_PP(passphrase) : NULL);
- BIO_free(b);
+static void add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int
+shortname)
+{
+ zval * subitem;
+ int i;
+ char * sn, * ln;
+ int nid;
+ X509_NAME_ENTRY * ne;
+ ASN1_STRING * str;
+ ASN1_OBJECT * obj;
+
+ MAKE_STD_ZVAL(subitem);
+ array_init(subitem);
+
+ for (i = 0; i < X509_NAME_entry_count(name); i++) {
+ ne = X509_NAME_get_entry(name, i);
+ obj = X509_NAME_ENTRY_get_object(ne);
+ str = X509_NAME_ENTRY_get_data(ne);
+ nid = OBJ_obj2nid(obj);
+ if (shortname) {
+ sn = (char*)OBJ_nid2sn(nid);
+ add_assoc_stringl(subitem, sn, str->data, str->length, 1);
+ }
+ else {
+ ln = (char*)OBJ_nid2ln(nid);
+ add_assoc_stringl(subitem, ln, str->data, str->length, 1);
+ }
+ }
+ zend_hash_update(HASH_OF(val), key, strlen(key) + 1, (void *)&subitem,
+sizeof(subitem), NULL);
+}
+
+static void add_assoc_asn1_string(zval * val, char * key, ASN1_STRING * str)
+{
+ add_assoc_stringl(val, key, str->data, str->length, 1);
+}
+
+static time_t asn1_time_to_time_t(ASN1_UTCTIME * timestr)
+{
+/*
+ This is how the time string is formatted:
+
+ sprintf(p,"%02d%02d%02d%02d%02d%02dZ",ts->tm_year%100,
+ ts->tm_mon+1,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec);
+*/
+
+ time_t ret;
+ struct tm thetime;
+ char * strbuf;
+ char * thestr;
+ long gmadjust = 0;
+
+ if (timestr->length < 13) {
+ zend_error(E_WARNING, "%s(): extension author too lazy to parse %s
+correctly", get_active_function_name(), timestr->data);
+ return (time_t)-1;
+ }
+
+ strbuf = estrdup(timestr->data);
+
+
+ memset(&thetime, 0, sizeof(thetime));
+
+ /* we work backwards so that we can use atoi more easily */
+
+ thestr = strbuf + timestr->length - 3;
+
+ thetime.tm_sec = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_min = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_hour = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_mday = atoi(thestr);
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_mon = atoi(thestr)-1;
+ *thestr = '\0';
+ thestr -= 2;
+ thetime.tm_year = atoi(thestr);
+
+ if (thetime.tm_year < 68)
+ thetime.tm_year += 100;
+
+ thetime.tm_isdst = -1;
+ ret = mktime(&thetime);
+
+#if HAVE_TM_GMTOFF
+ gmadjust = thetime.tm_gmtoff;
+#else
+ /*
+ ** If correcting for daylight savings time, we set the adjustment to
+ ** the value of timezone - 3600 seconds. Otherwise, we need to overcorrect and
+ ** set the adjustment to the main timezone + 3600 seconds.
+ */
+ gmadjust = -(is_dst ? timezone - 3600 : timezone + 3600);
+#endif
+ ret += gmadjust;
+
+ efree(strbuf);
+
+ return ret;
+}
+
+/* proto array openssl_x509_parse(mixed x509[, bool shortnames=true])
+ returns an array of the fields/values of the cert */
+PHP_FUNCTION(openssl_x509_parse)
+{
+ zval ** zcert, ** zshort = NULL;
+ X509 * cert = NULL;
+ long certresource = -1;
+ int i;
+ int argc = ZEND_NUM_ARGS();
+ int useshortnames = 1;
+ char * tmpstr;
+ zval * subitem;
- if (pkey == NULL) {
+ if (argc < 1 || argc > 2 || zend_get_parameters_ex(argc, &zcert, &zshort) ==
+FAILURE) {
+ WRONG_PARAM_COUNT;
+ }
+ if (argc == 2) {
+ convert_to_boolean_ex(zshort);
+ if (!Z_LVAL_PP(zshort))
+ useshortnames = 0;
+ }
+ cert = php_openssl_x509_from_zval(zcert, 0, &certresource);
+ if (cert == NULL)
RETURN_FALSE;
+
+ array_init(return_value);
+
+ if (cert->name)
+ add_assoc_string(return_value, "name", cert->name, 1);
+/* add_assoc_bool(return_value, "valid", cert->valid); */
+
+ add_assoc_name_entry(return_value, "subject",
+X509_get_subject_name(cert), useshortnames);
+ add_assoc_name_entry(return_value, "issuer",
+X509_get_issuer_name(cert), useshortnames);
+ add_assoc_long(return_value, "version",
+X509_get_version(cert));
+ add_assoc_long(return_value, "serialNumber",
+ASN1_INTEGER_get(X509_get_serialNumber(cert)));
+
+ add_assoc_asn1_string(return_value, "validFrom",
+X509_get_notBefore(cert));
+ add_assoc_asn1_string(return_value, "validTo",
+X509_get_notAfter(cert));
+
+ add_assoc_long(return_value, "validFrom_time_t",
+asn1_time_to_time_t(X509_get_notBefore(cert)));
+ add_assoc_long(return_value, "validTo_time_t",
+asn1_time_to_time_t(X509_get_notAfter(cert)));
+
+ tmpstr = X509_alias_get0(cert, NULL);
+ if (tmpstr)
+ add_assoc_string(return_value, "alias", tmpstr, 1);
+
+/*
+ add_assoc_long(return_value, "signaturetypeLONG",
+X509_get_signature_type(cert));
+ add_assoc_string(return_value, "signaturetype",
+OBJ_nid2sn(X509_get_signature_type(cert)), 1);
+ add_assoc_string(return_value, "signaturetypeLN",
+OBJ_nid2ln(X509_get_signature_type(cert)), 1);
+*/
+ MAKE_STD_ZVAL(subitem);
+ array_init(subitem);
+
+ /* NOTE: the purposes are added as integer keys - the keys match up to the
+X509_PURPOSE_SSL_XXX defines
+ in x509v3.h */
+ for (i = 0; i < X509_PURPOSE_get_count(); i++) {
+ int id, purpset;
+ char * pname;
+ X509_PURPOSE * purp;
+ zval * subsub;
+
+ MAKE_STD_ZVAL(subsub);
+ array_init(subsub);
+
+ purp = X509_PURPOSE_get0(i);
+ id = X509_PURPOSE_get_id(purp);
+
+ purpset = X509_check_purpose(cert, id, 0);
+ add_index_bool(subsub, 0, purpset);
+
+ purpset = X509_check_purpose(cert, id, 1);
+ add_index_bool(subsub, 1, purpset);
+
+ pname = useshortnames ? X509_PURPOSE_get0_sname(purp) :
+X509_PURPOSE_get0_name(purp);
+ add_index_string(subsub, 2, pname, 1);
+
+ /* NOTE: if purpset > 1 then it's a warning - we should mention it ? */
+
+ zend_hash_index_update(HASH_OF(subitem), id, (void *)&subsub,
+sizeof(subsub), NULL);
}
+ zend_hash_update(HASH_OF(return_value), "purposes", strlen("purposes")+1,
+(void*)&subitem, sizeof(subitem), NULL);
+
+ if (certresource == -1 && cert)
+ X509_free(cert);
+
+}
+/* }}} */
+
+
+static STACK_OF(X509) * load_all_certs_from_file(char *certfile)
+{
+ STACK_OF(X509_INFO) *sk=NULL;
+ STACK_OF(X509) *stack=NULL, *ret=NULL;
+ BIO *in=NULL;
+ X509_INFO *xi;
+
+ if(!(stack = sk_X509_new_null())) {
+ zend_error(E_ERROR, "%s(): memory allocation failure",
+get_active_function_name());
+ goto end;
+ }
+
+ if(!(in=BIO_new_file(certfile, "r"))) {
+ zend_error(E_ERROR, "%s(): error opening the file, %s",
+get_active_function_name(), certfile);
+ goto end;
+ }
+
+ /* This loads from a file, a stack of x509/crl/pkey sets */
+ if(!(sk=PEM_X509_INFO_read_bio(in, NULL, NULL, NULL))) {
+ zend_error(E_ERROR, "%s(): error reading the file, %s",
+get_active_function_name(), certfile);
+ goto end;
+ }
+
+ /* scan over it and pull out the certs */
+ while (sk_X509_INFO_num(sk))
+ {
+ xi=sk_X509_INFO_shift(sk);
+ if (xi->x509 != NULL)
+ {
+ sk_X509_push(stack,xi->x509);
+ xi->x509=NULL;
+ }
+ X509_INFO_free(xi);
+ }
+ if(!sk_X509_num(stack)) {
+ zend_error(E_ERROR, "%s(): no certificates in file, %s",
+get_active_function_name(), certfile);
+ sk_X509_free(stack);
+ goto end;
+ }
+ ret=stack;
+end:
+ BIO_free(in);
+ sk_X509_INFO_free(sk);
+
+ return ret;
+}
+
+static int check_cert(X509_STORE *ctx, X509 *x, STACK_OF(X509) *untrustedchain, int
+purpose)
+{
+ int ret=0;
+ X509_STORE_CTX *csc;
+
+ csc = X509_STORE_CTX_new();
+ if (csc == NULL)
+ {
+ zend_error(E_ERROR, "%s(): memory allocation failure",
+get_active_function_name());
+ return 0;
+ }
+ X509_STORE_CTX_init(csc, ctx, x, untrustedchain);
+
+ if(purpose >= 0)
+ X509_STORE_CTX_set_purpose(csc, purpose);
+
+ ret = X509_verify_cert(csc);
+ X509_STORE_CTX_free(csc);
+
+ return ret;
+}
+
+
+/* {{{ proto bool openssl_x509_checkpurpose(mixed x509cert, int purpose, array
+cainfo[, string untrustedfile])
+ check the cert to see if it can be used for the purpose in purpose. cainfo
+holds information about trusted CAs */
+PHP_FUNCTION(openssl_x509_checkpurpose)
+{
+ zval ** zcert, ** zpurpose, ** zcainfo, ** zuntrusted;
+ X509_STORE * cainfo = NULL;
+ X509 * cert = NULL;
+ long certresource = -1;
+ STACK_OF(X509) * untrustedchain = NULL;
+ int argc;
+
+ argc = ZEND_NUM_ARGS();
+
+ if (argc < 3 || argc > 4 || zend_get_parameters_ex(argc, &zcert, &zpurpose,
+&zcainfo, &zuntrusted) == FAILURE) {
+ WRONG_PARAM_COUNT;
+ }
+
+ RETVAL_FALSE;
- ZEND_REGISTER_RESOURCE(return_value, pkey, le_key);
+ if (argc == 4) {
+ convert_to_string_ex(zuntrusted);
+ untrustedchain = load_all_certs_from_file(Z_STRVAL_PP(zuntrusted));
+ if (untrustedchain == NULL)
+ goto clean_exit;
+ }
+ convert_to_long_ex(zpurpose);
+
+ cainfo = setup_verify(*zcainfo);
+ if (cainfo == NULL)
+ goto clean_exit;
+
+ cert = php_openssl_x509_from_zval(zcert, 0, &certresource);
+ if (cert == NULL)
+ goto clean_exit;
+
+ RETVAL_BOOL(check_cert(cainfo, cert, untrustedchain, Z_LVAL_PP(zpurpose)));
+
+clean_exit:
+ if (certresource == 1 && cert)
+ X509_free(cert);
+ if (cainfo)
+ X509_STORE_free(cainfo);
+ if (untrustedchain)
+ sk_X509_pop_free(untrustedchain, X509_free);
}
/* }}} */
-/* {{{ proto int openssl_get_publickey(string cert)
+
+/* {{{ proto int openssl_get_publickey(mixed cert)
Get public key from X.509 certificate */
PHP_FUNCTION(openssl_get_publickey)
{
zval **cert;
- X509 *x509;
- BIO *b;
EVP_PKEY *pkey;
if (ZEND_NUM_ARGS() != 1 ||
zend_get_parameters_ex(1, &cert) == FAILURE) {
WRONG_PARAM_COUNT;
}
- convert_to_string_ex(cert);
-
- b = BIO_new_mem_buf(Z_STRVAL_PP(cert), -1);
- if (b == NULL) {
- RETURN_FALSE;
- }
-
- x509 = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509,
- PEM_STRING_X509, b,
- NULL, NULL, NULL);
- BIO_free(b);
-
- if (x509 == NULL) {
- RETURN_FALSE;
- }
- pkey = (EVP_PKEY *) X509_get_pubkey(x509);
- X509_free(x509);
+ return_value->type = IS_RESOURCE;
+ pkey = php_openssl_evp_from_zval(cert, 1, NULL, 1,
+&(return_value->value.lval));
- if (pkey == NULL) {
+ if (pkey == NULL) {
RETURN_FALSE;
}
-
- ZEND_REGISTER_RESOURCE(return_value, pkey, le_key);
}
/* }}} */
+
+
/* {{{ proto void openssl_free_key(int key)
Free key */
PHP_FUNCTION(openssl_free_key)
@@ -194,86 +697,577 @@
zend_get_parameters_ex(1, &key) == FAILURE) {
WRONG_PARAM_COUNT;
}
-
+
ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, key, -1, "OpenSSL key", le_key);
zend_list_delete(Z_LVAL_PP(key));
}
/* }}} */
-#if 0
-/* {{{ proto int openssl_read_publickey(int x509)
- Read public key */
-PHP_FUNCTION(openssl_read_publickey)
+/* {{{ proto resource openssl_read_x509(mixed cert)
+ Read X.509 certificate */
+PHP_FUNCTION(openssl_x509_read)
+{
+ zval **cert;
+ X509 *x509;
+
+ if (ZEND_NUM_ARGS() != 1 ||
+ zend_get_parameters_ex(1, &cert) == FAILURE) {
+ WRONG_PARAM_COUNT;
+ }
+
+ return_value->type = IS_RESOURCE;
+ x509 = php_openssl_x509_from_zval(cert, 1, &(return_value->value.lval));
+
+ if (x509 == NULL) {
+ zend_error(E_ERROR, "%s() supplied parameter cannot be coerced into an
+X509 certificate!", get_active_function_name());
+ RETURN_FALSE;
+ }
+}
+/* }}} */
+
+/* {{{ proto void openssl_free_x509(resource x509)
+ Free X.509 certificate */
+PHP_FUNCTION(openssl_x509_free)
{
zval **x509;
X509 *cert;
- EVP_PKEY *pkey;
if (ZEND_NUM_ARGS() != 1 ||
zend_get_parameters_ex(1, &x509) == FAILURE) {
WRONG_PARAM_COUNT;
}
ZEND_FETCH_RESOURCE(cert, X509 *, x509, -1, "OpenSSL X.509", le_x509);
+ zend_list_delete(Z_LVAL_PP(x509));
+}
+/* }}} */
- pkey = (EVP_PKEY *) X509_get_pubkey(cert);
- if (pkey == NULL) {
- RETURN_FALSE;
+static struct {
+ const char * name;
+ unsigned long value;
+ int or;
+} php_pkcs7_option_values[] = {
+ {"detached", PKCS7_DETACHED, 1},
+ {"nodetached", ~PKCS7_DETACHED, 0},
+ {"text", PKCS7_TEXT, 1},
+ {"nointern", PKCS7_NOINTERN, 1},
+ {"noverify", PKCS7_NOVERIFY, 1},
+ {"nochain", PKCS7_NOCHAIN, 1},
+ {"nocerts", PKCS7_NOCERTS, 1},
+ {"noattr", PKCS7_NOATTR, 1},
+ {"binary", PKCS7_BINARY, 1},
+ {"nosigs", PKCS7_NOSIGS, 1},
+ {NULL, 0, 0}
+
+};
+
+/* given an array of named flags, parse it into a form we can pass to the pkcs7
+routines */
+static unsigned long php_openssl_parse_pkcs7_flags(zval ** val, unsigned long def)
+{
+ HashPosition pos;
+
+ if ((*val)->type == IS_NULL)
+ return def;
+
+ if ((*val)->type != IS_ARRAY)
+ {
+ zend_error(E_WARNING, "%s(): expected an array of flag names",
+get_active_function_name());
+ return def;
+ }
+
+ zend_hash_internal_pointer_reset_ex(HASH_OF(*val), &pos);
+ for (;; zend_hash_move_forward_ex(HASH_OF(*val), &pos)) {
+ zval ** item;
+ int i;
+
+ if (zend_hash_get_current_data_ex(HASH_OF(*val), (void**)&item, &pos)
+== FAILURE)
+ break;
+
+ convert_to_string_ex(item);
+
+ i = 0;
+ while (php_pkcs7_option_values[i].name) {
+ if (strcmp(php_pkcs7_option_values[i].name, Z_STRVAL_PP(item))
+== 0) {
+ if (php_pkcs7_option_values[i].or)
+ def |= php_pkcs7_option_values[i].value;
+ else
+ def &= php_pkcs7_option_values[i].value;
+ break;
+ }
+ i++;
+ }
}
- ZEND_REGISTER_RESOURCE(return_value, pkey, le_key);
+ return def;
}
-/* }}} */
-/* {{{ proto int openssl_read_x509(string cert)
- Read X.509 certificate */
-PHP_FUNCTION(openssl_read_x509)
+/* calist is an array containing file and directory names.
+ create a certificate store and add those certs to it for
+ use in verification.
+*/
+static X509_STORE * setup_verify(zval * calist)
{
- zval **cert;
- X509 *x509;
- BIO *b;
+ X509_STORE *store;
+ X509_LOOKUP * dir_lookup, * file_lookup;
+ HashPosition pos;
+ int ndirs = 0, nfiles = 0;
+
+ store = X509_STORE_new();
+
+ if (store == NULL)
+ return NULL;
+
+ if (calist && (calist->type == IS_ARRAY)) {
+ zend_hash_internal_pointer_reset_ex(HASH_OF(calist), &pos);
+ for (;; zend_hash_move_forward_ex(HASH_OF(calist), &pos)) {
+ zval ** item;
+ struct stat sb;
+
+ if (zend_hash_get_current_data_ex(HASH_OF(calist),
+(void**)&item, &pos) == FAILURE)
+ break;
+
+ convert_to_string_ex(item);
+
+ if (V_STAT(Z_STRVAL_PP(item), &sb) == -1) {
+ zend_error(E_WARNING, "%s() unable to stat %s",
+get_active_function_name(), Z_STRVAL_PP(item));
+ continue;
+ }
- if (ZEND_NUM_ARGS() != 1 ||
- zend_get_parameters_ex(1, &cert) == FAILURE) {
+ if ((sb.st_mode & S_IFREG) == S_IFREG) {
+ file_lookup = X509_STORE_add_lookup(store,
+X509_LOOKUP_file());
+ if (file_lookup == NULL ||
+!X509_LOOKUP_load_file(file_lookup, Z_STRVAL_PP(item), X509_FILETYPE_PEM))
+ zend_error(E_WARNING, "%s() error loading file
+%s", get_active_function_name(), Z_STRVAL_PP(item));
+ else
+ nfiles++;
+ file_lookup = NULL;
+ }
+ else {
+ dir_lookup = X509_STORE_add_lookup(store,
+X509_LOOKUP_hash_dir());
+ if (dir_lookup == NULL ||
+!X509_LOOKUP_add_dir(dir_lookup, Z_STRVAL_PP(item), X509_FILETYPE_PEM))
+ zend_error(E_WARNING, "%s() error loading
+directory %s", get_active_function_name(), Z_STRVAL_PP(item));
+ else
+ ndirs++;
+ dir_lookup = NULL;
+ }
+ }
+ }
+ if (nfiles == 0) {
+ file_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+ if (file_lookup)
+ X509_LOOKUP_load_file(file_lookup, NULL,
+X509_FILETYPE_DEFAULT);
+ }
+ if (ndirs == 0) {
+ dir_lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
+ if (dir_lookup)
+ X509_LOOKUP_add_dir(dir_lookup, NULL, X509_FILETYPE_DEFAULT);
+ }
+ return store;
+}
+
+/* {{{ proto mixed openssl_error_string()
+ returns a description of the last error, and alters the index of the error
+messages. returns false when the are no more messages. */
+PHP_FUNCTION(openssl_error_string)
+{
+ char buf[512];
+ unsigned long val;
+
+ if (ZEND_NUM_ARGS() != 0) {
WRONG_PARAM_COUNT;
}
- convert_to_string_ex(cert);
- b = BIO_new_mem_buf(Z_STRVAL_PP(cert), -1);
- if (b == NULL) {
+ val = ERR_get_error();
+ if (val)
+ {
+ RETURN_STRING(ERR_error_string(val, buf), 1);
+ }
+ else
+ {
RETURN_FALSE;
}
+}
+/* }}} */
- x509 = (X509 *) PEM_ASN1_read_bio((char *(*)())d2i_X509,
- PEM_STRING_X509, b,
- NULL, NULL, NULL);
- BIO_free(b);
+/* {{{ proto bool openssl_pkcs7_verify(string filename, array flags[, string
+signerscerts][, array cainfo])
+ verify that the data block is intact, the signer is who they say they are, and
+return the certs of the signers
+*/
+PHP_FUNCTION(openssl_pkcs7_verify)
+{
+ X509_STORE * store;
+ int argc = ZEND_NUM_ARGS();
+ zval ** data, ** zflags, ** signerscerts, ** cainfo = NULL;
+ char * signersfilename = NULL;
+ STACK_OF(X509) *signers;
+ PKCS7 * p7 = NULL;
+ BIO * in = NULL, * datain = NULL;
+ int flags = 0;
+
+ if (argc > 4 || argc < 1) {
+ WRONG_PARAM_COUNT;
+ }
- if (x509 == NULL) {
+ if (zend_get_parameters_ex(argc, &data, &zflags, &signerscerts, &cainfo) ==
+FAILURE) {
+ WRONG_PARAM_COUNT;
+ }
+
+ if (argc >= 4) {
+ if ((*cainfo)->type != IS_ARRAY) {
+ zend_error(E_ERROR, "%s(): 4th parameter must be an array",
+get_active_function_name());
+ RETURN_FALSE;
+ }
+ }
+ if (argc >= 3) {
+ convert_to_string_ex(signerscerts);
+ signersfilename = Z_STRVAL_PP(signerscerts);
+ }
+
+ convert_to_string_ex(data);
+
+ flags = php_openssl_parse_pkcs7_flags(zflags, 0);
+
+ store = setup_verify(cainfo ? *cainfo : NULL);
+
+ if (!store)
RETURN_FALSE;
+
+ in = BIO_new_file(Z_STRVAL_PP(data), "r");
+ if (in == NULL)
+ goto exit_fail;
+ p7 = SMIME_read_PKCS7(in, &datain);
+ if (p7 == NULL)
+ goto exit_fail;
+
+ if (PKCS7_verify(p7, NULL, store, datain, NULL, flags)) {
+
+ RETVAL_TRUE;
+
+ if (signersfilename) {
+ BIO * certout = BIO_new_file(signersfilename, "w");
+ if (certout) {
+ int i;
+ signers = PKCS7_get0_signers(p7, NULL, flags);
+
+ for(i = 0; i < sk_X509_num(signers); i++)
+ PEM_write_bio_X509(certout,
+sk_X509_value(signers, i));
+
+ BIO_free(certout);
+ sk_X509_free(signers);
+ }
+ }
+
+ goto exit_cleanup;
}
-
- ZEND_REGISTER_RESOURCE(return_value, x509, le_x509);
+
+exit_fail:
+ RETVAL_FALSE;
+exit_cleanup:
+ X509_STORE_free(store);
+ BIO_free(datain);
+ BIO_free(in);
+ PKCS7_free(p7);
}
/* }}} */
-/* {{{ proto void openssl_free_x509(int x509)
- Free X.509 certificate */
-PHP_FUNCTION(openssl_free_x509)
+/* {{{ proto bool openssl_pkcs7_encrypt(string infile, string outfile, array
+recipcerts, array headers[, array flags])
+ encrypt the message in the file named infile with the certificates in
+recipcerts and output the result to the file named outfile */
+PHP_FUNCTION(openssl_pkcs7_encrypt)
{
- zval **x509;
- X509 *cert;
+ zval ** zinfilename, ** zoutfilename, ** zrecipcerts, ** zheaders, ** zflags =
+NULL;
+ STACK_OF(X509) * recipcerts = NULL;
+ BIO * infile = NULL, * outfile = NULL;
+ int flags = 0;
+ PKCS7 * p7 = NULL;
+ HashPosition hpos;
+ zval ** zcertval;
+ X509 * cert;
+ int argc;
+ EVP_CIPHER *cipher = NULL;
+ ulong strindexlen, intindex;
+ char * strindex;
- if (ZEND_NUM_ARGS() != 1 ||
- zend_get_parameters_ex(1, &x509) == FAILURE) {
+ argc = ZEND_NUM_ARGS();
+
+ RETVAL_FALSE;
+
+ if (argc < 3 || argc > 5 || zend_get_parameters_ex(argc, &zinfilename,
+&zoutfilename, &zrecipcerts, &zheaders, &zflags) == FAILURE) {
WRONG_PARAM_COUNT;
}
- ZEND_FETCH_RESOURCE(cert, X509 *, x509, -1, "OpenSSL X.509", le_x509);
- zend_list_delete(Z_LVAL_PP(x509));
+ convert_to_string_ex(zinfilename);
+ convert_to_string_ex(zoutfilename);
+
+ if (argc >= 4) {
+ if ((*zheaders)->type == IS_NULL)
+ zheaders = NULL;
+ else if ((*zheaders)->type != IS_ARRAY) {
+ zend_error(E_ERROR, "%s(): 4th param must be an array/null
+value!", get_active_function_name());
+ goto clean_exit;
+ }
+ }
+
+
+ if (argc >= 5) {
+ if ((*zflags)->type != IS_ARRAY) {
+ zend_error(E_ERROR, "%s(): 5th param must be an array!",
+get_active_function_name());
+ goto clean_exit;
+ }
+ flags = php_openssl_parse_pkcs7_flags(zflags, 0);
+ }
+
+ if ((*zrecipcerts)->type != IS_ARRAY) {
+ zend_error(E_ERROR, "%s(): 3rd parameter must be an array",
+get_active_function_name());
+ goto clean_exit;
+ }
+
+ infile = BIO_new_file(Z_STRVAL_PP(zinfilename), "r");
+ if (infile == NULL)
+ goto clean_exit;
+
+ outfile = BIO_new_file(Z_STRVAL_PP(zoutfilename), "w");
+ if (outfile == NULL)
+ goto clean_exit;
+
+ recipcerts = sk_X509_new_null();
+
+ /* get certs */
+ zend_hash_internal_pointer_reset_ex(HASH_OF(*zrecipcerts), &hpos);
+ while(zend_hash_get_current_data_ex(HASH_OF(*zrecipcerts), (void**)&zcertval,
+&hpos) == SUCCESS) {
+ long certresource;
+
+ cert = php_openssl_x509_from_zval(zcertval, 0, &certresource);
+ if (cert == NULL)
+ goto clean_exit;
+
+ if (certresource != -1) {
+ /* we shouldn't free this particular cert, as it is a resource.
+ make a copy and push that on the stack instead */
+ cert = X509_dup(cert);
+ if (cert == NULL)
+ goto clean_exit;
+ }
+ sk_X509_push(recipcerts, cert);
+
+ zend_hash_move_forward_ex(HASH_OF(*zrecipcerts), &hpos);
+ }
+
+ /* TODO: allow user to choose a different cipher */
+ cipher = EVP_rc2_40_cbc();
+ if (cipher == NULL)
+ goto clean_exit;
+
+ p7 = PKCS7_encrypt(recipcerts, infile, cipher, flags);
+
+ if (p7 == NULL)
+ goto clean_exit;
+
+ /* tack on extra headers */
+ zend_hash_internal_pointer_reset_ex(HASH_OF(*zheaders), &hpos);
+ while(zend_hash_get_current_data_ex(HASH_OF(*zheaders), (void**)&zcertval,
+&hpos) == SUCCESS) {
+ zend_hash_get_current_key_ex(HASH_OF(*zheaders), &strindex,
+&strindexlen, &intindex, 0, &hpos);
+
+ convert_to_string_ex(zcertval);
+
+ if (strindex)
+ BIO_printf(outfile, "%s: %s\n", strindex,
+Z_STRVAL_PP(zcertval));
+ else
+ BIO_printf(outfile, "%s\n", Z_STRVAL_PP(zcertval));
+
+ zend_hash_move_forward_ex(HASH_OF(*zheaders), &hpos);
+ }
+
+ BIO_reset(infile);
+
+ /* write the encrypted data */
+ SMIME_write_PKCS7(outfile, p7, infile, flags);
+
+ RETVAL_TRUE;
+
+
+clean_exit:
+ PKCS7_free(p7);
+ BIO_free(infile);
+ BIO_free(outfile);
+ if (recipcerts)
+ sk_X509_pop_free(recipcerts, X509_free);
+
}
/* }}} */
-#endif
+
+/* {{{ proto bool openssl_pkcs7_sign(string infile, string outfile, mixed signcert,
+mixed signkey, array headers[, array flags][, string extracertsfilename])
+ sign the MIME message in the file named infile with signcert/signkey and
+output the result to file name outfile. headers lists plain text headers to exclude
+from the signed portion of the message, and should include to, from and subject as a
+minimum */
+
+PHP_FUNCTION(openssl_pkcs7_sign)
+{
+ zval ** zinfilename, ** zoutfilename, ** zcert, ** zprivkey, ** zheaders, **
+zflags = NULL, ** zextracerts;
+ zval ** hval;
+ X509 * cert = NULL;
+ EVP_PKEY * privkey = NULL;
+ int flags = PKCS7_DETACHED, argc;
+ PKCS7 * p7 = NULL;
+ BIO * infile = NULL, * outfile = NULL;
+ STACK_OF(X509) *others = NULL;
+ long certresource = -1, keyresource = -1;
+ ulong strindexlen, intindex;
+ HashPosition hpos;
+ char * strindex;
+
+ argc = ZEND_NUM_ARGS();
+
+ RETVAL_FALSE;
+
+ if (argc < 5 || argc > 7 || zend_get_parameters_ex(argc, &zinfilename,
+&zoutfilename, &zcert, &zprivkey, &zheaders, &zflags, &zextracerts) == FAILURE)
+ {
+ WRONG_PARAM_COUNT;
+ }
-/* {{{ proto bool openssl_sign(string data, string signature, int key)
+ if (argc >= 7) {
+ convert_to_string_ex(zextracerts);
+ others = load_all_certs_from_file(Z_STRVAL_PP(zextracerts));
+ if (others == NULL)
+ goto clean_exit;
+ }
+
+ if (argc >= 6) {
+ if ((*zflags)->type != IS_ARRAY) {
+ zend_error(E_ERROR, "%s(): 6th param must be an array!",
+get_active_function_name());
+ goto clean_exit;
+ }
+ flags = php_openssl_parse_pkcs7_flags(zflags, PKCS7_DETACHED);
+ }
+ if (argc >= 5) {
+ if ((*zheaders)->type == IS_NULL)
+ zheaders = NULL;
+ else if ((*zheaders)->type != IS_ARRAY) {
+ zend_error(E_ERROR, "%s(): 5th param must be an array/null
+value!", get_active_function_name());
+ goto clean_exit;
+ }
+ }
+
+ convert_to_string_ex(zinfilename);
+ convert_to_string_ex(zoutfilename);
+
+ privkey = php_openssl_evp_from_zval(zprivkey, 0, "", 0, &keyresource);
+ if (privkey == NULL)
+ goto clean_exit;
+
+ cert = php_openssl_x509_from_zval(zcert, 0, &certresource);
+ if (cert == NULL)
+ goto clean_exit;
+
+ infile = BIO_new_file(Z_STRVAL_PP(zinfilename), "r");
+ if (infile == NULL)
+ goto clean_exit;
+
+ outfile = BIO_new_file(Z_STRVAL_PP(zoutfilename), "w");
+ if (outfile == NULL)
+ goto clean_exit;
+
+ p7 = PKCS7_sign(cert, privkey, others, infile, flags);
+ if (p7 == NULL) {
+ zend_error(E_ERROR, "%s(): error creating PKCS7 structure!",
+get_active_function_name());
+ goto clean_exit;
+ }
+
+ BIO_reset(infile);
+
+ /* tack on extra headers */
+ zend_hash_internal_pointer_reset_ex(HASH_OF(*zheaders), &hpos);
+ while(zend_hash_get_current_data_ex(HASH_OF(*zheaders), (void**)&hval, &hpos)
+== SUCCESS) {
+ zend_hash_get_current_key_ex(HASH_OF(*zheaders), &strindex,
+&strindexlen, &intindex, 0, &hpos);
+
+ convert_to_string_ex(hval);
+
+ if (strindex)
+ BIO_printf(outfile, "%s: %s\n", strindex, Z_STRVAL_PP(hval));
+ else
+ BIO_printf(outfile, "%s\n", Z_STRVAL_PP(hval));
+
+ zend_hash_move_forward_ex(HASH_OF(*zheaders), &hpos);
+ }
+
+ /* write the signed data */
+ SMIME_write_PKCS7(outfile, p7, infile, flags);
+
+ RETVAL_TRUE;
+
+clean_exit:
+ PKCS7_free(p7);
+ BIO_free(infile);
+ BIO_free(outfile);
+ if (others)
+ sk_X509_pop_free(others, X509_free);
+ if (privkey && keyresource == -1)
+ EVP_PKEY_free(privkey);
+ if (cert && certresource == -1)
+ X509_free(cert);
+}
+/* }}} */
+
+
+
+
+
+/* {{{ proto bool openssl_pkcs7_decrypt(string infilename, string outfilename, mixed
+recipcert[, mixed recipkey])
+ decrypt the S/MIME message in the file name infilename and output the results
+to the file name outfilename. recipcert is a cert for one of the recipients.
+recipkey specifies the private key matching recipcert, if recipcert does not include
+the key */
+
+PHP_FUNCTION(openssl_pkcs7_decrypt)
+{
+ zval ** infilename, ** outfilename, ** recipcert, ** recipkey;
+ int argc = ZEND_NUM_ARGS();
+ X509 * cert = NULL;
+ EVP_PKEY * key = NULL;
+ long certresval, keyresval;
+ BIO * in = NULL, * out = NULL, * datain = NULL;
+ PKCS7 * p7 = NULL;
+
+ if (argc > 4 || argc < 3 || zend_get_parameters_ex(argc, &infilename,
+&outfilename, &recipcert, &recipkey) == FAILURE) {
+ WRONG_PARAM_COUNT;
+ }
+
+ RETVAL_FALSE;
+
+ cert = php_openssl_x509_from_zval(recipcert, 0, &certresval);
+ if (cert == NULL) {
+ zend_error(E_ERROR, "%s(): unable to coerce param 3 to x509 cert",
+get_active_function_name());
+ goto clean_exit;
+ }
+
+ key = php_openssl_evp_from_zval(argc == 3 ? recipcert : recipkey, 0, "", 0,
+&keyresval);
+ if (key == NULL) {
+ zend_error(E_ERROR, "%s(): unable to coerce param %d to a private
+key", get_active_function_name(), argc);
+ goto clean_exit;
+ }
+
+ convert_to_string_ex(outfilename);
+ convert_to_string_ex(infilename);
+
+ in = BIO_new_file(Z_STRVAL_PP(infilename), "r");
+ if (in == NULL) {
+ goto clean_exit;
+ }
+ out = BIO_new_file(Z_STRVAL_PP(outfilename), "w");
+ if (out == NULL) {
+ goto clean_exit;
+ }
+
+ p7 = SMIME_read_PKCS7(in, &datain);
+
+ if (p7 == NULL)
+ goto clean_exit;
+
+ if (PKCS7_decrypt(p7, key, cert, out, PKCS7_DETACHED))
+ RETVAL_TRUE;
+
+clean_exit:
+ PKCS7_free(p7);
+ BIO_free(datain);
+ BIO_free(in);
+ BIO_free(out);
+ if (cert && certresval == -1)
+ X509_free(cert);
+ if (key && keyresval == -1)
+ EVP_PKEY_free(key);
+}
+/* }}} */
+
+/* {{{ proto bool openssl_sign(string data, &string signature, mixed key)
Sign data */
PHP_FUNCTION(openssl_sign)
{
@@ -281,6 +1275,7 @@
EVP_PKEY *pkey;
int siglen;
unsigned char *sigbuf;
+ long keyresource = -1;
EVP_MD_CTX md_ctx;
if (ZEND_NUM_ARGS() != 3 ||
@@ -288,8 +1283,13 @@
WRONG_PARAM_COUNT;
}
convert_to_string_ex(data);
-
- ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, key, -1, "OpenSSL key", le_key);
+
+ pkey = php_openssl_evp_from_zval(key, 0, "", 0, &keyresource);
+ if (pkey == NULL) {
+ zend_error(E_ERROR, "%s(): supplied key param cannot be coerced into a
+private key", get_active_function_name());
+ RETURN_FALSE;
+ }
+
siglen = EVP_PKEY_size(pkey);
sigbuf = emalloc(siglen + 1);
@@ -299,15 +1299,17 @@
zval_dtor(*signature);
sigbuf[siglen] = '\0';
ZVAL_STRINGL(*signature, sigbuf, siglen, 0);
- RETURN_TRUE;
+ RETVAL_TRUE;
} else {
efree(sigbuf);
- RETURN_FALSE;
+ RETVAL_FALSE;
}
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
}
/* }}} */
-/* {{{ proto int openssl_verify(string data, string signature, int key)
+/* {{{ proto int openssl_verify(string data, string signature, mixed key)
Verify data */
PHP_FUNCTION(openssl_verify)
{
@@ -315,6 +1317,7 @@
EVP_PKEY *pkey;
int err;
EVP_MD_CTX md_ctx;
+ long keyresource = -1;
if (ZEND_NUM_ARGS() != 3 ||
zend_get_parameters_ex(3, &data, &signature, &key) == FAILURE) {
@@ -323,70 +1326,80 @@
convert_to_string_ex(data);
convert_to_string_ex(signature);
- ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, key, -1, "OpenSSL key", le_key);
+ pkey = php_openssl_evp_from_zval(key, 1, NULL, 0, &keyresource);
+ if (pkey == NULL) {
+ zend_error(E_ERROR, "%s(): supplied key param cannot be coerced into a
+public key", get_active_function_name());
+ RETURN_FALSE;
+ }
EVP_VerifyInit (&md_ctx, EVP_sha1());
EVP_VerifyUpdate (&md_ctx, Z_STRVAL_PP(data), Z_STRLEN_PP(data));
err = EVP_VerifyFinal (&md_ctx, Z_STRVAL_PP(signature),
Z_STRLEN_PP(signature), pkey);
+
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
+
RETURN_LONG(err);
}
/* }}} */
-/* {{{ proto int openssl_seal(string data, string sealdata, array ekeys, array
pubkeys)
+/* {{{ proto int openssl_seal(string data, &string sealdata, &array ekeys, array
+pubkeys)
Seal data */
PHP_FUNCTION(openssl_seal)
{
zval **pubkeys, **pubkey, **data, **sealdata, **ekeys;
HashTable *pubkeysht;
- HashPosition pos;
+ HashPosition pos;
EVP_PKEY **pkeys;
+ long * key_resources; /* so we know what to cleanup */
int i, len1, len2, *eksl, nkeys;
- unsigned char *buf, **eks;
+ unsigned char *buf = NULL, **eks;
EVP_CIPHER_CTX ctx;
if (ZEND_NUM_ARGS() != 4 ||
- zend_get_parameters_ex(4, &data, &sealdata, &ekeys,
- &pubkeys) == FAILURE) {
+ zend_get_parameters_ex(4, &data, &sealdata, &ekeys,
+ &pubkeys) == FAILURE) {
WRONG_PARAM_COUNT;
}
SEPARATE_ZVAL(pubkeys);
- pubkeysht = HASH_OF(*pubkeys);
+ pubkeysht = HASH_OF(*pubkeys);
nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0;
if (!nkeys) {
- php_error(E_WARNING,
- "Fifth argument to openssl_seal() must be a non-empty array");
- RETURN_FALSE;
+ php_error(E_WARNING,
+ "Fourth argument to openssl_seal() must be a non-empty
+array");
+ RETURN_FALSE;
}
pkeys = emalloc(nkeys * sizeof(*pkeys));
eksl = emalloc(nkeys * sizeof(*eksl));
eks = emalloc(nkeys * sizeof(*eks));
-
+ key_resources = emalloc(nkeys * sizeof(long));
+
convert_to_string_ex(data);
- zend_hash_internal_pointer_reset_ex(pubkeysht, &pos);
+ /* get the public keys we are using to seal this data */
+ zend_hash_internal_pointer_reset_ex(pubkeysht, &pos);
i = 0;
- while (zend_hash_get_current_data_ex(pubkeysht, (void **) &pubkey,
- &pos) == SUCCESS) {
- ZEND_FETCH_RESOURCE(pkeys[i], EVP_PKEY *, pubkey, -1,
- "OpenSSL key", le_key);
+ while (zend_hash_get_current_data_ex(pubkeysht, (void **) &pubkey,
+ &pos) == SUCCESS) {
+ pkeys[i] = php_openssl_evp_from_zval(pubkey, 1, NULL, 0,
+&key_resources[i]);
+ if (pkeys[i] == NULL) {
+ zend_error(E_ERROR, "%s(): not a public key (%dth member of
+pubkeys)", get_active_function_name(), i);
+ RETVAL_FALSE;
+ goto clean_exit;
+ }
eks[i] = emalloc(EVP_PKEY_size(pkeys[i]) + 1);
- zend_hash_move_forward_ex(pubkeysht, &pos);
+ zend_hash_move_forward_ex(pubkeysht, &pos);
i++;
}
#if OPENSSL_VERSION_NUMBER >= 0x0090600fL
if (!EVP_EncryptInit(&ctx,EVP_rc4(),NULL,NULL)) {
- for (i=0; i<nkeys; i++) {
- efree(eks[i]);
- }
- efree(eks);
- efree(eksl);
- efree(pkeys);
- RETURN_FALSE;
+ RETVAL_FALSE;
+ goto clean_exit;
}
#else
EVP_EncryptInit(&ctx,EVP_rc4(),NULL,NULL);
@@ -402,18 +1415,14 @@
if (!EVP_SealInit(&ctx, EVP_rc4(), eks, eksl, NULL, pkeys, nkeys)
#if OPENSSL_VERSION_NUMBER >= 0x0090600fL
- || !EVP_SealUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data),
- Z_STRLEN_PP(data))
+ || !EVP_SealUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data),
+ Z_STRLEN_PP(data))
#endif
- ) {
+ ) {
+ RETVAL_FALSE;
efree(buf);
- for (i=0; i<nkeys; i++) {
- efree(eks[i]);
- }
- efree(eks);
- efree(eksl);
- efree(pkeys);
- RETURN_FALSE;
+ goto clean_exit;
+
}
#if OPENSSL_VERSION_NUMBER < 0x0090600fL
@@ -421,33 +1430,24 @@
#endif
EVP_SealFinal(&ctx, buf + len1, &len2);
- efree(pkeys);
-
if (len1 + len2 > 0) {
zval_dtor(*sealdata);
buf[len1 + len2] = '\0';
- ZVAL_STRINGL(*sealdata, erealloc(buf, len1 + len2 + 1),
- len1 + len2, 0);
+ buf = erealloc(buf, len1 + len2 + 1);
+ ZVAL_STRINGL(*sealdata, buf, len1 + len2, 0);
zval_dtor(*ekeys);
if (array_init(*ekeys) == FAILURE) {
php_error(E_ERROR, "Cannot initialize return value");
- for (i=0; i<nkeys; i++) {
- efree(eks[i]);
- }
- efree(eks);
- efree(eksl);
- RETURN_FALSE;
+ RETVAL_FALSE;
+ efree(buf);
+ goto clean_exit;
}
for (i=0; i<nkeys; i++) {
eks[i][eksl[i]] = '\0';
- add_next_index_stringl(*ekeys,
- erealloc(eks[i], eksl[i] + 1),
- eksl[i], 0);
+ add_next_index_stringl(*ekeys, erealloc(eks[i], eksl[i] + 1),
+eksl[i], 0);
+ eks[i] = NULL;
}
- efree(eks);
- efree(eksl);
-
#if 0
/* If allow ciphers that need IV, we need this */
zval_dtor(*ivec);
@@ -458,64 +1458,80 @@
ZVAL_EMPTY_STRING(*ivec);
}
#endif
- } else {
+ }
+ else
efree(buf);
- for (i=0; i<nkeys; i++) {
+
+ RETVAL_LONG(len1 + len2);
+
+clean_exit:
+ for (i=0; i<nkeys; i++) {
+ if (key_resources[i] == -1)
+ EVP_PKEY_free(pkeys[i]);
+ if (eks[i])
efree(eks[i]);
- }
- efree(eks);
- efree(eksl);
}
-
- RETURN_LONG(len1 + len2);
+ efree(eks);
+ efree(eksl);
+ efree(pkeys);
+ efree(key_resources);
}
/* }}} */
-/* {{{ proto bool openssl_open(string data, string opendata, string ekey, int privkey)
+/* {{{ proto bool openssl_open(string data, &string opendata, string ekey, mixed
+privkey)
Open data */
PHP_FUNCTION(openssl_open)
{
zval **privkey, **data, **opendata, **ekey;
EVP_PKEY *pkey;
- int len1, len2, ekl;
- unsigned char *buf, *ek;
-
+ int len1, len2;
+ unsigned char *buf;
+ long keyresource = -1;
EVP_CIPHER_CTX ctx;
if (ZEND_NUM_ARGS() != 4 ||
- zend_get_parameters_ex(4, &data, &opendata, &ekey,
- &privkey) == FAILURE) {
+ zend_get_parameters_ex(4, &data, &opendata, &ekey,
+ &privkey) == FAILURE) {
WRONG_PARAM_COUNT;
}
convert_to_string_ex(data);
convert_to_string_ex(ekey);
-
- ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, privkey, -1, "OpenSSL key",
- le_key);
+ pkey = php_openssl_evp_from_zval(privkey, 0, "", 0, &keyresource);
+ if (pkey == NULL) {
+ zend_error(E_ERROR, "%s(): unable to coerce param 4 into a private
+key", get_active_function_name());
+ RETURN_FALSE;
+ }
buf = emalloc(Z_STRLEN_PP(data) + 1);
if (EVP_OpenInit(&ctx, EVP_rc4(), Z_STRVAL_PP(ekey),
- Z_STRLEN_PP(ekey), NULL, pkey)
+ Z_STRLEN_PP(ekey), NULL, pkey)
#if OPENSSL_VERSION_NUMBER >= 0x0090600fL
- && EVP_OpenUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data),
- Z_STRLEN_PP(data))
+ && EVP_OpenUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data),
+ Z_STRLEN_PP(data))
#endif
- ) {
+ ) {
#if OPENSSL_VERSION_NUMBER < 0x0090600fL
EVP_OpenUpdate(&ctx, buf, &len1, Z_STRVAL_PP(data),
- Z_STRLEN_PP(data));
+ Z_STRLEN_PP(data));
#endif
if (!EVP_OpenFinal(&ctx, buf + len1, &len2) ||
- (len1 + len2 == 0)) {
+ (len1 + len2 == 0)) {
efree(buf);
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
RETURN_FALSE;
}
} else {
efree(buf);
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
+
RETURN_FALSE;
}
-
+ if (keyresource == -1)
+ EVP_PKEY_free(pkey);
+
zval_dtor(*opendata);
buf[len1 + len2] = '\0';
ZVAL_STRINGL(*opendata, erealloc(buf, len1 + len2 + 1), len1 + len2, 0);
@@ -531,7 +1547,6 @@
}
/* }}} */
-#if 0
/* {{{ _php_x509_free() */
static void _php_x509_free(zend_rsrc_list_entry *rsrc)
{
@@ -539,7 +1554,6 @@
X509_free(x509);
}
/* }}} */
-#endif
/*
* Local variables:
Index: php4/ext/openssl/php_openssl.h
diff -u php4/ext/openssl/php_openssl.h:1.5 php4/ext/openssl/php_openssl.h:1.6
--- php4/ext/openssl/php_openssl.h:1.5 Sun Feb 25 22:07:10 2001
+++ php4/ext/openssl/php_openssl.h Sun Apr 1 16:06:14 2001
@@ -16,7 +16,7 @@
+----------------------------------------------------------------------+
*/
-/* $Id: php_openssl.h,v 1.5 2001/02/26 06:07:10 andi Exp $ */
+/* $Id: php_openssl.h,v 1.6 2001/04/01 23:06:14 wez Exp $ */
#ifndef PHP_OPENSSL_H
#define PHP_OPENSSL_H
@@ -31,15 +31,21 @@
PHP_FUNCTION(openssl_get_privatekey);
PHP_FUNCTION(openssl_get_publickey);
PHP_FUNCTION(openssl_free_key);
-#if 0
-PHP_FUNCTION(openssl_read_publickey);
-PHP_FUNCTION(openssl_read_x509);
-PHP_FUNCTION(openssl_free_x509);
-#endif
+PHP_FUNCTION(openssl_x509_read);
+PHP_FUNCTION(openssl_x509_free);
PHP_FUNCTION(openssl_sign);
PHP_FUNCTION(openssl_verify);
PHP_FUNCTION(openssl_seal);
PHP_FUNCTION(openssl_open);
+
+PHP_FUNCTION(openssl_pkcs7_verify);
+PHP_FUNCTION(openssl_pkcs7_decrypt);
+PHP_FUNCTION(openssl_pkcs7_sign);
+PHP_FUNCTION(openssl_pkcs7_encrypt);
+
+PHP_FUNCTION(openssl_error_string);
+PHP_FUNCTION(openssl_x509_parse);
+PHP_FUNCTION(openssl_x509_checkpurpose);
#else
--
PHP CVS Mailing List (http://www.php.net/)
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
To contact the list administrators, e-mail: [EMAIL PROTECTED]