Main

C++/MFC Archives

November 25, 2006

ESMTP Class

By Brandon W. Yuille

Well, this is my first post on the PT Development Blog, so I thought I would start with some code that I know many will find useful.

This is a class I worte for a billing system to send out emails to customers when their invoices were ready to be viewed online.

Hope you'll be able to make use of this class!

Class Declaration

/**************************************************************

Name: ESMTP Class Ver: 1.0.1.1 Author: Brandon W. Yuille

Desc: This class assumes MFC is used and all socket
initializations have taken place in
CWinApp::InitInstance(). Please don't consider this code
as production code for any sort of SMTP client. This is
simply provided so you can acomplish some simple tasks.
If you make modifications to this code that are useful,
please share them with others!

This class is free to use by anyone.

Copyright (C) 2006 Photon Technologies
**************************************************************/
#pragma once

#include <vector>

class CESMTP
{
public:
CESMTP(void);
~CESMTP(void);

void AddRecipient(CString address, CString name = "");
void SetFrom(CString address, CString name = "");
void SetSubject(CString subject);
void SetESMTPServerAddress(CString address);
void SetESMTPServerPort(int port = 25);
void SetMsgBody(CString data);

BOOL SendMsg();

protected:
BOOL RecvMessage(SOCKET *psock, CString requiredcode);

struct NAMEADDR
{
CString name;
CString addr;
};

std::vector<NAMEADDR> m_vto;
NAMEADDR m_from;
CString m_serveraddr;
int m_serverport;
CString m_data;
CString m_subject;
};


Class Implementation

#include "StdAfx.h"
#include ".\esmtp.h"

CESMTP::CESMTP(void)
{
m_serverport = 25;
}

CESMTP::~CESMTP(void)
{
}

void CESMTP::AddRecipient(CString address, CString name)
{
NAMEADDR na;
na.addr = address;
na.name = name;

m_vto.push_back(na);
}

void CESMTP::SetFrom(CString address, CString name)
{
m_from.addr = address;
m_from.name = name;
}

void CESMTP::SetSubject(CString subject)
{
m_subject = subject;
}

void CESMTP::SetESMTPServerAddress(CString address)
{
m_serveraddr = address;
}

void CESMTP::SetESMTPServerPort(int port)
{
m_serverport = port;
}

void CESMTP::SetMsgBody(CString data)
{
data.Replace("\r\n.\r\n", "\r\n..\r\n");
if(data.Left(3) == ".\r\n")
data.Insert(0, ".");

m_data = data;
}

BOOL CESMTP::SendMsg()
{
SOCKET sock;
SOCKADDR_IN saremote;
CString SendBuf;

// Create the TCP/IP CCCP socket.
if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
return FALSE;
}

saremote.sin_family = AF_INET;
saremote.sin_port = htons((short)m_serverport);
if(inet_addr(m_serveraddr.GetString()) == INADDR_NONE)
{
HOSTENT *hp;
hp = gethostbyname(m_serveraddr.GetString());
saremote.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
}
else
{
saremote.sin_addr.s_addr = inet_addr(m_serveraddr.GetString());
}

// Connect to CCCP server.
if(connect(sock, (SOCKADDR*)&saremote, sizeof(saremote)) == SOCKET_ERROR)
{
return FALSE;
}

// Recieve Banner
if(!RecvMessage(&sock, "220"))
{
closesocket(sock);
return FALSE;
}

// Send EHLO
SendBuf = "EHLO\r\n";
if(send(sock, SendBuf, SendBuf.GetLength(), 0) == SOCKET_ERROR)
{
closesocket(sock);
return FALSE;
}
// Recieve OK
if(!RecvMessage(&sock, "250"))
{
closesocket(sock);
return FALSE;
}

