Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

# Authors: John Dennis <jdennis@redhat.com> 

# 

# Copyright (C) 2012  Red Hat 

# see file 'COPYING' for use and warranty information 

# 

# This program is free software; you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with this program.  If not, see <http://www.gnu.org/licenses/>. 

 

import krbV 

import time 

import re 

from ipapython.ipa_log_manager import * 

 

#------------------------------------------------------------------------------- 

 

# Kerberos constants, should be defined in krbV, but aren't 

KRB5_GC_CACHED = 0x2 

 

# Kerberos error codes, should be defined in krbV, but aren't 

KRB5_CC_NOTFOUND                = -1765328243 # Matching credential not found 

KRB5_FCC_NOFILE                 = -1765328189 # No credentials cache found 

KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN = -1765328377 # Server not found in Kerberos database 

KRB5KRB_AP_ERR_TKT_EXPIRED      = -1765328352 # Ticket expired 

KRB5_FCC_PERM                   = -1765328190 # Credentials cache permissions incorrect 

KRB5_CC_FORMAT                  = -1765328185 # Bad format in credentials cache 

KRB5_REALM_CANT_RESOLVE         = -1765328164 # Cannot resolve network address for KDC in requested realm 

 

 

krb_ticket_expiration_threshold = 60*5 # number of seconds to accmodate clock skew 

krb5_time_fmt = '%m/%d/%y %H:%M:%S' 

ccache_name_re = re.compile(r'^((\w+):)?(.+)') 

 

#------------------------------------------------------------------------------- 

 

def krb5_parse_ccache(ccache_name): 

    ''' 

    Given a Kerberos ccache name parse it into it's scheme and 

    location components. Currently valid values for the scheme 

    are: 

 

      * FILE 

      * MEMORY 

 

    The scheme is always returned as upper case. If the scheme 

    does not exist it defaults to FILE. 

 

    :parameters: 

      ccache_name 

        The name of the Kerberos ccache. 

    :returns: 

      A two-tuple of (scheme, ccache) 

    ''' 

    match = ccache_name_re.search(ccache_name) 

    if match: 

        scheme = match.group(2) 

        location = match.group(3) 

        if scheme is None: 

            scheme = 'FILE' 

        else: 

            scheme = scheme.upper() 

 

        return scheme, location 

    else: 

        raise ValueError('Invalid ccache name = "%s"' % ccache_name) 

 

def krb5_unparse_ccache(scheme, name): 

    return '%s:%s' % (scheme.upper(), name) 

 

def krb5_format_principal_name(user, realm): 

    ''' 

    Given a Kerberos user principal name and a Kerberos realm 

    return the Kerberos V5 user principal name. 

 

    :parameters: 

      user 

        User principal name. 

      realm 

        The Kerberos realm the user exists in. 

    :returns: 

      Kerberos V5 user principal name. 

    ''' 

    return '%s@%s' % (user, realm) 

 

def krb5_format_service_principal_name(service, host, realm): 

    ''' 

 

    Given a Kerberos service principal name, the host where the 

    service is running and a Kerberos realm return the Kerberos V5 

    service principal name. 

 

    :parameters: 

      service 

        Service principal name. 

      host 

        The DNS name of the host where the service is located. 

      realm 

        The Kerberos realm the service exists in. 

    :returns: 

      Kerberos V5 service principal name. 

    ''' 

    return '%s/%s@%s' % (service, host, realm) 

 

def krb5_format_tgt_principal_name(realm): 

    ''' 

    Given a Kerberos realm return the Kerberos V5 TGT name. 

 

    :parameters: 

      realm 

        The Kerberos realm the TGT exists in. 

    :returns: 

      Kerberos V5 TGT name. 

    ''' 

    return krb5_format_service_principal_name('krbtgt', realm, realm) 

 

def krb5_format_time(timestamp): 

    ''' 

    Given a UNIX timestamp format it into a string in the same 

    manner the MIT Kerberos library does. Kerberos timestamps are 

    always in local time. 

 

    :parameters: 

      timestamp 

        Unix timestamp 

    :returns: 

      formated string 

    ''' 

    return time.strftime(krb5_time_fmt, time.localtime(timestamp)) 

 

