// --- BEGIN COPYRIGHT BLOCK ---
// 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; version 2 of the License.
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package com.netscape.cms.jobs;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Vector;
import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.base.EBaseException;
import com.netscape.certsrv.base.IConfigStore;
import com.netscape.certsrv.base.IExtendedPluginInfo;
import com.netscape.certsrv.base.ISubsystem;
import com.netscape.certsrv.base.MetaInfo;
import com.netscape.certsrv.ca.ICertificateAuthority;
import com.netscape.certsrv.dbs.IElementProcessor;
import com.netscape.certsrv.dbs.certdb.ICertRecord;
import com.netscape.certsrv.dbs.certdb.ICertificateRepository;
import com.netscape.certsrv.jobs.IJob;
import com.netscape.certsrv.jobs.IJobCron;
import com.netscape.certsrv.jobs.IJobsScheduler;
import com.netscape.certsrv.logging.ILogger;
import com.netscape.certsrv.notification.ENotificationException;
import com.netscape.certsrv.notification.IEmailFormProcessor;
import com.netscape.certsrv.notification.IEmailResolver;
import com.netscape.certsrv.notification.IEmailResolverKeys;
import com.netscape.certsrv.notification.IMailNotification;
import com.netscape.certsrv.request.IRequest;
import com.netscape.certsrv.request.RequestId;
/**
* A job for the Jobs Scheduler. This job checks in the internal ldap
* db for certs about to expire within the next configurable days and
* sends email notifications to the appropriate recipients.
*
* the $TOKENS that are available for the this jobs's summary outer form are:
*
* - $Status
*
- $InstanceID
*
- $SummaryItemList
*
- $SummaryTotalNum
*
- $SummaryTotalSuccess
*
- $SummaryTotalfailure
*
- $ExecutionTime
*
* and for the inner list items:
*
* - $SerialNumber
*
- $IssuerDN
*
- $SubjectDN
*
- $NotAfter
*
- $NotBefore
*
- $RequestorEmail
*
- $CertType
*
- $RequestType
*
- $HttpHost
*
- $HttpPort
*
*
* @version $Revision$, $Date$
* @see com.netscape.certsrv.jobs.IJob
* @see com.netscape.cms.jobs.AJobBase
*/
public class RenewalNotificationJob
extends AJobBase
implements IJob, Runnable, IExtendedPluginInfo {
// config parameters...
public static final String PROP_CRON = "cron";
/**
* Profile ID specifies which profile approves the certificate.
*/
public static final String PROP_PROFILE_ID = "profileId";
/**
* This job will send notification at this much time before the
* enpiration date
*/
public static final String PROP_NOTIFYTRIGGEROFFSET =
"notifyTriggerOffset";
/**
* This job will stop sending notification this much time after
* the expiration date
*/
public static final String PROP_NOTIFYENDOFFSET = "notifyEndOffset";
/**
* sender email address as appeared on the notification email
*/
public static final String PROP_SENDEREMAIL =
"senderEmail";
/**
* email subject line as appeared on the notification email
*/
public static final String PROP_EMAILSUBJECT =
"emailSubject";
/**
* location of the template file used for email notification
*/
public static final String PROP_EMAILTEMPLATE = "emailTemplate";
public static final String PROP_MAXNOTIFYCOUNT = "maxNotifyCount";
/**
* sender email as appeared on the notification summary email
*/
public static final String PROP_SUMMARY_SENDEREMAIL = "summary.senderEmail";
/**
* recipient of the notification summary email
*/
public static final String PROP_SUMMARY_RECIPIENTEMAIL = "summary.recipientEmail";
/**
* email subject as appeared on the notification summary email
*/
public static final String PROP_SUMMARY_SUBJECT = "summary.emailSubject";
/**
* location of the email template used for notification summary
*/
public static final String PROP_SUMMARY_TEMPLATE = "summary.emailTemplate";
/**
* location of the template file for each item appeared on the
* notification summary
*/
public static final String PROP_SUMMARY_ITEMTEMPLATE = "summary.itemTemplate";
/*
* Holds configuration parameters accepted by this implementation.
* This list is passed to the configuration console so configuration
* for instances of this implementation can be configured through the
* console.
*/
protected static String[] mConfigParams =
new String[] {
"enabled",
PROP_CRON,
PROP_PROFILE_ID,
PROP_NOTIFYTRIGGEROFFSET,
PROP_NOTIFYENDOFFSET,
PROP_SENDEREMAIL,
PROP_EMAILSUBJECT,
PROP_EMAILTEMPLATE,
"summary.enabled",
PROP_SUMMARY_RECIPIENTEMAIL,
PROP_SUMMARY_SENDEREMAIL,
PROP_SUMMARY_SUBJECT,
PROP_SUMMARY_ITEMTEMPLATE,
PROP_SUMMARY_TEMPLATE,
};
protected ICertificateRepository mCertDB = null;
protected ICertificateAuthority mCA = null;
protected boolean mSummary = false;
protected String mEmailSender = null;
protected String mEmailSubject = null;
protected String mEmailTemplateName = null;
protected String mSummaryItemTemplateName = null;
protected String mSummaryTemplateName = null;
protected boolean mSummaryHTML = false;
protected boolean mHTML = false;
protected String mHttpHost = null;
protected String mHttpPort = null;
private int mPreDays = 0;
private long mPreMS = 0;
private int mPostDays = 0;
private long mPostMS = 0;
private int mMaxNotifyCount = 1;
private String[] mProfileId = null;
/* Vector of extendedPluginInfo strings */
protected static Vector mExtendedPluginInfo = null;
static {
mExtendedPluginInfo = new Vector();
};
/**
* class constructor
*/
public RenewalNotificationJob () {
}
/**
* holds help text for this plugin
*/
public String[] getExtendedPluginInfo(Locale locale) {
String s[] = {
IExtendedPluginInfo.HELP_TEXT +
"; A job that checks for expiring or expired certs" +
"notifyTriggerOffset before and notifyEndOffset after " +
"the expiration date",
PROP_PROFILE_ID + ";string;Specify the ID of the profile which "+
"approved the certificates that are about to expire. For multiple "+
"profiles, each entry is separated by white space. For example, " +
"if the administrator just wants to give automated notification " +
"when the SSL server certificates are about to expire, then "+
"he should enter \"caServerCert caAgentServerCert\" in the profileId textfield. "+
"Blank field means all profiles.",
PROP_NOTIFYTRIGGEROFFSET + ";number,required;How long (in days) before " +
"certificate expiration will the first notification " +
"be sent",
PROP_NOTIFYENDOFFSET + ";number,required;How long (in days) after " +
"certificate expiration will notifications " +
"continue to be resent if certificate is not renewed",
PROP_CRON + ";string,required;Format: minute hour dayOfMonth Mmonth " +
"dayOfWeek. Use '*' for 'every'. For dayOfWeek, 0 is Sunday",
PROP_SENDEREMAIL + ";string,required;Specify the address to be used " +
"as the email's 'sender'. Bounces go to this address.",
PROP_EMAILSUBJECT + ";string,required;Email subject",
PROP_EMAILTEMPLATE + ";string,required;Fully qualified pathname of " +
"template file of email to be sent",
"enabled;boolean;Enable this plugin",
"summary.enabled;boolean;Enabled sending of summaries",
PROP_SUMMARY_SENDEREMAIL + ";string,required;Sender email address of summary",
PROP_SUMMARY_RECIPIENTEMAIL + ";string,required;Who should receive summaries",
PROP_SUMMARY_SUBJECT + ";string,required;Subject of summary email",
PROP_SUMMARY_TEMPLATE + ";string,required;Fully qualified pathname of " +
"template file of email to be sent",
PROP_SUMMARY_ITEMTEMPLATE + ";string,required;Fully qualified pathname of " +
"file with template to be used for each summary item",
IExtendedPluginInfo.HELP_TOKEN +
";configuration-jobrules-renewalnotification",
};
return s;
}
/**
* Initialize from the configuration file.
* @param id String name of this instance
* @param implName string name of this implementation
* @param config configuration store for this instance
* @exception EBaseException
*/
public void init(ISubsystem owner, String id, String implName, IConfigStore config) throws
EBaseException {
mConfig = config;
mId = id;
mImplName = implName;
mCA = (ICertificateAuthority)
CMS.getSubsystem("ca");
if (mCA == null) {
mSummary = false;
return;
}
mCertDB = (ICertificateRepository) mCA.getCertificateRepository();
mCron = mConfig.getString(IJobCron.PROP_CRON);
if (mCron == null) {
return;
}
// parse cron string into a JobCron class
IJobsScheduler scheduler = (IJobsScheduler) owner;
mJobCron = scheduler.createJobCron(mCron);
}
/**
* finds out which cert needs notification and notifies the
* responsible parties
*/
public void run() {
// for forming renewal URL at template
mHttpHost = CMS.getEEHost();
mHttpPort = CMS.getEESSLPort();
// read from the configuration file
try {
mPreDays = mConfig.getInteger(PROP_NOTIFYTRIGGEROFFSET, 30); // in days
mPostDays = mConfig.getInteger(PROP_NOTIFYENDOFFSET, 15); // in days
mEmailSender = mConfig.getString(PROP_SENDEREMAIL);
mEmailSubject = mConfig.getString(PROP_EMAILSUBJECT);
mEmailTemplateName = mConfig.getString(PROP_EMAILTEMPLATE);
// initialize the summary related config info
IConfigStore sc = mConfig.getSubStore(PROP_SUMMARY);
if (sc.getBoolean(PROP_ENABLED, false)) {
mSummary = true;
mSummaryItemTemplateName =
mConfig.getString(PROP_SUMMARY_ITEMTEMPLATE);
mSummarySenderEmail =
mConfig.getString(PROP_SUMMARY_SENDEREMAIL);
mSummaryReceiverEmail =
mConfig.getString(PROP_SUMMARY_RECIPIENTEMAIL);
mSummaryMailSubject =
mConfig.getString(PROP_SUMMARY_SUBJECT);
mSummaryTemplateName =
mConfig.getString(PROP_SUMMARY_TEMPLATE);
} else {
mSummary = false;
}
long msperday = 86400 * 1000;
long mspredays = mPreDays;
long mspostdays = mPostDays;
mPreMS = mspredays * msperday;
mPostMS = mspostdays * msperday;
Date now = CMS.getCurrentDate();
DateFormat dateFormat = DateFormat.getDateTimeInstance();
String nowString = dateFormat.format(now);
/*
* look in the internal db for certificateRecords that are
* 1. within the expiration notification period
* 2. has not yet been renewed
* 3. notify - use EmailTemplateProcessor to formulate
* content, then send
* if notified successfully, mark "STATUS_SUCCESS",
* else, if notified unsuccessfully, mark "STATUS_FAILURE".
*/
/* 1) make target notAfter string */
Date expiryDate = null;
Date stopDate = null;
/* 2) Assemble ldap Search filter string */
// date format: 19991215125306Z
long expiryMS = now.getTime() + mPreMS;
long stopMS = now.getTime() - mPostMS;
expiryDate = new Date(expiryMS);
stopDate = new Date(stopMS);
// All cert records which:
// 1) expire before the deadline
// 2) have not already been renewed
// filter format:
// (& (notafter<='time')(!(certAutoRenew=DONE))(!certAutoRenew=DISABLED))
StringBuffer f = new StringBuffer();
String profileId = "";
try {
profileId = mConfig.getString(PROP_PROFILE_ID, "");
} catch (EBaseException ee) {
}
if (profileId != null && profileId.length() > 0) {
StringTokenizer tokenizer = new StringTokenizer(profileId);
int num = tokenizer.countTokens();
mProfileId = new String[num];
for (int i=0; i=" + stopDate.getTime() + ")");
f.append("(!(" + ICertRecord.ATTR_AUTO_RENEW + "=" + ICertRecord.AUTO_RENEWAL_DONE + "))");
f.append("(!(" + ICertRecord.ATTR_AUTO_RENEW + "=" + ICertRecord.AUTO_RENEWAL_DISABLED + "))");
f.append("(!(" + ICertRecord.ATTR_CERT_STATUS + "=" + ICertRecord.STATUS_REVOKED + "))");
f.append("(!(" + ICertRecord.ATTR_CERT_STATUS + "=" + ICertRecord.STATUS_REVOKED_EXPIRED + "))");
f.append(")");
String filter = f.toString();
String emailTemplate =
getTemplateContent(mEmailTemplateName);
mHTML = mMailHTML;
try {
String summaryItemTemplate = null;
if (mSummary == true) {
summaryItemTemplate =
getTemplateContent(mSummaryItemTemplateName);
}
ItemCounter ic = new ItemCounter();
CertRecProcessor cp = new CertRecProcessor(this, emailTemplate, summaryItemTemplate, ic);
//CertRecordList list = mCertDB.findCertRecordsInList(filter, null, "serialno", 5);
//list.processCertRecords(0, list.getSize() - 1, cp);
Enumeration en = mCertDB.findCertRecs(filter);
while (en.hasMoreElements()) {
Object element = (Object) en.nextElement();
try {
cp.process(element);
} catch (Exception e) {
//Don't abort the entire operation. The error should already be logged
log(ILogger.LL_FAILURE, CMS.getLogMessage("JOBS_FAILED_PROCESS", e.toString()));
}
}
// Now send the summary
if (mSummary == true) {
try {
String summaryTemplate =
getTemplateContent(mSummaryTemplateName);
mSummaryHTML = mMailHTML;
buildContentParams(IEmailFormProcessor.TOKEN_ID,
mId);
buildContentParams(IEmailFormProcessor.TOKEN_SUMMARY_ITEM_LIST,
ic.mItemListContent);
buildContentParams(IEmailFormProcessor.TOKEN_SUMMARY_TOTAL_NUM,
String.valueOf(ic.mNumFail + ic.mNumSuccessful));
buildContentParams(IEmailFormProcessor.TOKEN_SUMMARY_SUCCESS_NUM,
String.valueOf(ic.mNumSuccessful));
buildContentParams(IEmailFormProcessor.TOKEN_SUMMARY_FAILURE_NUM,
String.valueOf(ic.mNumFail));
buildContentParams(IEmailFormProcessor.TOKEN_EXECUTION_TIME,
nowString);
IEmailFormProcessor summaryEmfp = CMS.getEmailFormProcessor();
String summaryContent =
summaryEmfp.getEmailContent(summaryTemplate,
mContentParams);
if (summaryContent == null) {
log(ILogger.LL_FAILURE, CMS.getLogMessage("JOBS_SUMMARY_CONTENT_NULL"));
mailSummary(" no summaryContent");
} else {
mMailHTML = mSummaryHTML;
mailSummary(summaryContent);
}
} catch (Exception e) {
// log error
log(ILogger.LL_FAILURE, CMS.getLogMessage("JOBS_EXCEPTION_IN_RUN", e.toString()));
}
}
} catch (EBaseException e) {
// log error
log(ILogger.LL_FAILURE, CMS.getLogMessage("OPERATION_ERROR", e.toString()));
}
} catch (EBaseException ex) {
log(ILogger.LL_FAILURE, CMS.getLogMessage("Configuration error:", ex.toString()));
}
}
private String makeLDAPDateString(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
String ldfYear = "" + Integer.toString(calendar.get(Calendar.YEAR) - 1900);
String ldfMonth = getPadded(calendar.get(Calendar.MONTH));
String ldfDate = getPadded(calendar.get(Calendar.DAY_OF_MONTH));
String ldfHours = getPadded(calendar.get(Calendar.HOUR));
String ldfMinutes = getPadded(calendar.get(Calendar.MINUTE));
String ldfSeconds = getPadded(calendar.get(Calendar.SECOND));
return ldfYear + ldfMonth + ldfDate + ldfHours + ldfMinutes + ldfSeconds + "Z";
}
private String getPadded(int i) {
if (i < 10) {
return "0" + Integer.toString(i);
} else {
return "" + Integer.toString(i);
}
}
/**
* get instance id.
* @return a String identifier
*/
public String getId() {
return mId;
}
/**
* set instance id.
* @param id String id of the instance
*/
public void setId(String id) {
mId = id;
}
/**
* get cron string associated with this job
* @return a JobCron object that represents the schedule of this job
*/
public IJobCron getJobCron() {
return mJobCron;
}
/**
* gets the plugin name of this job.
* @return a String that is the name of this implementation
*/
public String getImplName() {
return mImplName;
}
/**
* Gets the configuration substore used by this job
* @return configuration store
*/
public IConfigStore getConfigStore() {
return mConfig;
}
protected void mailUser(String subject,
String msg,
String sender,
IRequest req,
ICertRecord cr)
throws IOException, ENotificationException, EBaseException {
IMailNotification mn = CMS.getMailNotification();
String rcp = null;
// boolean sendFailed = false;
Exception sendFailedException = null;
IEmailResolverKeys keys = CMS.getEmailResolverKeys();
try {
if (req != null) {
keys.set(IEmailResolverKeys.KEY_REQUEST, req);
}
if (cr != null) {
Object c = cr.getCertificate();
if (c != null) {
keys.set(IEmailResolverKeys.KEY_CERT, cr.getCertificate());
}
}
IEmailResolver er = CMS.getReqCertSANameEmailResolver();
rcp = er.getEmail(keys);
} catch (Exception e) {
// already logged by the resolver
// sendFailed = true;
sendFailedException = e;
throw (ENotificationException) sendFailedException;
}
mn.setTo(rcp);
if (sender != null) mn.setFrom(sender);
else mn.setFrom("nobody");
if (subject != null) mn.setSubject(subject);
else mn.setFrom("Important message from Certificate Authority");
if (mHTML == true) mn.setContentType("text/html");
String failedString = null;
mn.setContent(msg);
mn.sendNotification();
}
/**
* Returns a list of configuration parameter names.
* The list is passed to the configuration console so instances of
* this implementation can be configured through the console.
*
* @return String array of configuration parameter names.
*/
public String[] getConfigParams() {
return (mConfigParams);
}
}
class CertRecProcessor implements IElementProcessor {
protected RenewalNotificationJob mJob;
protected String mEmailTemplate;
protected String mSummaryItemTemplate;
protected ItemCounter mIC;
public CertRecProcessor(RenewalNotificationJob job, String emailTemplate,
String summaryItemTemplate, ItemCounter ic) {
mJob = job;
mEmailTemplate = emailTemplate;
mSummaryItemTemplate = summaryItemTemplate;
mIC = ic;
}
public void process(Object o) throws EBaseException {
// Get each certRecord
ICertRecord cr = (ICertRecord) o;
String ridString = null;
boolean numFailCounted = false;
if (cr != null) {
mJob.buildItemParams(cr.getCertificate());
mJob.buildItemParams(IEmailFormProcessor.TOKEN_HTTP_HOST,
mJob.mHttpHost);
mJob.buildItemParams(IEmailFormProcessor.TOKEN_HTTP_PORT, mJob.mHttpPort);
MetaInfo metaInfo = null;
metaInfo = (MetaInfo) cr.get(ICertRecord.ATTR_META_INFO);
if (metaInfo == null) {
mIC.mNumFail++;
numFailCounted = true;
if (mJob.mSummary == true)
mJob.buildItemParams(IEmailFormProcessor.TOKEN_STATUS,
mJob.STATUS_FAILURE);
mJob.log(ILogger.LL_FAILURE,
CMS.getLogMessage("JOBS_GET_CERT_ERROR",
cr.getCertificate().getSerialNumber().toString(16)));
} else {
ridString = (String) metaInfo.get(ICertRecord.META_REQUEST_ID);
}
}
IRequest req = null;
if (ridString != null) {
RequestId rid = new RequestId(ridString);
try {
req = mJob.mCA.getRequestQueue().findRequest(rid);
} catch (Exception e) {
// it is ok not to be able to get the request. The main reason
// to get the request is to retrieve the requestor's email.
// We can retrieve the email from the CertRecord.
CMS.debug("huh RenewalNotificationJob Exception: "+e.toString());
}
if (req != null)
mJob.buildItemParams(req);
} // ridString != null
try {
// send mail to user
IEmailFormProcessor emfp = CMS.getEmailFormProcessor();
String message = emfp.getEmailContent(mEmailTemplate,
mJob.mItemParams);
mJob.mailUser(mJob.mEmailSubject,
message,
mJob.mEmailSender,
req,
cr);
mJob.buildItemParams(IEmailFormProcessor.TOKEN_STATUS,
mJob.STATUS_SUCCESS);
mIC.mNumSuccessful++;
} catch (Exception e) {
CMS.debug("RenewalNotificationJob Exception: "+e.toString());
mJob.buildItemParams(IEmailFormProcessor.TOKEN_STATUS, mJob.STATUS_FAILURE);
mJob.log(ILogger.LL_FAILURE, e.toString(), ILogger.L_MULTILINE);
if (numFailCounted == false) {
mIC.mNumFail++;
}
}
if (mJob.mSummary == true) {
IEmailFormProcessor summaryItemEmfp =
CMS.getEmailFormProcessor();
String c =
summaryItemEmfp.getEmailContent(mSummaryItemTemplate,
mJob.mItemParams);
if (mIC.mItemListContent == null) {
mIC.mItemListContent = c;
} else {
mIC.mItemListContent += c;
}
}
}
}
class ItemCounter {
public int mNumSuccessful = 0;
public int mNumFail = 0;
public String mItemListContent = null;
}