00001
00002
00003 const char *XrdSecgsiProxyCVSID = "$Id: XrdSecgsiProxy.cc 32800 2010-03-29 07:04:49Z ganis $";
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include <stdio.h>
00019 #include <stdlib.h>
00020 #include <string.h>
00021 #include <sys/types.h>
00022 #include <sys/stat.h>
00023 #include <unistd.h>
00024 #include <pwd.h>
00025 #include <time.h>
00026
00027
00028 #include <XrdOuc/XrdOucString.hh>
00029 #include <XrdSys/XrdSysLogger.hh>
00030 #include <XrdSys/XrdSysError.hh>
00031
00032 #include <XrdSut/XrdSutAux.hh>
00033
00034 #include <XrdCrypto/XrdCryptoAux.hh>
00035 #include <XrdCrypto/XrdCryptoFactory.hh>
00036 #include <XrdCrypto/XrdCryptoX509.hh>
00037 #include <XrdCrypto/XrdCryptoX509Req.hh>
00038 #include <XrdCrypto/XrdCryptoX509Chain.hh>
00039 #include <XrdCrypto/XrdCryptoX509Crl.hh>
00040
00041 #include <XrdCrypto/XrdCryptosslgsiX509Chain.hh>
00042 #include <XrdCrypto/XrdCryptosslgsiAux.hh>
00043
00044 #include <XrdSecgsi/XrdSecgsiTrace.hh>
00045
00046 #define PRT(x) {cerr <<x <<endl;}
00047
00048
00049
00050 enum kModes {
00051 kM_undef = 0,
00052 kM_init = 1,
00053 kM_info,
00054 kM_destroy,
00055 kM_help
00056 };
00057 const char *gModesStr[] = {
00058 "kM_undef",
00059 "kM_init",
00060 "kM_info",
00061 "kM_destroy",
00062 "kM_help"
00063 };
00064
00065
00066
00067
00068 void Menu();
00069 int ParseArguments(int argc, char **argv);
00070 bool CheckOption(XrdOucString opt, const char *ref, int &ival);
00071 void Display(XrdCryptoX509 *xp);
00072
00073
00074
00075
00076 int Mode = kM_undef;
00077 bool Debug = 0;
00078 bool Exists = 0;
00079 XrdCryptoFactory *gCryptoFactory = 0;
00080 XrdOucString CryptoMod = "ssl";
00081 XrdOucString CAdir = "/etc/grid-security/certificates/";
00082 XrdOucString CRLdir = "/etc/grid-security/certificates/";
00083 XrdOucString DefEEcert = "/.globus/usercert.pem";
00084 XrdOucString DefEEkey = "/.globus/userkey.pem";
00085 XrdOucString DefPXcert = "/tmp/x509up_u";
00086 XrdOucString EEcert = "";
00087 XrdOucString EEkey = "";
00088 XrdOucString PXcert = "";
00089 XrdOucString Valid = "12:00";
00090 int Bits = 512;
00091 int PathLength = 0;
00092 int ClockSkew = 30;
00093
00094 static XrdSysLogger Logger;
00095 static XrdSysError eDest(0,"proxy_");
00096 XrdOucTrace *gsiTrace = 0;
00097
00098 int main( int argc, char **argv )
00099 {
00100
00101 int secValid = 0;
00102 XrdProxyOpt_t pxopt;
00103 XrdCryptosslgsiX509Chain *cPXp = 0;
00104 XrdCryptoX509 *xPXp = 0;
00105 XrdCryptoRSA *kPXp = 0;
00106 XrdCryptoX509ParseFile_t ParseFile = 0;
00107 int prc = 0;
00108 int nci = 0;
00109 int exitrc = 0;
00110
00111
00112 if (ParseArguments(argc,argv)) {
00113 exit(1);
00114 }
00115
00116
00117
00118 eDest.logger(&Logger);
00119 if (!gsiTrace)
00120 gsiTrace = new XrdOucTrace(&eDest);
00121 if (gsiTrace) {
00122 if (Debug)
00123
00124 gsiTrace->What |= (TRACE_Authen | TRACE_Debug);
00125 }
00126
00127
00128 if (Debug) {
00129 XrdSutSetTrace(sutTRACE_Debug);
00130 XrdCryptoSetTrace(cryptoTRACE_Debug);
00131 }
00132
00133
00134
00135 if (!(gCryptoFactory = XrdCryptoFactory::GetCryptoFactory(CryptoMod.c_str()))) {
00136 PRT(": cannot instantiate factory "<<CryptoMod);
00137 exit(1);
00138 }
00139 if (Debug)
00140 gCryptoFactory->SetTrace(cryptoTRACE_Debug);
00141
00142
00143
00144 switch (Mode) {
00145 case kM_help:
00146
00147
00148 Menu();
00149 break;
00150 case kM_init:
00151
00152
00153 secValid = XrdSutParseTime(Valid.c_str(), 1);
00154 pxopt.bits = Bits;
00155 pxopt.valid = secValid;
00156 pxopt.depthlen = PathLength;
00157 cPXp = new XrdCryptosslgsiX509Chain();
00158 prc = XrdSslgsiX509CreateProxy(EEcert.c_str(), EEkey.c_str(), &pxopt,
00159 cPXp, &kPXp, PXcert.c_str());
00160 if (prc == 0) {
00161
00162 xPXp = cPXp->Begin();
00163 if (xPXp) {
00164 Display(xPXp);
00165 } else {
00166 PRT( ": proxy certificate not found");
00167 }
00168 } else {
00169 PRT( ": problems creating proxy");
00170 }
00171 break;
00172 case kM_destroy:
00173
00174
00175 if (unlink(PXcert.c_str()) == -1) {
00176 perror("xrdgsiproxy");
00177 }
00178
00179 break;
00180 case kM_info:
00181
00182
00183 if (!(ParseFile = gCryptoFactory->X509ParseFile())) {
00184 PRT("cannot attach to ParseFile function!");
00185 break;
00186 }
00187
00188 cPXp = new XrdCryptosslgsiX509Chain();
00189 nci = (*ParseFile)(PXcert.c_str(), cPXp);
00190 if (nci < 2) {
00191 if (Exists) {
00192 exitrc = 1;
00193 } else {
00194 PRT("proxy files must have at least two certificates"
00195 " (found only: "<<nci<<")");
00196 }
00197 break;
00198 }
00199
00200 xPXp = cPXp->Begin();
00201 if (xPXp) {
00202 if (!Exists) {
00203 Display(xPXp);
00204 } else {
00205
00206 secValid = XrdSutParseTime(Valid.c_str(), 1);
00207 int tl = xPXp->NotAfter() -(int)time(0);
00208 if (Debug)
00209 PRT("secValid: " << secValid<< ", tl: "<<tl<<", ClockSkew:"<<ClockSkew);
00210 if (secValid > tl + ClockSkew) {
00211 exitrc = 1;
00212 break;
00213 }
00214
00215 if (Debug)
00216 PRT("BitStrength: " << xPXp->BitStrength()<< ", Bits: "<<Bits);
00217 if (xPXp->BitStrength() < Bits) {
00218 exitrc = 1;
00219 break;
00220 }
00221 }
00222 } else {
00223 if (Exists) {
00224 exitrc = 1;
00225 } else {
00226 PRT( ": proxy certificate not found");
00227 }
00228 }
00229 break;
00230 default:
00231
00232
00233 Menu();
00234 }
00235
00236 exit(exitrc);
00237 }
00238
00239 int ParseArguments(int argc, char **argv)
00240 {
00241
00242
00243
00244 if (argc < 0 || !argv[0]) {
00245 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00246 PRT("+ Insufficient number or arguments! +");
00247 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00248
00249 Menu();
00250 return 1;
00251 }
00252 --argc;
00253 ++argv;
00254
00255
00256
00257 while ((argc >= 0) && (*argv)) {
00258
00259 XrdOucString opt = "";
00260 int ival = -1;
00261 if(*(argv)[0] == '-') {
00262
00263 opt = *argv;
00264 opt.erase(0,1);
00265 if (CheckOption(opt,"h",ival) || CheckOption(opt,"help",ival) ||
00266 CheckOption(opt,"menu",ival)) {
00267 Mode = kM_help;
00268 } else if (CheckOption(opt,"debug",ival)) {
00269 Debug = ival;
00270 } else if (CheckOption(opt,"e",ival)) {
00271 Exists = 1;
00272 } else if (CheckOption(opt,"exists",ival)) {
00273 Exists = 1;
00274 } else if (CheckOption(opt,"f",ival)) {
00275 --argc;
00276 ++argv;
00277 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00278 PXcert = *argv;
00279 } else {
00280 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00281 PRT("+ Option '-f' requires a proxy file name: ignoring +");
00282 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00283 argc++;
00284 argv--;
00285 }
00286 } else if (CheckOption(opt,"file",ival)) {
00287 --argc;
00288 ++argv;
00289 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00290 PXcert = *argv;
00291 } else {
00292 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00293 PRT("+ Option '-file' requires a proxy file name: ignoring +");
00294 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00295 argc++;
00296 argv--;
00297 }
00298 } else if (CheckOption(opt,"out",ival)) {
00299 --argc;
00300 ++argv;
00301 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00302 PXcert = *argv;
00303 } else {
00304 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00305 PRT("+ Option '-out' requires a proxy file name: ignoring +");
00306 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00307 argc++;
00308 argv--;
00309 }
00310 } else if (CheckOption(opt,"cert",ival)) {
00311 --argc;
00312 ++argv;
00313 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00314 EEcert = *argv;
00315 } else {
00316 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00317 PRT("+ Option '-cert' requires a cert file name: ignoring +");
00318 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00319 argc++;
00320 argv--;
00321 }
00322 } else if (CheckOption(opt,"key",ival)) {
00323 --argc;
00324 ++argv;
00325 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00326 EEkey = *argv;
00327 } else {
00328 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00329 PRT("+ Option '-key' requires a key file name: ignoring +");
00330 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00331 argc++;
00332 argv--;
00333 }
00334 } else if (CheckOption(opt,"certdir",ival)) {
00335 --argc;
00336 ++argv;
00337 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00338 CAdir = *argv;
00339 } else {
00340 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00341 PRT("+ Option '-certdir' requires a dir path: ignoring +");
00342 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00343 argc++;
00344 argv--;
00345 }
00346 } else if (CheckOption(opt,"valid",ival)) {
00347 --argc;
00348 ++argv;
00349 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00350 Valid = *argv;
00351 } else {
00352 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00353 PRT("+ Option '-valid' requires a time string: ignoring +");
00354 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00355 argc++;
00356 argv--;
00357 }
00358 } else if (CheckOption(opt,"path-length",ival)) {
00359 --argc;
00360 ++argv;
00361 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00362 PathLength = strtol(*argv,0,10);
00363 if (PathLength < -1 || errno == ERANGE) {
00364 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00365 PRT("+ Option '-path-length' requires a number >= -1: ignoring +");
00366 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00367 argc++;
00368 argv--;
00369 }
00370 } else {
00371 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00372 PRT("+ Option '-path-length' requires a number >= -1: ignoring +");
00373 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00374 argc++;
00375 argv--;
00376 }
00377 } else if (CheckOption(opt,"bits",ival)) {
00378 --argc;
00379 ++argv;
00380 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00381 Bits = strtol(*argv, 0, 10);
00382 Bits = (Bits > 512) ? Bits : 512;
00383 if (errno == ERANGE) {
00384 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00385 PRT("+ Option '-bits' requires a number: ignoring +");
00386 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00387 argc++;
00388 argv--;
00389 }
00390 } else {
00391 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00392 PRT("+ Option '-bits' requires a number: ignoring +");
00393 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00394 argc++;
00395 argv--;
00396 }
00397 } else if (CheckOption(opt,"clockskew",ival)) {
00398 --argc;
00399 ++argv;
00400 if (argc >= 0 && (*argv && *(argv)[0] != '-')) {
00401 ClockSkew = strtol(*argv, 0, 10);
00402 if (ClockSkew < -1 || errno == ERANGE) {
00403 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00404 PRT("+ Option '-clockskew' requires a number >= -1: ignoring +");
00405 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00406 argc++;
00407 argv--;
00408 }
00409 } else {
00410 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00411 PRT("+ Option '-clockskew' requires a number >= -1: ignoring +");
00412 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00413 argc++;
00414 argv--;
00415 }
00416 } else {
00417 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00418 PRT("+ Ignoring unrecognized option: "<<*argv);
00419 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00420 }
00421
00422 } else {
00423
00424
00425 opt = *argv;
00426 if (CheckOption(opt,"init",ival)) {
00427 Mode = kM_init;
00428 } else if (CheckOption(opt,"info",ival)) {
00429 Mode = kM_info;
00430 } else if (CheckOption(opt,"destroy",ival)) {
00431 Mode = kM_destroy;
00432 } else if (CheckOption(opt,"help",ival)) {
00433 Mode = kM_help;
00434 } else {
00435 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00436 PRT("+ Ignoring unrecognized keyword mode: "<<opt.c_str());
00437 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00438 }
00439 }
00440 --argc;
00441 ++argv;
00442 }
00443
00444
00445
00446 Mode = (Mode == 0) ? kM_info : Mode;
00447
00448
00449
00450 if (Mode == kM_help) {
00451
00452 Menu();
00453 return 1;
00454 }
00455
00456
00457
00458 struct passwd *pw = 0;
00459
00460
00461
00462 if (PXcert.length() <= 0) {
00463
00464 if (!pw && !(pw = getpwuid(getuid()))) {
00465
00466 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00467 PRT("+ Cannot get info about current user - exit ");
00468 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00469 return 1;
00470 }
00471
00472 PXcert = DefPXcert + (int)(pw->pw_uid);
00473 }
00474
00475
00476 XrdSutExpand(PXcert);
00477
00478 struct stat st;
00479 if (stat(PXcert.c_str(),&st) != 0) {
00480 if (errno != ENOENT) {
00481
00482 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00483 PRT("+ Cannot access requested proxy file: "<<PXcert.c_str());
00484 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00485 return 1;
00486 } else {
00487 if (Mode != kM_init) {
00488
00489 if (!Exists) {
00490 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00491 PRT("+ proxy file: "<<PXcert.c_str()<<" not found");
00492 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00493 }
00494 return 1;
00495 }
00496 }
00497 }
00498
00499
00500
00501 if (Mode == kM_init) {
00502
00503
00504 if (EEcert.length()) {
00505
00506
00507 XrdSutExpand(EEcert);
00508
00509 if (stat(EEcert.c_str(),&st) != 0) {
00510 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00511 PRT("+ Cannot access certificate file: "<<EEcert.c_str());
00512 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00513 return 1;
00514 }
00515 } else {
00516
00517 if (!pw && !(pw = getpwuid(getuid()))) {
00518
00519 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00520 PRT("+ Cannot get info about current user - exit ");
00521 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00522 return 1;
00523 }
00524 EEcert = DefEEcert;
00525 EEcert.insert(XrdSutHome(), 0);
00526 if (stat(EEcert.c_str(),&st) != 0) {
00527 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00528 PRT("+ Cannot access certificate file: "<<EEcert.c_str());
00529 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00530 return 1;
00531 }
00532 }
00533
00534
00535 if (EEkey.length()) {
00536
00537
00538 XrdSutExpand(EEkey);
00539
00540 if (stat(EEkey.c_str(),&st) != 0) {
00541 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00542 PRT("+ Cannot access private key file: "<<EEkey.c_str());
00543 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00544 return 1;
00545 }
00546 } else {
00547
00548 if (!pw && !(pw = getpwuid(getuid()))) {
00549
00550 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00551 PRT("+ Cannot get info about current user - exit ");
00552 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00553 return 1;
00554 }
00555 EEkey = DefEEkey;
00556 EEkey.insert(XrdSutHome(), 0);
00557 if (stat(EEkey.c_str(),&st) != 0) {
00558 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00559 PRT("+ Cannot access certificate file: "<<EEkey.c_str());
00560 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00561 return 1;
00562 }
00563 }
00564
00565 if (!S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) ||
00566 (st.st_mode & (S_IWGRP | S_IWOTH)) != 0 ||
00567 (st.st_mode & (S_IRGRP | S_IROTH)) != 0 ||
00568 (st.st_mode & (S_IWUSR)) != 0) {
00569 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00570 PRT("+ Wrong permissions for file: "<<EEkey.c_str());
00571 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00572 return 1;
00573 }
00574 }
00575
00576 return 0;
00577 }
00578
00579
00580
00581 void Menu()
00582 {
00583
00584
00585 PRT(" ");
00586 PRT(" xrdgsiproxy: application to manage GSI proxies ");
00587 PRT(" ");
00588 PRT(" Syntax:");
00589 PRT(" ");
00590 PRT(" xrdgsiproxy [-h] [<mode>] [options] ");
00591 PRT(" ");
00592 PRT(" ");
00593 PRT(" -h display this menu");
00594 PRT(" ");
00595 PRT(" mode (info, init, destroy) [info]");
00596 PRT(" ");
00597 PRT(" info: display content of existing proxy file");
00598 PRT(" ");
00599 PRT(" init: create proxy certificate and related proxy file");
00600 PRT(" ");
00601 PRT(" destroy: delete existing proxy file");
00602 PRT(" ");
00603 PRT(" options:");
00604 PRT(" ");
00605 PRT(" -debug Print more information while running this"
00606 " query (use if something goes wrong) ");
00607 PRT(" ");
00608 PRT(" -f,-file,-out <file> Non-standard location of proxy file");
00609 PRT(" ");
00610 PRT(" init mode only:");
00611 PRT(" ");
00612 PRT(" -certdir <dir> Non-standard location of directory"
00613 " with information about known CAs");
00614 PRT(" -cert <file> Non-standard location of certificate"
00615 " for which proxies are wanted");
00616 PRT(" -key <file> Non-standard location of the private"
00617 " key to be used to sign the proxy");
00618 PRT(" -bits <bits> strength in bits of the key [512]");
00619 PRT(" -valid <hh:mm> Time validity of the proxy certificate [12:00]");
00620 PRT(" -path-length <len> max number of descendent levels below"
00621 " this proxy [0] ");
00622 PRT(" -e,-exists [options] returns 0 if valid proxy exists, 1 otherwise;");
00623 PRT(" valid options: '-valid <hh:mm>', -bits <bits>");
00624 PRT(" -clockskew <secs> max clock-skewness allowed when checking time validity [30 secs]");
00625 PRT(" ");
00626 }
00627
00628 bool CheckOption(XrdOucString opt, const char *ref, int &ival)
00629 {
00630
00631
00632
00633
00634
00635 bool rc = 0;
00636
00637 int lref = (ref) ? strlen(ref) : 0;
00638 if (!lref)
00639 return rc;
00640 XrdOucString noref = ref;
00641 noref.insert("no",0);
00642
00643 ival = -1;
00644 if (opt == ref) {
00645 ival = 1;
00646 rc = 1;
00647 } else if (opt == noref) {
00648 ival = 0;
00649 rc = 1;
00650 }
00651
00652 return rc;
00653 }
00654
00655 void Display(XrdCryptoX509 *xp)
00656 {
00657
00658
00659 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00660 if (!xp) {
00661 PRT(" Empty certificate! ");
00662 return;
00663 }
00664
00665
00666 PRT("file : "<<PXcert);
00667
00668 PRT("issuer : "<<xp->Issuer());
00669
00670 PRT("subject : "<<xp->Subject());
00671
00672 int pathlen = 0;
00673 XrdSslgsiProxyCertInfo(xp->GetExtension(gsiProxyCertInfo_OID), pathlen);
00674 PRT("path length : "<<pathlen);
00675
00676 PRT("bits : "<<xp->BitStrength());
00677
00678 int tl = xp->NotAfter() -(int)time(0);
00679 int hh = (tl >= 3600) ? (tl/3600) : 0; tl -= (hh*3600);
00680 int mm = (tl >= 60) ? (tl/60) : 0; tl -= (mm*60);
00681 int ss = (tl >= 0) ? tl : 0;
00682 PRT("time left : "<<hh<<"h:"<<mm<<"m:"<<ss<<"s");
00683 PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
00684 }