1/* 2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24/* ----------------------------------------------------------------------------- 25 * 26 * Theory of operation : 27 * 28 * plugin to add a generic socket support to pppd, instead of tty. 29 * 30----------------------------------------------------------------------------- */ 31 32 33/* ----------------------------------------------------------------------------- 34 Includes 35----------------------------------------------------------------------------- */ 36 37#include <stdio.h> 38#include <ctype.h> 39#include <stdlib.h> 40#include <string.h> 41#include <unistd.h> 42#include <signal.h> 43#include <errno.h> 44#include <fcntl.h> 45#include <syslog.h> 46#include <netdb.h> 47#include <pwd.h> 48#include <setjmp.h> 49#include <sys/param.h> 50#include <sys/types.h> 51#include <sys/wait.h> 52#include <sys/time.h> 53#include <sys/resource.h> 54#include <sys/stat.h> 55#include <sys/socket.h> 56#include <netinet/in.h> 57#include <arpa/inet.h> 58#include <sys/ioctl.h> 59#include <pthread.h> 60 61#include <net/if.h> 62#include <CoreFoundation/CFBundle.h> 63#include <CoreFoundation/CFUserNotification.h> 64#if TARGET_OS_EMBEDDED 65#include <CoreFoundation/CFNumber.h> 66#endif 67 68#define APPLE 1 69 70#include "../Family/ppp_defs.h" 71#include "../Family/if_ppp.h" 72#include "../Family/ppp_domain.h" 73#include "../Helpers/pppd/pppd.h" 74#include "../Helpers/pppd/fsm.h" 75#include "../Helpers/pppd/lcp.h" 76 77 78/* ----------------------------------------------------------------------------- 79 Definitions 80----------------------------------------------------------------------------- */ 81 82#define ICON "NetworkConnect.icns" 83 84#define DIALOG_PASSWORD_ASK 0 85#define DIALOG_PASSWORD_RETRY 1 86#define DIALOG_PASSWORD_CHANGE 2 87 88/* ----------------------------------------------------------------------------- 89 Forward declarations 90----------------------------------------------------------------------------- */ 91 92#ifdef UNUSED 93static int dialog_idle(struct ppp_idle *idle); 94#endif 95static int dialog_start_link(); 96static int dialog_change_password(); 97static int dialog_retry_password(); 98static int dialog_link_up(); 99static int dialog_ask(CFStringRef message, CFStringRef ok, CFStringRef cancel, int timeout); 100static void dialog_reminder(void *arg); 101static void dialog_phasechange(void *arg, uintptr_t p); 102static void dialog_change_reminder(); 103 104/* ----------------------------------------------------------------------------- 105 PPP globals 106----------------------------------------------------------------------------- */ 107 108extern int kill_link; 109 110static CFBundleRef bundle = 0; /* our bundle ref */ 111static CFURLRef bundleURL = 0; 112static pthread_t reminderthread = 0; 113static int reminderresult = 0; 114 115/* option variables */ 116static bool askpasswordafter = 0; /* Ask password after physical connection is established */ 117static bool noaskpassword = 0; /* Don't ask for a password before to connect */ 118static bool noidleprompt = 0; /* Don't ask user before to disconnect on idle */ 119static int reminder = 0; /* Ask user to stay connected after reminder period */ 120static int dialogtype = 0; /* 0 = standard ppp, 1 = vpn */ 121 122static pthread_t dialog_ui_thread; /* UI thread */ 123static int dialog_ui_fds[2]; /* files descriptors for UI thread */ 124static CFUserNotificationRef dialog_alert = 0; /* the dialog ref */ 125 126 127/* option descriptors */ 128option_t dialogs_options[] = { 129 { "noaskpassword", o_bool, &noaskpassword, 130 "Don't ask for a password at all", 1 }, 131 { "askpasswordafter", o_bool, &askpasswordafter, 132 "Don't ask for a password before to connect", 1 }, 133 { "noidleprompt", o_bool, &noidleprompt, 134 "Don't ask user before to disconnect on idle", 1 }, 135 { "reminder", o_int, &reminder, 136 "Ask user to stay connected after reminder period", 0, 0, 0, 0xFFFFFFFF, 0, 0, 0, dialog_change_reminder }, 137 { "dialogtype", o_int, &dialogtype, 138 "Dialog type to display (PPP or VPN)"}, 139 { NULL } 140}; 141 142/* ----------------------------------------------------------------------------- 143plugin entry point, called by pppd 144----------------------------------------------------------------------------- */ 145int start(CFBundleRef ref) 146{ 147 148 bundle = ref; 149 bundleURL = CFBundleCopyBundleURL(bundle); 150 if (!bundleURL) 151 return 1; 152 153 CFRetain(bundle); 154 155 add_options(dialogs_options); 156 157 add_notifier(&phasechange, dialog_phasechange, 0); 158 159 start_link_hook = dialog_start_link; 160 change_password_hook = dialog_change_password; 161 retry_password_hook = dialog_retry_password; 162 link_up_hook = dialog_link_up; 163 //idle_time_hook = dialog_idle; 164 165 return 0; 166} 167 168/* ----------------------------------------------------------------------------- 169----------------------------------------------------------------------------- */ 170void dialog_phasechange(void *arg, uintptr_t p) 171{ 172 if (reminder && p == PHASE_RUNNING) 173 TIMEOUT(dialog_reminder, 0, reminder); 174 else 175 UNTIMEOUT(dialog_reminder, 0); 176} 177 178/* ----------------------------------------------------------------------------- 179----------------------------------------------------------------------------- */ 180void dialog_change_reminder() 181{ 182 UNTIMEOUT(dialog_reminder, 0); 183 if (reminder && phase == PHASE_RUNNING) 184 TIMEOUT(dialog_reminder, 0, reminder); 185} 186 187/* ----------------------------------------------------------------------------- 188----------------------------------------------------------------------------- */ 189void *dialog_reminder_thread(void *arg) 190{ 191 int status, timeout; 192 193 status = pthread_detach(pthread_self()); 194 if (status == noErr) { 195 196 // use an adaptative timeout 197 if (reminder < (60 * 5)) timeout = 30; 198 else if (reminder < (60 * 10)) timeout = 60; 199 else if (reminder < (60 * 20)) timeout = 120; 200 else if (reminder < (60 * 30)) timeout = 180; 201 else timeout = 240; 202 203 reminderresult = dialog_ask(CFSTR("Reminder timeout"), CFSTR("Stay connected"), CFSTR("Disconnect"), timeout); 204 } 205 return 0; 206} 207 208/* ----------------------------------------------------------------------------- 209----------------------------------------------------------------------------- */ 210void dialog_reminder_watch(void *arg) 211{ 212 int tlim; 213 214 switch (reminderresult) { 215 case -1: 216 // rearm reminder watch dog every 2 seconds 217 TIMEOUT(dialog_reminder_watch, 0, 2); 218 break; 219 case 0: 220 // user click stay connected 221 TIMEOUT(dialog_reminder, 0, reminder); 222 // reset the idle timer 223 UNTIMEOUT(check_idle, NULL); 224 if (idle_time_hook != 0) 225 tlim = (*idle_time_hook)(NULL); 226 else 227 tlim = idle_time_limit; 228 if (tlim > 0) 229 TIMEOUT(check_idle, NULL, tlim); 230 break; 231 default : 232 // user clicked Disconnect or timeout expired 233 lcp_close(0, "User request"); 234 status = EXIT_USER_REQUEST; 235 } 236} 237 238/* ----------------------------------------------------------------------------- 239----------------------------------------------------------------------------- */ 240void dialog_reminder(void *arg) 241{ 242 int status; 243 244 reminderresult = -1; 245 status = pthread_create(&reminderthread, NULL, dialog_reminder_thread, NULL); 246 if (status == noErr) { 247 // install a reminder watch dog every 2 seconds 248 TIMEOUT(dialog_reminder_watch, 0, 2); 249 } 250} 251 252#if TARGET_OS_EMBEDDED 253// extra CFUserNotification keys 254static CFStringRef const SBUserNotificationTextAutocapitalizationType = CFSTR("SBUserNotificationTextAutocapitalizationType"); 255static CFStringRef const SBUserNotificationTextAutocorrectionType = CFSTR("SBUserNotificationTextAutocorrectionType"); 256static CFStringRef const SBUserNotificationGroupsTextFields = CFSTR("SBUserNotificationGroupsTextFields"); 257#endif 258 259/* ----------------------------------------------------------------------------- 260Returns 1 on OK, 0 if cancelled 261user and password have max size 256 262----------------------------------------------------------------------------- */ 263int dialog_password(char *user, int maxuserlen, char *passwd, int maxpasswdlen, int dialog_type, char *message) 264{ 265 CFOptionFlags flags; 266 CFMutableDictionaryRef dict; 267 SInt32 err; 268 CFMutableArrayRef array; 269 CFURLRef url; 270 CFStringRef str, str1; 271 int ret = 0, loop = 0; 272#if TARGET_OS_EMBEDDED 273 int nbfields = 0; 274#endif 275 276 do { 277 ret = 0; 278 dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 279 if (dict) { 280 281 url = CFBundleCopyResourceURL(bundle, CFSTR(ICON), NULL, NULL); 282 if (url) { 283 CFDictionaryAddValue(dict, kCFUserNotificationIconURLKey, url); 284 CFRelease(url); 285 } 286 287 /* if there is a message, set it first, so it is not overriden by other text */ 288 if (message) { 289 if ((str = CFStringCreateWithCString(NULL, message, kCFStringEncodingUTF8))) { 290 CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, str); 291 CFRelease(str); 292 } 293 } 294 295 switch (dialog_type) { 296 case DIALOG_PASSWORD_CHANGE: 297 CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, (tokencard == 1) ? CFSTR("Expired token") : CFSTR("Expired password")); 298 break; 299 case DIALOG_PASSWORD_RETRY: 300 CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, (tokencard == 1) ? CFSTR("Incorrect token") : CFSTR("Incorrect password")); 301 break; 302 } 303 304 array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 305 if (array) { 306 switch (dialog_type) { 307 case DIALOG_PASSWORD_CHANGE: 308 CFArrayAppendValue(array, (tokencard == 1) ? CFSTR("New token:") : CFSTR("New password:")); 309 CFArrayAppendValue(array, (tokencard == 1) ? CFSTR("Confirm new token:") : CFSTR("Confirm new password:")); 310 break; 311 case DIALOG_PASSWORD_RETRY: 312 CFArrayAppendValue(array, CFSTR("Retry name:")); 313 CFArrayAppendValue(array, (tokencard == 1) ? CFSTR("Retry token:") : CFSTR("Retry password:")); 314 break; 315 case DIALOG_PASSWORD_ASK: 316 default: 317 CFArrayAppendValue(array, CFSTR("Account Name:")); 318 CFArrayAppendValue(array, (tokencard == 1) ? CFSTR("Token:") : CFSTR("Password:")); 319 break; 320 } 321 322#if TARGET_OS_EMBEDDED 323 nbfields = CFArrayGetCount(array); 324#endif 325 CFDictionaryAddValue(dict, kCFUserNotificationTextFieldTitlesKey, array); 326 CFRelease(array); 327 } 328 329 array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 330 if (array) { 331 switch (dialog_type) { 332 case DIALOG_PASSWORD_CHANGE: 333 break; 334 case DIALOG_PASSWORD_RETRY: 335 case DIALOG_PASSWORD_ASK: 336 default: 337 if ((str = CFStringCreateWithCString(NULL, user, kCFStringEncodingUTF8))) { 338 CFArrayAppendValue(array, str); 339 CFRelease(str); 340 } 341 } 342 CFDictionaryAddValue(dict, kCFUserNotificationTextFieldValuesKey, array); 343 CFRelease(array); 344 } 345 346 CFDictionaryAddValue(dict, kCFUserNotificationLocalizationURLKey, bundleURL); 347 CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, (dialogtype == 0) ? CFSTR("Network Connection") : CFSTR("VPN Connection")); 348 if (loop) 349 CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, (tokencard == 1) ? CFSTR("Incorrectly entered token") : CFSTR("Incorrectly entered password")); 350 //CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, CFSTR("Enter password")); 351 CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, CFSTR("Cancel")); 352 353 flags = CFUserNotificationSecureTextField(1); 354 if (dialog_type == DIALOG_PASSWORD_CHANGE) 355 flags += CFUserNotificationSecureTextField(0); 356 357#if TARGET_OS_EMBEDDED 358 if (nbfields > 0) { 359 CFMutableArrayRef autoCapsTypes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 360 CFMutableArrayRef autoCorrectionTypes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 361 int i, zero = 0, one = 1; 362 CFNumberRef zeroRef = CFNumberCreate(NULL, kCFNumberIntType, &zero); 363 CFNumberRef oneRef = CFNumberCreate(NULL, kCFNumberIntType, &one); 364 365 if (autoCapsTypes && autoCorrectionTypes && zeroRef && oneRef) { 366 for(i = 0; i < nbfields; i++) { 367 // no auto caps or autocorrection for any of our fields 368 CFArrayAppendValue(autoCapsTypes, zeroRef); 369 CFArrayAppendValue(autoCorrectionTypes, oneRef); 370 } 371 CFDictionarySetValue(dict, SBUserNotificationTextAutocapitalizationType, autoCapsTypes); 372 CFDictionarySetValue(dict, SBUserNotificationTextAutocorrectionType, autoCorrectionTypes); 373 } 374 if (autoCapsTypes) 375 CFRelease(autoCapsTypes); 376 if (autoCorrectionTypes) 377 CFRelease(autoCorrectionTypes); 378 if (zeroRef) 379 CFRelease(zeroRef); 380 if (oneRef) 381 CFRelease(oneRef); 382 383 // make CFUN prettier 384 CFDictionarySetValue(dict, SBUserNotificationGroupsTextFields, kCFBooleanTrue); 385 } 386#endif 387 388 dialog_alert = CFUserNotificationCreate(NULL, 0, flags, &err, dict); 389 if (dialog_alert) { 390 CFUserNotificationReceiveResponse(dialog_alert, 0, &flags); 391 // the 2 lower bits of the response flags will give the button pressed 392 // 0 --> default 393 // 1 --> alternate 394 if ((flags & 3) == 1) { 395 // user cancelled 396 } 397 else { 398 // user clicked OK 399 ret = 1; 400 str = CFUserNotificationGetResponseValue(dialog_alert, kCFUserNotificationTextFieldValuesKey, 0); 401 str1 = CFUserNotificationGetResponseValue(dialog_alert, kCFUserNotificationTextFieldValuesKey, 1); 402 403 switch (dialog_type) { 404 case DIALOG_PASSWORD_CHANGE: 405 if (!(str && str1 406 && (CFStringCompare(str, str1, 0) == kCFCompareEqualTo) 407 && CFStringGetCString(str1, passwd, maxpasswdlen, kCFStringEncodingUTF8))) 408 ret = -1; 409 break; 410 case DIALOG_PASSWORD_RETRY: 411 case DIALOG_PASSWORD_ASK: 412 default: 413 if (!(str && str1 414 && CFStringGetCString(str, user, maxuserlen, kCFStringEncodingUTF8) 415 && CFStringGetCString(str1, passwd, maxpasswdlen, kCFStringEncodingUTF8))) 416 ret = -1; 417 } 418 419 } 420 421 CFRelease(dialog_alert); 422 dialog_alert = 0; 423 } 424 425 CFRelease(dict); 426 } 427 loop++; 428 } while (ret < 0); 429 return ret; 430} 431 432/* ----------------------------------------------------------------------------- 433----------------------------------------------------------------------------- */ 434static void 435*dialog_UIThread(void *arg) 436{ 437 /* int unit = (int)arg; */ 438 char result = 0; 439 int ret; 440 441 if (pthread_detach(pthread_self()) == 0) { 442 443 // username can be changed 444 ret = dialog_password(username, MAXNAMELEN, passwd, MAXSECRETLEN, DIALOG_PASSWORD_ASK, 0); 445 446 if (ret == 1) 447 result = 1; 448 } 449 450 if (dialog_ui_fds[1] != -1) 451 write(dialog_ui_fds[1], &result, 1); 452 453 return 0; 454} 455 456/* ----------------------------------------------------------------------------- 457----------------------------------------------------------------------------- */ 458static int readn(int ref, void *data, int len) 459{ 460 int n, left = len; 461 void *p = data; 462 463 while (left > 0) { 464 if ((n = read(ref, p, left)) < 0) { 465 if (kill_link) 466 return 0; 467 if (errno != EINTR) 468 return -1; 469 n = 0; 470 } 471 else if (n == 0) 472 break; /* EOF */ 473 474 left -= n; 475 p += n; 476 } 477 return (len - left); 478} 479 480/* ----------------------------------------------------------------------------- 481Returns 1 if continue, 0 if cancel. 482----------------------------------------------------------------------------- */ 483int dialog_invoke_ui_thread() 484{ 485 int ret = 0; 486 char result; 487 488 if (pipe(dialog_ui_fds) < 0) { 489 error("Dialogs failed to create pipe for User Interface...\n"); 490 return -1; 491 } 492 493 if (pthread_create(&dialog_ui_thread, NULL, dialog_UIThread, 0 /* unit number */)) { 494 error("Dialogs failed to create thread for client User Interface...\n"); 495 close(dialog_ui_fds[0]); 496 close(dialog_ui_fds[1]); 497 return 1; 498 } 499 500 501 ret = readn(dialog_ui_fds[0], &result, 1); 502 503 close(dialog_ui_fds[0]); 504 dialog_ui_fds[0] = -1; 505 close(dialog_ui_fds[1]); 506 dialog_ui_fds[1] = -1; 507 508 if (ret <= 0) { 509 510 if (dialog_alert) { 511 CFUserNotificationCancel(dialog_alert); 512 // ui thread will finish itself 513 } 514 // cancel 515 return 0; 516 } 517 518 ret = result; 519 if (ret == 1) 520 strncpy(user, username, MAXNAMELEN); 521 522 return ret; 523} 524 525/* ----------------------------------------------------------------------------- 526Returns 1 if continue, 0 if cancel. 527----------------------------------------------------------------------------- */ 528int dialog_start_link() 529{ 530 531 if (noaskpassword || *passwd || askpasswordafter) 532 return 1; 533 534 return dialog_invoke_ui_thread(); 535} 536 537/* ----------------------------------------------------------------------------- 538msg can come from the server 539Returns 1 if continue, 0 if cancel. 540----------------------------------------------------------------------------- */ 541int dialog_change_password(char *msg) 542{ 543 int ret = 0; 544 545 if (noaskpassword) 546 return 1; 547 548 // does not change the username, only the password 549 ret = dialog_password(username, MAXNAMELEN, new_passwd, MAXSECRETLEN, DIALOG_PASSWORD_CHANGE, msg); 550 551 return ret; 552} 553 554/* ----------------------------------------------------------------------------- 555msg can come from the server 556Returns 1 if continue, 0 if cancel. 557----------------------------------------------------------------------------- */ 558int dialog_retry_password(char *msg) 559{ 560 int ret = 0; 561 562 if (noaskpassword) 563 return 1; 564 565 // username can be changed 566 ret = dialog_password(username, MAXNAMELEN, passwd, MAXSECRETLEN, DIALOG_PASSWORD_RETRY, msg); 567 568 if (ret == 1) 569 strncpy(user, username, MAXNAMELEN); 570 571 return ret; 572} 573 574/* ----------------------------------------------------------------------------- 575Returns 1 if continue, 0 if cancel. 576----------------------------------------------------------------------------- */ 577int dialog_link_up() 578{ 579 580 if (noaskpassword || *passwd || !askpasswordafter) 581 return 1; 582 583 return dialog_invoke_ui_thread(); 584} 585 586#ifdef UNUSED 587/* ----------------------------------------------------------------------------- 588----------------------------------------------------------------------------- */ 589int dialog_idle(struct ppp_idle *idle) 590{ 591 int itime = 0; 592 593 // called at init time 594 if (idle == 0) 595 return idle_time_limit; 596 597 itime = MIN(idle->xmit_idle, idle->recv_idle); 598 if ((idle_time_limit - itime) <= 0) { 599 600 if (noidleprompt || dialog_ask(CFSTR("Idle timeout"), CFSTR("Stay connected"), CFSTR("Disconnect"), 30)) { 601 // user clicked Disconnect 602 return 0; 603 } 604 605 // user clicked Stay Connected 606 return idle_time_limit; 607 } 608 609 // will rearm the timer 610 return idle_time_limit - itime; 611} 612#endif 613 614/* ----------------------------------------------------------------------------- 615return 0 : OK was pressed 616return 1 : Cancel was pressed 617return 2 : should not happen 618return 3 : nothing was pressed, timeout expired 619----------------------------------------------------------------------------- */ 620int dialog_ask(CFStringRef message, CFStringRef ok, CFStringRef cancel, int timeout) 621{ 622 CFUserNotificationRef alert; 623 CFOptionFlags flags; 624 CFMutableDictionaryRef dict; 625 SInt32 error; 626 CFURLRef url; 627 628 flags = 3; // nothing has been pressed 629 dict = CFDictionaryCreateMutable(NULL, 0, 630 &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 631 if (dict) { 632 633 url = CFBundleCopyResourceURL(bundle, CFSTR(ICON), NULL, NULL); 634 if (url) { 635 CFDictionaryAddValue(dict, kCFUserNotificationIconURLKey, url); 636 CFRelease(url); 637 } 638 639 CFDictionaryAddValue(dict, kCFUserNotificationLocalizationURLKey, bundleURL); 640 CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, (dialogtype == 0) ? CFSTR("Network Connection") : CFSTR("VPN Connection")); 641 CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, message); 642 if (ok) CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey, ok); 643 if (cancel) CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, cancel); 644 alert = CFUserNotificationCreate(NULL, timeout, kCFUserNotificationCautionAlertLevel, &error, dict); 645 if (alert) { 646 CFUserNotificationReceiveResponse(alert, timeout, &flags); 647 CFRelease(alert); 648 } 649 CFRelease(dict); 650 } 651 652 // the 2 lower bits of the response flags will give the button pressed 653 // 0 --> default 654 // 1 --> alternate 655 // 2 --> other 656 // 3 --> none of them, timeout expired 657 return (flags & 3); 658} 659