// Send MAIL FROM:<address@domain.com>
SendBuf = "MAIL FROM:" + m_from.addr + "\r\n";
if(send(sock, SendBuf, SendBuf.GetLength(), 0) == SOCKET_ERROR)
{
closesocket(sock);
return FALSE;
}
// Recieve OK
if(!RecvMessage(&sock, "250"))
{
closesocket(sock);
return FALSE;
}

for(int i = 0; i < m_vto.size(); i++)
{
// Send RCPT TO:<address@domain.com>
SendBuf = "RCPT TO:" + m_vto.at(i).addr + "\r\n";
if(send(sock, SendBuf, SendBuf.GetLength(), 0) == SOCKET_ERROR)
{
closesocket(sock);
return FALSE;
}
// Recieve OK
if(!RecvMessage(&sock, "250"))
{
closesocket(sock);
return FALSE;
}
}

// Send DATA
SendBuf = "DATA\r\n";
if(send(sock, SendBuf, SendBuf.GetLength(), 0) == SOCKET_ERROR)
{
closesocket(sock);
return FALSE;
}
// Recieve Begin DATA
if(!RecvMessage(&sock, "354"))
{
closesocket(sock);
return FALSE;
}

CString MsgBody;
MsgBody += "Subject: " + m_subject + "\r\n";
MsgBody += "To: ";
for(int i = 0; i < m_vto.size(); i++)
{
MsgBody += " " + m_vto.at(i).name + " <" + m_vto.at(i).addr + ">;";
}
MsgBody += "\r\n\r\n";
// Add the message data
MsgBody += m_data;
// End the message
MsgBody += "\r\n.\r\n";

SendBuf = MsgBody;
if(send(sock, SendBuf, SendBuf.GetLength(), 0) == SOCKET_ERROR)
{
closesocket(sock);
return FALSE;
}
// Recieve OK
if(!RecvMessage(&sock, "250"))
{
closesocket(sock);
return FALSE;
}

SendBuf = "QUIT\r\n";
if(send(sock, SendBuf, SendBuf.GetLength(), 0) == SOCKET_ERROR)
{
closesocket(sock);
return FALSE;
}
// Recieve Connection Closed
if(!RecvMessage(&sock, "221"))
{
closesocket(sock);
return FALSE;
}

closesocket(sock);
return TRUE;
}

BOOL CESMTP::RecvMessage(SOCKET *psock, CString requiredcode)
{
char szBuf[1025];
CString Buffer;
CString MsgCode;
int BufLen = 0;
int icount = 0;

while((BufLen = recv(*psock, szBuf, 1024, 0)) > 0)
{
szBuf[BufLen] = '\0';
Buffer += szBuf;

if(icount == 0)
MsgCode = Buffer.Left(3);

if(Buffer.Find(MsgCode + " ") != -1)
break;

icount++;
}

if(MsgCode == requiredcode)
return TRUE;

return FALSE;
}

November 30, 2006

Recursively Enumerating the Files within a Directory and its Subdirectories

By Brandon W. Yuille

I want to share a function that is very useful when dealing with file operations and management. The following function (void EnumDirFiles(...)), can be used as is to simply list all the files within a directory and all the files within the subdirectories of the directory. The usages for such a function are vast, as this is a basis for all file enumeration operations.

I hope you will find this as useful as I do!

Implementation

int main()
{
	EnumDirFiles("C:\\Program Files"); // List all files within the Program Files directory.

return 0;
}

Recursive Function Definition

