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