class KRB5_CCache(object): 

    ''' 

    Kerberos stores a TGT (Ticket Granting Ticket) and the service 

    tickets bound to it in a ccache (credentials cache). ccaches are 

    bound to a Kerberos user principal. This class opens a Kerberos 

    ccache and allows one to manipulate it. Most useful is the 

    extraction of ticket entries (cred's) in the ccache and the 

    ability to examine their attributes. 

    ''' 

 

    def __init__(self, ccache): 

        ''' 

        :parameters: 

          ccache 

            The name of a Kerberos ccache used to hold Kerberos tickets. 

        :returns: 

          `KRB5_CCache` object encapsulting the ccache. 

        ''' 

        log_mgr.get_logger(self, True) 

        self.context = None 

        self.scheme = None 

        self.name = None 

        self.ccache = None 

        self.principal = None 

 

        try: 

            self.context = krbV.default_context() 

            self.scheme, self.name = krb5_parse_ccache(ccache) 

            self.ccache = krbV.CCache(name=str(ccache), context=self.context) 

            self.principal = self.ccache.principal() 

        except krbV.Krb5Error, e: 

            error_code = e.args[0] 

            message = e.args[1] 

            if error_code == KRB5_FCC_NOFILE: 

                raise ValueError('"%s", ccache="%s"' % (message, ccache)) 

            else: 

                raise e 

 

    def ccache_str(self): 

        ''' 

        A Kerberos ccache is identified by a name comprised of a 

        scheme and location component. This function returns that 

        canonical name. See `krb5_parse_ccache()` 

 

        :returns: 

          The name of ccache with it's scheme and location components. 

        ''' 

 

        return '%s:%s' % (self.scheme, self.name) 

 

    def __str__(self): 

        return 'cache="%s" principal="%s"' % (self.ccache_str(), self.principal.name) 

 

    def get_credentials(self, principal): 

        ''' 

        Given a Kerberos principal return the krbV credentials 

        tuple describing the credential. If the principal does 

        not exist in the ccache a KeyError is raised. 

 

        :parameters: 

          principal 

            The Kerberos principal whose ticket is being retrieved. 

            The principal may be either a string formatted as a 

            Kerberos V5 principal or it may be a `krbV.Principal` 

            object. 

        :returns: 

          A krbV credentials tuple. If the principal is not in the 

          ccache a KeyError is raised. 

 

        ''' 

 

        if isinstance(principal, krbV.Principal): 

            krbV_principal = principal 

        else: 

            try: 

                krbV_principal = krbV.Principal(str(principal), self.context) 

            except Exception, e: 

                self.error('could not create krbV principal from "%s", %s', principal, e) 

                raise e 

 

        creds_tuple = (self.principal, 

                       krbV_principal, 

                       (0, None),    # keyblock: (enctype, contents) 

                       (0, 0, 0, 0), # times: (authtime, starttime, endtime, renew_till) 

                       0,0,          # is_skey, ticket_flags 

                       None,         # addrlist 

                       None,         # ticket_data 

                       None,         # second_ticket_data 

                       None)         # adlist 

        try: 

            cred = self.ccache.get_credentials(creds_tuple, KRB5_GC_CACHED) 

        except krbV.Krb5Error, e: 

            error_code = e.args[0] 

            if error_code == KRB5_CC_NOTFOUND: 

                raise KeyError('"%s" credential not found in "%s" ccache' % \ 

                               (krbV_principal.name, self.ccache_str())) 

            raise e 

        except Exception, e: 

            raise e 

 

        return cred 

 

    def get_credential_times(self, principal): 

        ''' 

        Given a Kerberos principal return the ticket timestamps if the 

        principal's ticket in the ccache is valid.  If the principal 

        does not exist in the ccache a KeyError is raised. 

 

        The return credential time values are Unix timestamps in 

        localtime. 

 

        The returned timestamps are: 

 

        authtime 

          The time when the ticket was issued. 

        starttime 

          The time when the ticket becomes valid. 

        endtime 

          The time when the ticket expires. 

        renew_till 

          The time when the ticket becomes no longer renewable (if renewable). 

 

        :parameters: 

          principal 

            The Kerberos principal whose ticket is being validated. 

            The principal may be either a string formatted as a 

            Kerberos V5 principal or it may be a `krbV.Principal` 

            object. 

        :returns: 

            return authtime, starttime, endtime, renew_till 

        ''' 

 

        if isinstance(principal, krbV.Principal): 

            krbV_principal = principal 

        else: 

            try: 

                krbV_principal = krbV.Principal(str(principal), self.context) 

            except Exception, e: 

                self.error('could not create krbV principal from "%s", %s', principal, e) 

                raise e 

 

        try: 

            cred = self.get_credentials(krbV_principal) 

            authtime, starttime, endtime, renew_till = cred[3] 

 

            self.debug('get_credential_times: principal=%s, authtime=%s, starttime=%s, endtime=%s, renew_till=%s', 

                       krbV_principal.name, 

                       krb5_format_time(authtime), krb5_format_time(starttime), 

                       krb5_format_time(endtime), krb5_format_time(renew_till)) 

 

            return authtime, starttime, endtime, renew_till 

 

        except KeyError, e: 

            raise e 

        except Exception, e: 

            self.error('get_credential_times failed, principal="%s" error="%s"', krbV_principal.name, e) 

            raise e 

 

    def credential_is_valid(self, principal): 

        ''' 

        Given a Kerberos principal return a boolean indicating if the 

        principal's ticket in the ccache is valid. If the ticket is 

        not in the ccache False is returned. If the ticket 

        exists in the ccache it's validity is checked and returned. 

 

        :parameters: 

          principal 

            The Kerberos principal whose ticket is being validated. 

            The principal may be either a string formatted as a 

            Kerberos V5 principal or it may be a `krbV.Principal` 

            object. 

        :returns: 

          True if the principal's ticket exists and is valid. False if 

          the ticket does not exist or if the ticket is not valid. 

        ''' 

 

        try: 

            authtime, starttime, endtime, renew_till = self.get_credential_times(principal) 

        except KeyError, e: 

            return False 

        except Exception, e: 

            self.error('credential_is_valid failed, principal="%s" error="%s"', principal, e) 

            raise e 

 

 

        now = time.time() 

        if starttime > now: 

            return False 

        if endtime < now: 

            return False 

        return True 

 

    def valid(self, host, realm): 

        ''' 

        Test to see if ldap service ticket or the TGT is valid. 

 

        :parameters: 

          host 

            ldap server 

          realm 

            kerberos realm 

        :returns: 

          True if either the ldap service ticket or the TGT is valid, 

          False otherwise. 

        ''' 

 

        try: 

            principal = krb5_format_service_principal_name('HTTP', host, realm) 

            valid = self.credential_is_valid(principal) 

            if valid: 

                return True 

        except KeyError: 

            pass 

 

        try: 

            principal = krb5_format_tgt_principal_name(realm) 

            valid = self.credential_is_valid(principal) 

            return valid 

        except KeyError: 

            return False 

 

    def endtime(self, host, realm): 

        ''' 

        Returns the minimum endtime for tickets of interest (ldap service or TGT). 

 

        :parameters: 

          host 

            ldap server 

          realm 

            kerberos realm 

        :returns: 

          UNIX timestamp value. 

        ''' 

 

        result = 0 

        try: 

            principal = krb5_format_service_principal_name('HTTP', host, realm) 

            authtime, starttime, endtime, renew_till = self.get_credential_times(principal) 

            if result: 

                result = min(result, endtime) 

            else: 

                result = endtime 

        except KeyError: 

            pass 

 

        try: 

            principal = krb5_format_tgt_principal_name(realm) 

            authtime, starttime, endtime, renew_till = self.get_credential_times(principal) 

            if result: 

                result = min(result, endtime) 

            else: 

                result = endtime 

        except KeyError: 

            pass 

 

        self.debug('KRB5_CCache %s endtime=%s (%s)', self.ccache_str(), result, krb5_format_time(result)) 

        return result