void EnumDirFiles(char *lpszDir)
{
	HANDLE hDir;
	BOOL bDone = FALSE;
	WIN32_FIND_DATA FileData;
	char szFind[512];

strcpy(szFind, lpszDir);
strcat(szFind, "\\*");
hDir = FindFirstFile(szFind, &FileData);
if(hDir == INVALID_HANDLE_VALUE)
{
printf("Invalid Directory: \"%s\"\n", lpszDir);
return;
}

while(!bDone)
{
if(FileData.cFileName[0] != '.') // Exclude "." and ".."
{
char szFullFile[512];
strcpy(szFullFile, lpszDir);
strcat(szFullFile, "\\");
strcat(szFullFile, FileData.cFileName);

if(FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
printf("- Listing Directory: %s\n", szFullFile);
EnumDirFiles(szFullFile);
}
else
printf("%s\n", szFullFile);
}

if(!FindNextFile(hDir, &FileData))
{
if(GetLastError() == ERROR_NO_MORE_FILES)
{
bDone = TRUE;
}
else
{
printf("Unable to enumerate the next file.\n");
bDone = TRUE;
}
}
}

// Close the search handle.
FindClose(hDir);
}

December 30, 2006

Redirecting Console Input/Output using Pipes

By Brandon W. Yuille

I wanted to create my own shell or command prompt, which I could customize (make transparent, ect). I started by using Pipes to accomplish this. Well, I ran into some road blocks when I tried to redirect applications like telnet.exe. The problem lies in the fact that telnet checks to make sure that the std handles are not being redirected via pipes. That's where I decided to give up on my little project (the main reason I started was to use my console for telnet).

With all the information I gained from attempting this, I figure may as well share it. Also, someone might be able to figure out how to fix this problem along the way. If so, please let me know!

The code provided is a basic class (CCmdWnd) that is derived from a CWnd. I would say that none of this code is anywhere near production code. But with some work, you could make it into something nice! When looking at this code, don't worry too much about how I draw my output or anything like that. The important part is how I redirect the console IO. So pay attention to: ExecuteExternal(), ReadAndHandleOutput(), and WriteToChild().

Class Declaration

// CmdWnd.h
#pragma once
// CCmdWnd

#define UWM_CONSOLEOUTPUT (WM_APP + 1)
#define UWM_CHILDEXIT (WM_APP + 2)

class CCmdWnd : public CWnd
{
DECLARE_DYNAMIC(CCmdWnd)

public:
CCmdWnd();
virtual ~CCmdWnd();

protected:
afx_msg void OnPaint();
afx_msg BOOL OnEraseBkgnd(CDC *pDC);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
afx_msg UINT OnGetDlgCode();
afx_msg LRESULT OnConsoleOutput(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnChildExit(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()

BOOL ExecuteInternalFile(CString ExeName, CString &Response);
BOOL ExecuteExternal(CString FileName);

int GetNewLineCount(CString text);

BOOL GetPrevCommand(CString &command){
if((m_commandindex-1) >= 0){
m_commandindex--; command = m_vcommands.at(m_commandindex);
return TRUE;}
return FALSE;};
BOOL GetNextCommand(CString &command){
if((m_commandindex+1) < m_vcommands.size()){
m_commandindex++; command = m_vcommands.at(m_commandindex);
return TRUE;}
return FALSE;};
void AddCommand(CString command){
m_vcommands.push_back(command);
m_commandindex = m_vcommands.size();
};

std::vector m_vcommands;
int m_commandindex;
CString m_curcommand;
CString m_output;
CFont m_font;

static UINT ReadAndHandleOutput(LPVOID pParam);

BOOL WriteToChild(CString text);
HANDLE m_hChildProcess;
HANDLE m_hPipeRead;
HANDLE m_hPipeWrite;
};

Class Implementation

// CmdWnd.cpp : implementation file
//

#include "stdafx.h"
#include "CmdWnd.h"


// CCmdWnd

IMPLEMENT_DYNAMIC(CCmdWnd, CWnd)

CCmdWnd::CCmdWnd()
{
NONCLIENTMETRICS ncm;
ncm.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
strcpy(ncm.lfCaptionFont.lfFaceName, "Calibri");
m_font.CreateFontIndirect(&ncm.lfCaptionFont);

m_commandindex = 0;
m_hChildProcess = NULL;
}

CCmdWnd::~CCmdWnd()
{
}


BEGIN_MESSAGE_MAP(CCmdWnd, CWnd)
ON_WM_CREATE()
ON_WM_PAINT()
ON_WM_ERASEBKGND()
ON_WM_DESTROY()
ON_WM_CHAR()
ON_WM_KEYDOWN()
ON_WM_GETDLGCODE()
ON_MESSAGE(UWM_CONSOLEOUTPUT, OnConsoleOutput)
ON_MESSAGE(UWM_CHILDEXIT, OnChildExit)
END_MESSAGE_MAP()

// CCmdWnd message handlers
UINT CCmdWnd::OnGetDlgCode()
{
return DLGC_WANTTAB | DLGC_WANTARROWS | DLGC_WANTALLKEYS;
}

int CCmdWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CWnd::OnCreate(lpCreateStruct) == -1)
return -1;

return 0;
}

