[0001]
[0002]
[0003]
[0004]
[0005]
[0006]
[0007]
[0008]
[0009]
[0010]
[0011]
[0012]
[0013]
[0014]
[0015]
[0016]
[0017]
[0018]
[0019]
[0020]
[0021]
[0022]
[0023]
[0024]
[0025]
[0026]
[0027]
[0028]
[0029]
[0030]
[0031]
[0032]
[0033]
[0034]
[0035]
[0036]
[0037]
[0038]
[0039]
[0040]
[0041]
[0042]
[0043]
[0044]
[0045]
[0046]
[0047]
[0048]
[0049]
[0050]
[0051]
[0052]
[0053]
[0054]
[0055]
[0056]
[0057]
[0058]
[0059]
[0060]
[0061]
[0062]
[0063]
[0064]
[0065]
[0066]
[0067]
[0068]
[0069]
[0070]
[0071]
[0072]
[0073]
[0074]
[0075]
[0076]
[0077]
[0078]
[0079]
[0080]
[0081]
[0082]
[0083]
[0084]
[0085]
[0086]
[0087]
[0088]
[0089]
[0090]
[0091]
[0092]
[0093]
[0094]
[0095]
[0096]
[0097]
[0098]
[0099]
[0100]
[0101]
[0102]
[0103]
[0104]
[0105]
[0106]
[0107]
[0108]
[0109]
[0110]
[0111]
[0112]
[0113]
[0114]
[0115]
[0116]
[0117]
[0118]
[0119]
[0120]
[0121]
[0122]
[0123]
[0124]
[0125]
[0126]
[0127]
[0128]
[0129]
[0130]
[0131]
[0132]
[0133]
[0134]
[0135]
[0136]
[0137]
[0138]
[0139]
[0140]
[0141]
[0142]
[0143]
[0144]
[0145]
[0146]
[0147]
[0148]
[0149]
[0150]
[0151]
[0152]
[0153]
[0154]
[0155]
[0156]
[0157]
[0158]
[0159]
[0160]
[0161]
[0162]
[0163]
[0164]
[0165]
[0166]
[0167]
[0168]
[0169]
[0170]
[0171]
[0172]
[0173]
[0174]
[0175]
[0176]
[0177]
[0178]
[0179]
[0180]
[0181]
[0182]
[0183]
[0184]
[0185]
[0186]
[0187]
[0188]
[0189]
[0190]
[0191]
[0192]
[0193]
[0194]
[0195]
[0196]
[0197]
[0198]
[0199]
[0200]
[0201]
[0202]
[0203]
[0204]
[0205]
[0206]
[0207]
[0208]
[0209]
[0210]
[0211]
[0212]
[0213]
[0214]
[0215]
[0216]
[0217]
[0218]
[0219]
[0220]
[0221]
[0222]
[0223]
[0224]
[0225]
[0226]
[0227]
[0228]
[0229]
[0230]
[0231]
[0232]
[0233]
[0234]
[0235]
[0236]
[0237]
[0238]
[0239]
[0240]
[0241]
[0242]
[0243]
[0244]
[0245]
[0246]
[0247]
[0248]
[0249]
[0250]
[0251]
[0252]
[0253]
[0254]
[0255]
[0256]
[0257]
[0258]
[0259]
[0260]
[0261]
[0262]
[0263]
[0264]
[0265]
[0266]
[0267]
[0268]
[0269]
[0270]
[0271]
[0272]
[0273]
[0274]
[0275]
[0276]
[0277]
[0278]
[0279]
[0280]
[0281]
[0282]
[0283]
[0284]
[0285]
[0286]
[0287]
[0288]
[0289]
[0290]
[0291]
[0292]
[0293]
[0294]
[0295]
[0296]
[0297]
[0298]
[0299]
[0300]
[0301]
[0302]
[0303]
[0304]
[0305]
[0306]
[0307]
[0308]
[0309]
[0310]
[0311]
[0312]
[0313]
[0314]
[0315]
[0316]
[0317]
[0318]
[0319]
[0320]
[0321]
[0322]
[0323]
[0324]
[0325]
[0326]
[0327]
[0328]
[0329]
[0330]
[0331]
[0332]
[0333]
[0334]
[0335]
[0336]
[0337]
[0338]
[0339]
[0340]
[0341]
[0342]
[0343]
[0344]
[0345]
[0346]
[0347]
[0348]
[0349]
[0350]
[0351]
[0352]
[0353]
[0354]
[0355]
[0356]
[0357]
[0358]
[0359]
[0360]
[0361]
[0362]
[0363]
[0364]
[0365]
[0366]
[0367]
[0368]
[0369]
[0370]
[0371]
[0372]
[0373]
[0374]
[0375]
[0376]
[0377]
[0378]
[0379]
[0380]
[0381]
[0382]
[0383]
[0384]
[0385]
[0386]
[0387]
[0388]
[0389]
[0390]
[0391]
[0392]
[0393]
[0394]
[0395]
[0396]
[0397]
[0398]
[0399]
[0400]
[0401]
[0402]
[0403]
[0404]
[0405]
[0406]
[0407]
[0408]
[0409]
[0410]
[0411]
[0412]
[0413]
[0414]
[0415]
[0416]
[0417]
[0418]
[0419]
[0420]
[0421]
[0422]
[0423]
[0424]
[0425]
[0426]
[0427]
[0428]
[0429]
[0430]
[0431]
[0432]
[0433]
[0434]
[0435]
[0436]
[0437]
[0438]
[0439]
[0440]
[0441]
[0442]
[0443]
[0444]
[0445]
[0446]
[0447]
[0448]
[0449]
[0450]
[0451]
[0452]
[0453]
[0454]
[0455]
[0456]
[0457]
[0458]
[0459]
[0460]
[0461]
[0462]
[0463]
[0464]
[0465]
[0466]
[0467]
[0468]
[0469]
[0470]
[0471]
[0472]
[0473]
[0474]
[0475]
[0476]
[0477]
[0478]
[0479]
[0480]
[0481]
[0482]
[0483]
[0484]
[0485]
[0486]
[0487]
[0488]
[0489]
[0490]
[0491]
[0492]
[0493]
[0494]
[0495]
[0496]
[0497]
[0498]
[0499]
[0500]
[0501]
[0502]
[0503]
[0504]
[0505]
[0506]
[0507]
[0508]
[0509]
[0510]
[0511]
[0512]
[0513]
[0514]
[0515]
[0516]
[0517]
[0518]
[0519]
[0520]
[0521]
[0522]
[0523]
[0524]
[0525]
[0526]
[0527]
[0528]
[0529]
[0530]
[0531]
[0532]
[0533]
[0534]
[0535]
[0536]
[0537]
[0538]
[0539]
[0540]
[0541]
[0542]
[0543]
[0544]
[0545]
[0546]
[0547]
[0548]
[0549]
[0550]
[0551]
[0552]
[0553]
[0554]
[0555]
[0556]
[0557]
[0558]
[0559]
[0560]
[0561]
[0562]
[0563]
[0564]
[0565]
[0566]
[0567]
[0568]
[0569]
[0570]
[0571]
[0572]
[0573]
[0574]
[0575]
[0576]
[0577]
[0578]
[0579]
[0580]
[0581]
[0582]
[0583]
[0584]
[0585]
[0586]
[0587]
[0588]
[0589]
[0590]
[0591]
[0592]
[0593]
[0594]
[0595]
[0596]
[0597]
[0598]
[0599]
[0600]
[0601]
[0602]
[0603]
[0604]
[0605]
[0606]
[0607]
[0608]
[0609]
[0610]
[0611]
[0612]
[0613]
[0614]
[0615]
[0616]
[0617]
[0618]
[0619]
[0620]
[0621]
[0622]
[0623]
[0624]
[0625]
[0626]
[0627]
[0628]
[0629]
[0630]
[0631]
[0632]
[0633]
[0634]
[0635]
[0636]
[0637]
[0638]
[0639]
[0640]
[0641]
[0642]
[0643]
[0644]
[0645]
[0646]
[0647]
[0648]
[0649]
[0650]
[0651]
[0652]
[0653]
[0654]
[0655]
[0656]
[0657]
[0658]
[0659]
[0660]
[0661]
[0662]
[0663]
[0664]
[0665]
[0666]
[0667]
[0668]
[0669]
[0670]
[0671]
[0672]
[0673]
[0674]
[0675]
[0676]
[0677]
[0678]
[0679]
[0680]
[0681]
[0682]
[0683]
[0684]
[0685]
[0686]
[0687]
[0688]
[0689]
[0690]
[0691]
[0692]
[0693]
[0694]
[0695]
[0696]
[0697]
[0698]
[0699]
[0700]
[0701]
[0702]
[0703]
[0704]
[0705]
[0706]
[0707]
[0708]
[0709]
[0710]
[0711]
[0712]
[0713]
[0714]
[0715]
[0716]
[0717]
[0718]
[0719]
[0720]
[0721]
[0722]
[0723]
[0724]
[0725]
[0726]
[0727]
[0728]
[0729]
[0730]
[0731]
[0732]
[0733]
[0734]
[0735]
[0736]
[0737]
[0738]
[0739]
[0740]
[0741]
[0742]
[0743]
[0744]
[0745]
[0746]
[0747]
[0748]
[0749]
[0750]
[0751]
[0752]
[0753]
[0754]
[0755]
[0756]
[0757]
[0758]
[0759]
[0760]
[0761]
[0762]
[0763]
[0764]
[0765]
[0766]
[0767]
[0768]
[0769]
[0770]
[0771]
[0772]
[0773]
[0774]
[0775]
[0776]
[0777]
[0778]
[0779]
[0780]
[0781]
[0782]
[0783]
[0784]
[0785]
[0786]
[0787]
[0788]
[0789]
[0790]
[0791]
[0792]
[0793]
[0794]
[0795]
[0796]
[0797]
[0798]
[0799]
[0800]
[0801]
[0802]
[0803]
[0804]
[0805]
[0806]
[0807]
[0808]
[0809]
[0810]
[0811]
[0812]
[0813]
[0814]
[0815]
[0816]
[0817]
[0818]
[0819]
[0820]
[0821]
[0822]
[0823]
[0824]
[0825]
[0826]
[0827]
[0828]
[0829]
[0830]
[0831]
[0832]
[0833]
[0834]
[0835]
[0836]
[0837]
[0838]
[0839]
[0840]
[0841]
[0842]
[0843]
[0844]
[0845]
[0846]
[0847]
[0848]
[0849]
[0850]
[0851]
[0852]
[0853]
[0854]
[0855]
[0856]
[0857]
[0858]
[0859]
[0860]
[0861]
[0862]
[0863]
[0864]
[0865]
[0866]
[0867]
[0868]
[0869]
[0870]
[0871]
[0872]
[0873]
[0874]
[0875]
[0876]
[0877]
[0878]
[0879]
[0880]
[0881]
[0882]
[0883]
[0884]
[0885]
[0886]
[0887]
[0888]
[0889]
[0890]
[0891]
[0892]
[0893]
[0894]
[0895]
[0896]
[0897]
[0898]
[0899]
[0900]
[0901]
[0902]
[0903]
[0904]
[0905]
[0906]
[0907]
[0908]
[0909]
[0910]
[0911]
[0912]
[0913]
[0914]
[0915]
[0916]
[0917]
[0918]
[0919]
[0920]
[0921]
[0922]
[0923]
[0924]
[0925]
[0926]
[0927]
[0928]
[0929]
[0930]
[0931]
[0932]
[0933]
[0934]
[0935]
[0936]
[0937]
[0938]
[0939]
[0940]
[0941]
[0942]
[0943]
[0944]
[0945]
[0946]
[0947]
[0948]
[0949]
[0950]
[0951]
[0952]
[0953]
[0954]
[0955]
[0956]
[0957]
[0958]
[0959]
[0960]
[0961]
[0962]
[0963]
[0964]
[0965]
[0966]
[0967]
[0968]
[0969]
[0970]
[0971]
[0972]
[0973]
[0974]
[0975]
[0976]
[0977]
[0978]
[0979]
[0980]
[0981]
[0982]
[0983]
[0984]
[0985]
[0986]
[0987]
[0988]
[0989]
[0990]
[0991]
[0992]
[0993]
[0994]
[0995]
[0996]
[0997]
[0998]
[0999]
[1000]
[1001]
[1002]
[1003]
[1004]
[1005]
[1006]
[1007]
[1008]
[1009]
[1010]
[1011]
[1012]
[1013]
[1014]
[1015]
[1016]
[1017]
[1018]
[1019]
[1020]
[1021]
[1022]
[1023]
[1024]
[1025]
[1026]
[1027]
[1028]
[1029]
[1030]
[1031]
[1032]
[1033]
[1034]
[1035]
[1036]
[1037]
[1038]
[1039]
[1040]
[1041]
[1042]
[1043]
[1044]
[1045]
[1046]
[1047]
[1048]
[1049]
[1050]
[1051]
[1052]
[1053]
[1054]
[1055]
[1056]
[1057]
[1058]
[1059]
[1060]
[1061]
[1062]
[1063]
[1064]
[1065]
[1066]
[1067]
[1068]
[1069]
[1070]
[1071]
[1072]
[1073]
[1074]
[1075]
[1076]
[1077]
[1078]
[1079]
[1080]
[1081]
[1082]
[1083]
[1084]
[1085]
[1086]
[1087]
[1088]
[1089]
[1090]
[1091]
[1092]
[1093]
[1094]
[1095]
[1096]
[1097]
[1098]
[1099]
[1100]
[1101]
[1102]
[1103]
[1104]
[1105]
[1106]
[1107]
[1108]
[1109]
[1110]
[1111]
[1112]
[1113]
[1114]
[1115]
[1116]
[1117]
[1118]
[1119]
[1120]
[1121]
[1122]
[1123]
[1124]
[1125]
[1126]
[1127]
[1128]
[1129]
[1130]
[1131]
[1132]
[1133]
[1134]
[1135]
[1136]
[1137]
[1138]
[1139]
[1140]
[1141]
[1142]
[1143]
[1144]
[1145]
[1146]
[1147]
[1148]
[1149]
[1150]
[1151]
[1152]
[1153]
[1154]
[1155]
[1156]
[1157]
[1158]
[1159]
[1160]
[1161]
[1162]
[1163]
[1164]
[1165]
[1166]
[1167]
[1168]
[1169]
[1170]
[1171]
[1172]
[1173]
[1174]
[1175]
[1176]
[1177]
[1178]
[1179]
[1180]
[1181]
[1182]
[1183]
[1184]
[1185]
[1186]
[1187]
[1188]
[1189]
[1190]
[1191]
[1192]
[1193]
[1194]
[1195]
[1196]
[1197]
[1198]
[1199]
[1200]
[1201]
[1202]
[1203]
[1204]
[1205]
[1206]
[1207]
[1208]
[1209]
[1210]
[1211]
[1212]
[1213]
[1214]
[1215]
[1216]
[1217]
[1218]
[1219]
[1220]
[1221]
[1222]
[1223]
[1224]
[1225]
[1226]
[1227]
[1228]
[1229]
[1230]
[1231]
[1232]
[1233]
[1234]
[1235]
[1236]
[1237]
[1238]
[1239]
[1240]
[1241]
[1242]
[1243]
[1244]
[1245]
[1246]
[1247]
[1248]
[1249]
[1250]
[1251]
[1252]
[1253]
[1254]
[1255]
[1256]
[1257]
[1258]
[1259]
[1260]
[1261]
[1262]
[1263]
[1264]
[1265]
[1266]
[1267]
[1268]
[1269]
[1270]
[1271]
[1272]
[1273]
[1274]
[1275]
[1276]
[1277]
[1278]
[1279]
[1280]
[1281]
[1282]
[1283]
[1284]
[1285]
[1286]
[1287]
[1288]
[1289]
[1290]
[1291]
[1292]
[1293]
[1294]
[1295]
[1296]
[1297]
[1298]
[1299]
[1300]
[1301]
[1302]
[1303]
[1304]
[1305]
[1306]
[1307]
[1308]
[1309]
[1310]
[1311]
[1312]
[1313]
[1314]
[1315]
[1316]
[1317]
[1318]
[1319]
[1320]
[1321]
[1322]
[1323]
[1324]
[1325]
[1326]
[1327]
[1328]
[1329]
[1330]
[1331]
[1332]
[1333]
[1334]
[1335]
[1336]
[1337]
[1338]
[1339]
[1340]
[1341]
[1342]
[1343]
[1344]
[1345]
[1346]
[1347]
[1348]
[1349]
[1350]
[1351]
[1352]
[1353]
[1354]
[1355]
[1356]
[1357]
[1358]
[1359]
[1360]
[1361]
[1362]
[1363]
[1364]
[1365]
[1366]
[1367]
[1368]
[1369]
[1370]
[1371]
[1372]
[1373]
[1374]
[1375]
[1376]
[1377]
[1378]
[1379]
[1380]
[1381]
[1382]
[1383]
[1384]
[1385]
[1386]
[1387]
[1388]
[1389]
[1390]
[1391]
[1392]
[1393]
[1394]
[1395]
[1396]
[1397]
[1398]
[1399]
[1400]
[1401]
[1402]
[1403]
[1404]
[1405]
[1406]
[1407]
[1408]
[1409]
[1410]
[1411]
[1412]
[1413]
[1414]
[1415]
[1416]
[1417]
[1418]
[1419]
[1420]
[1421]
[1422]
[1423]
[1424]
[1425]
[1426]
[1427]
[1428]
[1429]
[1430]
[1431]
[1432]
[1433]
[1434]
[1435]
[1436]
[1437]
[1438]
[1439]
[1440]
[1441]
[1442]
[1443]
[1444]
[1445]
[1446]
[1447]
[1448]
[1449]
[1450]
[1451]
[1452]
[1453]
[1454]
[1455]
[1456]
[1457]
[1458]
[1459]
[1460]
[1461]
[1462]
[1463]
[1464]
[1465]
[1466]
[1467]
[1468]
[1469]
[1470]
[1471]
[1472]
[1473]
[1474]
[1475]
[1476]
[1477]
[1478]
[1479]
[1480]
[1481]
[1482]
[1483]
[1484]
[1485]
[1486]
[1487]
[1488]
[1489]
[1490]
[1491]
[1492]
[1493]
[1494]
[1495]
[1496]
[1497]
[1498]
[1499]
[1500]
[1501]
[1502]
[1503]
[1504]
[1505]
[1506]
[1507]
[1508]
[1509]
[1510]
[1511]
[1512]
[1513]
[1514]
[1515]
[1516]
[1517]
[1518]
[1519]
[1520]
[1521]
[1522]
[1523]
[1524]
[1525]
[1526]
[1527]
[1528]
[1529]
[1530]
[1531]
[1532]
[1533]
[1534]
[1535]
[1536]
[1537]
[1538]
[1539]
[1540]
[1541]
[1542]
[1543]
[1544]
[1545]
[1546]
[1547]
[1548]
[1549]
[1550]
[1551]
[1552]
[1553]
[1554]
[1555]
[1556]
[1557]
[1558]
[1559]
[1560]
[1561]
[1562]
[1563]
[1564]
[1565]
[1566]
[1567]
[1568]
[1569]
[1570]
[1571]
[1572]
[1573]
[1574]
[1575]
[1576]
[1577]
[1578]
[1579]
[1580]
[1581]
[1582]
[1583]
[1584]
[1585]
[1586]
[1587]
[1588]
[1589]
[1590]
[1591]
[1592]
[1593]
[1594]
[1595]
[1596]
[1597]
[1598]
[1599]
[1600]
[1601]
[1602]
[1603]
[1604]
[1605]
[1606]
[1607]
[1608]
[1609]
[1610]
[1611]
[1612]
[1613]
[1614]
[1615]
[1616]
[1617]
[1618]
[1619]
[1620]
[1621]
[1622]
[1623]
[1624]
[1625]
[1626]
[1627]
[1628]
[1629]
[1630]
[1631]
[1632]
[1633]
[1634]
[1635]
[1636]
[1637]
[1638]
[1639]
[1640]
[1641]
[1642]
[1643]
[1644]
[1645]
[1646]
[1647]
[1648]
[1649]
[1650]
[1651]
[1652]
[1653]
[1654]
[1655]
[1656]
[1657]
[1658]
[1659]
[1660]
[1661]
[1662]
[1663]
/*****************************************************************************/
/*
                               SesolaCert.c

Parse X509 certificates.  See description in SESOLA.C.

https://tools.ietf.org/html/rfc5280
https://zakird.com/2013/10/13/certificate-parsing-with-openssl/
https://stackoverflow.com/questions/22966461/reading-an-othername-value-from-a-subjectaltname-certificate-extension
https://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg


VERSION HISTORY
---------------
02-MAR-2020  MGD  bugfix; SesolaCertExtension() BIO_NOCLOSE memory leak
12-APR-2019  MGD  bugfix; SesolaCertExtension() storage reset
                  bugfix; SesolaCertParseDn() restore from 25-AUG-2015
                    [ru:/CN=<pattern>] allows multiple to be selected between
                      (e.g. "[ru:/CN=user*]", "[ru:/CN=^^\[^/=\]*$]" )
                    [ru:..] escape characters using '\' (especially ']')
20-NOV-2018  MGD  SesolaCertExtension() eliminate need for UPN NID by always
                    coverting the OID to text and then compare the string
26-SEP-2018  MGD  SesolaCertVerifyCallback() moved to here from SESOLA.C
16-JUN-2017  MGD  bugfix; SesolaCertExtension() generate UPN independently
                    for each of pre- and post- OpenSSL 1.1.n
03-AUG-2016  MGD  OpenSSL v1.1.0(-pre6) required code changes including
                    #if (OPENSSL_VERSION_NUMBER < 0x10100000L) compilation
22-OCT-2015  MGD  move from SESOLA.C and refine
                  SesolaCertName() to parse X509 issuer and subject
                  SesolaCertExtension() to parse X509 extensions
                  SesolaCertParseDn() from original SesolaParseCertDn()
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VMS related header files */
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#define SESOLA_REQUIRED
#include "Sesola.h"

