1/*
2 * kPPP: A pppd Front End for the KDE project
3 *
4 * $Id$
5 *
6 * Copyright (C) 1997 Bernd Johannes Wuebben
7 * wuebben@math.cornell.edu
8 *
9 * This file was added by Harri Porten <porten@tu-harburg.de>
10 *
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Library General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with this program; if not, write to the Free
24 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26
27#include <errno.h>
28#include <stdlib.h>
29#include <fcntl.h>
30#include <signal.h>
31#include <sys/ioctl.h>
32#include <setjmp.h>
33#include <qregexp.h>
34//Added by qt3to4:
35#include <assert.h>
36
37#include "modem.h"
38#include "pppdata.h"
39#include "requester.h"
40#include <klocale.h>
41#include <kdebug.h>
42#include <kcmdlineargs.h>
43#include <config-kppp.h>
44
45static sigjmp_buf jmp_buffer;
46
47Modem *Modem::modem = 0;
48
49Modem::Modem() :
50 modemfd(-1),
51 sn(0L),
52 data_mode(false),
53 modem_is_locked(false)
54{
55 assert(modem==0);
56 modem = this;
57 args = KCmdLineArgs::parsedArgs();
58}
59
60
61Modem::~Modem() {
62 modem = 0;
63}
64
65
66speed_t Modem::modemspeed() {
67 // convert the string modem speed int the gpppdata object to a t_speed type
68 // to set the modem. The constants here should all be ifdef'd because
69 // other systems may not have them
70 int i = gpppdata.speed().toInt()/100;
71
72 switch(i) {
73 case 24:
74 return B2400;
75 break;
76 case 96:
77 return B9600;
78 break;
79 case 192:
80 return B19200;
81 break;
82 case 384:
83 return B38400;
84 break;
85#ifdef B57600
86 case 576:
87 return B57600;
88 break;
89#endif
90
91#ifdef B115200
92 case 1152:
93 return B115200;
94 break;
95#endif
96
97#ifdef B230400
98 case 2304:
99 return B230400;
100 break;
101#endif
102
103#ifdef B460800
104 case 4608:
105 return B460800;
106 break;
107#endif
108
109 default:
110 return B38400;
111 break;
112 }
113}
114
115
116bool Modem::opentty() {
117 // int flags;
118 QString device = "";
119 if (args->isSet("dev"))
120 device = args->getOption("dev");
121 else
122 device = gpppdata.modemDevice();
123 kDebug() << "Opening Device: " << device;
124
125 if((modemfd = Requester::rq->openModem(device))<0) {
126 errmsg = i18n("Unable to open modem.");
127 return false;
128 }
129
130#if 0
131 if(gpppdata.UseCDLine()) {
132 if(ioctl(modemfd, TIOCMGET, &flags) == -1) {
133 errmsg = i18n("Unable to detect state of CD line.");
134 ::close(modemfd);
135 modemfd = -1;
136 return false;
137 }
138 if ((flags&TIOCM_CD) == 0) {
139 errmsg = i18n("The modem is not ready.");
140 ::close(modemfd);
141 modemfd = -1;
142 return false;
143 }
144 }
145#endif
146
147 tcdrain (modemfd);
148 tcflush (modemfd, TCIOFLUSH);
149
150 if(tcgetattr(modemfd, &tty) < 0){
151 // this helps in some cases
152 tcsendbreak(modemfd, 0);
153 sleep(1);
154 if(tcgetattr(modemfd, &tty) < 0){
155 errmsg = i18n("The modem is busy.");
156 ::close(modemfd);
157 modemfd = -1;
158 return false;
159 }
160 }
161
162 memset(&initial_tty,'\0',sizeof(initial_tty));
163
164 initial_tty = tty;
165
166 tty.c_cc[VMIN] = 0; // nonblocking
167 tty.c_cc[VTIME] = 0;
168 tty.c_oflag = 0;
169 tty.c_lflag = 0;
170
171 tty.c_cflag &= ~(CSIZE | CSTOPB | PARENB);
172 tty.c_cflag |= CS8 | CREAD;
173 tty.c_cflag |= CLOCAL; // ignore modem status lines
174 tty.c_iflag = IGNBRK | IGNPAR /* | ISTRIP */ ;
175 tty.c_lflag &= ~ICANON; // non-canonical mode
176 tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHOKE);
177
178
179 // the english/i18n mix below is ugly but we want to keep working
180 // after someone changed the code to use i18n'ed config values
181 QString flowCtrl = gpppdata.flowcontrol();
182 if(flowCtrl != "None" && flowCtrl != i18n("None")) {
183 if(flowCtrl == "CRTSCTS" || flowCtrl == i18n("Hardware [CRTSCTS]")) {
184 tty.c_cflag |= CRTSCTS;
185 }
186 else {
187 tty.c_iflag |= IXON | IXOFF;
188 tty.c_cc[VSTOP] = 0x13; /* DC3 = XOFF = ^S */
189 tty.c_cc[VSTART] = 0x11; /* DC1 = XON = ^Q */
190 }
191 }
192 else {
193 tty.c_cflag &= ~CRTSCTS;
194 tty.c_iflag &= ~(IXON | IXOFF);
195 }
196
197 cfsetospeed(&tty, modemspeed());
198 cfsetispeed(&tty, modemspeed());
199
200 tcdrain(modemfd);
201
202 if(tcsetattr(modemfd, TCSANOW, &tty) < 0){
203 errmsg = i18n("The modem is busy.");
204 ::close(modemfd);
205 modemfd=-1;
206 return false;
207 }
208
209 errmsg = i18n("Modem Ready.");
210 return true;
211}
212
213
214bool Modem::closetty() {
215 if(modemfd >=0 ) {
216 stop();
217 /* discard data not read or transmitted */
218 tcflush(modemfd, TCIOFLUSH);
219
220 if(tcsetattr(modemfd, TCSANOW, &initial_tty) < 0){
221 errmsg = i18n("Can not restore tty settings: tcsetattr()\n");
222 ::close(modemfd);
223 modemfd = -1;
224 return false;
225 }
226 ::close(modemfd);
227 modemfd = -1;
228 }
229
230 return true;
231}
232
233
234void Modem::readtty(int) {
235 char buffer[200];
236 unsigned char c;
237 int len;
238
239 // read data in chunks of up to 200 bytes
240 if((len = ::read(modemfd, buffer, 200)) > 0) {
241 // split buffer into single characters for further processing
242 for(int i = 0; i < len; i++) {
243 c = buffer[i] & 0x7F;
244 emit charWaiting(c);
245 }
246 }
247}
248
249
250void Modem::notify(const QObject *receiver, const char *member) {
251 connect(this, SIGNAL(charWaiting(unsigned char)), receiver, member);
252 startNotifier();
253}
254
255
256void Modem::stop() {
257 disconnect(SIGNAL(charWaiting(unsigned char)));
258 stopNotifier();
259}
260
261
262void Modem::startNotifier() {
263 if(modemfd >= 0) {
264 if(sn == 0) {
265 sn = new QSocketNotifier(modemfd, QSocketNotifier::Read, this);
266 connect(sn, SIGNAL(activated(int)), SLOT(readtty(int)));
267 kDebug(5002) << "QSocketNotifier started!";
268 } else {
269 // Debug("QSocketNotifier re-enabled!");
270 sn->setEnabled(true);
271 }
272 }
273}
274
275
276void Modem::stopNotifier() {
277 if(sn != 0) {
278 sn->setEnabled(false);
279 disconnect(sn);
280 delete sn;
281 sn = 0;
282 kDebug(5002) << "QSocketNotifier stopped!";
283 }
284}
285
286
287void Modem::flush() {
288 char c;
289 while(read(modemfd, &c, 1) == 1);
290}
291
292
293bool Modem::writeChar(unsigned char c) {
294 int s;
295 do {
296 s = write(modemfd, &c, 1);
297 if (s < 0) {
298 kError(5002) << "write() in Modem::writeChar failed" << endl;
299 return false;
300 }
301 } while(s == 0);
302
303 return true;
304}
305
306
307bool Modem::writeLine(const char *buf) {
308 int len = strlen(buf);
309 char *b = new char[len+2];
310 memcpy(b, buf, len);
311 // different modems seem to need different line terminations
312 QString term = gpppdata.enter();
313 if(term == "LF")
314 b[len++]='\n';
315 else if(term == "CR")
316 b[len++]='\r';
317 else if(term == "CR/LF") {
318 b[len++]='\r';
319 b[len++]='\n';
320 }
321 int l = len;
322 while(l) {
323 int wr = write(modemfd, &b[len-l], l);
324 if(wr < 0) {
325 if (errno == EAGAIN)
326 continue;
327 // TODO do something meaningful with the error code (or ignore it
328 kError(5002) << "write() in Modem::writeLine failed" << endl;
329 delete[] b;
330 return false;
331 }
332 l -= wr;
333 }
334 delete[] b;
335 return true;
336}
337
338
339bool Modem::hangup() {
340 // this should really get the modem to hang up and go into command mode
341 // If anyone sees a fault in the following please let me know, since
342 // this is probably the most imporant snippet of code in the whole of
343 // kppp. If people complain about kppp being stuck, this piece of code
344 // is most likely the reason.
345 struct termios temptty;
346
347 if(modemfd >= 0) {
348
349 // is this Escape & HangupStr stuff really necessary ? (Harri)
350
351 if (data_mode) escape_to_command_mode();
352
353 // Then hangup command
354 writeLine(gpppdata.modemHangupStr().toLocal8Bit());
355
356 usleep(gpppdata.modemInitDelay() * 10000); // 0.01 - 3.0 sec
357
358#ifndef DEBUG_WO_DIALING
359 if (sigsetjmp(jmp_buffer, 1) == 0) {
360 // set alarm in case tcsendbreak() hangs
361 signal(SIGALRM, alarm_handler);
362 alarm(2);
363
364 tcsendbreak(modemfd, 0);
365
366 alarm(0);
367 signal(SIGALRM, SIG_IGN);
368 } else {
369 // we reach this point if the alarm handler got called
370 closetty();
371 close(modemfd);
372 modemfd = -1;
373 errmsg = i18n("The modem does not respond.");
374 return false;
375 }
376
377#ifndef __SVR4 // drops DTR but doesn't set it afterwards again. not good for init.
378 tcgetattr(modemfd, &temptty);
379 cfsetospeed(&temptty, B0);
380 cfsetispeed(&temptty, B0);
381 tcsetattr(modemfd, TCSAFLUSH, &temptty);
382#else
383 int modemstat;
384 ioctl(modemfd, TIOCMGET, &modemstat);
385 modemstat &= ~TIOCM_DTR;
386 ioctl(modemfd, TIOCMSET, &modemstat);
387 ioctl(modemfd, TIOCMGET, &modemstat);
388 modemstat |= TIOCM_DTR;
389 ioctl(modemfd, TIOCMSET, &modemstat);
390#endif
391
392 usleep(gpppdata.modemInitDelay() * 10000); // 0.01 - 3.0 secs
393
394 cfsetospeed(&temptty, modemspeed());
395 cfsetispeed(&temptty, modemspeed());
396 tcsetattr(modemfd, TCSAFLUSH, &temptty);
397#endif
398 return true;
399 } else
400 return false;
401}
402
403
404void Modem::escape_to_command_mode() {
405 // Send Properly bracketed escape code to put the modem back into command state.
406 // A modem will accept AT commands only when it is in command state.
407 // When a modem sends the host the CONNECT string, that signals
408 // that the modem is now in the connect state (no long accepts AT commands.)
409 // Need to send properly timed escape sequence to put modem in command state.
410 // Escape codes and guard times are controlled by S2 and S12 values.
411 //
412 tcflush(modemfd, TCIOFLUSH);
413
414 // +3 because quiet time must be greater than guard time.
415 usleep((gpppdata.modemEscapeGuardTime()+3)*20000);
416 QByteArray tmp = gpppdata.modemEscapeStr().toLocal8Bit();
417 write(modemfd, tmp.data(), tmp.length());
418 tcflush(modemfd, TCIOFLUSH);
419 usleep((gpppdata.modemEscapeGuardTime()+3)*20000);
420
421 data_mode = false;
422}
423
424
425const QString Modem::modemMessage() {
426 return errmsg;
427}
428
429
430QString Modem::parseModemSpeed(const QString &s) {
431 // this is a small (and bad) parser for modem speeds
432 int rx = -1;
433 int tx = -1;
434 int i;
435 QString result;
436
437 kDebug(5002) << "Modem reported result string: " << s;
438
439 const int RXMAX = 7;
440 const int TXMAX = 2;
441 QRegExp rrx[RXMAX] = {
442 QRegExp("[0-9]+[:/ ]RX", Qt::CaseInsensitive),
443 QRegExp("[0-9]+RX", Qt::CaseInsensitive),
444 QRegExp("[/: -][0-9]+[/: ]", Qt::CaseInsensitive),
445 QRegExp("[/: -][0-9]+$", Qt::CaseInsensitive),
446 QRegExp("CARRIER [^0-9]*[0-9]+", Qt::CaseInsensitive),
447 QRegExp("CONNECT [^0-9]*[0-9]+", Qt::CaseInsensitive),
448 QRegExp("[0-9]+") // panic mode
449 };
450
451 QRegExp trx[TXMAX] = {
452 QRegExp("[0-9]+[:/ ]TX", Qt::CaseInsensitive),
453 QRegExp("[0-9]+TX", Qt::CaseInsensitive)
454 };
455
456 for(i = 0; i < RXMAX; i++) {
457 int len, idx, result;
458 if((idx = rrx[i].indexIn(s)) > -1) {
459 len = rrx[i].matchedLength();
460
461 //
462 // rrx[i] has been matched, idx contains the start of the match
463 // and len contains how long the match is. Extract the match.
464 //
465 QString sub = s.mid(idx, len);
466
467 //
468 // Now extract the digits only from the match, which will
469 // then be converted to an int.
470 //
471 if ((idx = rrx[RXMAX-1].indexIn( sub )) > -1) {
472 len = rrx[RXMAX-1].matchedLength();
473 sub = sub.mid(idx, len);
474 result = sub.toInt();
475 if(result > 0) {
476 rx = result;
477 break;
478 }
479 }
480 }
481 }
482
483 for(i = 0; i < TXMAX; i++) {
484 int len, idx, result;
485 if((idx = trx[i].indexIn(s)) > -1) {
486 len = trx[i].matchedLength();
487
488 //
489 // trx[i] has been matched, idx contains the start of the match
490 // and len contains how long the match is. Extract the match.
491 //
492 QString sub = s.mid(idx, len);
493
494 //
495 // Now extract the digits only from the match, which will then
496 // be converted to an int.
497 //
498 if((idx = rrx[RXMAX-1].indexIn(sub)) > -1) {
499 len = rrx[RXMAX-1].matchedLength();
500 sub = sub.mid(idx, len);
501 result = sub.toInt();
502 if(result > 0) {
503 tx = result;
504 break;
505 }
506 }
507 }
508 }
509
510 if(rx == -1 && tx == -1)
511 result = i18n("Unknown speed");
512 else if(tx == -1)
513 result.setNum(rx);
514 else if(rx == -1) // should not happen
515 result.setNum(tx);
516 else
517 result.sprintf("%d/%d", rx, tx);
518
519 kDebug(5002) << "The parsed result is: " << result;
520
521 return result;
522}
523
524
525// Lock modem device. Returns 0 on success 1 if the modem is locked and -1 if
526// a lock file can't be created ( permission problem )
527int Modem::lockdevice() {
528 int fd;
529 char newlock[80]=""; // safe
530
531 if(!gpppdata.modemLockFile()) {
532 kDebug(5002) << "The user doesn't want a lockfile.";
533 return 0;
534 }
535
536 if (modem_is_locked)
537 return 1;
538
539 QString device = "";
540 if (args->isSet("dev"))
541 device = args->getOption("dev");
542 else
543 device = gpppdata.modemDevice();
544
545 QString lockfile = LOCK_DIR"/LCK..";
546 lockfile += device.mid(5); // append everything after /dev/
547
548 if(access(QFile::encodeName(lockfile), F_OK) == 0) {
549 if ((fd = Requester::rq->openLockfile(QFile::encodeName(lockfile), O_RDONLY)) >= 0) {
550 // Mario: it's not necessary to read more than lets say 32 bytes. If
551 // file has more than 32 bytes, skip the rest
552 char oldlock[33]; // safe
553 int sz = read(fd, &oldlock, 32);
554 close (fd);
555 if (sz <= 0)
556 return 1;
557 oldlock[sz] = '\0';
558
559 kDebug(5002) << "Device is locked by: " << &oldlock;
560
561 int oldpid;
562 int match = sscanf(oldlock, "%d", &oldpid);
563
564 // found a pid in lockfile ?
565 if (match < 1 || oldpid <= 0)
566 return 1;
567
568 // check if process exists
569 if (kill((pid_t)oldpid, 0) == 0 || errno != ESRCH)
570 return 1;
571
572 kDebug(5002) << "lockfile is stale";
573 }
574 }
575
576 fd = Requester::rq->openLockfile(device,
577 O_WRONLY|O_TRUNC|O_CREAT);
578 if(fd >= 0) {
579 sprintf(newlock,"%010d\n", getpid());
580 kDebug(5002) << "Locking Device: " << newlock;
581
582 write(fd, newlock, strlen(newlock));
583 close(fd);
584 modem_is_locked=true;
585
586 return 0;
587 }
588
589 return -1;
590
591}
592
593
594// UnLock modem device
595void Modem::unlockdevice() {
596 if (modem_is_locked) {
597 kDebug(5002) << "UnLocking Modem Device";
598 Requester::rq->removeLockfile();
599 modem_is_locked=false;
600 }
601}
602
603void alarm_handler(int) {
604 // fprintf(stderr, "alarm_handler(): Received SIGALRM\n");
605
606 // jump
607 siglongjmp(jmp_buffer, 1);
608}
609
610#include "modem.moc"
611