void CCmdWnd::OnDestroy()
{
CloseHandle(m_hChildProcess);
m_hChildProcess = NULL;

CWnd::OnDestroy();
}

BOOL CCmdWnd::OnEraseBkgnd(CDC *pDC)
{
return TRUE;
}

void CCmdWnd::OnPaint()
{
CPaintDC dc(this);

CString output;
CRect rWnd;
CRect rOutput;
GetClientRect(rWnd);
CDC bufDC;
CBitmap bufBmp;

bufDC.CreateCompatibleDC(&dc);
bufBmp.CreateCompatibleBitmap(&dc, rWnd.Width(), rWnd.Height());
CBitmap *pOldBmp = bufDC.SelectObject(&bufBmp);

bufDC.FillSolidRect(rWnd, RGB(0, 0, 0));

bufDC.SetBkMode(TRANSPARENT);
bufDC.SetTextColor(RGB(0, 0xFF, 0));
CFont *pOldFont = bufDC.SelectObject(&m_font);

// Draw all the output
rOutput.left = rWnd.left;
rOutput.right = rWnd.right;
rOutput.top = 0;
rOutput.bottom = 0;

output = m_output;
int lHeight = bufDC.GetTextExtent(output).cy;
rOutput.bottom = lHeight;
CString tmp;
for(int i = 0; i < output.GetLength(); i++)
{
tmp += output.GetAt(i);
CRect rTmp(0,0,0,0);
bufDC.DrawText(tmp, rTmp, DT_SINGLELINE | DT_CALCRECT);
if(rTmp.right > rWnd.right || tmp.Right(1) == "\n")
{
CString hold; hold = "";
if(tmp.Right(1) == "\n")
{
tmp.Replace("\n", "");
tmp.Replace("\r", "");
}
else
{
hold = tmp.Right(1);
tmp = tmp.Left(tmp.GetLength()-1);
}

tmp.Replace("\n", "");
tmp.Replace("\r", "");

bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
rOutput.top += lHeight; rOutput.bottom += lHeight;
tmp = hold;
}
}
tmp.Replace("\n", "");
tmp.Replace("\r", "");
bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
rOutput.top += lHeight; rOutput.bottom += lHeight;

if(m_hChildProcess == NULL)
{
// Draw the current command line
rOutput.left = rWnd.left;
rOutput.right = rWnd.right;

CHAR szPath[MAX_PATH]; //MAX_PATH is defined as 260
INT iLen = 0;
iLen = GetCurrentDirectory(MAX_PATH, szPath);
strcat(szPath, ">");

output = szPath;
output += m_curcommand;
if(rOutput.bottom == rOutput.top)
{
bufDC.DrawText(output, rOutput, DT_SINGLELINE | DT_CALCRECT);
if(lHeight == 0)
lHeight = rOutput.bottom;
}
tmp = "";
for(int i = 0; i < output.GetLength(); i++)
{
tmp += output.GetAt(i);
CRect rTmp(0,0,0,0);
bufDC.DrawText(tmp, rTmp, DT_SINGLELINE | DT_CALCRECT);
if(rTmp.right > rWnd.right)
{
CString hold;
hold = tmp.Right(1);
tmp = tmp.Left(tmp.GetLength()-1);
bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
rOutput.top += lHeight; rOutput.bottom += lHeight;
tmp = hold;
}
}
bufDC.DrawText(tmp, rOutput, DT_SINGLELINE);
}

dc.BitBlt(0, 0, rWnd.Width(), rWnd.Height(), &bufDC, 0, 0, SRCCOPY);

bufDC.SelectObject(pOldFont);
bufDC.SelectObject(pOldBmp);
}

void CCmdWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(m_hChildProcess != NULL)
{
CString tmp;
tmp.Format("%c", nChar);
WriteToChild(tmp);
}
else if(nChar >= 32)
{
CString tmp;
tmp.Format("%c", nChar);
m_curcommand += tmp;
Invalidate();
}
else if(nChar == VK_BACK)
{
m_curcommand = m_curcommand.Left(m_curcommand.GetLength()-1);
Invalidate();
}
else
CWnd::OnChar(nChar, nRepCnt, nFlags);
}

void CCmdWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(m_hChildProcess == NULL)
{
if(nChar == VK_RETURN)
{
CString output;
CHAR szPath[MAX_PATH]; //MAX_PATH is defined as 260
INT iLen = 0;
iLen = GetCurrentDirectory(MAX_PATH, szPath);
strcat(szPath, ">");

output = szPath;
output += m_curcommand;
if(m_output != "")
m_output += "\r\n";
m_output += output + "\r\n";

CString tmpout;
if(m_curcommand == "")
{
}
else if(ExecuteInternalFile(m_curcommand, tmpout))
{
AddCommand(m_curcommand);
if(tmpout != "")
m_output += tmpout;
}
else if(ExecuteExternal(m_curcommand))
{
AddCommand(m_curcommand);
if(tmpout != "")
m_output += tmpout;
}
else
{
AddCommand(m_curcommand);
m_output += m_curcommand + "' is not recognized as an internal or external command.";
}

m_curcommand = "";

Invalidate();
}
else if(nChar == VK_UP)
{
GetPrevCommand(m_curcommand);
Invalidate();
}
else if(nChar == VK_DOWN)
{
GetNextCommand(m_curcommand);
Invalidate();
}
}
else // The child process is running
{
if(nChar == VK_RETURN)
{
WriteToChild("\r\n");
}
}
}

BOOL CCmdWnd::ExecuteInternalFile(CString ExeName, CString &Response)
{
if(ExeName == "cls")
{
m_output = "";
return TRUE;
}
else if(ExeName == "exit")
{
GetParent()->PostMessage(WM_CLOSE);
return TRUE;
}

return FALSE;
}

int CCmdWnd::GetNewLineCount(CString text)
{
int icount = 0;
for(int i = 0; i < text.GetLength(); i++)
{
if(text.GetAt(i) == '\n')
icount++;
}
return icount;
}

BOOL CCmdWnd::ExecuteExternal(CString FileName)
{
HANDLE hOutputReadTmp, hOutputWrite;
HANDLE hInputWriteTmp, hInputRead;
HANDLE hErrorWrite;
HANDLE hThread;
DWORD ThreadId;
SECURITY_ATTRIBUTES sa;

if(FileName.GetLength() > 1023)
return FALSE;

char command[1024];
strcpy(command, FileName.GetBuffer(FileName.GetLength()));

m_hChildProcess = NULL;

// Set up the security attributes struct.
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

// Create the child output pipe.
if(!CreatePipe(&hOutputReadTmp, &hOutputWrite, &sa, 0))
{
TRACE0("CreatePipe Failed!\r\n");
}

// Create a duplicate of the output write handle for the std error
// write handle. This is necessary in case the child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite,
GetCurrentProcess(),&hErrorWrite,0,
TRUE,DUPLICATE_SAME_ACCESS))
{
TRACE0("DuplicateHandle Failed!\r\n");
}