#define WASD_MODULE "SESOLACERT"

/***************************************/
#ifdef SESOLA  /* secure sockets layer */
/***************************************/

static struct SesolaCertDnRecStruct
{
   char *name;
   int length;
}
SesolaCertDnRec [] =
{
   { "/C=", 3 },   /* countryName */
   { "/ST=", 4 },  /* stateOrProvinceName */
   { "/SP=", 4 },  /* stateOrProvinceName */
   { "/L=", 3 },   /* localityName */
   { "/O=", 3 },   /* organizationName */
   { "/OU=", 4 },  /* organizationalUnitName */
   { "/CN=", 4 },  /* commonName */
   { "/T=", 3 },   /* title */
   { "/I=", 3 },   /* initials */
   { "/G=", 3 },   /* givenName */
   { "/S=", 3 },   /* surname */
   { "/D=", 3 },   /* description */
   { "/Uid=", 5 },  /* uniqueIdentifier */
   { "/Email=", 7 }, /* pkcs9_emailAddress */
   { "/emailAddress=", 13 }, /* pkcs9_emailAddress */
   { NULL, 0 }
};

/********************/
/* external storage */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  ErrorSanityCheck[],
             SoftwareID[];

extern BIO  *SesolaBioMemPtr;

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
OpenSSL calls this function at each stage during certificate verification
processing (client or CA).  It is used to report on that progress and to add
other processing as required.
*/

