18f55a568SDoug Rabson /*- 28f55a568SDoug Rabson * Copyright (c) 2008 Doug Rabson 38f55a568SDoug Rabson * All rights reserved. 48f55a568SDoug Rabson * 58f55a568SDoug Rabson * Redistribution and use in source and binary forms, with or without 68f55a568SDoug Rabson * modification, are permitted provided that the following conditions 78f55a568SDoug Rabson * are met: 88f55a568SDoug Rabson * 1. Redistributions of source code must retain the above copyright 98f55a568SDoug Rabson * notice, this list of conditions and the following disclaimer. 108f55a568SDoug Rabson * 2. Redistributions in binary form must reproduce the above copyright 118f55a568SDoug Rabson * notice, this list of conditions and the following disclaimer in the 128f55a568SDoug Rabson * documentation and/or other materials provided with the distribution. 138f55a568SDoug Rabson * 148f55a568SDoug Rabson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 158f55a568SDoug Rabson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 168f55a568SDoug Rabson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 178f55a568SDoug Rabson * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 188f55a568SDoug Rabson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 198f55a568SDoug Rabson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 208f55a568SDoug Rabson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 218f55a568SDoug Rabson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 228f55a568SDoug Rabson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 238f55a568SDoug Rabson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 248f55a568SDoug Rabson * SUCH DAMAGE. 258f55a568SDoug Rabson * 268f55a568SDoug Rabson * $FreeBSD$ 278f55a568SDoug Rabson */ 288f55a568SDoug Rabson 298f55a568SDoug Rabson #include <ctype.h> 308f55a568SDoug Rabson #include <stdio.h> 318f55a568SDoug Rabson #include <string.h> 328f55a568SDoug Rabson #include <stdlib.h> 338f55a568SDoug Rabson #include <errno.h> 348f55a568SDoug Rabson #include <sys/queue.h> 358f55a568SDoug Rabson #include <rpc/rpc.h> 368f55a568SDoug Rabson #include <rpc/rpcsec_gss.h> 378f55a568SDoug Rabson 388f55a568SDoug Rabson #include "rpcsec_gss_int.h" 398f55a568SDoug Rabson 408f55a568SDoug Rabson #ifndef _PATH_GSS_MECH 418f55a568SDoug Rabson #define _PATH_GSS_MECH "/etc/gss/mech" 428f55a568SDoug Rabson #endif 438f55a568SDoug Rabson 448f55a568SDoug Rabson #ifndef _PATH_GSS_QOP 458f55a568SDoug Rabson #define _PATH_GSS_QOP "/etc/gss/qop" 468f55a568SDoug Rabson #endif 478f55a568SDoug Rabson 488f55a568SDoug Rabson struct mech_info { 498f55a568SDoug Rabson SLIST_ENTRY(mech_info) link; 508f55a568SDoug Rabson char *name; 518f55a568SDoug Rabson gss_OID_desc oid; 528f55a568SDoug Rabson const char **qops; 538f55a568SDoug Rabson char *lib; 548f55a568SDoug Rabson char *kobj; 558f55a568SDoug Rabson }; 568f55a568SDoug Rabson SLIST_HEAD(mech_info_list, mech_info); 578f55a568SDoug Rabson 5813e403fdSAntoine Brodin static struct mech_info_list mechs = SLIST_HEAD_INITIALIZER(mechs); 598f55a568SDoug Rabson static const char **mech_names; 608f55a568SDoug Rabson 618f55a568SDoug Rabson struct qop_info { 628f55a568SDoug Rabson SLIST_ENTRY(qop_info) link; 638f55a568SDoug Rabson char *name; 648f55a568SDoug Rabson char* mech; 658f55a568SDoug Rabson u_int qop; 668f55a568SDoug Rabson }; 678f55a568SDoug Rabson SLIST_HEAD(qop_info_list, qop_info); 688f55a568SDoug Rabson 6913e403fdSAntoine Brodin static struct qop_info_list qops = SLIST_HEAD_INITIALIZER(qops); 708f55a568SDoug Rabson 718f55a568SDoug Rabson static int 728f55a568SDoug Rabson _rpc_gss_string_to_oid(const char* s, gss_OID oid) 738f55a568SDoug Rabson { 748f55a568SDoug Rabson int number_count, i, j; 758f55a568SDoug Rabson int byte_count; 768f55a568SDoug Rabson const char *p, *q; 778f55a568SDoug Rabson char *res; 788f55a568SDoug Rabson 798f55a568SDoug Rabson /* 808f55a568SDoug Rabson * First figure out how many numbers in the oid, then 818f55a568SDoug Rabson * calculate the compiled oid size. 828f55a568SDoug Rabson */ 838f55a568SDoug Rabson number_count = 0; 848f55a568SDoug Rabson for (p = s; p; p = q) { 858f55a568SDoug Rabson q = strchr(p, '.'); 868f55a568SDoug Rabson if (q) q = q + 1; 878f55a568SDoug Rabson number_count++; 888f55a568SDoug Rabson } 898f55a568SDoug Rabson 908f55a568SDoug Rabson /* 918f55a568SDoug Rabson * The first two numbers are in the first byte and each 928f55a568SDoug Rabson * subsequent number is encoded in a variable byte sequence. 938f55a568SDoug Rabson */ 948f55a568SDoug Rabson if (number_count < 2) 958f55a568SDoug Rabson return (EINVAL); 968f55a568SDoug Rabson 978f55a568SDoug Rabson /* 988f55a568SDoug Rabson * We do this in two passes. The first pass, we just figure 998f55a568SDoug Rabson * out the size. Second time around, we actually encode the 1008f55a568SDoug Rabson * number. 1018f55a568SDoug Rabson */ 1028f55a568SDoug Rabson res = 0; 1038f55a568SDoug Rabson for (i = 0; i < 2; i++) { 1048f55a568SDoug Rabson byte_count = 0; 1058f55a568SDoug Rabson for (p = s, j = 0; p; p = q, j++) { 1068f55a568SDoug Rabson u_int number = 0; 1078f55a568SDoug Rabson 1088f55a568SDoug Rabson /* 1098f55a568SDoug Rabson * Find the end of this number. 1108f55a568SDoug Rabson */ 1118f55a568SDoug Rabson q = strchr(p, '.'); 1128f55a568SDoug Rabson if (q) q = q + 1; 1138f55a568SDoug Rabson 1148f55a568SDoug Rabson /* 1158f55a568SDoug Rabson * Read the number of of the string. Don't 1168f55a568SDoug Rabson * bother with anything except base ten. 1178f55a568SDoug Rabson */ 1188f55a568SDoug Rabson while (*p && *p != '.') { 1198f55a568SDoug Rabson number = 10 * number + (*p - '0'); 1208f55a568SDoug Rabson p++; 1218f55a568SDoug Rabson } 1228f55a568SDoug Rabson 1238f55a568SDoug Rabson /* 1248f55a568SDoug Rabson * Encode the number. The first two numbers 1258f55a568SDoug Rabson * are packed into the first byte. Subsequent 1268f55a568SDoug Rabson * numbers are encoded in bytes seven bits at 1278f55a568SDoug Rabson * a time with the last byte having the high 1288f55a568SDoug Rabson * bit set. 1298f55a568SDoug Rabson */ 1308f55a568SDoug Rabson if (j == 0) { 1318f55a568SDoug Rabson if (res) 1328f55a568SDoug Rabson *res = number * 40; 1338f55a568SDoug Rabson } else if (j == 1) { 1348f55a568SDoug Rabson if (res) { 1358f55a568SDoug Rabson *res += number; 1368f55a568SDoug Rabson res++; 1378f55a568SDoug Rabson } 1388f55a568SDoug Rabson byte_count++; 1398f55a568SDoug Rabson } else if (j >= 2) { 1408f55a568SDoug Rabson /* 1418f55a568SDoug Rabson * The number is encoded in seven bit chunks. 1428f55a568SDoug Rabson */ 1438f55a568SDoug Rabson u_int t; 1448f55a568SDoug Rabson int bytes; 1458f55a568SDoug Rabson 1468f55a568SDoug Rabson bytes = 0; 1478f55a568SDoug Rabson for (t = number; t; t >>= 7) 1488f55a568SDoug Rabson bytes++; 1498f55a568SDoug Rabson if (bytes == 0) bytes = 1; 1508f55a568SDoug Rabson while (bytes) { 1518f55a568SDoug Rabson if (res) { 1528f55a568SDoug Rabson int bit = 7*(bytes-1); 1538f55a568SDoug Rabson 1548f55a568SDoug Rabson *res = (number >> bit) & 0x7f; 1558f55a568SDoug Rabson if (bytes != 1) 1568f55a568SDoug Rabson *res |= 0x80; 1578f55a568SDoug Rabson res++; 1588f55a568SDoug Rabson } 1598f55a568SDoug Rabson byte_count++; 1608f55a568SDoug Rabson bytes--; 1618f55a568SDoug Rabson } 1628f55a568SDoug Rabson } 1638f55a568SDoug Rabson } 1648f55a568SDoug Rabson if (!res) { 1658f55a568SDoug Rabson res = malloc(byte_count); 1668f55a568SDoug Rabson if (!res) 1678f55a568SDoug Rabson return (ENOMEM); 1688f55a568SDoug Rabson oid->length = byte_count; 1698f55a568SDoug Rabson oid->elements = res; 1708f55a568SDoug Rabson } 1718f55a568SDoug Rabson } 1728f55a568SDoug Rabson 1738f55a568SDoug Rabson return (0); 1748f55a568SDoug Rabson } 1758f55a568SDoug Rabson 1768f55a568SDoug Rabson static void 1778f55a568SDoug Rabson _rpc_gss_load_mech(void) 1788f55a568SDoug Rabson { 1798f55a568SDoug Rabson FILE *fp; 1808f55a568SDoug Rabson char buf[256]; 1818f55a568SDoug Rabson char *p; 1828f55a568SDoug Rabson char *name, *oid, *lib, *kobj; 1838f55a568SDoug Rabson struct mech_info *info; 1848f55a568SDoug Rabson int count; 1858f55a568SDoug Rabson const char **pp; 1868f55a568SDoug Rabson 1878f55a568SDoug Rabson if (SLIST_FIRST(&mechs)) 1888f55a568SDoug Rabson return; 1898f55a568SDoug Rabson 1908f55a568SDoug Rabson fp = fopen(_PATH_GSS_MECH, "r"); 1918f55a568SDoug Rabson if (!fp) 1928f55a568SDoug Rabson return; 1938f55a568SDoug Rabson 1948f55a568SDoug Rabson count = 0; 1958f55a568SDoug Rabson while (fgets(buf, sizeof(buf), fp)) { 1968f55a568SDoug Rabson if (*buf == '#') 1978f55a568SDoug Rabson continue; 1988f55a568SDoug Rabson p = buf; 1998f55a568SDoug Rabson name = strsep(&p, "\t\n "); 2008f55a568SDoug Rabson if (p) while (isspace(*p)) p++; 2018f55a568SDoug Rabson oid = strsep(&p, "\t\n "); 2028f55a568SDoug Rabson if (p) while (isspace(*p)) p++; 2038f55a568SDoug Rabson lib = strsep(&p, "\t\n "); 2048f55a568SDoug Rabson if (p) while (isspace(*p)) p++; 2058f55a568SDoug Rabson kobj = strsep(&p, "\t\n "); 2068f55a568SDoug Rabson if (!name || !oid || !lib || !kobj) 2078f55a568SDoug Rabson continue; 2088f55a568SDoug Rabson 2098f55a568SDoug Rabson info = malloc(sizeof(struct mech_info)); 2108f55a568SDoug Rabson if (!info) 2118f55a568SDoug Rabson break; 2128f55a568SDoug Rabson if (_rpc_gss_string_to_oid(oid, &info->oid)) { 2138f55a568SDoug Rabson free(info); 2148f55a568SDoug Rabson continue; 2158f55a568SDoug Rabson } 2168f55a568SDoug Rabson info->name = strdup(name); 2178f55a568SDoug Rabson info->qops = NULL; 2188f55a568SDoug Rabson info->lib = strdup(lib); 2198f55a568SDoug Rabson info->kobj = strdup(kobj); 2208f55a568SDoug Rabson SLIST_INSERT_HEAD(&mechs, info, link); 2218f55a568SDoug Rabson count++; 2228f55a568SDoug Rabson } 2238f55a568SDoug Rabson fclose(fp); 2248f55a568SDoug Rabson 2258f55a568SDoug Rabson mech_names = malloc((count + 1) * sizeof(char*)); 2268f55a568SDoug Rabson pp = mech_names; 2278f55a568SDoug Rabson SLIST_FOREACH(info, &mechs, link) { 2288f55a568SDoug Rabson *pp++ = info->name; 2298f55a568SDoug Rabson } 2308f55a568SDoug Rabson *pp = NULL; 2318f55a568SDoug Rabson } 2328f55a568SDoug Rabson 2338f55a568SDoug Rabson static void 2348f55a568SDoug Rabson _rpc_gss_load_qop(void) 2358f55a568SDoug Rabson { 2368f55a568SDoug Rabson FILE *fp; 2378f55a568SDoug Rabson char buf[256]; 2388f55a568SDoug Rabson char *p; 2398f55a568SDoug Rabson char *name, *num, *mech; 2408f55a568SDoug Rabson struct mech_info *minfo; 2418f55a568SDoug Rabson struct qop_info *info; 2428f55a568SDoug Rabson int count; 2438f55a568SDoug Rabson const char **mech_qops; 2448f55a568SDoug Rabson const char **pp; 2458f55a568SDoug Rabson 2468f55a568SDoug Rabson if (SLIST_FIRST(&qops)) 2478f55a568SDoug Rabson return; 2488f55a568SDoug Rabson 2498f55a568SDoug Rabson fp = fopen(_PATH_GSS_QOP, "r"); 2508f55a568SDoug Rabson if (!fp) 2518f55a568SDoug Rabson return; 2528f55a568SDoug Rabson 2538f55a568SDoug Rabson while (fgets(buf, sizeof(buf), fp)) { 2548f55a568SDoug Rabson if (*buf == '#') 2558f55a568SDoug Rabson continue; 2568f55a568SDoug Rabson p = buf; 2578f55a568SDoug Rabson name = strsep(&p, "\t\n "); 2588f55a568SDoug Rabson if (p) while (isspace(*p)) p++; 2598f55a568SDoug Rabson num = strsep(&p, "\t\n "); 2608f55a568SDoug Rabson if (p) while (isspace(*p)) p++; 2618f55a568SDoug Rabson mech = strsep(&p, "\t\n "); 2628f55a568SDoug Rabson if (!name || !num || !mech) 2638f55a568SDoug Rabson continue; 2648f55a568SDoug Rabson 2658f55a568SDoug Rabson info = malloc(sizeof(struct qop_info)); 2668f55a568SDoug Rabson if (!info) 2678f55a568SDoug Rabson break; 2688f55a568SDoug Rabson info->name = strdup(name); 2698f55a568SDoug Rabson info->qop = strtoul(name, 0, 0); 2708f55a568SDoug Rabson info->mech = strdup(mech); 2718f55a568SDoug Rabson SLIST_INSERT_HEAD(&qops, info, link); 2728f55a568SDoug Rabson } 2738f55a568SDoug Rabson fclose(fp); 2748f55a568SDoug Rabson 2758f55a568SDoug Rabson /* 2768f55a568SDoug Rabson * Compile lists of qops for each mechanism. 2778f55a568SDoug Rabson */ 2788f55a568SDoug Rabson SLIST_FOREACH(minfo, &mechs, link) { 2798f55a568SDoug Rabson count = 0; 2808f55a568SDoug Rabson SLIST_FOREACH(info, &qops, link) { 2818f55a568SDoug Rabson if (strcmp(info->mech, minfo->name) == 0) 2828f55a568SDoug Rabson count++; 2838f55a568SDoug Rabson } 2848f55a568SDoug Rabson mech_qops = malloc((count + 1) * sizeof(char*)); 2858f55a568SDoug Rabson pp = mech_qops; 2868f55a568SDoug Rabson SLIST_FOREACH(info, &qops, link) { 2878f55a568SDoug Rabson if (strcmp(info->mech, minfo->name) == 0) 2888f55a568SDoug Rabson *pp++ = info->name; 2898f55a568SDoug Rabson } 2908f55a568SDoug Rabson *pp = NULL; 2918f55a568SDoug Rabson minfo->qops = mech_qops; 2928f55a568SDoug Rabson } 2938f55a568SDoug Rabson } 2948f55a568SDoug Rabson 2958f55a568SDoug Rabson bool_t 2968f55a568SDoug Rabson rpc_gss_mech_to_oid(const char *mech, gss_OID *oid_ret) 2978f55a568SDoug Rabson { 2988f55a568SDoug Rabson struct mech_info *info; 2998f55a568SDoug Rabson 3008f55a568SDoug Rabson _rpc_gss_load_mech(); 3018f55a568SDoug Rabson SLIST_FOREACH(info, &mechs, link) { 3028f55a568SDoug Rabson if (!strcmp(info->name, mech)) { 3038f55a568SDoug Rabson *oid_ret = &info->oid; 3048f55a568SDoug Rabson return (TRUE); 3058f55a568SDoug Rabson } 3068f55a568SDoug Rabson } 3078f55a568SDoug Rabson _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT); 3088f55a568SDoug Rabson return (FALSE); 3098f55a568SDoug Rabson } 3108f55a568SDoug Rabson 3118f55a568SDoug Rabson bool_t 3128f55a568SDoug Rabson rpc_gss_oid_to_mech(gss_OID oid, const char **mech_ret) 3138f55a568SDoug Rabson { 3148f55a568SDoug Rabson struct mech_info *info; 3158f55a568SDoug Rabson 3168f55a568SDoug Rabson _rpc_gss_load_mech(); 3178f55a568SDoug Rabson SLIST_FOREACH(info, &mechs, link) { 3188f55a568SDoug Rabson if (oid->length == info->oid.length 3198f55a568SDoug Rabson && !memcmp(oid->elements, info->oid.elements, 3208f55a568SDoug Rabson oid->length)) { 3218f55a568SDoug Rabson *mech_ret = info->name; 3228f55a568SDoug Rabson return (TRUE); 3238f55a568SDoug Rabson } 3248f55a568SDoug Rabson } 3258f55a568SDoug Rabson _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT); 3268f55a568SDoug Rabson return (FALSE); 3278f55a568SDoug Rabson } 3288f55a568SDoug Rabson 3298f55a568SDoug Rabson bool_t 3308f55a568SDoug Rabson rpc_gss_qop_to_num(const char *qop, const char *mech, u_int *num_ret) 3318f55a568SDoug Rabson { 3328f55a568SDoug Rabson struct qop_info *info; 3338f55a568SDoug Rabson 3348f55a568SDoug Rabson _rpc_gss_load_qop(); 3358f55a568SDoug Rabson SLIST_FOREACH(info, &qops, link) { 3368f55a568SDoug Rabson if (strcmp(info->name, qop) == 0 3378f55a568SDoug Rabson && strcmp(info->mech, mech) == 0) { 3388f55a568SDoug Rabson *num_ret = info->qop; 3398f55a568SDoug Rabson return (TRUE); 3408f55a568SDoug Rabson } 3418f55a568SDoug Rabson } 3428f55a568SDoug Rabson _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT); 3438f55a568SDoug Rabson return (FALSE); 3448f55a568SDoug Rabson } 3458f55a568SDoug Rabson 3468f55a568SDoug Rabson const char * 3478f55a568SDoug Rabson _rpc_gss_num_to_qop(const char *mech, u_int num) 3488f55a568SDoug Rabson { 3498f55a568SDoug Rabson struct qop_info *info; 3508f55a568SDoug Rabson 3518f55a568SDoug Rabson if (num == GSS_C_QOP_DEFAULT) 3528f55a568SDoug Rabson return "default"; 3538f55a568SDoug Rabson 3548f55a568SDoug Rabson _rpc_gss_load_qop(); 3558f55a568SDoug Rabson SLIST_FOREACH(info, &qops, link) { 3568f55a568SDoug Rabson if (info->qop == num && strcmp(info->mech, mech) == 0) { 3578f55a568SDoug Rabson return (info->name); 3588f55a568SDoug Rabson } 3598f55a568SDoug Rabson } 3608f55a568SDoug Rabson return (NULL); 3618f55a568SDoug Rabson } 3628f55a568SDoug Rabson 3638f55a568SDoug Rabson const char ** 3648f55a568SDoug Rabson rpc_gss_get_mechanisms(void) 3658f55a568SDoug Rabson { 3668f55a568SDoug Rabson 3678f55a568SDoug Rabson _rpc_gss_load_mech(); 3688f55a568SDoug Rabson return (mech_names); 3698f55a568SDoug Rabson } 3708f55a568SDoug Rabson 3718f55a568SDoug Rabson const char ** 3728f55a568SDoug Rabson rpc_gss_get_mech_info(const char *mech, rpc_gss_service_t *service) 3738f55a568SDoug Rabson { 3748f55a568SDoug Rabson struct mech_info *info; 3758f55a568SDoug Rabson 3768f55a568SDoug Rabson _rpc_gss_load_mech(); 3778f55a568SDoug Rabson _rpc_gss_load_qop(); 3788f55a568SDoug Rabson SLIST_FOREACH(info, &mechs, link) { 3798f55a568SDoug Rabson if (!strcmp(mech, info->name)) { 3808f55a568SDoug Rabson /* 3818f55a568SDoug Rabson * I'm not sure what to do with service 3828f55a568SDoug Rabson * here. The Solaris manpages are not clear on 3838f55a568SDoug Rabson * the subject and the OpenSolaris code just 3848f55a568SDoug Rabson * sets it to rpc_gss_svc_privacy 3858f55a568SDoug Rabson * unconditionally with a comment noting that 3868f55a568SDoug Rabson * it is bogus. 3878f55a568SDoug Rabson */ 3888f55a568SDoug Rabson *service = rpc_gss_svc_privacy; 3898f55a568SDoug Rabson return info->qops; 3908f55a568SDoug Rabson } 3918f55a568SDoug Rabson } 3928f55a568SDoug Rabson 3938f55a568SDoug Rabson _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOENT); 3948f55a568SDoug Rabson return (NULL); 3958f55a568SDoug Rabson } 3968f55a568SDoug Rabson 3978f55a568SDoug Rabson bool_t 3988f55a568SDoug Rabson rpc_gss_get_versions(u_int *vers_hi, u_int *vers_lo) 3998f55a568SDoug Rabson { 4008f55a568SDoug Rabson 4018f55a568SDoug Rabson *vers_hi = 1; 4028f55a568SDoug Rabson *vers_lo = 1; 4038f55a568SDoug Rabson return (TRUE); 4048f55a568SDoug Rabson } 4058f55a568SDoug Rabson 4068f55a568SDoug Rabson bool_t 4078f55a568SDoug Rabson rpc_gss_is_installed(const char *mech) 4088f55a568SDoug Rabson { 4098f55a568SDoug Rabson struct mech_info *info; 4108f55a568SDoug Rabson 4118f55a568SDoug Rabson _rpc_gss_load_mech(); 4128f55a568SDoug Rabson SLIST_FOREACH(info, &mechs, link) 4138f55a568SDoug Rabson if (!strcmp(mech, info->name)) 4148f55a568SDoug Rabson return (TRUE); 4158f55a568SDoug Rabson return (FALSE); 4168f55a568SDoug Rabson } 4178f55a568SDoug Rabson 418