#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <stdarg.h>
#include <sys/errno.h>
#include <OpenDirectory/OpenDirectory.h>
#include <OpenDirectory/OpenDirectoryPriv.h>
#include <DirectoryService/DirectoryService.h>
#include "odkerb.h"
#include "dserr.h"
#include <membership.h>
static ODNodeRef gSearchNode = NULL;
static Boolean odkerb_CFStringHasPrefixWithOptions(CFStringRef theString, CFStringRef prefix, CFOptionFlags searchOptions);
static Boolean odkerb_CFStringHasSuffixWithOptions(CFStringRef theString, CFStringRef suffix, CFOptionFlags searchOptions);
static CFStringRef odkerb_create_alleged_principal_id(char *service_principal_id);
static CFStringRef odkerb_create_config_record_name(CFStringRef cfPrincipalID);
static CFStringRef odkerb_create_short_name(char *service_principal_id);
static CFStringRef odkerb_create_alleged_alt_security_identity(CFStringRef principalID);
static int odkerb_has_foreign_realm(char *service_principal_id);
static int odkerb_copy_user_record_with_alt_security_identity(CFStringRef principalID, ODRecordRef *out);
static int odkerb_copy_search_node_with_config_record_name(CFStringRef configRecordName, ODNodeRef *out);
static int odkerb_copy_user_record_with_short_name(CFStringRef shortName, ODNodeRef searchNode, ODRecordRef *out);
static int odkerb_get_im_handle_with_user_record(ODRecordRef userRecord, CFStringRef imType, CFStringRef realm, CFStringRef allegedShortName, char im_handle[], size_t im_handle_size);
static int odkerb_get_fabricated_im_handle(ODRecordRef userRecord, CFStringRef allegedShortName, CFStringRef realm, char im_handle[], size_t im_handle_size);
static void odkerb_log_debug(char *fmt, ...);
static void odkerb_log(int priority, char *fmt, ...);
static void odkerb_log_cferror(int priority, char *message, CFErrorRef error);
static void odkerb_log_cfstring(int priority, char *message, CFStringRef string);
#define ODKERB_PARAM_ASSERT(expression) \
do { if (!(expression)) { ODKERB_LOG(LOG_ERR, "Bad parameter"); return -1; } } while(0)
#define CF_SAFE_RELEASE(cfobj) \
do { if ((cfobj) != NULL) CFRelease((cfobj)); cfobj = NULL; } while (0)
#define ODKERB_LOG(priority, message) \
odkerb_log(priority, "%s", message)
#define ODKERB_LOG_CFERROR(priority, message, error) \
odkerb_log_cferror(priority, message, error)
#define ODKERB_LOG_CFSTRING(priority, message, string) \
odkerb_log_cfstring(priority, message, string)
#define ODKERB_LOG_ERRNO(priority, errnum) \
odkerb_log(priority, "%s", strerror(errnum));
#define ODKERB_LOG1(priority, fmt, arg0) \
do { odkerb_log(priority, fmt, arg0); if (0) printf(fmt, arg0); } while (0)
void
odkerb_log(int priority, char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog(priority, fmt, ap);
va_end(ap);
}
void
odkerb_log_cferror(int priority, char *message, CFErrorRef error)
{
if (error == NULL) {
odkerb_log(priority, message);
}
else {
char buf[256];
CFStringRef errorString = CFErrorCopyFailureReason(error);
CFStringGetCString(errorString, buf, sizeof(buf), kCFStringEncodingUTF8);
odkerb_log(priority, "%s: %s", message, buf);
}
}
void
odkerb_log_cfstring(int priority, char *message, CFStringRef string)
{
if (string == NULL) {
odkerb_log(priority, "%s", message);
}
else {
char buf[256];
if (CFStringGetCString(string, buf, sizeof(buf), kCFStringEncodingUTF8))
odkerb_log(priority, "%s: %s", message, buf);
else
odkerb_log(priority, "%s", message);
}
}
Boolean
odkerb_CFStringHasPrefixWithOptions(CFStringRef theString, CFStringRef prefix, CFOptionFlags searchOptions)
{
searchOptions &= (kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareDiacriticInsensitive | kCFCompareWidthInsensitive);
searchOptions |= kCFCompareAnchored;
return CFStringFindWithOptions(theString, prefix, CFRangeMake(0, CFStringGetLength(theString)),
searchOptions, NULL);
}
Boolean
odkerb_CFStringHasSuffixWithOptions(CFStringRef theString, CFStringRef suffix, CFOptionFlags searchOptions)
{
searchOptions &= (kCFCompareCaseInsensitive | kCFCompareNonliteral | kCFCompareLocalized | kCFCompareDiacriticInsensitive | kCFCompareWidthInsensitive);
searchOptions |= (kCFCompareBackwards | kCFCompareAnchored);
return CFStringFindWithOptions(theString, suffix, CFRangeMake(0, CFStringGetLength(theString)),
searchOptions, NULL);
}
int
odkerb_has_foreign_realm(char *service_principal_id)
{
char *c = strchr(service_principal_id, '@');
if (c) {
c = strchr(c+1, '@');
if (c)
return 1;
}
return 0;
}
CFStringRef
odkerb_create_alleged_principal_id(char *service_principal_id)
{
CFStringRef cfPrincipalID = NULL;
assert(service_principal_id != 0);
char *s = strdup(service_principal_id);
if (s) {
char *c = strrchr(s, '@');
if (c)
*c = '\0';
else
ODKERB_LOG1(LOG_WARNING, "Bad service principal: %s", service_principal_id);
cfPrincipalID = CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingUTF8);
free(s);
}
if (cfPrincipalID == NULL)
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
return cfPrincipalID;
}
CFStringRef
odkerb_create_alleged_alt_security_identity(CFStringRef principalID)
{
CFStringRef cfAltSecurityIdentity = NULL;
assert(principalID != NULL);
cfAltSecurityIdentity = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
CFSTR("Kerberos:%@"), principalID);
if (cfAltSecurityIdentity == NULL)
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
return cfAltSecurityIdentity;
}
CFStringRef
odkerb_create_config_record_name(CFStringRef principalID)
{
CFStringRef cfConfigRecordName = NULL;
char buffer[1024];
assert(principalID != NULL);
if (CFStringGetCString(principalID, buffer, sizeof(buffer)-1, kCFStringEncodingUTF8) == FALSE)
return NULL;
char *c = strchr(buffer, '@');
if (c != 0) {
++c;
cfConfigRecordName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
CFSTR("Kerberos:%s"), c);
if (cfConfigRecordName == NULL)
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
}
else {
ODKERB_LOG_CFSTRING(LOG_WARNING, "Bad principal", principalID);
}
return cfConfigRecordName;
}
CFStringRef
odkerb_create_short_name(char *service_principal_id)
{
CFStringRef cfShortName = NULL;
assert(service_principal_id != 0);
char *s = strdup(service_principal_id);
if (s != 0) {
char *c = strchr(s, '@');
if (c != 0)
*c = '\0';
cfShortName = CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingUTF8);
free(s);
}
if (cfShortName == NULL)
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
return cfShortName;
}
int
odkerb_configure_search_node(void)
{
int retval = -1;
CFErrorRef cfError = NULL;
if (gSearchNode != NULL) {
if (ODNodeGetName(gSearchNode) == NULL) {
ODKERB_LOG(LOG_DEBUG, "Flushing search node");
CF_SAFE_RELEASE(gSearchNode);
gSearchNode = NULL;
}
}
if (gSearchNode == NULL) {
gSearchNode = ODNodeCreateWithNodeType(kCFAllocatorDefault, kODSessionDefault,
kODNodeTypeAuthentication, &cfError);
if (gSearchNode == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_INFO, "Unable to get a reference to the search node", cfError);
goto failure;
}
}
retval = 0;
failure:
CF_SAFE_RELEASE(cfError);
return retval;
}
int
odkerb_possibly_reset_search_node(CFErrorRef error)
{
if (error != NULL && gSearchNode != NULL) {
if (! IS_EXPECTED_DS_ERROR(CFErrorGetCode(error))) {
ODKERB_LOG_CFERROR(LOG_DEBUG, "Flushing search node because of unexpected error", error);
CF_SAFE_RELEASE(gSearchNode);
gSearchNode = NULL;
}
}
return 0;
}
int
odkerb_copy_user_record_with_alt_security_identity(CFStringRef principalID, ODRecordRef *out)
{
int retval = -1;
CFStringRef cfAltSecurityIdentity = NULL;
CFErrorRef cfError = NULL;
ODQueryRef cfQueryRef = NULL;
CFArrayRef cfUserRecords = NULL;
int mbrErr = 0;
uuid_t user_uuid = { 0 };
uuid_string_t uuidStr;
char principal[1024];
ODKERB_PARAM_ASSERT(principalID != NULL);
ODKERB_PARAM_ASSERT(out != 0);
*out = NULL;
if (odkerb_configure_search_node() != 0)
goto failure;
CFStringGetCString(principalID, principal, sizeof(principal), kCFStringEncodingUTF8);
mbrErr = mbr_identifier_to_uuid(ID_TYPE_KERBEROS, principal, strlen(principal), user_uuid);
if (mbrErr != 0) {
ODKERB_LOG_CFSTRING(LOG_DEBUG, "No UUID found for principal by mbr_identifier_to_uuid ID_TYPE_KERBEROS", principalID);
goto failure;
}
uuid_unparse_upper(user_uuid, uuidStr);
CFStringRef cfUserUUID = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s"), uuidStr);
cfQueryRef = ODQueryCreateWithNode(kCFAllocatorDefault,
gSearchNode,
kODRecordTypeUsers,
kODAttributeTypeGUID,
kODMatchEqualTo,
cfUserUUID,
kODAttributeTypeRecordName,
1, &cfError);
if (cfQueryRef == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_ERR, "Unable to query the search node", cfError);
goto failure;
}
cfUserRecords = ODQueryCopyResults(cfQueryRef, false, &cfError);
if (cfUserRecords == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_ERR, "Unable to find user record", cfError);
goto failure;
}
else if (CFArrayGetCount(cfUserRecords) == 0) {
ODKERB_LOG_CFSTRING(LOG_INFO, "Unable to find user record", cfUserUUID);
goto failure;
}
ODRecordRef cfUserRecord = (ODRecordRef)CFArrayGetValueAtIndex(cfUserRecords, 0);
*out = (ODRecordRef)CFRetain(cfUserRecord);
retval = 0;
failure:
if (cfError != NULL)
odkerb_possibly_reset_search_node(cfError);
CF_SAFE_RELEASE(cfError);
CF_SAFE_RELEASE(cfAltSecurityIdentity);
CF_SAFE_RELEASE(cfQueryRef);
CF_SAFE_RELEASE(cfUserRecords);
return retval;
}
int
odkerb_copy_search_node_with_config_record_name(CFStringRef configRecordName, ODNodeRef *out)
{
static CFTypeRef cfVals[2];
static CFArrayRef cfReqAttrs = NULL;
int retval = -1;
ODRecordRef cfConfigRecord = NULL;
CFArrayRef cfOriginalNodeNames = NULL;
CFErrorRef cfError = NULL;
ODKERB_PARAM_ASSERT(configRecordName != NULL);
ODKERB_PARAM_ASSERT(out != 0);
*out = NULL;
if (odkerb_configure_search_node() != 0)
goto failure;
if (cfReqAttrs == NULL) {
cfVals[0] = kODAttributeTypeOriginalNodeName;
cfVals[1] = kODAttributeTypeIMHandle;
cfReqAttrs = CFArrayCreate(NULL, cfVals, 1, &kCFTypeArrayCallBacks);
}
cfConfigRecord = ODNodeCopyRecord(gSearchNode, kODRecordTypeConfiguration,
configRecordName, cfReqAttrs, &cfError);
if (cfConfigRecord == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_ERR, "Unable to find the configuration record", cfError);
goto failure;
}
cfOriginalNodeNames = ODRecordCopyValues(cfConfigRecord, kODAttributeTypeOriginalNodeName, &cfError);
if (cfOriginalNodeNames == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_ERR, "Unable to find the original node name", cfError);
goto failure;
}
else if (CFArrayGetCount(cfOriginalNodeNames) == 0) {
ODKERB_LOG_CFSTRING(LOG_ERR, "Unable to find the original node name", configRecordName);
goto failure;
}
else if (CFArrayGetCount(cfOriginalNodeNames) > 1) {
ODKERB_LOG_CFSTRING(LOG_DEBUG, "Too many original node names, using the first", configRecordName);
}
CFStringRef cfNodeName = CFArrayGetValueAtIndex(cfOriginalNodeNames, 0);
if (cfNodeName == NULL) {
ODKERB_LOG(LOG_ERR, "Missing original node name");
goto failure;
}
ODNodeRef cfSearchNode2 = ODNodeCreateWithName(kCFAllocatorDefault, kODSessionDefault,
cfNodeName, &cfError);
if (cfSearchNode2 == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_ERR, "Unable to create the search record", cfError);
goto failure;
}
*out = cfSearchNode2;
retval = 0;
failure:
if (cfError != NULL)
odkerb_possibly_reset_search_node(cfError);
CF_SAFE_RELEASE(cfError);
CF_SAFE_RELEASE(cfOriginalNodeNames);
CF_SAFE_RELEASE(cfConfigRecord);
return retval;
}
int
odkerb_copy_user_record_with_short_name(CFStringRef shortName, ODNodeRef searchNode, ODRecordRef *out)
{
static CFTypeRef cfVals[2];
static CFArrayRef cfReqAttrs = NULL;
int retval = -1;
CFErrorRef cfError = NULL;
ODKERB_PARAM_ASSERT(shortName != NULL);
ODKERB_PARAM_ASSERT(searchNode != NULL);
ODKERB_PARAM_ASSERT(out != 0);
*out = NULL;
if (cfReqAttrs == NULL) {
cfVals[0] = kODAttributeTypeOriginalNodeName;
cfVals[1] = kODAttributeTypeIMHandle;
cfReqAttrs = CFArrayCreate(NULL, cfVals, 1, &kCFTypeArrayCallBacks);
}
ODRecordRef cfUserRecord = ODNodeCopyRecord(searchNode, kODRecordTypeUsers,
shortName, cfReqAttrs, &cfError);
if (cfUserRecord == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_DEBUG, "Unable to find the user record", cfError);
goto failure;
}
*out = cfUserRecord;
retval = 0;
failure:
CF_SAFE_RELEASE(cfError);
return retval;
}
int
odkerb_get_im_handle_with_user_record(ODRecordRef userRecord, CFStringRef imType, CFStringRef realm, CFStringRef allegedShortName, char im_handle[], size_t im_handle_size)
{
int retval = -1;
CFArrayRef cfIMHandles = NULL;
CFErrorRef cfError = NULL;
CFMutableArrayRef cfMatches = NULL;
CFStringRef cfRealID = NULL;
int i;
ODKERB_PARAM_ASSERT(userRecord != NULL);
ODKERB_PARAM_ASSERT(allegedShortName != NULL);
ODKERB_PARAM_ASSERT(im_handle != 0);
ODKERB_PARAM_ASSERT(im_handle_size > 0);
*im_handle = '\0';
cfIMHandles = ODRecordCopyValues(userRecord, kODAttributeTypeIMHandle, &cfError);
if (cfIMHandles == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_ERR, "Unable to obtain IM handles", cfError);
goto failure;
}
else if (CFArrayGetCount(cfIMHandles) == 0) {
ODKERB_LOG_CFSTRING(LOG_DEBUG, "No IM handles", allegedShortName);
goto failure;
}
cfMatches = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, cfIMHandles);
if (cfMatches == NULL) {
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
goto failure;
}
for (i = CFArrayGetCount(cfMatches) - 1; i >= 0; --i) {
CFStringRef cfID = CFArrayGetValueAtIndex(cfMatches, i);
if (cfID != NULL
&& odkerb_CFStringHasPrefixWithOptions(cfID, imType, kCFCompareCaseInsensitive)
&& odkerb_CFStringHasSuffixWithOptions(cfID, realm, kCFCompareCaseInsensitive))
continue;
CFArrayRemoveValueAtIndex(cfMatches, i);
}
CFStringRef match = NULL;
if (CFArrayGetCount(cfMatches) == 0) {
ODKERB_LOG_CFSTRING(LOG_INFO, "No IM handles matching type and realm", allegedShortName);
goto failure;
}
else if (CFArrayGetCount(cfMatches) == 1) {
match = CFArrayGetValueAtIndex(cfMatches, 0);
goto found_match;
}
for (i = 0; i < CFArrayGetCount(cfMatches); ++i) {
CFStringRef cfID = CFArrayGetValueAtIndex(cfMatches, i);
if (cfID == NULL)
continue;
CFRange where = CFStringFind(cfID, allegedShortName, kCFCompareCaseInsensitive | kCFCompareDiacriticInsensitive);
if (where.location != kCFNotFound) {
match = cfID;
goto found_match;
}
}
match = CFArrayGetValueAtIndex(cfMatches, 0);
found_match:
assert(match != NULL);
assert(CFStringGetLength(match) > CFStringGetLength(imType));
cfRealID = CFStringCreateWithSubstring(kCFAllocatorDefault, match,
CFRangeMake(CFStringGetLength(imType), CFStringGetLength(match)-CFStringGetLength(imType)));
if (cfRealID == NULL) {
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
goto failure;
}
if (CFStringGetCString(cfRealID, im_handle, im_handle_size-1, kCFStringEncodingUTF8) == FALSE) {
ODKERB_LOG_CFSTRING(LOG_ERR, "Cannot obtain IM handle string", cfRealID);
goto failure;
}
retval = 0;
failure:
CF_SAFE_RELEASE(cfError);
CF_SAFE_RELEASE(cfRealID);
CF_SAFE_RELEASE(cfMatches);
CF_SAFE_RELEASE(cfIMHandles);
return retval;
}
int
odkerb_get_fabricated_im_handle(ODRecordRef userRecord, CFStringRef allegedShortName, CFStringRef realm, char im_handle[], size_t im_handle_size)
{
int retval = -1;
CFArrayRef cfShortNames = NULL;
CFStringRef cfIMHandle = NULL;
CFErrorRef cfError = NULL;
CFStringRef shortName = allegedShortName;
ODKERB_PARAM_ASSERT(allegedShortName != NULL);
ODKERB_PARAM_ASSERT(realm != NULL);
ODKERB_PARAM_ASSERT(im_handle != 0);
ODKERB_PARAM_ASSERT(im_handle_size > 0);
*im_handle = '\0';
if (userRecord != NULL) {
cfShortNames = ODRecordCopyValues(userRecord, kODAttributeTypeRecordName, &cfError);
if (cfShortNames == NULL || cfError != NULL) {
ODKERB_LOG_CFERROR(LOG_DEBUG, "Unable to find the short names", cfError);
}
else if (CFArrayGetCount(cfShortNames) == 0) {
ODKERB_LOG_CFSTRING(LOG_DEBUG, "Unable to find the short names", allegedShortName);
}
else {
CFStringRef cfShortName = CFArrayGetValueAtIndex(cfShortNames, 0);
assert(cfShortName != 0);
shortName = cfShortName;
}
}
cfIMHandle = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@@%@"), shortName, realm);
if (cfIMHandle == NULL) {
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
goto failure;
}
if (CFStringGetCString(cfIMHandle, im_handle, im_handle_size-1, kCFStringEncodingUTF8) == FALSE)
goto failure;
retval = 0;
failure:
CF_SAFE_RELEASE(cfError);
CF_SAFE_RELEASE(cfIMHandle);
CF_SAFE_RELEASE(cfShortNames);
return retval;
}
int
odkerb_get_im_handle(char *service_principal_id, char *realm, char *im_type, char im_handle[], size_t im_handle_size)
{
int retval = -1;
int is_cross_realm;
CFStringRef cfAllegedShortName = NULL;
CFStringRef cfRealm = NULL;
CFStringRef cfPrincipalID = NULL;
ODRecordRef cfUserRecord = NULL;
CFStringRef cfConfigRecordName = NULL;
ODNodeRef cfSearchNode = NULL;
CFStringRef cfIMType = NULL;
ODKERB_PARAM_ASSERT(service_principal_id != 0);
ODKERB_PARAM_ASSERT(realm != 0);
ODKERB_PARAM_ASSERT(im_type != 0);
ODKERB_PARAM_ASSERT(im_handle != 0);
ODKERB_PARAM_ASSERT(im_handle_size > 0);
*im_handle = '\0';
cfAllegedShortName = odkerb_create_short_name(service_principal_id);
if (cfAllegedShortName == NULL) {
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
goto failure;
}
cfRealm = CFStringCreateWithCString(kCFAllocatorDefault, realm, kCFStringEncodingUTF8);
if (cfRealm == NULL) {
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
goto failure;
}
is_cross_realm = odkerb_has_foreign_realm(service_principal_id);
if (is_cross_realm) {
cfPrincipalID = odkerb_create_alleged_principal_id(service_principal_id);
}
else {
cfPrincipalID = CFStringCreateWithCString(kCFAllocatorDefault, service_principal_id, kCFStringEncodingUTF8);
if (cfPrincipalID == 0)
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
}
if (cfPrincipalID == 0)
goto failure;
if (odkerb_copy_user_record_with_alt_security_identity(cfPrincipalID, &cfUserRecord) != 0) {
cfConfigRecordName = odkerb_create_config_record_name(cfPrincipalID);
if (odkerb_copy_search_node_with_config_record_name(cfConfigRecordName, &cfSearchNode) != 0)
goto failure;
if (odkerb_copy_user_record_with_short_name(cfAllegedShortName, cfSearchNode, &cfUserRecord) != 0)
goto failure;
}
cfIMType = CFStringCreateWithCString(kCFAllocatorDefault, im_type, kCFStringEncodingUTF8);
if (cfIMType == NULL) {
ODKERB_LOG_ERRNO(LOG_ERR, ENOMEM);
goto failure;
}
if (odkerb_get_im_handle_with_user_record(cfUserRecord, cfIMType, cfRealm, cfAllegedShortName, im_handle, im_handle_size) != 0)
goto failure;
retval = 0;
failure:
if (retval != 0) {
if (is_cross_realm) {
ODKERB_LOG_CFSTRING(LOG_WARNING, "Unable to construct IM handle for cross-realm user", cfPrincipalID);
}
else {
if (odkerb_get_fabricated_im_handle(cfUserRecord, cfAllegedShortName, cfRealm, im_handle, im_handle_size) == 0)
retval = 0;
}
}
CF_SAFE_RELEASE(cfIMType);
CF_SAFE_RELEASE(cfUserRecord);
CF_SAFE_RELEASE(cfSearchNode);
CF_SAFE_RELEASE(cfConfigRecordName);
CF_SAFE_RELEASE(cfPrincipalID);
CF_SAFE_RELEASE(cfRealm);
CF_SAFE_RELEASE(cfAllegedShortName);
return retval;
}