int SesolaCertVerifyCallback
(
int OkValue,
/* void* for convenience in compiling non-SSL version */
void *StoreCtxPtr
)
{
   int  ErrorNumber,
        ErrorDepth,
        VerifyDepth,
        VerifyMode,
        WatchThisType;
   char  *cptr, *sptr;
   char  String [512];
   REQUEST_STRUCT  *rqptr;
   SESOLA_STRUCT  *sesolaptr;
   SSL  *SslPtr;
   X509  *CertPtr;
   X509_STORE_CTX  *CtxPtr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertVerifyCallback() !UL !&X", OkValue, StoreCtxPtr);

   CtxPtr = (X509_STORE_CTX*)StoreCtxPtr;

   SslPtr = (SSL*)X509_STORE_CTX_get_ex_data
                  (CtxPtr, SSL_get_ex_data_X509_STORE_CTX_idx());

   sesolaptr = (SESOLA_STRUCT*)SSL_get_ex_data (SslPtr, 0);

   CertPtr = X509_STORE_CTX_get_current_cert (CtxPtr);
   ErrorNumber = X509_STORE_CTX_get_error (CtxPtr);
   ErrorDepth = X509_STORE_CTX_get_error_depth (CtxPtr);
      
   VerifyMode = SSL_get_verify_mode (SslPtr);
   if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                 "VERIFY !UL", VerifyMode);
   if (!VerifyMode)
      return (1);
   else
   if (VerifyMode & SSL_VERIFY_PEER)
      sesolaptr->X509CertRequested = true;
   else
   if (VerifyMode & SSL_VERIFY_CLIENT_ONCE)
      sesolaptr->X509CertRequested = true;

   if (!sesolaptr)
   {
      ErrorNoticed (NULL, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      return (0);
   }

   VerifyDepth = sesolaptr->CertVerifyDepth;

   sesolaptr->CertVerifyCallbackCount++;

   WatchThisType = 0;
   if (WATCH_CAT && WATCHPNT(sesolaptr))
   {
      if (WATCH_CATEGORY(WATCH_SESOLA))
         WatchThisType = WATCH_SESOLA;
      else
      if (WATCH_CATEGORY(WATCH_AUTH))
         WatchThisType = WATCH_AUTH;
   }

   if (WatchThisType)
   {
      BIO_reset (SesolaBioMemPtr);
      WatchThis (WATCHITM(sesolaptr), WatchThisType,
                 "X509 VERIFY callback !UL !AZ !AZ",
                 sesolaptr->CertVerifyCallbackCount,
                 !(VerifyMode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) ?
                    "(no fail on error)" : "(fail on error)",
                 OkValue ? "OK" : "NOT-OK");
      X509_NAME_oneline (X509_get_issuer_name(CertPtr),
                         String, sizeof(String));
      WatchDataFormatted ("ISSUER: !AZ\n", String);
      X509_NAME_oneline (X509_get_subject_name(CertPtr),
                        String, sizeof(String));
      WatchDataFormatted ("SUBJECT: !AZ\n", String);
      ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notBefore(CertPtr));
      BIO_gets (SesolaBioMemPtr, String, sizeof(String));
      WatchDataFormatted ("NOTBEFORE: !AZ\n", String);
      ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(CertPtr));
      BIO_gets (SesolaBioMemPtr, String, sizeof(String));
      WatchDataFormatted ("NOTAFTER: !AZ\n", String);
      SesolaCertFingerprint (CertPtr, &EVP_sha256, String, sizeof(String));
      WatchDataFormatted ("SHA256: !AZ\n", String);
      SesolaCertFingerprint (CertPtr, &EVP_sha1, String, sizeof(String));
      WatchDataFormatted ("SHA1: !AZ\n", String);
      SesolaCertFingerprint (CertPtr, &EVP_md5, String, sizeof(String));
      WatchDataFormatted ("MD5: !AZ\n", String);
      for (cptr = sptr = String; *cptr; cptr++)
         if (*cptr != ':') *sptr++ = *cptr;
      *sptr = '\0';
      WatchDataFormatted ("FINGERPRINT: !AZ\n", String);
      BIO_reset (SesolaBioMemPtr);
   }

   if (OkValue)
      sesolaptr->CertVerifyFailed = false;
   else
   {
      sesolaptr->CertVerifyFailed = true;
      if (WATCH_CAT && WatchThisType)
         WatchThis (WATCHITM(sesolaptr), WatchThisType,
                    "X509 VERIFY error, !UL \"!AZ\"",
                    ErrorNumber, X509_verify_cert_error_string(ErrorNumber));
      if (ErrorDepth > VerifyDepth)
         if (WATCH_CAT && WatchThisType)
            WatchThis (WATCHITM(sesolaptr), WatchThisType,
                       "X509 VERIFY certificate chain too long (!UL>!UL)",
                       ErrorDepth, VerifyDepth);
   }


   /* always OK if verifying peer certificate but not for authorization */
   if ((VerifyMode & SSL_VERIFY_PEER) &&
       !(VerifyMode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT))
      return (1);

   return (OkValue);
}

