added Linux-only /proc/net/route check, IsOnline proceeds check always, not only...
[wxWidgets.git] / src / unix / dialup.cpp
1 // -*- c++ -*- ///////////////////////////////////////////////////////////////
2 // Name: unix/dialup.cpp
3 // Purpose: Network related wxWindows classes and functions
4 // Author: Karsten Ballüder
5 // Modified by:
6 // Created: 03.10.99
7 // RCS-ID: $Id$
8 // Copyright: (c) Karsten Ballüder
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/setup.h"
13
14 #ifdef __GNUG__
15 # pragma implementation "dialup.h"
16 #endif
17
18 #if wxUSE_DIALUP_MANAGER
19
20 #ifndef WX_PRECOMP
21 # include "wx/defs.h"
22 #endif // !PCH
23
24 #include "wx/string.h"
25 #include "wx/event.h"
26 #include "wx/dialup.h"
27 #include "wx/timer.h"
28 #include "wx/filefn.h"
29 #include "wx/utils.h"
30 #include "wx/log.h"
31 #include "wx/file.h"
32 #include "wx/process.h"
33 #include "wx/intl.h"
34 #include "wx/app.h"
35 #include "wx/wxchar.h"
36
37 #include <stdlib.h>
38
39 #include <signal.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #define __STRICT_ANSI__
43 #include <sys/socket.h>
44 #include <netdb.h>
45 #include <netinet/in.h>
46 #include <arpa/inet.h>
47 #include <errno.h>
48
49 // ----------------------------------------------------------------------------
50 // A class which groups functions dealing with connecting to the network from a
51 // workstation using dial-up access to the net. There is at most one instance
52 // of this class in the program accessed via GetDialUpManager().
53 // ----------------------------------------------------------------------------
54
55 /* TODO
56 *
57 * 1. more configurability for Unix: i.e. how to initiate the connection, how
58 * to check for online status, &c.
59 * 2. add a "long Dial(long connectionId = -1)" function which asks the user
60 * about which connection to dial (this may be done using native dialogs
61 * under NT, need generic dialogs for all others) and returns the identifier
62 * of the selected connection (it's opaque to the application) - it may be
63 * reused later to dial the same connection later (or use strings instead of
64 * longs may be?)
65 * 3. add an async version of dialing functions which notify the caller about
66 * the progress (or may be even start another thread to monitor it)
67 * 4. the static creation/accessor functions are not MT-safe - but is this
68 * really crucial? I think we may suppose they're always called from the
69 * main thread?
70 */
71
72 class WXDLLEXPORT wxDialUpManagerImpl : public wxDialUpManager
73 {
74 public:
75 wxDialUpManagerImpl();
76 ~wxDialUpManagerImpl();
77
78 /** Could the dialup manager be initialized correctly? If this function
79 returns FALSE, no other functions will work neither, so it's a good idea
80 to call this function and check its result before calling any other
81 wxDialUpManager methods.
82 */
83 virtual bool IsOk() const
84 { return TRUE; }
85
86 /** The simplest way to initiate a dial up: this function dials the given
87 ISP (exact meaning of the parameter depends on the platform), returns
88 TRUE on success or FALSE on failure and logs the appropriate error
89 message in the latter case.
90 @param nameOfISP optional paramater for dial program
91 @param username unused
92 @param password unused
93 */
94 virtual bool Dial(const wxString& nameOfISP,
95 const wxString& WXUNUSED(username),
96 const wxString& WXUNUSED(password),
97 bool async);
98
99 /// Hang up the currently active dial up connection.
100 virtual bool HangUp();
101
102 // returns TRUE if the computer is connected to the network: under Windows,
103 // this just means that a RAS connection exists, under Unix we check that
104 // the "well-known host" (as specified by SetWellKnownHost) is reachable
105 virtual bool IsOnline() const
106 {
107 CheckStatus();
108 return m_IsOnline > 0;
109 }
110
111 /// do we have a constant net connection? -- GUESS!
112 bool IsAlwaysOnline() const
113 {
114 ((wxDialUpManagerImpl *) this)->HangUp(); // brutal but necessary
115 return IsOnline();
116 }
117 /// returns TRUE if (async) dialing is in progress
118 inline virtual bool IsDialing() const
119 { return m_DialProcess != NULL; }
120
121 // cancel dialing the number initiated with Dial(async = TRUE)
122 // NB: this won't result in DISCONNECTED event being sent
123 virtual bool CancelDialing();
124
125 size_t GetISPNames(class wxArrayString &) const
126 { return 0; }
127
128 // sometimes the built-in logic for determining the online status may fail,
129 // so, in general, the user should be allowed to override it. This function
130 // allows to forcefully set the online status - whatever our internal
131 // algorithm may think about it.
132 virtual void SetOnlineStatus(bool isOnline = TRUE)
133 { m_IsOnline = isOnline; }
134
135 // set misc wxDialUpManager options
136 // --------------------------------
137
138 // enable automatical checks for the connection status and sending of
139 // wxEVT_DIALUP_CONNECTED/wxEVT_DIALUP_DISCONNECTED events. The interval
140 // parameter is only for Unix where we do the check manually: under
141 // Windows, the notification about the change of connection status is
142 // instantenous.
143 //
144 // Returns FALSE if couldn't set up automatic check for online status.
145 virtual bool EnableAutoCheckOnlineStatus(size_t nSeconds);
146
147 // disable automatic check for connection status change - notice that the
148 // wxEVT_DIALUP_XXX events won't be sent any more neither.
149 virtual void DisableAutoCheckOnlineStatus();
150
151 // under Unix, the value of well-known host is used to check whether we're
152 // connected to the internet. It's unused under Windows, but this function
153 // is always safe to call. The default value is www.yahoo.com.
154 virtual void SetWellKnownHost(const wxString& hostname,
155 int portno = 80);
156 /** Sets the commands to start up the network and to hang up
157 again. Used by the Unix implementations only.
158 */
159 virtual void SetConnectCommand(const wxString &command, const wxString &hupcmd)
160 { m_ConnectCommand = command; m_HangUpCommand = hupcmd; }
161
162 private:
163 /// -1: don´t know, 0 = no, 1 = yes
164 int m_IsOnline;
165
166 /// Can we use ifconfig to list active devices?
167 int m_CanUseIfconfig;
168 /// The path to ifconfig
169 wxString m_IfconfigPath;
170
171 /// Can we use ping to find hosts?
172 int m_CanUsePing;
173 /// The path to ping program
174 wxString m_PingPath;
175
176 /// beacon host:
177 wxString m_BeaconHost;
178 /// beacon host portnumber for connect:
179 int m_BeaconPort;
180
181 /// command to connect to network
182 wxString m_ConnectCommand;
183 /// command to hang up
184 wxString m_HangUpCommand;
185 /// name of ISP
186 wxString m_ISPname;
187 /// a timer for regular testing
188 class AutoCheckTimer *m_timer;
189 friend class AutoCheckTimer;
190
191 /// a wxProcess for dialling in background
192 class wxDialProcess *m_DialProcess;
193 /// pid of dial process
194 int m_DialPId;
195 friend class wxDialProcess;
196
197 /// determine status
198 void CheckStatus(bool fromAsync = FALSE) const;
199
200 /// real status check
201 void CheckStatusInternal(void);
202
203 /// Check /proc/net (Linux only)
204 int CheckProcNet(void);
205 /// Check output of ifconfig command for PPP/SLIP/PLIP devices
206 int CheckIfconfig(void);
207 /// Ping a host: 1 on success, -1 if it cannot be used, 0 if unreachable
208 int CheckPing(void);
209 /// Check by connecting to host on given port.
210 int CheckConnect(void);
211
212 };
213
214
215 class AutoCheckTimer : public wxTimer
216 {
217 public:
218 AutoCheckTimer(wxDialUpManagerImpl *dupman)
219 {
220 m_dupman = dupman;
221 m_started = FALSE;
222 }
223
224 virtual bool Start( int millisecs = -1, bool WXUNUSED(one_shot) = FALSE )
225 { m_started = TRUE; return wxTimer::Start(millisecs, FALSE); }
226
227 virtual void Notify()
228 { wxLogTrace(wxT("Checking dial up network status.")); m_dupman->CheckStatus(); }
229
230 virtual void Stop()
231 { if ( m_started ) wxTimer::Stop(); }
232 public:
233 bool m_started;
234 wxDialUpManagerImpl *m_dupman;
235 };
236
237 class wxDialProcess : public wxProcess
238 {
239 public:
240 wxDialProcess(wxDialUpManagerImpl *dupman)
241 {
242 m_DupMan = dupman;
243 }
244 void Disconnect(void) { m_DupMan = NULL; }
245 virtual void OnTerminate(int WXUNUSED(pid), int WXUNUSED(status))
246 {
247 if(m_DupMan)
248 {
249 m_DupMan->m_DialProcess = NULL;
250 m_DupMan->CheckStatus(TRUE);
251 }
252 }
253 private:
254 wxDialUpManagerImpl *m_DupMan;
255 };
256
257
258 wxDialUpManagerImpl::wxDialUpManagerImpl()
259 {
260 /* The isOnline flag can have the following values internally:
261 0 : not connected
262 1 : connected
263 -1 : unknown/undefined status
264 */
265 m_IsOnline = -1;
266 m_DialProcess = NULL;
267 m_timer = NULL;
268 m_CanUseIfconfig = -1; // unknown
269 m_CanUsePing = -1; // unknown
270 m_BeaconHost = WXDIALUP_MANAGER_DEFAULT_BEACONHOST;
271 m_BeaconPort = 80;
272
273 #ifdef __SGI__
274 m_ConnectCommand = _T("/usr/etc/ppp");
275 #elif defined(__LINUX__)
276 // default values for Debian/GNU linux
277 m_ConnectCommand = _T("pon");
278 m_HangUpCommand = _T("poff");
279 #endif
280
281 wxChar * dial = wxGetenv(_T("WXDIALUP_DIALCMD"));
282 wxChar * hup = wxGetenv(_T("WXDIALUP_HUPCMD"));
283 SetConnectCommand(dial ? wxString(dial) : m_ConnectCommand,
284 hup ? wxString(hup) : m_HangUpCommand);
285 }
286
287 wxDialUpManagerImpl::~wxDialUpManagerImpl()
288 {
289 if(m_timer) delete m_timer;
290 if(m_DialProcess)
291 {
292 m_DialProcess->Disconnect();
293 m_DialProcess->Detach();
294 }
295 }
296
297 bool
298 wxDialUpManagerImpl::Dial(const wxString &isp,
299 const wxString & WXUNUSED(username),
300 const wxString & WXUNUSED(password),
301 bool async)
302 {
303 if(m_IsOnline == 1)
304 return FALSE;
305 m_ISPname = isp;
306 wxString cmd;
307 if(m_ConnectCommand.Find(wxT("%s")))
308 cmd.Printf(m_ConnectCommand,m_ISPname.c_str());
309 else
310 cmd = m_ConnectCommand;
311
312 if ( async )
313 {
314 m_DialProcess = new wxDialProcess(this);
315 m_DialPId = (int)wxExecute(cmd, FALSE, m_DialProcess);
316 if(m_DialPId == 0)
317 {
318 delete m_DialProcess;
319 m_DialProcess = NULL;
320 return FALSE;
321 }
322 else
323 return TRUE;
324 }
325 else
326 return wxExecute(cmd, /* sync */ TRUE) == 0;
327 }
328
329 bool
330 wxDialUpManagerImpl::HangUp(void)
331 {
332 if(m_IsOnline == 0)
333 return FALSE;
334 if(IsDialing())
335 {
336 wxLogError(_("Already dialling ISP."));
337 return FALSE;
338 }
339 wxString cmd;
340 if(m_HangUpCommand.Find(wxT("%s")))
341 cmd.Printf(m_HangUpCommand,m_ISPname.c_str(), m_DialProcess);
342 else
343 cmd = m_HangUpCommand;
344 return wxExecute(cmd, /* sync */ TRUE) == 0;
345 }
346
347
348 bool
349 wxDialUpManagerImpl::CancelDialing()
350 {
351 if(! IsDialing())
352 return FALSE;
353 return kill(m_DialPId, SIGTERM) > 0;
354 }
355
356 bool
357 wxDialUpManagerImpl::EnableAutoCheckOnlineStatus(size_t nSeconds)
358 {
359 DisableAutoCheckOnlineStatus();
360 m_timer = new AutoCheckTimer(this);
361 bool rc = m_timer->Start(nSeconds*1000);
362 if(! rc)
363 {
364 delete m_timer;
365 m_timer = NULL;
366 }
367 return rc;
368 }
369
370 void
371 wxDialUpManagerImpl::DisableAutoCheckOnlineStatus()
372 {
373 if(m_timer != NULL)
374 {
375 m_timer->Stop();
376 delete m_timer;
377 m_timer = NULL;
378 }
379 }
380
381
382 void
383 wxDialUpManagerImpl::SetWellKnownHost(const wxString& hostname, int portno)
384 {
385 if(hostname.Length() == 0)
386 {
387 m_BeaconHost = WXDIALUP_MANAGER_DEFAULT_BEACONHOST;
388 m_BeaconPort = 80;
389 return;
390 }
391
392 /// does hostname contain a port number?
393 wxString port = hostname.After(wxT(':'));
394 if(port.Length())
395 {
396 m_BeaconHost = hostname.Before(wxT(':'));
397 m_BeaconPort = wxAtoi(port);
398 }
399 else
400 {
401 m_BeaconHost = hostname;
402 m_BeaconPort = portno;
403 }
404 }
405
406
407 void
408 wxDialUpManagerImpl::CheckStatus(bool fromAsync) const
409 {
410 // This function calls the CheckStatusInternal() helper function
411 // which is OS - specific and then sends the events.
412
413 int oldIsOnline = m_IsOnline;
414 ( /* non-const */ (wxDialUpManagerImpl *)this)->CheckStatusInternal();
415
416 // now send the events as appropriate:
417 if(m_IsOnline != oldIsOnline // it changed
418 && ( m_IsOnline == 1 // and it is a defined status
419 || m_IsOnline == 0)
420 // only send events for well defined transitions
421 && ( oldIsOnline == 1 || oldIsOnline == 0)
422 )
423 {
424 wxDialUpEvent event(m_IsOnline, ! fromAsync);
425 (void)wxTheApp->ProcessEvent(event);
426 }
427 }
428
429 /*
430 We have three methods that we can use:
431
432 1. test via /sbin/ifconfig and grep for "sl", "ppp", "pl"
433 --> should be fast enough for regular polling
434 2. test if we can reach the well known beacon host
435 --> too slow for polling
436 3. check /proc/net/dev on linux??
437 This method should be preferred, if possible. Need to do more
438 testing.
439
440 */
441
442 void
443 wxDialUpManagerImpl::CheckStatusInternal(void)
444 {
445 m_IsOnline = -1;
446
447 int testResult;
448
449 testResult = CheckProcNet();
450 if(testResult == -1)
451 testResult = CheckIfconfig();
452 if(testResult == -1)
453 testResult = CheckConnect();
454 if(testResult == -1)
455 testResult = CheckPing();
456 m_IsOnline = testResult;
457 }
458
459 int
460 wxDialUpManagerImpl::CheckConnect(void)
461 {
462 // second method: try to connect to a well known host:
463 // This can be used under Win 9x, too!
464 struct hostent *hp;
465 struct sockaddr_in serv_addr;
466
467 if((hp = gethostbyname(m_BeaconHost.mb_str())) == NULL)
468 return 0; // no DNS no net
469
470 serv_addr.sin_family = hp->h_addrtype;
471 memcpy(&serv_addr.sin_addr,hp->h_addr, hp->h_length);
472 serv_addr.sin_port = htons(m_BeaconPort);
473
474 int sockfd;
475 if( ( sockfd = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
476 {
477 return -1; // no info
478 }
479
480 if( connect(sockfd, (struct sockaddr *) &serv_addr,
481 sizeof(serv_addr)) >= 0)
482 {
483 close(sockfd);
484 return 1; // we cant connect, so we have a network!
485 }
486 //connected!
487 close(sockfd);
488 if(errno == ENETUNREACH)
489 return 0; // network is unreachable
490 // connect failed, but don't know why
491 return -1;
492 }
493
494
495 int
496 wxDialUpManagerImpl::CheckProcNet(void)
497 {
498 int rc = -1;
499
500 #ifdef __LINUX__
501 if (wxFileExists(_T("/proc/net/route")))
502 {
503 // NOTE: cannot use wxFile::Length because file doesn't support
504 // seeking
505 FILE *f = fopen("/proc/net/route", "rt");
506 if (f != NULL)
507 {
508 char output[256];
509
510 while (fgets(output, 256, f) != NULL)
511 {
512 if (strstr(output,"ppp") // ppp
513 || strstr(output,"sl") // slip
514 || strstr(output,"pl")) // plip
515 rc = 1;
516 }
517 if (rc == -1) rc = 0;
518 fclose(f);
519 }
520 }
521 #endif
522
523 return rc;
524 }
525
526
527 int
528 wxDialUpManagerImpl::CheckIfconfig(void)
529 {
530 int rc = -1;
531
532 // First time check for ifconfig location. We only use the variant which
533 // does not take arguments, a la GNU.
534 if ( m_CanUseIfconfig == -1 ) // unknown
535 {
536 static const wxChar *ifconfigLocations[] =
537 {
538 _T("/sbin"), // Linux, FreeBSD
539 _T("/usr/sbin"), // SunOS, Solaris, AIX, HP-UX
540 _T("/usr/etc"), // IRIX
541 };
542
543 for ( size_t n = 0; n < WXSIZEOF(ifconfigLocations); n++ )
544 {
545 wxString path(ifconfigLocations[n]);
546 path << _T("/ifconfig");
547
548 if ( wxFileExists(path) )
549 {
550 m_IfconfigPath = path;
551 break;
552 }
553 }
554 }
555
556 wxLogNull ln; // suppress all error messages
557 // Let´s try the ifconfig method first, should be fastest:
558 if(m_CanUseIfconfig != 0) // unknown or yes
559 {
560 wxASSERT(m_IfconfigPath.length());
561
562 wxString tmpfile = wxGetTempFileName("_wxdialuptest");
563 wxString cmd = "/bin/sh -c \'";
564 cmd << m_IfconfigPath;
565 #if defined(__SOLARIS__) || defined (__SUNOS__)
566 // need to add -a flag
567 cmd << " -a";
568 #elif defined(__LINUX__) || defined (__FREEBSD__) || defined(__SGI__)
569 // nothing to be added to ifconfig
570 #elif defined(__HPUX__)
571 // VZ: a wild guess (but without it, ifconfig fails completely)
572 cmd << _T(" ppp0");
573 #else
574 # pragma warning "No ifconfig information for this OS."
575 m_CanUseIfconfig = 0;
576 return -1;
577 #endif
578 cmd << " >" << tmpfile << '\'';
579 /* I tried to add an option to wxExecute() to not close stdout,
580 so we could let ifconfig write directly to the tmpfile, but
581 this does not work. That should be faster, as it doesn´t call
582 the shell first. I have no idea why. :-( (KB) */
583 if(wxExecute(cmd,TRUE /* sync */) == 0)
584 {
585 m_CanUseIfconfig = 1;
586 wxFile file;
587 if( file.Open(tmpfile) )
588 {
589 char *output = new char [file.Length()+1];
590 output[file.Length()] = '\0';
591 if(file.Read(output,file.Length()) == file.Length())
592 {
593 // FIXME shouldn't we grep for "^ppp"? (VZ)
594
595 #if defined(__SOLARIS__) || defined (__SUNOS__)
596 // dialup device under SunOS/Solaris
597 rc = strstr(output,"ipdptp") != (char *)NULL;
598 #elif defined(__LINUX__) || defined (__FREEBSD__)
599 rc = strstr(output,"ppp") // ppp
600 || strstr(output,"sl") // slip
601 || strstr(output,"pl"); // plip
602 #elif defined(__SGI__) // IRIX
603 rc = (int) strstr(output, "ppp"); // PPP
604 #elif defined(__HPUX__)
605 // if could run ifconfig on interface, then it exists
606 rc = TRUE;
607 #endif
608 }
609 file.Close();
610 delete [] output;
611 }
612 // else rc remains -1 as we don't know for sure
613 }
614 else // could not run ifconfig correctly
615 m_CanUseIfconfig = 0; // don´t try again
616 (void) wxRemoveFile(tmpfile);
617 }
618
619 return rc;
620 }
621
622 int
623 wxDialUpManagerImpl::CheckPing(void)
624 {
625 if(! m_CanUsePing)
626 return -1;
627
628 // First time check for ping location. We only use the variant
629 // which does not take arguments, a la GNU.
630 if(m_CanUsePing == -1) // unknown
631 {
632 if(wxFileExists("/bin/ping"))
633 m_PingPath = "/bin/ping";
634 else if(wxFileExists("/usr/sbin/ping"))
635 m_PingPath = "/usr/sbin/ping";
636 if(! m_PingPath)
637 {
638 m_CanUsePing = 0;
639 return -1;
640 }
641 }
642
643 wxLogNull ln; // suppress all error messages
644 wxASSERT(m_PingPath.length());
645 wxString cmd;
646 cmd << m_PingPath << ' ';
647 #if defined(__SOLARIS__) || defined (__SUNOS__)
648 // nothing to add to ping command
649 #elif defined(__LINUX__)
650 cmd << "-c 1 "; // only ping once
651 #elif defined(__HPUX__)
652 cmd << "64 1 "; // only ping once (need also specify the packet size)
653 #else
654 # pragma warning "No Ping information for this OS."
655 m_CanUsePing = 0;
656 return -1;
657 #endif
658 cmd << m_BeaconHost;
659 if(wxExecute(cmd, TRUE /* sync */) == 0)
660 return 1;
661 else
662 return 0;
663 }
664
665 /* static */
666 wxDialUpManager *
667 wxDialUpManager::Create(void)
668 {
669 return new wxDialUpManagerImpl;
670 }
671
672 #endif // wxUSE_DIALUP_MANAGER