* Copyright 2003-2004 Waldemar Kornewald. All rights reserved.
* Copyright 2017 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "DialUpView.h"
#include "DialUpAddon.h"
#include <cstring>
#include "InterfaceUtils.h"
#include "MessageDriverSettingsUtils.h"
#include "TextRequestDialog.h"
#include "ConnectionOptionsAddon.h"
#include "GeneralAddon.h"
#include "IPCPAddon.h"
#include "PPPoEAddon.h"
#include <PPPInterface.h>
#include <settings_tools.h>
#include <TemplateList.h>
#include <Application.h>
#include <Alert.h>
#include <Button.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Messenger.h>
#include <PopUpMenu.h>
#include <LayoutBuilder.h>
#include <StringView.h>
#include <TabView.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <Path.h>
static const uint32 kMsgCreateNew = 'NEWI';
static const uint32 kMsgFinishCreateNew = 'FNEW';
static const uint32 kMsgDeleteCurrent = 'DELI';
static const uint32 kMsgSelectInterface = 'SELI';
static const uint32 kMsgConnectButton = 'CONI';
static const char *kLabelInterface = "Interface: ";
static const char *kLabelInterfaceName = "Interface Name: ";
static const char *kLabelCreateNewInterface = "Create New Interface";
static const char *kLabelCreateNew = "Create New...";
static const char *kLabelDeleteCurrent = "Delete Current";
static const char *kLabelConnect = "Connect";
static const char *kLabelDisconnect = "Disconnect";
static const char *kLabelOK = "OK";
static const char *kTextConnecting = "Connecting...";
static const char *kTextConnectionEstablished = "Connection established.";
static const char *kTextNotConnected = "Not connected.";
static const char *kTextDeviceUpFailed = "Failed to connect.";
static const char *kTextAuthenticating = "Authenticating...";
static const char *kTextAuthenticationFailed = "Authentication failed!";
static const char *kTextConnectionLost = "Connection lost!";
static const char *kTextCreationError = "Error creating interface!";
static const char *kTextNoInterfacesFound = "Please create a new interface...";
static const char *kTextChooseInterfaceName = "Please choose a new name for this "
"interface.";
static const char *kErrorTitle = "Error";
static const char *kErrorNoPPPStack = "Error: Could not access the PPP stack!";
static const char *kErrorInterfaceExists = "Error: An interface with this name "
"already exists!";
static const char *kErrorLoadingFailed = "Error: Failed loading interface! The "
"current settings will be deleted.";
static const char *kErrorSavingFailed = "Error: Failed saving interface settings!";
static
status_t
up_down_thread(void *data)
{
static_cast<DialUpView*>(data)->UpDownThread();
return B_OK;
}
DialUpView::DialUpView()
: BView("DialUpView", 0),
fListener(this),
fUpDownThread(-1),
fDriverSettings(NULL),
fCurrentItem(NULL),
fWatching(PPP_UNDEFINED_INTERFACE_ID),
fKeepLabel(false)
{
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
BMessenger messenger(this);
fAddons.AddMessenger(DUN_MESSENGER, messenger);
fInterfaceMenu = new BPopUpMenu(kLabelCreateNew);
fMenuField = new BMenuField("Interfaces", kLabelInterface, fInterfaceMenu);
fTabView = new BTabView("TabView", B_WIDTH_FROM_LABEL);
BRect tabViewRect(fTabView->Bounds());
fAddons.AddRect(DUN_TAB_VIEW_RECT, tabViewRect);
fStringView = new BStringView("NoInterfacesFound",
kTextNoInterfacesFound);
fStringView->SetAlignment(B_ALIGN_CENTER);
fStringView->Hide();
fCreateNewButton = new BButton("CreateNewButton",
kLabelCreateNewInterface, new BMessage(kMsgCreateNew));
fCreateNewButton->Hide();
fStatusView = new BStringView("StatusView", kTextNotConnected);
fConnectButton = new BButton("ConnectButton", kLabelConnect,
new BMessage(kMsgConnectButton));
BLayoutBuilder::Group<>(this, B_VERTICAL)
.Add(fMenuField)
.Add(fTabView)
.Add(fStringView)
.Add(fCreateNewButton)
.AddGroup(B_HORIZONTAL)
.Add(fStatusView)
.AddGlue()
.Add(fConnectButton)
.End()
.End();
LoadInterfaces();
LoadAddons();
CreateTabs();
fCurrentItem = NULL;
SelectInterface(0);
UpdateControls();
}
DialUpView::~DialUpView()
{
SaveSettingsToFile();
int32 tmp;
wait_for_thread(fUpDownThread, &tmp);
DialUpAddon *addon;
for(int32 index = 0;
fAddons.FindPointer(DUN_DELETE_ON_QUIT, index,
reinterpret_cast<void**>(&addon)) == B_OK;
index++)
delete addon;
}
void
DialUpView::AttachedToWindow()
{
fInterfaceMenu->SetTargetForItems(this);
fCreateNewButton->SetTarget(this);
fConnectButton->SetTarget(this);
if(fListener.InitCheck() != B_OK) {
(new BAlert(kErrorTitle, kErrorNoPPPStack, kLabelOK,
NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
fConnectButton->Hide();
}
}
void
DialUpView::MessageReceived(BMessage *message)
{
switch(message->what) {
case PPP_REPORT_MESSAGE:
HandleReportMessage(message);
break;
case kMsgCreateNew: {
(new TextRequestDialog(kLabelCreateNewInterface, kTextChooseInterfaceName,
kLabelInterfaceName))->Go(
new BInvoker(new BMessage(kMsgFinishCreateNew), this));
} break;
case kMsgFinishCreateNew: {
int32 which;
message->FindInt32("which", &which);
const char *name = message->FindString("text");
if(which == 1 && name && strlen(name) > 0)
AddInterface(name, true);
if(fCurrentItem)
fCurrentItem->SetMarked(true);
} break;
case kMsgDeleteCurrent: {
if(!fCurrentItem)
return;
fInterfaceMenu->RemoveItem(fCurrentItem);
BDirectory settings, profile;
GetPPPDirectories(&settings, &profile);
BEntry entry;
settings.FindEntry(fCurrentItem->Label(), &entry);
entry.Remove();
profile.FindEntry(fCurrentItem->Label(), &entry);
entry.Remove();
delete fCurrentItem;
fCurrentItem = NULL;
BMenuItem *marked = fInterfaceMenu->FindMarked();
if(marked)
marked->SetMarked(false);
UpdateControls();
SelectInterface(0);
} break;
case kMsgSelectInterface: {
int32 index;
message->FindInt32("index", &index);
SelectInterface(index);
} break;
case kMsgConnectButton: {
if(!fCurrentItem || fUpDownThread != -1)
return;
fUpDownThread = spawn_thread(up_down_thread, "up_down_thread",
B_NORMAL_PRIORITY, this);
resume_thread(fUpDownThread);
} break;
default:
BView::MessageReceived(message);
}
}
bool
DialUpView::SelectInterfaceNamed(const char *name)
{
BMenuItem *item = fInterfaceMenu->FindItem(name);
int32 index = fInterfaceMenu->IndexOf(item);
if(!item || index >= CountInterfaces())
return false;
SelectInterface(index);
return true;
}
bool
DialUpView::NeedsRequest() const
{
return fGeneralAddon ? fGeneralAddon->NeedsAuthenticationRequest() : false;
}
BView*
DialUpView::StatusView() const
{
return fStatusView;
}
BView*
DialUpView::ConnectButton() const
{
return fConnectButton;
}
bool
DialUpView::LoadSettings(bool isNew)
{
fSettings.MakeEmpty();
fProfile.MakeEmpty();
BMessage *settingsPointer = fCurrentItem ? &fSettings : NULL,
*profilePointer = fCurrentItem ? &fProfile : NULL;
if(fCurrentItem && !isNew) {
BString name("pppidf/");
name << fCurrentItem->Label();
if(!ReadMessageDriverSettings(name.String(), &fSettings))
return false;
name = "pppidf/profile/";
name << fCurrentItem->Label();
if(!ReadMessageDriverSettings(name.String(), &fProfile))
profilePointer = settingsPointer;
}
DialUpAddon *addon;
for(int32 index = 0; fAddons.FindPointer(DUN_TAB_ADDON_TYPE, index,
reinterpret_cast<void**>(&addon)) == B_OK; index++) {
if(!addon)
continue;
if(!addon->LoadSettings(settingsPointer, profilePointer, isNew))
return false;
}
return true;
}
void
DialUpView::IsModified(bool *settings, bool *profile)
{
*settings = *profile = false;
bool addonSettingsChanged, addonProfileChanged;
DialUpAddon *addon;
for(int32 index = 0; fAddons.FindPointer(DUN_TAB_ADDON_TYPE, index,
reinterpret_cast<void**>(&addon)) == B_OK; index++) {
if(!addon)
continue;
addon->IsModified(&addonSettingsChanged, &addonProfileChanged);
if(addonSettingsChanged)
*settings = true;
if(addonProfileChanged)
*profile = true;
}
}
bool
DialUpView::SaveSettings(BMessage *settings, BMessage *profile, bool saveTemporary)
{
if(!fCurrentItem || !settings || !profile)
return false;
DialUpAddon *addon;
TemplateList<DialUpAddon*> addons;
for(int32 index = 0;
fAddons.FindPointer(DUN_TAB_ADDON_TYPE, index,
reinterpret_cast<void**>(&addon)) == B_OK; index++) {
if(!addon)
continue;
int32 insertIndex = 0;
for(; insertIndex < addons.CountItems(); insertIndex++)
if(addons.ItemAt(insertIndex)->Priority() <= addon->Priority())
break;
addons.AddItem(addon, insertIndex);
}
settings->AddInt32("Interface", static_cast<int32>(fWatching));
if(fCurrentItem)
settings->AddString("InterfaceName", fCurrentItem->Label());
for(int32 index = 0; index < addons.CountItems(); index++)
if(!addons.ItemAt(index)->SaveSettings(settings, profile, saveTemporary))
return false;
return true;
}
bool
DialUpView::SaveSettingsToFile()
{
bool settingsChanged, profileChanged;
IsModified(&settingsChanged, &profileChanged);
if(!settingsChanged && !profileChanged)
return true;
BMessage settings, profile;
if(!SaveSettings(&settings, &profile, false))
return false;
BDirectory settingsDirectory;
BDirectory profileDirectory;
GetPPPDirectories(&settingsDirectory, &profileDirectory);
if(settingsDirectory.InitCheck() != B_OK || profileDirectory.InitCheck() != B_OK)
return false;
BFile file;
if(settingsChanged) {
settingsDirectory.CreateFile(fCurrentItem->Label(), &file);
WriteMessageDriverSettings(file, settings);
}
if(profileChanged) {
profileDirectory.CreateFile(fCurrentItem->Label(), &file);
WriteMessageDriverSettings(file, profile);
}
return true;
}
void
DialUpView::UpDownThread()
{
SaveSettingsToFile();
BMessage settings, profile;
SaveSettings(&settings, &profile, true);
PPPInterface interface;
if (fWatching == PPP_UNDEFINED_INTERFACE_ID) {
interface = fListener.Manager().InterfaceWithName(fCurrentItem->Label());
if(interface.InitCheck() != B_OK)
interface = fListener.Manager().CreateInterfaceWithName(
fCurrentItem->Label());
} else {
interface = fWatching;
}
if(interface.InitCheck() != B_OK) {
Window()->Lock();
fStatusView->SetText(kTextCreationError);
Window()->Unlock();
return;
}
ppp_interface_info_t info;
interface.GetInterfaceInfo(&info);
if(info.info.phase == PPP_DOWN_PHASE)
interface.Up();
else
interface.Down();
fUpDownThread = -1;
}
#define PPP_INTERFACE_SETTINGS_PATH "" // FIXME!
void
DialUpView::GetPPPDirectories(BDirectory *settingsDirectory,
BDirectory *profileDirectory) const
{
if(settingsDirectory) {
BDirectory settings(PPP_INTERFACE_SETTINGS_PATH);
if(settings.InitCheck() != B_OK) {
create_directory(PPP_INTERFACE_SETTINGS_PATH, 0750);
settings.SetTo(PPP_INTERFACE_SETTINGS_PATH);
}
*settingsDirectory = settings;
}
if(profileDirectory) {
BDirectory profile(PPP_INTERFACE_SETTINGS_PATH "/profile");
if(profile.InitCheck() != B_OK) {
create_directory(PPP_INTERFACE_SETTINGS_PATH "/profile", 0750);
profile.SetTo(PPP_INTERFACE_SETTINGS_PATH "/profile");
}
*profileDirectory = profile;
}
}
void
DialUpView::HandleReportMessage(BMessage *message)
{
thread_id sender;
message->FindInt32("sender", &sender);
send_data(sender, B_OK, NULL, 0);
if(!fCurrentItem)
return;
ppp_interface_id id;
if(message->FindInt32("interface", reinterpret_cast<int32*>(&id)) != B_OK
|| (fWatching != PPP_UNDEFINED_INTERFACE_ID && id != fWatching))
return;
int32 type, code;
message->FindInt32("type", &type);
message->FindInt32("code", &code);
if(type == PPP_MANAGER_REPORT && code == PPP_REPORT_INTERFACE_CREATED) {
PPPInterface interface(id);
if(interface.InitCheck() != B_OK)
return;
ppp_interface_info_t info;
interface.GetInterfaceInfo(&info);
if(strcasecmp(info.info.name, fCurrentItem->Label()))
return;
WatchInterface(id);
} else if(type == PPP_CONNECTION_REPORT) {
if(fWatching == PPP_UNDEFINED_INTERFACE_ID)
return;
UpdateStatus(code);
} else if(type == PPP_DESTRUCTION_REPORT) {
if(fWatching == PPP_UNDEFINED_INTERFACE_ID)
return;
WatchInterface(fListener.Manager().InterfaceWithName(fCurrentItem->Label()));
}
}
void
DialUpView::CreateTabs()
{
DialUpAddon *addon = NULL;
BView *target = NULL;
TemplateList<DialUpAddon*> addons;
for (int32 index = 0;
fAddons.FindPointer(DUN_TAB_ADDON_TYPE, index,
reinterpret_cast<void**>(&addon)) == B_OK;
index++) {
if (!addon || addon->Position() < 0)
continue;
int32 insertIndex = 0;
for(; insertIndex < addons.CountItems(); insertIndex++) {
if (addons.ItemAt(insertIndex)->Position() > addon->Position())
break;
}
addons.AddItem(addon, insertIndex);
}
for (int32 index = 0; index < addons.CountItems(); index++) {
addon = addons.ItemAt(index);
target = addon->CreateView();
if (target == NULL)
continue;
fTabView->AddTab(target, NULL);
}
}
void
DialUpView::UpdateStatus(int32 code)
{
switch(code) {
case PPP_REPORT_DEVICE_UP_FAILED:
case PPP_REPORT_AUTHENTICATION_FAILED:
case PPP_REPORT_DOWN_SUCCESSFUL:
case PPP_REPORT_CONNECTION_LOST: {
fConnectButton->SetLabel(kLabelConnect);
} break;
default:
fConnectButton->SetLabel(kLabelDisconnect);
}
if(fKeepLabel && code != PPP_REPORT_GOING_UP && code != PPP_REPORT_UP_SUCCESSFUL)
return;
if(fListener.InitCheck() != B_OK) {
fStatusView->SetText(kErrorNoPPPStack);
return;
}
switch(code) {
case PPP_REPORT_GOING_UP:
fKeepLabel = false;
fStatusView->SetText(kTextConnecting);
break;
case PPP_REPORT_UP_SUCCESSFUL:
fKeepLabel = false;
fStatusView->SetText(kTextConnectionEstablished);
break;
case PPP_REPORT_DOWN_SUCCESSFUL:
fStatusView->SetText(kTextNotConnected);
break;
case PPP_REPORT_DEVICE_UP_FAILED:
fKeepLabel = true;
fStatusView->SetText(kTextDeviceUpFailed);
break;
case PPP_REPORT_AUTHENTICATION_REQUESTED:
fStatusView->SetText(kTextAuthenticating);
break;
case PPP_REPORT_AUTHENTICATION_FAILED:
fKeepLabel = true;
fStatusView->SetText(kTextAuthenticationFailed);
break;
case PPP_REPORT_CONNECTION_LOST:
fKeepLabel = true;
fStatusView->SetText(kTextConnectionLost);
break;
}
}
void
DialUpView::WatchInterface(ppp_interface_id ID)
{
if(fWatching != ID) {
if(ID == PPP_UNDEFINED_INTERFACE_ID || fListener.WatchInterface(ID))
fWatching = ID;
}
PPPInterface interface(fWatching);
if(interface.InitCheck() != B_OK) {
UpdateStatus(PPP_REPORT_DOWN_SUCCESSFUL);
return;
}
ppp_interface_info_t info;
interface.GetInterfaceInfo(&info);
switch(info.info.phase) {
case PPP_DOWN_PHASE:
UpdateStatus(PPP_REPORT_DOWN_SUCCESSFUL);
break;
case PPP_TERMINATION_PHASE:
break;
case PPP_ESTABLISHED_PHASE:
UpdateStatus(PPP_REPORT_UP_SUCCESSFUL);
break;
default:
UpdateStatus(PPP_REPORT_GOING_UP);
}
}
void
DialUpView::LoadInterfaces()
{
fInterfaceMenu->AddSeparatorItem();
fInterfaceMenu->AddItem(new BMenuItem(kLabelCreateNewInterface,
new BMessage(kMsgCreateNew)));
fDeleterItem = new BMenuItem(kLabelDeleteCurrent,
new BMessage(kMsgDeleteCurrent));
fInterfaceMenu->AddItem(fDeleterItem);
BDirectory settingsDirectory;
BEntry entry;
BPath path;
GetPPPDirectories(&settingsDirectory, NULL);
while(settingsDirectory.GetNextEntry(&entry) == B_OK) {
if(entry.IsFile()) {
entry.GetPath(&path);
AddInterface(path.Leaf(), true);
}
}
}
void
DialUpView::LoadAddons()
{
ConnectionOptionsAddon *connectionOptionsAddon =
new ConnectionOptionsAddon(&fAddons);
fAddons.AddPointer(DUN_TAB_ADDON_TYPE, connectionOptionsAddon);
fAddons.AddPointer(DUN_DELETE_ON_QUIT, connectionOptionsAddon);
GeneralAddon *fGeneralAddon = new GeneralAddon(&fAddons);
fAddons.AddPointer(DUN_TAB_ADDON_TYPE, fGeneralAddon);
fAddons.AddPointer(DUN_DELETE_ON_QUIT, fGeneralAddon);
IPCPAddon *ipcpAddon = new IPCPAddon(&fAddons);
fAddons.AddPointer(DUN_TAB_ADDON_TYPE, ipcpAddon);
fAddons.AddPointer(DUN_DELETE_ON_QUIT, ipcpAddon);
PPPoEAddon *pppoeAddon = new PPPoEAddon(&fAddons);
fAddons.AddPointer(DUN_DEVICE_ADDON_TYPE, pppoeAddon);
fAddons.AddPointer(DUN_DELETE_ON_QUIT, pppoeAddon);
BMessage addon;
addon.AddString("FriendlyName", "Plain-text Authentication");
addon.AddString("TechnicalName", "PAP");
addon.AddString("KernelModuleName", "pap");
fAddons.AddMessage(DUN_AUTHENTICATOR_ADDON_TYPE, &addon);
}
void
DialUpView::AddInterface(const char *name, bool isNew)
{
if(fInterfaceMenu->FindItem(name)) {
(new BAlert(kErrorTitle, kErrorInterfaceExists, kLabelOK,
NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
return;
}
BMenuItem *item = new BMenuItem(name, new BMessage(kMsgSelectInterface));
item->SetTarget(this);
int32 index = FindNextMenuInsertionIndex(fInterfaceMenu, name);
if(index > CountInterfaces())
index = CountInterfaces();
fInterfaceMenu->AddItem(item, index);
UpdateControls();
item->SetMarked(true);
SelectInterface(index, isNew);
}
void
DialUpView::SelectInterface(int32 index, bool isNew)
{
BMenuItem *item = fInterfaceMenu->FindMarked();
if(fCurrentItem && item == fCurrentItem)
return;
if(fCurrentItem && !SaveSettingsToFile())
(new BAlert(kErrorTitle, kErrorSavingFailed, kLabelOK,
NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
if(index >= CountInterfaces() || index < 0) {
if(CountInterfaces() > 0)
SelectInterface(0);
else {
fCurrentItem = NULL;
WatchInterface(PPP_UNDEFINED_INTERFACE_ID);
}
} else {
fCurrentItem = fInterfaceMenu->ItemAt(index);
if(!fCurrentItem) {
SelectInterface(0);
return;
}
fCurrentItem->SetMarked(true);
fDeleterItem->SetEnabled(true);
WatchInterface(fListener.Manager().InterfaceWithName(fCurrentItem->Label()));
}
if(!fCurrentItem)
LoadSettings(false);
else if(!isNew && !LoadSettings(false)) {
(new BAlert(kErrorTitle, kErrorLoadingFailed, kLabelOK,
NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
LoadSettings(true);
} else if(isNew && !LoadSettings(true))
(new BAlert(kErrorTitle, kErrorLoadingFailed, kLabelOK,
NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
}
int32
DialUpView::CountInterfaces() const
{
return fInterfaceMenu->CountItems() - 3;
}
void
DialUpView::UpdateControls()
{
if(fTabView->IsHidden() && CountInterfaces() > 0) {
fInterfaceMenu->SetLabelFromMarked(true);
fStringView->Hide();
fCreateNewButton->Hide();
fTabView->Show();
fConnectButton->SetEnabled(true);
} else if(!fTabView->IsHidden() && CountInterfaces() == 0) {
fDeleterItem->SetEnabled(false);
fInterfaceMenu->SetRadioMode(false);
fInterfaceMenu->Superitem()->SetLabel(kLabelCreateNew);
fTabView->Hide();
fStringView->Show();
fCreateNewButton->Show();
fConnectButton->SetEnabled(false);
}
}