/*****************************************************************************/
/*
Performs two functions parsing X.509 certificate extensions.

First, if supplied with an record name using |RecordNamePtr| it searches
(case sensitive) for the matching record in the certificate supplied by
|CertPtr|.  If  found it returns a pointer to the full record (e.g.
"name=Testing Only").  If not found (and any other condition) it returns a
NULL.  |RecordNamePtr| can be any record name where underscores are
substituted for spaces, with or without any string following on from the equate
symbol.

The second use is to progressively parse all extension name=value pairs,
returning each of the records with each call, until the certificate is
exhausted and a NULL is returned.  The first call must include the |CertPtr|
parameter and subsequent calls NULL.  The |RecordNamePtr| parameter must be
NULL.

Needless-to-say (because of the use of returned static storage) this function
is not reentrant!!
*/ 

char* SesolaCertName
(
void *CertPtr,
char *RecordNamePtr
)
{
   static int  EntryCount = 0,
               EntryNumber = 0,
               NameValueSize = 0;
   static uchar  *NameValuePtr;
   static char  RecordNameValue [2048];
   static X509_NAME  *NamePtr;

   int  nid;
   uchar  *cptr, *sptr, *zptr,
          *SelectPtr,
          *ValuePtr;
   uchar  ObjectName [256],
          RecordName [256];
   ASN1_OBJECT  *obj;
   ASN1_STRING  *data;
   X509_NAME_ENTRY  *EntryPtr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertName() !UL/!UL !AZ",
                 EntryNumber, EntryCount, RecordNamePtr);

   if (CertPtr)
   {
      /* initialise (subject is the default) */
      EntryNumber = 0;
      if (RecordNamePtr)
      {
         if (MATCH6 (RecordNamePtr, "ISSUER"))
         {
            NamePtr = X509_get_issuer_name(CertPtr);
            RecordNamePtr += 6;
            if (*RecordNamePtr) RecordNamePtr++;
         }
         else
         if (MATCH7 (RecordNamePtr, "SUBJECT"))
         {
            NamePtr = X509_get_subject_name(CertPtr);
            RecordNamePtr += 7;
            if (*RecordNamePtr) RecordNamePtr++;
         }
         else
            NamePtr = X509_get_subject_name(CertPtr);
         if (!*RecordNamePtr) RecordNamePtr = NULL;
      }
      else
         NamePtr = X509_get_subject_name(CertPtr);
      EntryCount = X509_NAME_entry_count (NamePtr);
      if (!RecordNamePtr) return (NULL);
   }

   /* e.g. "[ru:commonName=email:]" */
   RecordName[0] = '\0';
   if (cptr = RecordNamePtr)
   {
      zptr = (sptr = RecordName) + sizeof(RecordName)-1;
      while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      if (*cptr == '=')
         SelectPtr = cptr;
      else
         SelectPtr = NULL;
   }
   
   /************************/
   /* loop through records */
   /************************/

   while (EntryNumber < EntryCount)
   {
      if (!(EntryPtr = X509_NAME_get_entry (NamePtr, EntryNumber++)))
      {
         ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI);
         continue;
      }
      if (!(obj = X509_NAME_ENTRY_get_object (EntryPtr)))
      {
         ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI);
         continue;
      }

      if (MATCH4 (RecordName, "OID_"))
      {
         OBJ_obj2txt (RecordNameValue+4, sizeof(RecordNameValue)-4, obj, 1);
         sptr = RecordNameValue;
         *(ULONGPTR)sptr = 'OID_'; sptr += 4;
      }
      else
      {
         if ((nid = OBJ_obj2nid(obj)) == NID_undef)
         {
            *(ULONGPTR)(cptr = ObjectName) = 'OID_';
            OBJ_obj2txt (ObjectName+4, sizeof(ObjectName)-4, obj, 1);
         }
         else
         if (!(cptr = OBJ_nid2ln (nid)))
         {
            ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI);
            continue;
         }
         zptr = (sptr = RecordNameValue) + sizeof(RecordNameValue)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         sptr = RecordNameValue;
      }
      while (*sptr)
      {
         /* only alphanumerics and underscores (a la DCL symbols) */
         if (!((*sptr >= '0' && *sptr <= '9') ||
               (*sptr >= 'A' && *sptr <= 'Z') ||
               (*sptr >= 'a' && *sptr <= 'z'))) *sptr = '_';
         sptr++;
      }

      if (WATCH_MODULE(WATCH_MOD_SESOLA))
         WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", RecordNameValue);

      /****************/
      /* match record */
      /****************/

      /* if looking for a particular record */
      if (RecordNamePtr)
         if (strcmp (RecordName, RecordNameValue))
            continue;

      /****************/
      /* record value */
      /****************/

      if (!(data = X509_NAME_ENTRY_get_data (EntryPtr)))
      {
         ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI);
         continue;
      }
      cptr = ASN1_STRING_data (data);

      if (sptr < zptr) *sptr++ = '=';
      ValuePtr = sptr;
      while (*cptr && sptr < zptr)
      {
         if (*cptr == '\\') *sptr++ = '\\';
         if (sptr < zptr) *sptr++ = *cptr++;            
      }
      /* if the buffer overflowed then just set an empty string! */
      if (sptr >= zptr) sptr = RecordNameValue;
      *sptr = '\0';

      if (WATCH_MODULE(WATCH_MOD_SESOLA))
         WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", RecordNameValue);

      /**********************/
      /* drill down further */
      /**********************/

      if (RecordNamePtr)
      {
         if (SelectPtr)
         {
            if (!StringMatchRegex (NULL, ValuePtr, SelectPtr))
            {
               RecordNameValue[0] = '\0';
               continue;
            }
         }
      }
      return (RecordNameValue);
   }

   EntryCount = EntryNumber = 0;
   NamePtr = NULL;

   return (NULL);
}