// Create the child input pipe.
if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0))
{
}

if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
GetCurrentProcess(),
&m_hPipeRead, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
{
}

if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
GetCurrentProcess(),
&m_hPipeWrite, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
{
}

if(!CloseHandle(hOutputReadTmp))
{
}
if(!CloseHandle(hInputWriteTmp))
{
}

PROCESS_INFORMATION pi;
STARTUPINFO si;

// Set up the start up info struct.
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hOutputWrite;
si.hStdInput = hInputRead;
si.hStdError = hErrorWrite;

if(!CreateProcess(NULL, command, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
{
CString path;
char szSysPath[MAX_PATH];
GetEnvironmentVariable(_T("WinDir"), szSysPath, MAX_PATH);
path = szSysPath;
path += "\\system32\\";
path += command;
strcpy(command, path);
if(!CreateProcess(NULL, command, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
{
return FALSE;
}
}

// Set global child process handle to cause threads to exit.
m_hChildProcess = pi.hProcess;

// Close any unnecessary handles.
if(!CloseHandle(pi.hThread))
{
}
if(!CloseHandle(hOutputWrite))
{
}
if(!CloseHandle(hInputRead))
{
}
if(!CloseHandle(hErrorWrite))
{
}


// Read the child's output.
AfxBeginThread(ReadAndHandleOutput, this);
// Redirection is complete

return TRUE;
}

UINT CCmdWnd::ReadAndHandleOutput(LPVOID pParam)
{
CCmdWnd *pWnd = (CCmdWnd*)pParam;
HANDLE hPipeRead = pWnd->m_hPipeRead;
CHAR *lpBuffer;
DWORD nBytesRead;
DWORD nCharsWritten;

while(TRUE)
{
lpBuffer = new CHAR[1024];
if(!ReadFile(hPipeRead, lpBuffer, 1020, &nBytesRead, NULL) || !nBytesRead)
{
if(GetLastError() == ERROR_BROKEN_PIPE)
{
delete [] lpBuffer;
break; // pipe done - normal exit path.
}
else
{
delete [] lpBuffer;
break;
// Something bad happened.
}
}

lpBuffer[nBytesRead] = '\0';
pWnd->PostMessage(UWM_CONSOLEOUTPUT, 0, (LPARAM)lpBuffer);
}

pWnd->SendMessage(UWM_CHILDEXIT);
return 0;
}

LRESULT CCmdWnd::OnConsoleOutput(WPARAM wParam, LPARAM lParam)
{
m_output += (CHAR*)lParam;

delete [] (CHAR*)lParam;

Invalidate();

return 0;
}

BOOL CCmdWnd::WriteToChild(CString text)
{
m_output += text;
char *lpszBuf = new char[text.GetLength()+1];
strcpy(lpszBuf, text);
DWORD nBytesWrite = strlen(lpszBuf);
DWORD nBytesWrote;
if(!WriteFile(m_hPipeWrite, lpszBuf, nBytesWrite, &nBytesWrote, NULL))
{
delete [] lpszBuf;
if(GetLastError() == ERROR_NO_DATA)
return FALSE; // Pipe was closed (normal exit path).
else
return FALSE;
}

delete [] lpszBuf;
Invalidate();
return TRUE;
}

LRESULT CCmdWnd::OnChildExit(WPARAM wParam, LPARAM lParam)
{
CloseHandle(m_hPipeRead);
CloseHandle(m_hPipeWrite);
CloseHandle(m_hChildProcess);
m_hChildProcess = NULL;

Invalidate();
return 0;
}

About C++/MFC

This page contains an archive of all entries posted to Photon Development in the C++/MFC category. They are listed from oldest to newest.

Call Capture is the next category.

Many more can be found on the main index page or by looking through the archives.