diff --git a/apps/app_directory.c b/apps/app_directory.c
new file mode 100755
index 0000000000000000000000000000000000000000..f7ec414879926258d10ed0e7c524a822fb68a372
--- /dev/null
+++ b/apps/app_directory.c
@@ -0,0 +1,264 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Provide a directory of extensions
+ * 
+ * Copyright (C) 1999, Adtran Inc. and Linux Support Services, LLC
+ *
+ * Mark Spencer <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+ 
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/config.h>
+#include <asterisk/say.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <stdio.h>
+#include "../asterisk.h"
+
+static char *tdesc = "Extension Directory";
+static char *app = "Directory";
+
+/* For simplicity, I'm keeping the format compatible with the voicemail config,
+   but i'm open to suggestions for isolating it */
+
+#define DIRECTORY_CONFIG "voicemail.conf"
+
+/* How many digits to read in */
+#define NUMDIGITS 3
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static char *convert(char *lastname)
+{
+	char *tmp;
+	int lcount = 0;
+	tmp = malloc(NUMDIGITS + 1);
+	if (tmp) {
+		while((*lastname > 32) && lcount < NUMDIGITS) {
+			switch(toupper(*lastname)) {
+			case '1':
+				tmp[lcount++] = '1';
+				break;
+			case '2':
+			case 'A':
+			case 'B':
+			case 'C':
+				tmp[lcount++] = '2';
+				break;
+			case '3':
+			case 'D':
+			case 'E':
+			case 'F':
+				tmp[lcount++] = '3';
+				break;
+			case '4':
+			case 'G':
+			case 'H':
+			case 'I':
+				tmp[lcount++] = '4';
+				break;
+			case '5':
+			case 'J':
+			case 'K':
+			case 'L':
+				tmp[lcount++] = '5';
+				break;
+			case '6':
+			case 'M':
+			case 'N':
+			case 'O':
+				tmp[lcount++] = '6';
+				break;
+			case '7':
+			case 'P':
+			case 'Q':
+			case 'R':
+			case 'S':
+				tmp[lcount++] = '7';
+				break;
+			case '8':
+			case 'T':
+			case 'U':
+			case 'V':
+				tmp[lcount++] = '8';
+				break;
+			case '9':
+			case 'W':
+			case 'X':
+			case 'Y':
+			case 'Z':
+				tmp[lcount++] = '9';
+				break;
+			default:
+			}
+			lastname++;
+		}
+		tmp[lcount] = '\0';
+	}
+	return tmp;
+}
+
+static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char digit)
+{
+	/* Read in the first three digits..  "digit" is the first digit, already read */
+	char ext[NUMDIGITS + 1];
+	struct ast_variable *v;
+	int res;
+	int found=0;
+	char *start, *pos, *conv;
+	char fn[256];
+	memset(ext, 0, sizeof(ext));
+	ext[0] = digit;
+	res = ast_readstring(chan, ext + 1, NUMDIGITS, 3000, 3000, "#");
+	if (!res) {
+		/* Search for all names which start with those digits */
+		v = ast_variable_browse(cfg, context);
+		while(v && !res) {
+			/* Find all candidate extensions */
+			while(v) {
+				/* Find a candidate extension */
+				start = strdup(v->value);
+				if (start) {
+					strtok(start, ",");
+					pos = strtok(NULL, ",");
+					if (pos) {
+						/* Grab the last name */
+						if (strrchr(pos, ' '))
+							pos = strrchr(pos, ' ') + 1;
+						conv = convert(pos);
+						if (conv) {
+							if (!strcmp(conv, ext)) {
+								/* Match! */
+								found++;
+								free(conv);
+								free(start);
+								break;
+							}
+							free(conv);
+						}
+					}
+					free(start);
+				}
+				v = v->next;
+			}
+			if (v) {
+				/* We have a match -- play a greeting if they have it */
+				snprintf(fn, sizeof(fn), "%s/vm/%s/greet", AST_SPOOL_DIR, v->name);
+				if (ast_fileexists(fn, NULL)) {
+					res = ast_streamfile(chan, fn);
+					if (!res)
+						res = ast_waitstream(chan, AST_DIGIT_ANY);
+					ast_stopstream(chan);
+				} else {
+					res = ast_say_digit_str(chan, v->name);
+				}
+ahem:
+				if (!res)
+					res = ast_streamfile(chan, "dir-instr");
+				if (!res)
+					res = ast_waitstream(chan, AST_DIGIT_ANY);
+				if (!res)
+					res = ast_waitfordigit(chan, 3000);
+				ast_stopstream(chan);
+				if (res > -1) {
+					if (res == '1') {
+						strncpy(chan->exten, v->name, sizeof(chan->exten));
+						chan->priority = 0;
+						strncpy(chan->context, context, sizeof(chan->context));
+						res = 0;
+						break;
+					} else if (res == '*') {
+						res = 0;
+						v = v->next;
+					} else {
+						res = 0;
+						goto ahem;
+					}
+				}
+			} else {
+				if (found) 
+					res = ast_streamfile(chan, "dir-nomore");
+				else
+					res = ast_streamfile(chan, "dir-nomatch");
+				if (!res)
+					res = 1;
+				return res;
+			}
+		}
+		
+	}
+	return res;
+}
+
+static int directory_exec(struct ast_channel *chan, void *data)
+{
+	int res = 0;
+	struct localuser *u;
+	struct ast_config *cfg;
+	if (!data) {
+		ast_log(LOG_WARNING, "directory requires an argument (context)\n");
+		return -1;
+	}
+	cfg = ast_load(DIRECTORY_CONFIG);
+	if (!cfg) {
+		ast_log(LOG_WARNING, "Unable to open directory configuration %s\n", DIRECTORY_CONFIG);
+		return -1;
+	}
+	LOCAL_USER_ADD(u);
+top:
+	if (!res)
+		res = ast_streamfile(chan, "dir-intro");
+	if (!res)
+		res = ast_waitstream(chan, AST_DIGIT_ANY);
+	ast_stopstream(chan);
+	if (!res)
+		res = ast_waitfordigit(chan, 5000);
+	if (res > 0) {
+		res = do_directory(chan, cfg, (char *)data, res);
+		if (res > 0) {
+			res = ast_waitstream(chan, AST_DIGIT_ANY);
+			ast_stopstream(chan);
+			if (res >= 0) {
+				goto top;
+			}
+		}
+	}
+	ast_destroy(cfg);
+	LOCAL_USER_REMOVE(u);
+	return res;
+}
+
+int unload_module(void)
+{
+	STANDARD_HANGUP_LOCALUSERS;
+	return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+	return ast_register_application(app, directory_exec);
+}
+
+char *description(void)
+{
+	return tdesc;
+}
+
+int usecount(void)
+{
+	int res;
+	STANDARD_USECOUNT(res);
+	return res;
+}
diff --git a/include/asterisk/say.h b/include/asterisk/say.h
new file mode 100755
index 0000000000000000000000000000000000000000..357d44f9fb178014eecbd3b3b76ef0d0d02a2fba
--- /dev/null
+++ b/include/asterisk/say.h
@@ -0,0 +1,32 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Say numbers and dates (maybe words one day too)
+ * 
+ * Copyright (C) 1999, Adtran Inc. and Linux Support Services, LLC
+ *
+ * Mark Spencer <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#ifndef _ASTERISK_SAY_H
+#define _ASTERISK_SAY_H
+
+#include <asterisk/channel.h>
+#include <asterisk/file.h>
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+int ast_say_number(struct ast_channel *chan, int num);
+int ast_say_digits(struct ast_channel *chan, int num);
+int ast_say_digit_str(struct ast_channel *chan, char *num);
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif
diff --git a/say.c b/say.c
new file mode 100755
index 0000000000000000000000000000000000000000..9ec87e7e7bae114cfbacae24931d0292cd743bd8
--- /dev/null
+++ b/say.c
@@ -0,0 +1,67 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Say numbers and dates (maybe words one day too)
+ * 
+ * Copyright (C) 1999, Adtran Inc. and Linux Support Services, LLC
+ *
+ * Mark Spencer <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/channel.h>
+#include <asterisk/logger.h>
+#include <asterisk/say.h>
+#include <stdio.h>
+
+int ast_say_digit_str(struct ast_channel *chan, char *fn2)
+{
+	char fn[256] = "";
+	int num = 0;
+	int res = 0;
+	while(fn2[num] && !res) {
+		snprintf(fn, sizeof(fn), "digits/%c", fn2[num]);
+		res = ast_streamfile(chan, fn);
+		if (!res) 
+			res = ast_waitstream(chan, AST_DIGIT_ANY);
+		ast_stopstream(chan);
+		num++;
+	}
+	return res;
+}
+
+int ast_say_digits(struct ast_channel *chan, int num)
+{
+	char fn2[256];
+	snprintf(fn2, sizeof(fn2), "%d", num);
+	return ast_say_digit_str(chan, fn2);
+}
+int ast_say_number(struct ast_channel *chan, int num)
+{
+	int res = 0;
+	char fn[256] = "";
+	while(num && !res) {
+		if (num < 20) {
+			snprintf(fn, sizeof(fn), "digits/%d", num);
+			num = 0;
+		} else
+		if (num < 100) {
+			snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10);
+			num -= ((num / 10) * 10);
+		} else {
+			ast_log(LOG_DEBUG, "Number '%d' is too big for me\n", num);
+			res = -1;
+		}
+		if (!res) {
+			res = ast_streamfile(chan, fn);
+			if (!res) 
+				res = ast_waitstream(chan, AST_DIGIT_ANY);
+			ast_stopstream(chan);
+		}
+		
+	}
+	return res;
+}