/*****************************************************************************/
/*
Performs two functions parsing X.509 certificate extensions.

First, if supplied with an extension name using |RecordNamePtr| it searches
(case insensitive) for the matching extension in the certificate supplied by
|CertPtr|.  If found it returns a pointer to the full record (e.g.
"name=Testing Only").  If not found (and any other condition) it returns a
NULL.  |RecordNamePtr| can be any extension name where underscores are
substituted for spaces, with or without any string following on from the equate
symbol.

The second use is to progressively parse all extension name=value pairs,
returning each of the extensions with each call, until the certificate is
exhausted and a NULL is returned.  The first call must include the |CertPtr|
parameter and subsequent calls NULL.  The |RecordNamePtr| parameter must be
NULL.

|BufferPtr| and |ExtensionsPtr| point to the buffers of counted null-terminated
strings terminated by a zero length string.  That is,
"[ushort]string\0[ushort]string\0[zero]". The terminating null is counted as
one character.

The first outer loop populates |ExtensionsPtr| with the extensions parsed into
one of the above strings.  The second loop parses the |ExtensionsPtr| series of
strings into a |BufferPtr| second series of strings where the various sub
fields of the extension data are indivisual strings.  All very involuted.

Needless-to-say (because of the use of returned static storage) this function
is not reentrant!!
*/ 

