|
SASC
DVBSoftCAM
|
00001 /* 00002 * Softcam plugin to VDR (C++) 00003 * 00004 * This code is free software; you can redistribute it and/or 00005 * modify it under the terms of the GNU General Public License 00006 * as published by the Free Software Foundation; either version 2 00007 * of the License, or (at your option) any later version. 00008 * 00009 * This code is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License 00015 * along with this program; if not, write to the Free Software 00016 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 00017 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html 00018 */ 00019 00020 #include <stdio.h> 00021 #include <stdlib.h> 00022 #include <string.h> 00023 #include <ctype.h> 00024 00025 #include "system-common.h" 00026 #include "smartcard.h" 00027 #include "crypto.h" 00028 #include "parse.h" 00029 #include "opts.h" 00030 #include "misc.h" 00031 #include "log-sc.h" 00032 #include "log-core.h" 00033 #include "version.h" 00034 00035 SCAPIVERSTAG(); 00036 00037 #define SYSTEM_VIACCESS 0x0500 00038 00039 #define SYSTEM_NAME "SC-Viaccess" 00040 #define SYSTEM_PRI -5 00041 #define SYSTEM_CAN_HANDLE(x) ((x)==SYSTEM_VIACCESS) 00042 00043 #define SC_NAME "Viaccess" 00044 #define SC_ID MAKE_SC_ID('V','i','a','s') 00045 00046 #define L_SC 13 00047 #define L_SC_ALL LALL(L_SC_LASTDEF) 00048 00049 static const struct LogModule lm_sc = { 00050 (LMOD_ENABLE|L_SC_ALL)&LOPT_MASK, 00051 (LMOD_ENABLE|L_SC_DEFDEF)&LOPT_MASK, 00052 "sc-viaccess", 00053 { L_SC_DEFNAMES } 00054 }; 00055 ADD_MODULE(L_SC,lm_sc) 00056 00057 static int disableParental=0; 00058 00059 // -- cSystemScViaccess ------------------------------------------------------------------ 00060 00061 class cSystemScViaccess : public cSystemScCore { //, private cTPS { 00062 public: 00063 cSystemScViaccess(void); 00064 virtual void ParseCADescriptor(cSimpleList<cEcmInfo> *ecms, unsigned short sysId, int source, const unsigned char *data, int len); 00065 }; 00066 00067 cSystemScViaccess::cSystemScViaccess(void) 00068 :cSystemScCore(SYSTEM_NAME,SYSTEM_PRI,SC_ID,"SC Viaccess") 00069 { 00070 hasLogger=true; 00071 } 00072 00073 void cSystemScViaccess::ParseCADescriptor(cSimpleList<cEcmInfo> *ecms, unsigned short sysId, int source, const unsigned char *data, int len) 00074 { 00075 const int pid=WORD(data,2,0x1FFF); 00076 if(pid>=0xAA && pid<=0xCF) { 00077 PRINTF(L_CORE_ECMPROC,"sc-viaccess: dropped \"fake\" ecm pid 0x%04xn",pid); 00078 return; 00079 } 00080 cSystem::ParseCADescriptor(ecms,sysId,source,data,len); 00081 } 00082 00083 // -- cSystemLinkScViaccess -------------------------------------------------------------- 00084 00085 class cSystemLinkScViaccess : public cSystemLink { 00086 public: 00087 cSystemLinkScViaccess(void); 00088 virtual bool CanHandle(unsigned short SysId); 00089 virtual cSystem *Create(void) { return new cSystemScViaccess; } 00090 }; 00091 00092 static cSystemLinkScViaccess staticInit; 00093 00094 cSystemLinkScViaccess::cSystemLinkScViaccess(void) 00095 :cSystemLink(SYSTEM_NAME,SYSTEM_PRI) 00096 { 00097 static const char *rat[] = { 00098 trNOOP("don't touch"), 00099 trNOOP("disable") 00100 }; 00101 00102 opts=new cOpts(SYSTEM_NAME,1); 00103 opts->Add(new cOptSel("DisableParental",trNOOP("SC-Viaccess: Parental lock"),&disableParental,sizeof(rat)/sizeof(char *),rat)); 00104 Feature.NeedsSmartCard(); 00105 } 00106 00107 bool cSystemLinkScViaccess::CanHandle(unsigned short SysId) 00108 { 00109 return smartcards.HaveCard(SC_ID) && SYSTEM_CAN_HANDLE(SysId); 00110 } 00111 00112 // -- cSmartCardDataViaccess --------------------------------------------------- 00113 00114 enum eDataType { dtPIN, dtAES }; 00115 00116 class cSmartCardDataViaccess : public cSmartCardData { 00117 private: 00118 int IdLen(void) const { return 5; } 00119 public: 00120 eDataType type; 00121 unsigned char id[5], pin[8], aes[16]; 00122 // 00123 cSmartCardDataViaccess(void); 00124 cSmartCardDataViaccess(eDataType Type, unsigned char *Id); 00125 virtual bool Parse(const char *line); 00126 virtual bool Matches(cSmartCardData *param); 00127 }; 00128 00129 cSmartCardDataViaccess::cSmartCardDataViaccess(void) 00130 :cSmartCardData(SC_ID) 00131 { 00132 memset(id,0,sizeof(id)); 00133 } 00134 00135 cSmartCardDataViaccess::cSmartCardDataViaccess(eDataType Type, unsigned char *Id) 00136 :cSmartCardData(SC_ID) 00137 { 00138 type=Type; 00139 memset(id,0,sizeof(id)); 00140 memcpy(id,Id,IdLen()); 00141 } 00142 00143 bool cSmartCardDataViaccess::Matches(cSmartCardData *param) 00144 { 00145 cSmartCardDataViaccess *cd=(cSmartCardDataViaccess *)param; 00146 return cd->type==type && !memcmp(cd->id,id,IdLen()); 00147 } 00148 00149 bool cSmartCardDataViaccess::Parse(const char *line) 00150 { 00151 line=skipspace(line); 00152 if(!strncasecmp(line,"PIN",3)) { type=dtPIN; line+=3; } 00153 else if(!strncasecmp(line,"AES",3)) { type=dtAES; line+=3; } 00154 else { 00155 PRINTF(L_CORE_LOAD,"smartcarddataviaccess: format error: datatype"); 00156 return false; 00157 } 00158 line=skipspace(line); 00159 if(GetHex(line,id,IdLen())!=IdLen()) { 00160 PRINTF(L_CORE_LOAD,"smartcarddataviaccess: format error: serial"); 00161 return false; 00162 } 00163 line=skipspace(line); 00164 if(type==dtPIN) { 00165 memset(pin,0,sizeof(pin)); 00166 int i, shift=4; 00167 for(i=0; i<16; i++) { 00168 if(!isdigit(*line)) { 00169 PRINTF(L_CORE_LOAD,"smartcarddataviaccess: format error: pin"); 00170 return false; 00171 } 00172 pin[i]|=(*line++ - '0')<<shift; 00173 shift^=4; 00174 } 00175 i=(i+1)/2; // num bytes 00176 memmove(&pin[8-i],pin,i); 00177 //XXX 00178 LDUMP(L_CORE_LOAD,pin,sizeof(pin),"smartcarddataviaccess: pin "); 00179 } 00180 else { 00181 if(GetHex(line,aes,16)!=16) { 00182 PRINTF(L_CORE_LOAD,"smartcarddataviaccess: format error: aes"); 00183 return false; 00184 } 00185 } 00186 return true; 00187 } 00188 00189 // -- cProviderScViaccess ---------------------------------------------------------- 00190 00191 class cProviderScViaccess : public cProviderViaccess { 00192 public: 00193 unsigned char availKeys[16]; 00194 char name[33]; 00195 // 00196 cProviderScViaccess(const unsigned char *id, const unsigned char *s):cProviderViaccess(id,s) {} 00197 }; 00198 00199 // -- cSmartCardViaccess ----------------------------------------------------------------- 00200 00201 class cSmartCardViaccess : public cSmartCard, public cIdSet, private cAES { 00202 private: 00203 unsigned char ua[5], sa0[4], lastId[3]; 00204 // 00205 unsigned char GetSW1(void) { return sb[1]; } 00206 bool CheckKey(cProviderScViaccess *p, const unsigned char keynr); 00207 bool SetProvider(const unsigned char *id); 00208 public: 00209 cSmartCardViaccess(void); 00210 virtual bool Init(void); 00211 virtual bool Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw); 00212 virtual bool Update(int pid, int caid, const unsigned char *data); 00213 }; 00214 00215 static const struct StatusMsg msgs[] = { 00216 { { 0x6b,0x00 }, "Instruction not supported", false }, 00217 { { 0x6d,0x00 }, "Instruction not supported", false }, 00218 { { 0x90,0x00 }, "Instruction executed without errors", true }, 00219 { { 0x90,0x08 }, "Instruction executed without errors", true }, 00220 { { 0xFF,0xFF }, 0, false } 00221 }; 00222 00223 static const struct CardConfig cardCfg = { 00224 SM_8O2,500,300 00225 }; 00226 00227 cSmartCardViaccess::cSmartCardViaccess(void) 00228 :cSmartCard(&cardCfg,msgs) 00229 { 00230 memset(lastId,0,sizeof(lastId)); 00231 } 00232 00233 bool cSmartCardViaccess::Init(void) 00234 { 00235 static const unsigned char verifyBytes[] = { 0x90,0x00 }; 00236 if(atr->T!=0 || atr->histLen<7 || memcmp(&atr->hist[5],verifyBytes,sizeof(verifyBytes))) { 00237 PRINTF(L_SC_INIT,"doesn't look like a Viaccess card"); 00238 return false; 00239 } 00240 00241 infoStr.Begin(); 00242 infoStr.Strcat("Viaccess smartcard\n"); 00243 const char *ver=0; 00244 switch((atr->hist[3]<<8)|atr->hist[4]) { 00245 case 0x6268: ver="2.3"; break; 00246 case 0x6468: 00247 case 0x6668: ver="2.4"; break; 00248 case 0xa268: ver="2.5"; break; 00249 case 0xc168: ver="2.6"; break; 00250 default: ver="unknown"; break; 00251 } 00252 00253 PRINTF(L_SC_INIT,"card v.%s",ver); 00254 snprintf(idStr,sizeof(idStr),"%s (V.%s)",SC_NAME,ver); 00255 00256 static unsigned char insac[] = { 0xca, 0xac, 0x00, 0x00, 0x00 }; // select data 00257 static unsigned char insb8[] = { 0xca, 0xb8, 0x00, 0x00, 0x00 }; // read selected data 00258 static unsigned char insa4[] = { 0xca, 0xa4, 0x00, 0x00, 0x00 }; // select issuer 00259 static unsigned char insc0[] = { 0xca, 0xc0, 0x00, 0x00, 0x00 }; // read data item 00260 unsigned char buff[MAX_LEN]; 00261 00262 ResetIdSet(); 00263 insac[2]=0xa4; // request unique id 00264 if(!IsoWrite(insac,buff) || !Status()) { 00265 PRINTF(L_SC_ERROR,"failed to request ua"); 00266 return false; 00267 } 00268 insb8[4]=0x07; // read unique id 00269 if(!IsoRead(insb8,buff) || !Status() || buff[1]!=0x05) { 00270 PRINTF(L_SC_ERROR,"failed to read ua"); 00271 return false; 00272 } 00273 memcpy(ua,&buff[2],sizeof(ua)); 00274 SetCard(new cCardViaccess(ua)); 00275 PRINTF(L_SC_INIT,"card UA: %llu",Bin2LongLong(ua,5)); 00276 infoStr.Printf("Card v.%s UA %010llu\n",ver,Bin2LongLong(ua,5)); 00277 00278 insa4[2]=0x00; // select issuer 0 00279 if(!IsoWrite(insa4,buff) || !Status()) { 00280 PRINTF(L_SC_ERROR,"failed to select issuer 0"); 00281 return false; 00282 } 00283 int issuer=0; 00284 do { 00285 insc0[4]=0x1a; // show provider properties 00286 if(!IsoRead(insc0,buff) || !Status()) { 00287 PRINTF(L_SC_ERROR,"failed to read prov properties"); 00288 return false; 00289 } 00290 00291 unsigned char buff2[MAX_LEN]; 00292 insac[2]=0xa5; // request sa 00293 if(!IsoWrite(insac,buff2) || !Status()) { 00294 PRINTF(L_SC_ERROR,"failed to request sa"); 00295 return false; 00296 } 00297 insb8[4]=0x06; // read sa 00298 if(!IsoRead(insb8,buff2) || !Status()) { 00299 PRINTF(L_SC_ERROR,"failed to read sa"); 00300 return false; 00301 } 00302 if(issuer==0) memcpy(sa0,&buff[2],sizeof(sa0)); 00303 cProviderScViaccess *p=new cProviderScViaccess(&buff[0],&buff2[2]); 00304 if(p) { 00305 AddProv(p); 00306 memcpy(p->availKeys,buff+10,sizeof(p->availKeys)); 00307 00308 insac[2]=0xa7; // request name 00309 if(!IsoWrite(insac,buff) || !Status()) { 00310 PRINTF(L_SC_ERROR,"failed to request prov name"); 00311 return false; 00312 } 00313 insb8[4]=0x02; // read name nano + len 00314 if(!IsoRead(insb8,buff) || !Status()) { 00315 PRINTF(L_SC_ERROR,"failed to read prov name length"); 00316 return false; 00317 } 00318 unsigned int nameLen=buff[1]; 00319 if(nameLen>=sizeof(p->name)) { 00320 PRINTF(L_SC_ERROR,"provider name buffer overflow"); 00321 nameLen=sizeof(p->name)-1; 00322 } 00323 insb8[4]=nameLen; 00324 if(!IsoRead(insb8,buff) || !Status()) { 00325 PRINTF(L_SC_ERROR,"failed to read prov name"); 00326 return false; 00327 } 00328 memcpy(p->name,buff,nameLen); p->name[nameLen]=0; 00329 00330 PRINTF(L_SC_INIT,"provider %06x (%s)",(int)p->ProvId(),p->name); 00331 infoStr.Printf("Prov %06x (%s) SA %08u\n",(int)p->ProvId(),p->name,Bin2Int(&buff2[2],4)); 00332 } 00333 else PRINTF(L_SC_ERROR,"no memory for provider"); 00334 00335 insa4[2]=0x02; // next issuer 00336 if(!IsoWrite(insa4,buff) || !Status()) { 00337 PRINTF(L_SC_ERROR,"failed to select next issuer"); 00338 return false; 00339 } 00340 issuer++; 00341 } while(GetSW1()==0x00); 00342 infoStr.Finish(); 00343 00344 if(disableParental) { 00345 static unsigned char ins24[] = { 0xca,0x24,0x02,0x00,0x09 }; 00346 static unsigned char pindt[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F }; 00347 cSmartCardDataViaccess cd(dtPIN,ua); 00348 cSmartCardDataViaccess *entry=(cSmartCardDataViaccess *)smartcards.FindCardData(&cd); 00349 if(entry) { 00350 PRINTF(L_SC_INIT,"got PIN from smartcard.conf."); 00351 memcpy(&pindt[0],entry->pin,8); 00352 if(!IsoWrite(ins24,pindt) || !Status()) 00353 PRINTF(L_SC_ERROR,"failed to disable parental lock"); 00354 } 00355 else PRINTF(L_SC_INIT,"no PIN available."); 00356 } 00357 cSmartCardDataViaccess cd(dtAES,ua); 00358 cSmartCardDataViaccess *entry=(cSmartCardDataViaccess *)smartcards.FindCardData(&cd); 00359 if(entry) { 00360 PRINTF(L_SC_INIT,"got AES key from smartcard.conf."); 00361 SetKey(entry->aes); 00362 } 00363 00364 return true; 00365 } 00366 00367 bool cSmartCardViaccess::CheckKey(cProviderScViaccess *p, const unsigned char keynr) 00368 { 00369 for(unsigned int j=0; j<sizeof(p->availKeys); j++) 00370 if(p->availKeys[j]==keynr) return true; 00371 return false; 00372 } 00373 00374 bool cSmartCardViaccess::SetProvider(const unsigned char *id) 00375 { 00376 static const unsigned char insa4[] = { 0xca,0xa4,0x04,0x00,0x03 }; 00377 00378 if(id[0]!=lastId[0] || id[1]!=lastId[1] || (id[2]&0xF0)!=lastId[2]) { 00379 memcpy(lastId,id,3); 00380 lastId[2]&=0xF0; 00381 if(!IsoWrite(insa4,lastId) || !Status()) return false; 00382 } 00383 return true; 00384 } 00385 00386 bool cSmartCardViaccess::Decode(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw) 00387 { 00388 static unsigned char ins88[] = { 0xca,0x88,0x00,0x00,0x00 }; // set ecm 00389 static unsigned char insf8[] = { 0xca,0xf8,0x00,0x00,0x00 }; // set geographic info 00390 static unsigned char insc0[] = { 0xca,0xc0,0x00,0x00,0x12 }; // read dcw 00391 00392 cProviderScViaccess *p=(cProviderScViaccess *)FindProv(data); 00393 if(p) { 00394 unsigned char keynr=cParseViaccess::KeyNr(data); 00395 if(CheckKey(p,keynr)) { 00396 if(!SetProvider(cParseViaccess::ProvIdPtr(data))) return false; 00397 00398 const unsigned char *start=cParseViaccess::PayloadStart(data); 00399 bool hasD2=false; 00400 if(start[0]==0xD2) { // TPS alike, TNTSAT/TNTop 00401 start+=start[1]+2; 00402 hasD2=true; 00403 } 00404 start+=5; 00405 00406 const unsigned char *DE04=0; 00407 if(start[0]==0xDE && start[1]==0x04) { 00408 DE04=start; 00409 start+=6; 00410 } 00411 const unsigned char *ecm88Data=start; 00412 int ecm88Len=SCT_LEN(data)-(start-data); 00413 int ecmf8Len=0; 00414 while(ecm88Len>0 && ecm88Data[0]<0xA0) { 00415 const int nanoLen=ecm88Data[1]+2; 00416 ecmf8Len+=nanoLen; 00417 ecm88Len-=nanoLen; ecm88Data+=nanoLen; 00418 } 00419 if(ecmf8Len) { 00420 insf8[3]=keynr; 00421 insf8[4]=ecmf8Len; 00422 if(!IsoWrite(insf8,start) || !Status()) return false; 00423 } 00424 00425 ins88[2]=ecmf8Len?1:0; 00426 ins88[3]=keynr; 00427 ins88[4]=ecm88Len; 00428 if(DE04) { 00429 unsigned char *mem=AUTOMEM(ecm88Len+6); 00430 memcpy(mem,DE04,6); 00431 memcpy(mem+6,ecm88Data,ecm88Len); 00432 ins88[4]+=6; 00433 ecm88Data=mem; 00434 } 00435 if(!IsoWrite(ins88,ecm88Data) || !Status()) return false; // request dcw 00436 unsigned char buff[MAX_LEN]; 00437 if(!IsoRead(insc0,buff) || !Status()) return false; // read dcw 00438 unsigned char cwbuff[16]; 00439 int cwmask=0; 00440 switch(buff[0]) { 00441 case 0xe8: // even 00442 if(buff[1]==8) { memcpy(cwbuff,buff+2,8); cwmask=1; } 00443 break; 00444 case 0xe9: // odd 00445 if(buff[1]==8) { memcpy(cwbuff+8,buff+2,8); cwmask=2; } 00446 break; 00447 case 0xea: // complete 00448 if(buff[1]==16) { memcpy(cwbuff,buff+2,16); cwmask=3; } 00449 break; 00450 } 00451 if(cwmask) { 00452 if(hasD2 && !Decrypt(cwbuff,16)) 00453 PRINTF(L_SC_ERROR,"no AES key for this card"); 00454 if(cwmask&1) memcpy(cw,cwbuff,8); 00455 if(cwmask&2) memcpy(cw+8,cwbuff+8,8); 00456 return true; 00457 } 00458 } 00459 else PRINTF(L_SC_ERROR,"key not found on card"); 00460 } 00461 else PRINTF(L_SC_ERROR,"provider not found on card"); 00462 return false; 00463 } 00464 00465 bool cSmartCardViaccess::Update(int pid, int caid, const unsigned char *data) 00466 { 00467 static unsigned char insf0[] = { 0xca,0xf0,0x00,0x00,0x00 }; 00468 static unsigned char ins18[] = { 0xca,0x18,0x00,0x00,0x00 }; 00469 static unsigned char insf4[] = { 0xca,0xf4,0x00,0x00,0x00 }; 00470 static unsigned char ins1c[] = { 0xca,0x1c,0x01,0x00,0x00 }; 00471 00472 int updtype; 00473 cAssembleData ad(data); 00474 if(MatchAndAssemble(&ad,&updtype,0)) { 00475 while((data=ad.Assembled())) { 00476 const unsigned char *start=cParseViaccess::NanoStart(data); 00477 int nanolen=SCT_LEN(data)-(start-data); 00478 00479 if(start && nanolen>=5) { 00480 const unsigned char *nano81=0, *nano91=0, *nano92=0, *nano9e=0, *nanof0=0; 00481 unsigned char *nanos18=AUTOMEM(nanolen+10); 00482 bool provOK=false; 00483 int len18=0, keynr=0; 00484 00485 while(nanolen>0) { 00486 switch(start[0]) { 00487 case 0x90: 00488 if(SetProvider(&start[2])) { 00489 provOK=true; 00490 keynr=cParseViaccess::KeyNrFromNano(start); 00491 } 00492 break; 00493 case 0x9E: // ADF 00494 if(!nano91) { // not crypted 00495 unsigned char custwp=sa0[3]; 00496 const unsigned char *afd=&start[2]; 00497 if(!(afd[31-custwp/8] & (1<<(custwp&7)))) { // not for this card 00498 provOK=false; nanolen=0; // skip, next assemble data 00499 break; 00500 } 00501 } 00502 nano9e=start; 00503 break; 00504 case 0x81: nano81=start; break; 00505 case 0x91: nano91=start; break; 00506 case 0x92: nano92=start; break; 00507 case 0xF0: nanof0=start; break; 00508 default: 00509 { 00510 int l=start[1]+2; 00511 memcpy(nanos18+len18,start,l); 00512 len18+=l; 00513 } 00514 } 00515 int l=start[1]+2; 00516 start+=l; nanolen-=l; 00517 } 00518 00519 if(provOK && nanof0) { 00520 unsigned char buff[MAX_LEN*2]; 00521 if(nano9e) { 00522 int l9e=nano9e[1]+2; 00523 if(!nano91) { 00524 insf0[3]=keynr; 00525 insf0[4]=l9e; 00526 if(!IsoWrite(insf0,nano9e) || !Status()) continue; 00527 } 00528 else { 00529 int l91=nano91[1]+2; 00530 insf4[3]=keynr; 00531 insf4[4]=l91+l9e; 00532 memcpy(buff,nano91,l91); 00533 memcpy(buff+l91,nano9e,l9e); 00534 if(!IsoWrite(insf4,buff) || !Status()) continue; 00535 } 00536 } 00537 if(!nano92) { 00538 int lf0=nanof0[1]+2; 00539 ins18[2]=updtype>0 ? updtype-1 : updtype; 00540 ins18[3]=keynr; 00541 ins18[4]=len18+lf0; 00542 memcpy(buff,nanos18,len18); 00543 memcpy(buff+len18,nanof0,lf0); 00544 if(IsoWrite(ins18,buff)) Status(); 00545 } 00546 else if(nano81) { 00547 int l92=nano92[1]+2, l81=nano81[1]+2, lf0=nanof0[1]+2; 00548 ins1c[3]=keynr; 00549 ins1c[4]=l92+l81+lf0; 00550 memcpy(buff,nano92,l92); 00551 memcpy(buff+l92,nano81,l81); 00552 memcpy(buff+l92+l81,nanof0,lf0); 00553 if(IsoWrite(ins1c,buff)) Status(); 00554 } 00555 } 00556 } 00557 } 00558 return true; 00559 } 00560 return false; 00561 } 00562 00563 // -- cSmartCardLinkViaccess ------------------------------------------------------------- 00564 00565 class cSmartCardLinkViaccess : public cSmartCardLink { 00566 public: 00567 cSmartCardLinkViaccess(void):cSmartCardLink(SC_NAME,SC_ID) {} 00568 virtual cSmartCard *Create(void) { return new cSmartCardViaccess(); } 00569 }; 00570 00571 static cSmartCardLinkViaccess staticScInit;