char* SesolaCertExtension
(
void *CertPtr,
char *RecordNamePtr
)
{
#define EXTENSION_BUFFER_QUANTUM 4096
#define EXTENSION_SHORTHAND_QUANTUM 256

   static int  BufferLength = 0,
               BufferSize = 0,
               ExtCount = 0,
               ExtNumber = 0,
               ExtensionsSize = 0,
               NameDigit = 0,
               ShortHandLength = 0,
               ShortHandSize = 0;
   static uchar  *BufferPtr = NULL,
                 *ContextPtr = NULL,
                 *ExtensionsPtr = NULL,
                 *ShortHandPtr = NULL;
   static STACK_OF(X509_EXTENSION)  *ExtPtr;

   BOOL  NoMulti,
         OnePart,
         TwoParts,
         UseOids,
         WithSelect;
   int  cnt, idx, ncnt, nid,
        NameCount;
   uchar  *aptr, *cptr, *sptr, *nptr, *zptr,
          *FindPtr,
          *HitPtr,
          *ExtensionNamePtr,
          *KeywordPtr,
          *ShortPtr,
          *StringPtr,
          *ValuePtr;
   uchar  ObjectName [256],
          RecordName [256],
          String [2048];
   ASN1_OBJECT  *obj;
   BUF_MEM  *bufptr;
   GENERAL_NAME  *NamePtr;
   STACK_OF(GENERAL_NAME)  *NamesPtr;
   X509_EXTENSION  *ext;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertExtension() 0x!8XL !UL/!UL !&Z",
                 CertPtr, ExtNumber, ExtCount,
                 RecordNamePtr == (char*)-1 ? "-1" : RecordNamePtr);

   NoMulti = OnePart = TwoParts = UseOids = WithSelect = false;

   if (cptr = RecordNamePtr)
   {
      if (cptr == (char*)-1)
      {
         NoMulti = true;
         RecordNamePtr = NULL;
      }
      else
      if (MATCH3 (cptr, "OID") || MATCH3 (cptr, "oid"))
      {
         UseOids = true;
         if (MATCH4 (cptr, "OID") || MATCH4 (cptr, "oid"))
            RecordNamePtr = NULL;
      }
      if (cptr = RecordNamePtr)
      {
         /* is there a "one=two"? */
         while (*cptr && *cptr != '=') cptr++;
         if (*cptr)
         {
            /* is there a "one=two=<select>"? */
            for (sptr = cptr + 1; *sptr && *sptr != '='; sptr++);
            if (*sptr)
               TwoParts = WithSelect = true;
            else
            {
               /* check if it's a "one=<select>" (puntuation == wildcards) */
               for (sptr = cptr + 1; *sptr; sptr++)
                  if (ispunct(*sptr)) break;
               if (*sptr)
                  OnePart = WithSelect = true;
               else
                  TwoParts = true;
            }
         }
         else
            OnePart = true;
      }
   }

   if (CertPtr)
   {
      /* intialise the certificate parsing */
      ExtNumber = ExtCount = 0;
      zptr = (sptr = ExtensionsPtr) + ExtensionsSize;
#if SESOLA_SINCE_110
      if (ExtPtr = (X509_get0_extensions (CertPtr)))
#else
      if (ExtPtr = (((X509*)CertPtr)->cert_info)->extensions)
#endif
      {
         ExtCount = sk_X509_EXTENSION_num (ExtPtr);
         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                       "sk_X509_EXTENSION_num() !SL", ExtCount);
      }
   }
   /* otherwise use the already-buffered certificate data */

   /***************************/
   /* loop through extensions */
   /***************************/

   /* only do this loop when a certificate has been supplied */
   zptr = (sptr = ExtensionsPtr) + ExtensionsSize;
   while (CertPtr)
   {
      if (sptr >= zptr)
      {
         /* need (more) buffer space */
         ExtensionsSize += EXTENSION_BUFFER_QUANTUM;
         ExtensionsPtr = VmRealloc (ExtensionsPtr, ExtensionsSize, FI_LI);
         ExtNumber = 0;
      }
      zptr = (sptr = ExtensionsPtr) + ExtensionsSize;

      while (ExtNumber < ExtCount)
      {
         ShortPtr = sptr;
         sptr += sizeof(ushort);
         StringPtr = sptr;

         if (!(ext = sk_X509_EXTENSION_value (ExtPtr, ExtNumber++)))
         {
            ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI);
            continue;
         }
         if (!(obj = X509_EXTENSION_get_object (ext)))
         {
            ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI);
            continue;
         }
         if ((nid = OBJ_obj2nid(obj)) == NID_undef)
         {
            *(ULONGPTR)(cptr = ObjectName) = 'OID_';
            OBJ_obj2txt (ObjectName+4, sizeof(ObjectName)-4, obj, 1);
         }
         else
         if (!(cptr = OBJ_nid2ln (nid)))
         {
            ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI);
            continue;
         }
         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                       "OBJ_nid2ln() !AZ", cptr);
   
         /***************/
         /* record name */
         /***************/
   
         if (UseOids)
         {
            *(ULONGPTR)sptr = 'OID_'; sptr += 4;
            OBJ_obj2txt (sptr, BufferSize-(sptr-BufferPtr), obj, 1);
            while (*sptr) sptr++;
         }
         else
         {
            while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            *sptr = '\0';
         }

         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", StringPtr);
   
         /****************/
         /* record value */
         /****************/
   
         if (sptr < zptr) *sptr++ = '=';
         ValuePtr = sptr;
         *sptr = '\0';
   
         NamesPtr = NULL;
   
         if (nid == NID_subject_alt_name)
         {
            /****************************/
            /* subject alternative name */
            /****************************/
   
            /* parse SAN value as a stack of general names */
            NamesPtr = X509_get_ext_d2i (CertPtr, NID_subject_alt_name,
                                         NULL, NULL);
            if (NamesPtr == NULL)
            {
               if (WATCH_MODULE(WATCH_MOD_SESOLA))
                  WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                             "X509_get_ext_d2i() NULL");
               continue;
            }
   
            NameCount = sk_GENERAL_NAME_num(NamesPtr);
            for (ncnt = 0; ncnt < NameCount; ncnt++)
            {
               /* each name has an implicit context tag that identifies type */
               NamePtr = sk_GENERAL_NAME_value(NamesPtr, ncnt);

               switch (NamePtr->type)
               {
                  case GEN_OTHERNAME:
                     /* generate the OID number */
                     aptr = nptr = ObjectName;
                     *(ULONGPTR)aptr = 'OID_'; aptr += 4;
                     OBJ_obj2txt (aptr, sizeof(ObjectName)-(aptr-ObjectName),
                                  NamePtr->d.otherName->type_id, 1);
                     while (*aptr) aptr++;
                     *(USHORTPTR)aptr = ':\0';

                     /* if it's the Microsoft UPN OID */
                     if (!memcmp (nptr, "OID_1.3.6.1.4.1.311.20.2.3:", 27)) 
                        nptr = "userPrincipalName:";

                     cptr = (char*)ASN1_STRING_data (NamePtr->d.otherName->
                                                     value->value.asn1_string);
                     break;
   
                  case GEN_EMAIL:
                     nptr = "rfc822Name:";
                     cptr = (char*)ASN1_STRING_data (NamePtr->d.rfc822Name);
                     break;
   
                  case GEN_DNS:
                     nptr = "dNSName:";
                     cptr = (char*)ASN1_STRING_data (NamePtr->d.dNSName);
                     break;
   
                  case GEN_X400:
                     /* TODO: add support for X400 names */
                     nptr = "x400Address:";
                     cptr = "[unimplemented]";
                     break;
   
                  case GEN_DIRNAME:
                     nptr = "directoryName:";
                     cptr = X509_NAME_oneline (NamePtr->d.directoryName,
                                               String, sizeof(String));
                     break;
   
                  case GEN_EDIPARTY:
                     /* TODO: add support for EDI party names */
                     nptr = "ediPartyName:";
                     cptr = "[unimplemented]";
                     break;
   
                  case GEN_URI:
                     nptr = "uniformResourceIdentifier:";
                     cptr = (char*)ASN1_STRING_data (NamePtr->
                                                     d.uniformResourceIdentifier);
                     break;
   
                  case GEN_IPADD:
                     nptr = "iPAddress:";
                     cptr = (uchar*)ASN1_STRING_data (NamePtr->d.iPAddress);
                     if (ASN1_STRING_length (NamePtr->d.iPAddress) == 4)
                        cptr = TcpIpAddressToString (*(uint*)cptr, 4);
                     else
                     if (ASN1_STRING_length (NamePtr->d.iPAddress) == 16)
                        cptr = TcpIpAddressToString (cptr, 6);
                     else
                        cptr = "?";
                     break;
   
                  case GEN_RID:
                     /* TODO: add support for registered IDs */
                     nptr = "registeredID:";
                     cptr = "[unimplemented]";
                     break;
   
                  default:
                     nptr = "GEN_UNKNOWN:";
                     cptr = "[unimplemented]";
               }
   
               /* if multi value add carriage-control to preceding line */
               if (sptr > ValuePtr)
               {
                  if (sptr < zptr) *sptr++ = '\r';
                  if (sptr < zptr) *sptr++ = '\n';
               }

               /* copy the name in */
               while (*nptr && sptr < zptr)
               {
                  if (*nptr == '\\' && *(nptr+1)) *sptr++ = '\\';
                  if (sptr < zptr) *sptr++ = *nptr++;            
               }
               *sptr = '\0';
               if (!*StringPtr) break;

               /* copy the value in */
               while (*cptr && sptr < zptr)
               {
                  if (*cptr == '\\' && *(cptr+1)) *sptr++ = '\\';
                  if (sptr < zptr) *sptr++ = *cptr++;            
               }
            }

            if (NamesPtr) GENERAL_NAMES_free (NamesPtr);
         }
         else
         {
            /************************/
            /* some other extension */
            /************************/

            BIO_reset (SesolaBioMemPtr);
 
            if (!X509V3_EXT_print (SesolaBioMemPtr, ext, 0, 0))
               ASN1_STRING_print (SesolaBioMemPtr,
                                  X509_EXTENSION_get_data(ext));

            BIO_get_mem_ptr (SesolaBioMemPtr, &bufptr);

            cptr = bufptr->data;
            cnt = bufptr->length;

            /* append value to name */
            while (cnt-- && sptr < zptr)
            {
               if (*cptr == '\0' || (*cptr == '\\' && *(cptr+1)))
                  *sptr++ = '\\';
               if (sptr < zptr) *sptr++ = *cptr++;            
            }

            BIO_reset (SesolaBioMemPtr);

            *sptr = '\0';
            if (!*StringPtr) break;
         }

         /* yes, we do increment */
         *sptr++ = '\0';

         if (!*StringPtr) break;

         /*********************/
         /* end of name=value */
         /*********************/

         /* if overflowed then leave this loop */
         if (sptr >= zptr) break;

         cnt = sptr - ShortPtr - sizeof(ushort);

         /* maximum string length of 65k */
         if (cnt & 0xffff0000)
            ErrorExitVmsStatus (SS$_RESULTOVF, ErrorSanityCheck, FI_LI);

         /* insert the length of the string including terminating null */
         *(USHORTPTR)ShortPtr = cnt;

         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!UL !&Z",
                       *(USHORTPTR)ShortPtr, ShortPtr + sizeof(ushort));

         /* add the zero-length string sentinal */
         *(USHORTPTR)sptr = 0;
      }

      /* if overflowed, go back, get more buffer space, do it all again */
      if (sptr >= zptr) continue;

      break;
   }

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      SesolaCertFind (ExtensionsPtr, "*", NULL);

   /******************/
   /* parse elements */
   /******************/

   /* only do this loop when a certificate has been supplied */
   zptr = (sptr = BufferPtr) + BufferSize;
   while (CertPtr)
   {
      if (sptr >= zptr)
      {
         /* need (more) buffer space */
         BufferSize += EXTENSION_BUFFER_QUANTUM;
         BufferPtr = VmRealloc (BufferPtr, BufferSize, FI_LI);
         ExtNumber = 0;
      }
      zptr = (sptr = BufferPtr) + BufferSize;

      ContextPtr = NULL;
      SesolaCertFind (ExtensionsPtr, NULL, &ContextPtr);

      while (cptr = SesolaCertFind (NULL, NULL, &ContextPtr))
      {
         ExtensionNamePtr = NULL;
         cptr += sizeof(ushort);
         while (*cptr)
         {
            /****************/
            /* copy in name */
            /****************/

            /* set to a zero-length sentinal so we can SesolaCertFind() */
            *(USHORTPTR)(ShortPtr = sptr) = 0;
            sptr += sizeof(ushort);
            StringPtr = sptr;

            if (ExtensionNamePtr)
               aptr = ExtensionNamePtr;
            else
               aptr = cptr;
            while (*aptr && *aptr != '=' && sptr < zptr)
            {
               if (*aptr == '\\' && *(aptr+1)) *sptr++ = *aptr++;
               if (sptr < zptr) *sptr++ = *aptr++;
            }
            *sptr = '\0';
            if (!*StringPtr) break;
            if (ExtensionNamePtr)
            {
               /* second pass check for any known keywords */
               KeywordPtr = SesolaCertKeyword (cptr);
               if (!KeywordPtr)
               {
                  sptr = ShortPtr;
                  break;
               }
            }
            else
            {
               /* buffer this for use with multiple keyword values */
               ExtensionNamePtr = cptr;
               if (*aptr == '=') aptr++;
               cptr = aptr;
               KeywordPtr = NULL;
            }

            if (KeywordPtr)
            {
               /*******************/
               /* copy in keyword */
               /*******************/

               /* go to the start of the keyword */
               cptr = KeywordPtr;
               /* where two parts are separated by a '=' substitute a '$' */
               if (sptr < zptr) *sptr++ = (TwoParts ? '$' : '_');
               while (*cptr && *cptr != ':' && sptr < zptr)
               {
                  if (*cptr == '\\' && *(cptr+1)) *sptr++ = *cptr++;
                  if (sptr < zptr) *sptr++ = *cptr++;
               }
               *sptr = '\0';
               if (*cptr) cptr++;
            }

            /* name has only alphanums and underscores (a la DCL symbols) */
            for (sptr = StringPtr; *sptr; sptr++)
               if (!((*sptr >= '0' && *sptr <= '9') ||
                     (*sptr >= 'A' && *sptr <= 'Z') ||
                     (*sptr >= 'a' && *sptr <= 'z') ||
                     (TwoParts && *sptr == '$')))
                  *sptr = '_';

            /* compress multiple underscores into singles */
            for (aptr = sptr = StringPtr; *aptr; aptr++)
            {
               while (*(USHORTPTR)aptr == '__') aptr++;
               *sptr++ = *aptr;
            }
            *sptr = '\0';

            /********************/
            /* handle multiples */
            /********************/

            /* if the name already exists append TWO undercores and a number */
            nptr = sptr;
            for (cnt = 2; cnt < 32; cnt++)
            {
               if (!SesolaCertFind (BufferPtr, StringPtr, NULL)) break;
               sprintf (nptr, "__%d", cnt);
            }
            while (*sptr) sptr++;
            if (cnt >= 32) ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);

            /*****************/
            /* copy in value */
            /*****************/

            /* separate the name from the value */
            if (sptr < zptr) *sptr++ = '=';

            if (KeywordPtr)
            {
               /* up until carriage-control (end of line) */
               while (*cptr && *cptr != '\r' && *cptr != '\n' && sptr < zptr)
               {
                  if (*cptr == '\\' && *(cptr+1)) *sptr++ = *cptr++;
                  if (sptr < zptr) *sptr++ = *cptr++;
               }
               /* scan over any trailing carriage-control */
               while (*cptr == '\r' || *cptr == '\n') cptr++;
           }
           else
           {
               ValuePtr = cptr;
               while (*cptr && sptr < zptr)
               {
                  if (*cptr == '\\' && *(cptr+1)) *sptr++ = *cptr++;
                  if (sptr < zptr) *sptr++ = *cptr++;
               }
               /* a second pass will check for any keywords */
               cptr = ValuePtr;
            }

            /* yes, we do increment */
            *sptr++ = '\0';

            /*********************/
            /* end of name=value */
            /*********************/

            /* if overflowed then leave this loop */
            if (sptr >= zptr) break;

            cnt = sptr - StringPtr;

            /* maximum string length of 65k */
            if (cnt & 0xffff0000)
               ErrorExitVmsStatus (SS$_RESULTOVF, ErrorSanityCheck, FI_LI);

            /* insert the length of the string including terminating null */
            *(USHORTPTR)ShortPtr = cnt;

            if (WATCH_MODULE(WATCH_MOD_SESOLA))
               WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!UL !&Z",
                          *(USHORTPTR)ShortPtr, ShortPtr + sizeof(ushort));

            /* add a zero-length string sentinal */
            *(USHORTPTR)sptr = 0;
         }

         /* if overflowed then leave this loop */
         if (sptr >= zptr) break;
      }

      /* if overflowed, go back, get more buffer space, do it all again */
      if (sptr >= zptr) continue;

      break;
   }

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      SesolaCertFind (BufferPtr, "*", NULL);

   /**********************/
   /* end parse elements */
   /**********************/

   /* e.g. "[ru:X509v3_Subject_Alternative_Name_email=]" */
   if (cptr = RecordNamePtr)
   {
      /**********************/
      /* search for element */
      /**********************/

      BOOL  ShortHand = false;

      RecordName[0] = '\0';

      /* first some shorthands (see also SesolaCgiVariablesExtension()) */
      if (TOUP(*cptr) == 'X' && strsame (cptr, "X509V3_SAN", 10))
      {
         /*****************/
         /* SAN shorthand */
         /*****************/

         ShortHand = true;
         zptr = (sptr = RecordName) + sizeof(RecordName)-1;
         if (cptr[10] == '_')
         {
            if (strsame (cptr+10, "_UPN", 4) &&
                (cptr[14] == '\0' || cptr[14] == '='))
               cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME_USERPRINCIPALNAME";
            else
            if (strsame (cptr+10, "_822", 4) &&
                (cptr[14] == '\0' || cptr[14] == '='))
               cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME_RFC822NAME";
         }
         else
         if (cptr[10] == '=')
         {
            if (strsame (cptr+10, "=UPN", 4) &&
                (cptr[14] == '\0' || cptr[14] == '='))
               cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME$USERPRINCIPALNAME";
            else
            if (strsame (cptr+10, "=822", 4) &&
                (cptr[14] == '\0' || cptr[14] == '='))
               cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME$RFC822NAME";
         }
         if (cptr == RecordNamePtr)
         {
            /* concatenate the expanded SAN with what follows */
            for (aptr = "X509V3_SUBJECT_ALTERNATIVE_NAME";
                 *aptr && sptr < zptr;
                 *sptr++ = *aptr++);
            /* step over the "X509_SAN" */
            cptr += 10;
            /* where two parts are separated by a '=' substitute a '$' */
            if (TwoParts)
            {
               if (*cptr) cptr++;
               if (sptr < zptr) *sptr++ = '$';
            }
         }
         /* up to but excluding any selection expression */
         while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
      }
      else
      {
         zptr = (sptr = RecordName) + sizeof(RecordName)-1;
         while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
         if (TwoParts)
         {
            if (*cptr && sptr < zptr) *sptr++ = *cptr++;
            while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++;
         }
         *sptr = '\0';
      }

      /********/
      /* find */
      /********/

      HitPtr = SesolaCertFind (BufferPtr, RecordName, NULL);

      if (!HitPtr) return (NULL);

      if (WithSelect)
      {
         if (ShortHand)
         {
            /* refind the selection expression after the shorthand */
            for (cptr = RecordNamePtr; *cptr; cptr++);
            while (cptr > RecordNamePtr && *cptr != '=') cptr--;
            if (*cptr) cptr++;
         }

         for (sptr = HitPtr + sizeof(ushort); *sptr && *sptr != '='; sptr++);
         if (*sptr) sptr++;

         if (!StringMatchRegex (NULL, sptr, cptr)) return (NULL);
      }

      if (!ShortHand) return (HitPtr + sizeof(short));

      /* put the shorthand form in a buffer of its own to return */
      for (;;)
      {
         zptr = (sptr = ShortHandPtr) + ShortHandSize;
         if (sptr >= zptr)
         {
            /* need (more) shorthand space */
            ShortHandSize += EXTENSION_SHORTHAND_QUANTUM;
            ShortHandPtr = VmRealloc (ShortHandPtr,
                                      ShortHandSize, FI_LI);
            zptr = (sptr = ShortHandPtr) + ShortHandSize;
         }

         /* original shorthand name */
         for (cptr = RecordNamePtr;
              *cptr && *cptr != '=' && sptr < zptr;
              *sptr++ = *cptr++);
         if (sptr < zptr) *sptr++ = '=';

         /* then append the returned hit value */
         for (cptr = HitPtr + sizeof(ushort); *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;

         /* if overflow then go back and do it again */
         if (sptr >= zptr) continue;

         *sptr = '\0';
         break;
      }
      return (ShortHandPtr);
   }
   else
   {
      /***********************/
      /* one-by-one per call */
      /***********************/

      if (CertPtr)
         SesolaCertFind (BufferPtr, NULL, NULL); 
      else
      {
         HitPtr = SesolaCertFind (NULL, NULL, NULL);
         if (HitPtr)
         {
            HitPtr += sizeof(ushort);
            return (HitPtr);
         }
      }
   }

   return (NULL);
}

/*****************************************************************************/
/*
Search the supplied string for instances of the internal and configurable
"<keyword>:" keyword.  The <keyword> can be any string terminated by a colon
and the search is case-insensitive.  If |StringPtr| doesn't point at the start
of the keyword then the string is spanned for white-space leading into the
"<keyword>:".

The logical name WASD_X509_EXTENSION_KEYWORDS defines a string in the format
"<keyword>:<keyword>:" for site-specific keywords.  If the string begins with a
plus symbol (e.g. "+<keyword>:<keyword>:") these words supplement the internal
keywords.  When  there is no leading '+' the string overrides the internal
keywords.  The logical name content can only be refreshed by server startup.
*/ 

uchar* SesolaCertKeyword (uchar* StringPtr)

{
   static int  StartCount = 2;
   static uchar  *Keywords [3] = {  NULL,NULL,SESOLA_X509_EXTENSION_KEYWORDS };

   int  cnt, len;
   uchar  *aptr, *cptr, *sptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertKeyword() !&Z", StringPtr);

   if (!Keywords[1])
   {
      /* read once at first use after server startup */
      if (cptr = SysTrnLnm (WASD_X509_EXTENSION_KEYWORDS))
      {
         Keywords[1] = VmGet (strlen(cptr)+1);
         strcpy (Keywords[1], cptr);
         if (Keywords[1][0] == '+')
            Keywords[1]++;
         else
            StartCount = 1;
      }
      else
         Keywords[1] = "";
   }

   while (*StringPtr)
   {
      len = 0;
      for (sptr = StringPtr; *sptr && isspace(*sptr); sptr++);
      if (!*sptr) break;
      StringPtr = sptr;

      for (cnt = StartCount; cnt; cnt--)
      {
         cptr = Keywords[cnt];
         while (*cptr)
         {
            for (aptr = cptr; *aptr && *aptr != ':'; aptr++);
            if (!*aptr) break;
            if (!(len = aptr - cptr)) break;
            sptr = StringPtr;
            while (cptr < aptr && TOLO(*cptr) == TOLO(*sptr))
            {
               cptr++;
               sptr++;
            }
            if (*cptr == ':' && *sptr == ':') break;
            cptr = aptr + 1;
            len = 0;
         }
         if (len) break;
      }
      if (len) break;

      for (sptr = StringPtr; *sptr && !isspace(*sptr); sptr++);
      if (!*sptr) break;
      StringPtr = sptr;
   }

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "!UL !#AZ", len, len, len ? (char*)StringPtr : "");

   if (len) return (StringPtr);

   return (NULL);
}

/*****************************************************************************/
/*
|BufferPtr| points to a buffer of counted null-terminated strings, terminated
by a zero length string.  That is, "[ushort]string\0[ushort]string\0[zero]".
The strings are in the form of a "[name]=[value]\0" pair.  The terminating null
is counted as one character.  Return is to the [ushort] length, or NULL.
*/ 

uchar* SesolaCertFind
(
uchar *BufferPtr,
uchar *NamePtr,
uchar **ContextPtr
)
{
   static uchar  *InternalPtr = NULL;

   ushort  slen;
   uchar  *aptr, *cptr, *sptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertFind() !&Z", NamePtr);

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
   {
      if (BufferPtr && NamePtr && *NamePtr == '*')
      {
         /* debug display all entries from the buffer pointer on */
         int  cnt = 0;
         WatchDataFormatted ("!80*~\n", BufferPtr);
         cptr = BufferPtr;
         for (;;)
         {
            if (!(slen = *(USHORTPTR)cptr)) break;
            WatchDataFormatted ("!2ZL: !3ZL !&Z\n",
                                ++cnt, slen, cptr + sizeof(ushort));
            cptr += sizeof(ushort) + slen;
         }
         WatchDataFormatted ("!80*~\n", BufferPtr);
         return (NULL);
      }
   }

   if (!BufferPtr && !NamePtr && !ContextPtr) ContextPtr = &InternalPtr;
   if (BufferPtr)
   {
      InternalPtr = BufferPtr;
      if (ContextPtr) *ContextPtr = BufferPtr;
      /* if (re)initialising and not searching */
      if (!NamePtr) return (NULL);
   }
   else
      BufferPtr = *ContextPtr;

   if (!BufferPtr) return (NULL);

   cptr = BufferPtr;
   for (;;)
   {
      /* if zero length sentinal */
      if (!(slen = *(USHORTPTR)cptr)) break;
      if (!(sptr = NamePtr))
      {
         /* NULL |NamePtr| returns next string */
         *ContextPtr = cptr + sizeof(ushort) + slen;
         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                       "NEXT: !&Z", cptr + sizeof(ushort));
         return (cptr);
      }
      /* search for specified name of "[ushort][name]=[value]\0" */
      aptr = cptr;
      cptr += 2;
      while (*cptr && *sptr &&
             ((TOUP(*cptr) == TOUP(*sptr) ||
              /* where two parts are separated by a '=' substituted a '$' */
              (*cptr == '$' && *sptr == '='))))
      {
         cptr++;
         sptr++;
      }
      if (!*sptr && *cptr && !isalnum(*cptr))
      {
         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                       "HIT: !&Z", aptr + sizeof(ushort));
         return (aptr);
      }
      cptr = aptr + sizeof(ushort) + slen;
   }
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
       WatchThis (WATCHALL, WATCH_MOD_SESOLA, "MISS!!");

   if (ContextPtr) *ContextPtr = NULL;

   return (NULL);
}

/*****************************************************************************/
/*
Performs two functions parsing X.509 Distinguished Names.

First, if supplied with a DN record string using |RecordNamePtr| it searches
(case sensitive) for the matching record in the string supplied by |DnPtr|.  If 
found it returns a pointer to the full record (e.g. "/OU=Testing Only").  If
not found (and any other condition) it returns a NULL.  'RecordNamePtr' can be
"/OU=", "/CN=", etc., with or without any string following on from the equate
symbol.

The second use is to progressively parse a DN string, returning each of the
full records with each call, until the string is exhausted and a NULL is
returned.  To reset the parse call with |RecordNamePtr| a NULL.

Needless-to-say (because of the use of returned static storage) this function
is not reentrant!!
*/ 

char* SesolaCertParseDn
(
char *DnPtr,
char *RecordNamePtr
)
{
   static char  *ContextPtr = NULL;
   static char  RecordNameValue [1024];

   int  RecordNameLength;
   char  *cptr, *sptr, *zptr,
         *SelectPtr;
   struct SesolaCertDnRecStruct  *cdnptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertParseDn() !AZ !AZ", DnPtr, RecordNamePtr);

   if (!ContextPtr) ContextPtr = DnPtr;
   if (!(cptr = ContextPtr)) return (ContextPtr = NULL);
   if (!*cptr) return (ContextPtr = NULL);

   if (RecordNamePtr)
   {
      /* if a one-shot search just reset the context pointer */
      if (RecordNamePtr) ContextPtr = NULL;
      /* search the subject string for the specified record name */
      for (sptr = RecordNamePtr; *sptr && *sptr != '='; sptr++);
      SelectPtr = NULL;
      if (*sptr)
      {
         sptr++;
         if (*sptr) SelectPtr = sptr;
      }
      RecordNameLength = sptr - RecordNamePtr;
   }

   for (;;)
   {
      if (RecordNamePtr)
      {
         while (*cptr)
         {
            while (*cptr && *cptr != '/') cptr++;
            if (!*cptr) break;
            for (cdnptr = &SesolaCertDnRec; cdnptr->name; cdnptr++)
               if (!strncmp (cptr, cdnptr->name, cdnptr->length)) break;
            if (cdnptr->name && RecordNameLength == cdnptr->length &&
                !strncmp (cdnptr->name, RecordNamePtr, RecordNameLength))
               break;
            cptr++;
         }
         if (WATCH_MODULE(WATCH_MOD_SESOLA))
            WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", cptr);

         /* if the certificate text has been exhausted */
         if (!*cptr) return (NULL);
      }

      zptr = (sptr = RecordNameValue) + sizeof(RecordNameValue);
      while (*cptr && sptr < zptr)
      {
         if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++;
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (!*cptr) break;
         /* check it's a recognized record delimiter */
         for (cdnptr = &SesolaCertDnRec; cdnptr->name; cdnptr++)
            if (!strncmp (cptr, cdnptr->name, cdnptr->length)) break;
         if (cdnptr->name) break;
      }
      /* if the buffer overflowed then just set an empty string! */
      if (sptr >= zptr) sptr = RecordNameValue;
      *sptr = '\0';

      if (WATCH_MODULE(WATCH_MOD_SESOLA))
         WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!AZ", RecordNameValue);

      if (RecordNamePtr)
      {
         if (SelectPtr)
         {
            if (!StringMatchRegex (NULL, RecordNameValue +
                                         RecordNameLength, SelectPtr))
            {
               RecordNameValue[0] = '\0';
               continue;
            }
         }
      }
      else
         ContextPtr = cptr;

      break;
   }

   return (RecordNameValue);
}

/*****************************************************************************/
/*
Creates a "fingerprint" of the certificate.  'Colon' should be either a colon
character (':') or a null character ('\0').  The first produces the common,
printable fingerprint.  The second just a string of hex digits used for
certificate identification (a sort of hash).  Return a string describing the
digest algorithm (MD5) or an empty string indicating an error of some sort.
*/

char* SesolaCertFingerprint
(
void *CertPtr,
EVP_MD* (*DigestFunction)(void),
char *BufferPtr,
int SizeOfBuffer
)
{
   static char  HexDigits [] = "0123456789ABCDEF";

   int  idx,
        CertDigestLength;
   char  *cptr, *sptr, *zptr;
   unsigned char  CertDigest [EVP_MAX_MD_SIZE];
   EVP_MD  *DigestPtr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertFingerprint()");

   if (SizeOfBuffer <= 0) return ("");
   zptr = (sptr = BufferPtr) + SizeOfBuffer - 1;
   DigestPtr = (*DigestFunction)();
   if (X509_digest ((X509*)CertPtr, DigestPtr, CertDigest, &CertDigestLength))
   {
      for (idx = 0; idx < CertDigestLength; idx++)
      {
         if (idx && sptr < zptr) *sptr++ = ':';
         if (sptr < zptr) *sptr++ = HexDigits[(CertDigest[idx] & 0xf0) >> 4];
         if (sptr < zptr) *sptr++ = HexDigits[(CertDigest[idx] & 0x0f)];
      }
      *sptr = '\0';
      return ((char*)OBJ_nid2sn (EVP_MD_type (DigestPtr)));
   }
   cptr = "X509_digest() ERROR!";
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   return ("");
}

/*****************************************************************************/
/*
Place each name-value element from the distiguishing name into the buffer
separated by newlines.  |WhichPtr| parameter should be either "ISSUER" or
"SUBJECT" (the default).
*/

int SesolaCertReportName
(
void *CertPtr,
char *WhichPtr,
char *OutputBuffer,
int SizeOfOutput
)
{
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertReportName()");

   SesolaCertName (CertPtr, WhichPtr);
   zptr = (sptr = OutputBuffer) + SizeOfOutput - 1;
   while (cptr = SesolaCertName (NULL, NULL))
   {
      if (sptr > OutputBuffer && sptr < zptr) *sptr++ = '\n';
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!AZ", OutputBuffer);

   return (sptr - OutputBuffer);
}

/*****************************************************************************/
/*
Just put a newline the certificate DN format at each component (meant to be
placed inside <pre></pre>).
*/

int SesolaCertReportDn
(
char *InputDn,
char *OutputBuffer,
int SizeOfOutput
)
{
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertReportDn() !AZ", InputDn);

   zptr = (sptr = OutputBuffer) + SizeOfOutput - 1;
   while (cptr = SesolaCertParseDn (InputDn, NULL))
   {
      if (sptr > OutputBuffer && sptr < zptr) *sptr++ = '\n';
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!AZ", OutputBuffer);

   return (sptr - OutputBuffer);
}

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

/************************/
#endif  /* ifdef SESOLA */
/************************/