1556 lines
26 KiB
C
1556 lines
26 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <auth.h>
|
|
#include <fcall.h>
|
|
|
|
#define LOGFILE "telco"
|
|
|
|
/*
|
|
* Rather than reading /adm/users, which is a lot of work for
|
|
* a toy progdev, we assume all groups have the form
|
|
* NNN:user:user:
|
|
* meaning that each user is the leader of his own group.
|
|
*/
|
|
|
|
enum
|
|
{
|
|
OPERM = 0x3, /* mask of all permission types in open mode */
|
|
Ndev = 8,
|
|
Nreq = (Ndev*3)/2,
|
|
Nrbuf = 32*1024,
|
|
};
|
|
|
|
typedef struct Fid Fid;
|
|
typedef struct Dev Dev;
|
|
typedef struct Request Request;
|
|
typedef struct Type Type;
|
|
|
|
struct Fid
|
|
{
|
|
Qid qid;
|
|
short busy;
|
|
short open;
|
|
int fid;
|
|
Fid *next;
|
|
char *user;
|
|
};
|
|
|
|
struct Request
|
|
{
|
|
Request *next;
|
|
|
|
Fid *fid;
|
|
ulong tag;
|
|
int count;
|
|
int flushed;
|
|
};
|
|
|
|
struct Dev
|
|
{
|
|
Lock;
|
|
|
|
/* device state */
|
|
int ctl; /* control fd */
|
|
int data; /* data fd */
|
|
char *path; /* to device */
|
|
Type *t;
|
|
Type *baset;
|
|
int speed;
|
|
int fclass;
|
|
|
|
/* fs emulation */
|
|
int open;
|
|
long perm;
|
|
char *name;
|
|
char *user;
|
|
char msgbuf[128];
|
|
Request *r;
|
|
Request *rlast;
|
|
|
|
/* input reader */
|
|
int monitoring; /* monitor pid */
|
|
char rbuf[Nrbuf];
|
|
char *rp;
|
|
char *wp;
|
|
long pid;
|
|
};
|
|
|
|
enum
|
|
{
|
|
Devmask= (Ndev-1)<<8,
|
|
|
|
Qlvl1= 0,
|
|
Qlvl2= 1,
|
|
Qclone= 2,
|
|
Qlvl3= 3,
|
|
Qdata= 4,
|
|
Qctl= 5,
|
|
|
|
Pexec = 1,
|
|
Pwrite = 2,
|
|
Pread = 4,
|
|
Pother = 1,
|
|
Pgroup = 8,
|
|
Powner = 64,
|
|
};
|
|
|
|
char *names[] =
|
|
{
|
|
[Qlvl1] "/",
|
|
[Qlvl2] "telco",
|
|
[Qclone] "clone",
|
|
[Qlvl3] "",
|
|
[Qdata] "data",
|
|
[Qctl] "ctl",
|
|
};
|
|
|
|
#define DEV(q) ((((ulong)(q).path)&Devmask)>>8)
|
|
#define TYPE(q) (((ulong)(q).path)&((1<<8)-1))
|
|
#define MKQID(t, i) ((((i)<<8)&Devmask) | (t))
|
|
|
|
enum
|
|
{
|
|
/*
|
|
* modem specific commands
|
|
*/
|
|
Cerrorcorrection = 0, /* error correction */
|
|
Ccompression, /* compression */
|
|
Cflowctl, /* CTS/RTS */
|
|
Crateadjust, /* follow line speed */
|
|
Cfclass2, /* set up for fax */
|
|
Cfclass0, /* set up for data */
|
|
Ncommand,
|
|
};
|
|
|
|
struct Type
|
|
{
|
|
char *name;
|
|
char *ident; /* inquire request */
|
|
char *response; /* inquire response (strstr) */
|
|
char *basetype; /* name of base type */
|
|
|
|
char *commands[Ncommand];
|
|
};
|
|
|
|
/*
|
|
* Fax setup summary
|
|
*
|
|
* FCLASS=2 - set to service class 2, i.e., one where the fax handles timing
|
|
* FTBC=0 - ???
|
|
* FREL=1 - ???
|
|
* FCQ=1 - receive copy quality checking enabled
|
|
* FBOR=1 - set reversed bit order for phase C data
|
|
* FCR=1 - the DCE can receive message data, bit 10 in the DIS or
|
|
* DTC frame will be set
|
|
* FDIS=,3 - limit com speed to 9600 baud
|
|
*/
|
|
|
|
Type typetab[] =
|
|
{
|
|
{ "Rockwell", 0, 0, 0,
|
|
"AT\\N7", /* auto reliable (V.42, fall back to MNP, to none) */
|
|
"AT%C1\\J0", /* negotiate for compression, don't change port baud rate */
|
|
"AT\\Q3", /* CTS/RTS flow control */
|
|
"AT\\J1",
|
|
"AT+FCLASS=2\rAT+FCR=1\r",
|
|
"AT+FCLASS=0",
|
|
},
|
|
|
|
{ "ATT2400", "ATI9", "E2400", "Rockwell",
|
|
"AT\\N3", /* auto reliable (MNP, fall back to none) */
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
},
|
|
|
|
{ "ATT14400", "ATI9", "E14400", "Rockwell",
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
},
|
|
|
|
{ "MT1432", "ATI2", "MT1432", 0,
|
|
"AT&E1", /* auto reliable (V.42, fall back to none) */
|
|
"AT&E15$BA0", /* negotiate for compression */
|
|
"AT&E4", /* CTS/RTS flow control */
|
|
"AT$BA1", /* don't change port baud rate */
|
|
"AT+FCLASS=2\rAT+FTBC=0\rAT+FREL=1\rAT+FCQ=1\rAT+FBOR=1\rAT+FCR=1\rAT+FDIS=,3",
|
|
"AT+FCLASS=0",
|
|
},
|
|
|
|
{ "MT2834", "ATI2", "MT2834", "MT1432",
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
"AT+FCLASS=2\rAT+FTBC=0\rAT+FREL=1\rAT+FCQ=1\rAT+FBOR=1\rAT+FCR=1",
|
|
0,
|
|
},
|
|
|
|
{ "VOCAL", "ATI6", "144DPL+FAX", "Rockwell",
|
|
"AT\\N3", /* auto reliable (V.42, fall back to MNP, fall back to none) */
|
|
"AT%C3\\J0", /* negotiate for compression, don't change port baud rate */
|
|
0,
|
|
0,
|
|
"AT+FCLASS=2\rAT+FTBC=0\rAT+FREL=1\rAT+FCQ=1\rAT+FBOR=1\rAT+FCR=1",
|
|
"AT+FCLASS=0",
|
|
},
|
|
|
|
{ 0, },
|
|
};
|
|
|
|
/*
|
|
* modem return codes
|
|
*/
|
|
enum
|
|
{
|
|
Ok,
|
|
Success,
|
|
Failure,
|
|
Noise,
|
|
Found,
|
|
};
|
|
|
|
/*
|
|
* modem return messages
|
|
*/
|
|
typedef struct Msg Msg;
|
|
struct Msg
|
|
{
|
|
char *text;
|
|
int type;
|
|
};
|
|
|
|
Msg msgs[] =
|
|
{
|
|
{ "OK", Ok, },
|
|
{ "NO CARRIER", Failure, },
|
|
{ "ERROR", Failure, },
|
|
{ "NO DIALTONE", Failure, },
|
|
{ "BUSY", Failure, },
|
|
{ "NO ANSWER", Failure, },
|
|
{ "CONNECT", Success, },
|
|
{ 0, 0 },
|
|
};
|
|
|
|
Fid *fids;
|
|
Dev *dev;
|
|
int ndev;
|
|
int mfd[2];
|
|
char *user;
|
|
uchar mdata[8192+IOHDRSZ];
|
|
int messagesize = sizeof mdata;
|
|
Fcall thdr;
|
|
Fcall rhdr;
|
|
char errbuf[ERRMAX];
|
|
uchar statbuf[STATMAX];
|
|
int pulsed;
|
|
int verbose;
|
|
int maxspeed = 56000;
|
|
char *srcid = "plan9";
|
|
int answer = 1;
|
|
|
|
Fid *newfid(int);
|
|
int devstat(Dir*, uchar*, int);
|
|
int devgen(Qid, int, Dir*, uchar*, int);
|
|
void error(char*);
|
|
void io(void);
|
|
void *erealloc(void*, ulong);
|
|
void *emalloc(ulong);
|
|
void usage(void);
|
|
int perm(Fid*, Dev*, int);
|
|
void setspeed(Dev*, int);
|
|
int getspeed(char*, int);
|
|
char *dialout(Dev*, char*);
|
|
void onhook(Dev*);
|
|
int readmsg(Dev*, int, char*);
|
|
void monitor(Dev*);
|
|
int getinput(Dev*, char*, int);
|
|
void serve(Dev*);
|
|
void receiver(Dev*);
|
|
char* modemtype(Dev*, int, int);
|
|
|
|
|
|
char *rflush(Fid*), *rversion(Fid*),
|
|
*rattach(Fid*), *rauth(Fid*), *rwalk(Fid*),
|
|
*ropen(Fid*), *rcreate(Fid*),
|
|
*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
|
|
*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
|
|
|
|
char *(*fcalls[])(Fid*) = {
|
|
[Tflush] rflush,
|
|
[Tversion] rversion,
|
|
[Tattach] rattach,
|
|
[Tauth] rauth,
|
|
[Twalk] rwalk,
|
|
[Topen] ropen,
|
|
[Tcreate] rcreate,
|
|
[Tread] rread,
|
|
[Twrite] rwrite,
|
|
[Tclunk] rclunk,
|
|
[Tremove] rremove,
|
|
[Tstat] rstat,
|
|
[Twstat] rwstat,
|
|
};
|
|
|
|
char Eperm[] = "permission denied";
|
|
char Enotdir[] = "not a directory";
|
|
char Enotexist[] = "file does not exist";
|
|
char Ebadaddr[] = "bad address";
|
|
char Eattn[] = "can't get modem's attention";
|
|
char Edial[] = "can't dial";
|
|
char Enoauth[] = "telco: authentication not required";
|
|
char Eisopen[] = "file already open for I/O";
|
|
char Enodev[] = "no free modems";
|
|
char Enostream[] = "stream closed prematurely";
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: %s [-vp] [-i srcid] dev ...\n", argv0);
|
|
exits("usage");
|
|
}
|
|
|
|
void
|
|
notifyf(void *a, char *s)
|
|
{
|
|
USED(a);
|
|
if(strncmp(s, "interrupt", 9) == 0)
|
|
noted(NCONT);
|
|
noted(NDFLT);
|
|
}
|
|
|
|
void
|
|
main(int argc, char *argv[])
|
|
{
|
|
int p[2];
|
|
int fd;
|
|
char buf[10];
|
|
Dev *d;
|
|
|
|
ARGBEGIN{
|
|
case 'p':
|
|
pulsed = 1;
|
|
break;
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
case 'i':
|
|
srcid = ARGF();
|
|
break;
|
|
case 's':
|
|
maxspeed = atoi(ARGF());
|
|
break;
|
|
case 'n':
|
|
answer = 0;
|
|
break;
|
|
default:
|
|
usage();
|
|
}ARGEND
|
|
|
|
if(argc == 0)
|
|
usage();
|
|
if(argc > Ndev)
|
|
argc = Ndev;
|
|
|
|
if(pipe(p) < 0)
|
|
error("pipe failed");
|
|
|
|
notify(notifyf);
|
|
fmtinstall('F', fcallfmt);
|
|
user = getuser();
|
|
|
|
switch(rfork(RFFDG|RFPROC|RFREND|RFNOTEG)){
|
|
case -1:
|
|
error("fork");
|
|
case 0:
|
|
close(p[1]);
|
|
mfd[0] = mfd[1] = p[0];
|
|
break;
|
|
default:
|
|
close(p[0]);
|
|
fd = create("/srv/telco", OWRITE, 0666);
|
|
if(fd < 0)
|
|
error("create of /srv/telco failed");
|
|
sprint(buf, "%d", p[1]);
|
|
if(write(fd, buf, strlen(buf)) < 0)
|
|
error("writing /srv/telco");
|
|
close(fd);
|
|
if(mount(p[1], -1, "/net", MBEFORE, "") < 0)
|
|
error("mount failed");
|
|
exits(0);
|
|
}
|
|
|
|
dev = mallocz(argc*sizeof(Dev), 1);
|
|
for(ndev = 0; ndev < argc; ndev++){
|
|
d = &dev[ndev];
|
|
d->path = argv[ndev];
|
|
d->rp = d->wp = d->rbuf;
|
|
monitor(d);
|
|
d->open++;
|
|
onhook(d);
|
|
d->open--;
|
|
}
|
|
|
|
io();
|
|
}
|
|
|
|
/*
|
|
* generate a stat structure for a qid
|
|
*/
|
|
int
|
|
devstat(Dir *dir, uchar *buf, int nbuf)
|
|
{
|
|
Dev *d;
|
|
int t;
|
|
static char tmp[10][32];
|
|
static int ntmp;
|
|
|
|
t = TYPE(dir->qid);
|
|
if(t != Qlvl3)
|
|
dir->name = names[t];
|
|
else{
|
|
dir->name = tmp[ntmp % nelem(tmp)];
|
|
sprint(dir->name, "%lud", DEV(dir->qid));
|
|
ntmp++;
|
|
}
|
|
dir->mode = 0755;
|
|
dir->uid = user;
|
|
dir->gid = user;
|
|
dir->muid = user;
|
|
if(t >= Qlvl3){
|
|
d = &dev[DEV(dir->qid)];
|
|
if(d->open){
|
|
dir->mode = d->perm;
|
|
dir->uid = d->user;
|
|
}
|
|
}
|
|
if(dir->qid.type & QTDIR)
|
|
dir->mode |= DMDIR;
|
|
if(t == Qdata){
|
|
d = &dev[DEV(dir->qid)];
|
|
dir->length = d->wp - d->rp;
|
|
if(dir->length < 0)
|
|
dir->length += Nrbuf;
|
|
} else
|
|
dir->length = 0;
|
|
dir->atime = time(0);
|
|
dir->mtime = dir->atime;
|
|
if(buf)
|
|
return convD2M(dir, buf, nbuf);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* enumerate file's we can walk to from q
|
|
*/
|
|
int
|
|
devgen(Qid q, int i, Dir *d, uchar *buf, int nbuf)
|
|
{
|
|
static ulong v;
|
|
|
|
d->qid.vers = v++;
|
|
switch(TYPE(q)){
|
|
case Qlvl1:
|
|
if(i != 0)
|
|
return -1;
|
|
d->qid.type = QTDIR;
|
|
d->qid.path = Qlvl2;
|
|
break;
|
|
case Qlvl2:
|
|
switch(i){
|
|
case -1:
|
|
d->qid.type = QTDIR;
|
|
d->qid.path = Qlvl1;
|
|
break;
|
|
case 0:
|
|
d->qid.type = QTFILE;
|
|
d->qid.path = Qclone;
|
|
break;
|
|
default:
|
|
if(i > ndev)
|
|
return -1;
|
|
d->qid.type = QTDIR;
|
|
d->qid.path = MKQID(Qlvl3, i-1);
|
|
break;
|
|
}
|
|
break;
|
|
case Qlvl3:
|
|
switch(i){
|
|
case -1:
|
|
d->qid.type = QTDIR;
|
|
d->qid.path = Qlvl2;
|
|
break;
|
|
case 0:
|
|
d->qid.type = QTFILE;
|
|
d->qid.path = MKQID(Qdata, DEV(q));
|
|
break;
|
|
case 1:
|
|
d->qid.type = QTFILE;
|
|
d->qid.path = MKQID(Qctl, DEV(q));
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return devstat(d, buf, nbuf);
|
|
}
|
|
|
|
char*
|
|
rversion(Fid *)
|
|
{
|
|
Fid *f;
|
|
|
|
for(f = fids; f; f = f->next)
|
|
if(f->busy)
|
|
rclunk(f);
|
|
|
|
if(thdr.msize < 256)
|
|
return "version: message size too small";
|
|
messagesize = thdr.msize;
|
|
if(messagesize > sizeof mdata)
|
|
messagesize = sizeof mdata;
|
|
rhdr.msize = messagesize;
|
|
if(strncmp(thdr.version, "9P2000", 6) != 0)
|
|
return "unrecognized 9P version";
|
|
rhdr.version = "9P2000";
|
|
return 0;
|
|
}
|
|
|
|
char*
|
|
rflush(Fid *f)
|
|
{
|
|
Request *r, **l;
|
|
Dev *d;
|
|
|
|
USED(f);
|
|
for(d = dev; d < &dev[ndev]; d++){
|
|
lock(d);
|
|
for(l = &d->r; r = *l; l = &r->next)
|
|
if(r->tag == thdr.oldtag){
|
|
*l = r->next;
|
|
free(r);
|
|
break;
|
|
}
|
|
unlock(d);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
rauth(Fid *f)
|
|
{
|
|
USED(f);
|
|
return Enoauth;
|
|
}
|
|
|
|
char*
|
|
rattach(Fid *f)
|
|
{
|
|
f->busy = 1;
|
|
f->qid.type = QTDIR;
|
|
f->qid.path = Qlvl1;
|
|
f->qid.vers = 0;
|
|
rhdr.qid = f->qid;
|
|
if(thdr.uname[0])
|
|
f->user = strdup(thdr.uname);
|
|
else
|
|
f->user = "none";
|
|
return 0;
|
|
}
|
|
|
|
char*
|
|
rwalk(Fid *f)
|
|
{
|
|
Fid *nf;
|
|
int i, nqid;
|
|
char *name, *err;
|
|
Dir dir;
|
|
Qid q;
|
|
|
|
nf = nil;
|
|
if(thdr.fid != thdr.newfid){
|
|
if(f->open)
|
|
return Eisopen;
|
|
if(f->busy == 0)
|
|
return Enotexist;
|
|
nf = newfid(thdr.newfid);
|
|
nf->busy = 1;
|
|
nf->open = 0;
|
|
nf->qid = f->qid;
|
|
nf->user = strdup(f->user);
|
|
f = nf; /* walk f */
|
|
}
|
|
|
|
err = nil;
|
|
dir.qid = f->qid;
|
|
nqid = 0;
|
|
if(thdr.nwname > 0){
|
|
for(; nqid < thdr.nwname; nqid++) {
|
|
if((dir.qid.type & QTDIR) == 0){
|
|
err = Enotdir;
|
|
break;
|
|
}
|
|
name = thdr.wname[nqid];
|
|
if(strcmp(name, ".") == 0){
|
|
/* nothing to do */
|
|
}else if(strcmp(name, "..") == 0) {
|
|
if(devgen(f->qid, -1, &dir, 0, 0) < 0)
|
|
break;
|
|
}
|
|
else{
|
|
q = dir.qid;
|
|
for(i = 0;; i++){
|
|
if(devgen(q, i, &dir, 0, 0) < 0)
|
|
goto Out;
|
|
if(strcmp(name, dir.name) == 0)
|
|
break;
|
|
}
|
|
}
|
|
rhdr.wqid[nqid] = dir.qid;
|
|
}
|
|
Out:
|
|
if(nqid == 0 && err == nil)
|
|
err = Enotexist;
|
|
if(nf != nil && thdr.fid != thdr.newfid && nqid < thdr.nwname)
|
|
rclunk(nf);
|
|
}
|
|
|
|
rhdr.nwqid = nqid;
|
|
if(nqid > 0 && nqid == thdr.nwname)
|
|
f->qid = dir.qid;
|
|
return err;
|
|
}
|
|
|
|
char *
|
|
ropen(Fid *f)
|
|
{
|
|
Dev *d;
|
|
int mode, t;
|
|
|
|
if(f->open)
|
|
return Eisopen;
|
|
mode = thdr.mode;
|
|
mode &= OPERM;
|
|
if(f->qid.type & QTDIR){
|
|
if(mode != OREAD)
|
|
return Eperm;
|
|
rhdr.qid = f->qid;
|
|
return 0;
|
|
}
|
|
if(mode==OEXEC)
|
|
return Eperm;
|
|
t = TYPE(f->qid);
|
|
if(t == Qclone){
|
|
for(d = dev; d < &dev[ndev]; d++)
|
|
if(d->open == 0)
|
|
break;
|
|
if(d == &dev[ndev])
|
|
return Enodev;
|
|
f->qid.path = MKQID(Qctl, d-dev);
|
|
t = Qctl;
|
|
}
|
|
switch(t){
|
|
case Qdata:
|
|
case Qctl:
|
|
d = &dev[DEV(f->qid)];
|
|
if(d->open == 0){
|
|
d->user = strdup(f->user);
|
|
d->perm = 0660;
|
|
}else {
|
|
if(mode==OWRITE || mode==ORDWR)
|
|
if(!perm(f, d, Pwrite))
|
|
return Eperm;
|
|
if(mode==OREAD || mode==ORDWR)
|
|
if(!perm(f, d, Pread))
|
|
return Eperm;
|
|
}
|
|
d->open++;
|
|
break;
|
|
}
|
|
rhdr.qid = f->qid;
|
|
rhdr.iounit = messagesize - IOHDRSZ;
|
|
f->open = 1;
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
rcreate(Fid *f)
|
|
{
|
|
USED(f);
|
|
return Eperm;
|
|
}
|
|
|
|
/*
|
|
* intercept a note
|
|
*/
|
|
void
|
|
takeanote(void *u, char *note)
|
|
{
|
|
USED(u);
|
|
if(strstr(note, "interrupted"))
|
|
noted(NCONT);
|
|
noted(NDFLT);
|
|
}
|
|
|
|
char*
|
|
rread(Fid *f)
|
|
{
|
|
char *buf;
|
|
long off, start;
|
|
int i, m, n, cnt, t;
|
|
Dir dir;
|
|
char num[32];
|
|
Dev *d;
|
|
Request *r;
|
|
|
|
n = 0;
|
|
rhdr.count = 0;
|
|
off = thdr.offset;
|
|
cnt = thdr.count;
|
|
buf = rhdr.data;
|
|
t = TYPE(f->qid);
|
|
switch(t){
|
|
default:
|
|
start = 0;
|
|
for(i = 0; n < cnt; i++){
|
|
m = devgen(f->qid, i, &dir, (uchar*)buf+n, cnt-n);
|
|
if(m <= BIT16SZ)
|
|
break;
|
|
if(start >= off)
|
|
n += m;
|
|
start += m;
|
|
}
|
|
break;
|
|
case Qctl:
|
|
i = sprint(num, "%lud", DEV(f->qid));
|
|
if(off < i){
|
|
n = cnt;
|
|
if(off + n > i)
|
|
n = i - off;
|
|
memmove(buf, num + off, n);
|
|
} else
|
|
n = 0;
|
|
break;
|
|
case Qdata:
|
|
d = &dev[DEV(f->qid)];
|
|
r = mallocz(sizeof(Request), 1);
|
|
r->tag = thdr.tag;
|
|
r->count = thdr.count;
|
|
r->fid = f;
|
|
r->flushed = 0;
|
|
lock(d);
|
|
if(d->r)
|
|
d->rlast->next = r;
|
|
else
|
|
d->r = r;
|
|
d->rlast = r;
|
|
serve(d);
|
|
unlock(d);
|
|
return "";
|
|
}
|
|
rhdr.count = n;
|
|
return 0;
|
|
}
|
|
|
|
char *cmsg = "connect ";
|
|
int clen;
|
|
|
|
char*
|
|
rwrite(Fid *f)
|
|
{
|
|
Dev *d;
|
|
ulong off;
|
|
int cnt;
|
|
char *cp;
|
|
char buf[64];
|
|
|
|
off = thdr.offset;
|
|
cnt = thdr.count;
|
|
switch(TYPE(f->qid)){
|
|
default:
|
|
return "file is a directory";
|
|
case Qctl:
|
|
d = &dev[DEV(f->qid)];
|
|
clen = strlen(cmsg);
|
|
if(cnt < clen || strncmp(thdr.data, cmsg, clen) != 0){
|
|
/*
|
|
* send control message to real control file
|
|
*/
|
|
if(seek(d->ctl, off, 0) < 0 || write(d->ctl, thdr.data, cnt) < 0){
|
|
errstr(errbuf, sizeof errbuf);
|
|
return errbuf;
|
|
}
|
|
} else {
|
|
/*
|
|
* connect
|
|
*/
|
|
cnt -= clen;
|
|
if(cnt >= sizeof(buf))
|
|
cnt = sizeof(buf) - 1;
|
|
if(cnt < 0)
|
|
return Ebadaddr;
|
|
strncpy(buf, &thdr.data[clen], cnt);
|
|
buf[cnt] = 0;
|
|
cp = dialout(d, buf);
|
|
if(cp)
|
|
return cp;
|
|
}
|
|
rhdr.count = cnt;
|
|
break;
|
|
case Qdata:
|
|
d = &dev[DEV(f->qid)];
|
|
if(write(d->data, thdr.data, cnt) < 0){
|
|
errstr(errbuf, sizeof errbuf);
|
|
return errbuf;
|
|
}
|
|
rhdr.count = cnt;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
rclunk(Fid *f)
|
|
{
|
|
Dev *d;
|
|
|
|
if(f->open)
|
|
switch(TYPE(f->qid)){
|
|
case Qdata:
|
|
case Qctl:
|
|
d = &dev[DEV(f->qid)];
|
|
if(d->open == 1)
|
|
onhook(d);
|
|
d->open--;
|
|
break;
|
|
}
|
|
free(f->user);
|
|
f->busy = 0;
|
|
f->open = 0;
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
rremove(Fid *f)
|
|
{
|
|
USED(f);
|
|
return Eperm;
|
|
}
|
|
|
|
char *
|
|
rstat(Fid *f)
|
|
{
|
|
Dir d;
|
|
|
|
d.qid = f->qid;
|
|
rhdr.stat = statbuf;
|
|
rhdr.nstat = devstat(&d, statbuf, sizeof statbuf);
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
rwstat(Fid *f)
|
|
{
|
|
Dev *d;
|
|
Dir dir;
|
|
|
|
if(TYPE(f->qid) < Qlvl3)
|
|
return Eperm;
|
|
|
|
convM2D(thdr.stat, thdr.nstat, &dir, rhdr.data); /* rhdr.data is a known place to scribble */
|
|
d = &dev[DEV(f->qid)];
|
|
|
|
/*
|
|
* To change mode, must be owner
|
|
*/
|
|
if(d->perm != dir.mode){
|
|
if(strcmp(f->user, d->user) != 0)
|
|
if(strcmp(f->user, user) != 0)
|
|
return Eperm;
|
|
}
|
|
|
|
/* all ok; do it */
|
|
d->perm = dir.mode & ~DMDIR;
|
|
return 0;
|
|
}
|
|
|
|
Fid *
|
|
newfid(int fid)
|
|
{
|
|
Fid *f, *ff;
|
|
|
|
ff = 0;
|
|
for(f = fids; f; f = f->next)
|
|
if(f->fid == fid)
|
|
return f;
|
|
else if(!ff && !f->busy)
|
|
ff = f;
|
|
if(ff){
|
|
ff->fid = fid;
|
|
return ff;
|
|
}
|
|
f = emalloc(sizeof *f);
|
|
f->fid = fid;
|
|
f->next = fids;
|
|
fids = f;
|
|
return f;
|
|
}
|
|
|
|
/*
|
|
* read fs requests and dispatch them
|
|
*/
|
|
void
|
|
io(void)
|
|
{
|
|
char *err;
|
|
int n;
|
|
|
|
for(;;){
|
|
/*
|
|
* reading from a pipe or a network device
|
|
* will give an error after a few eof reads
|
|
* however, we cannot tell the difference
|
|
* between a zero-length read and an interrupt
|
|
* on the processes writing to us,
|
|
* so we wait for the error
|
|
*/
|
|
n = read9pmsg(mfd[0], mdata, messagesize);
|
|
if(n == 0)
|
|
continue;
|
|
if(n < 0)
|
|
error("mount read");
|
|
if(convM2S(mdata, n, &thdr) != n)
|
|
error("convM2S error");
|
|
|
|
rhdr.data = (char*)mdata + IOHDRSZ;
|
|
if(!fcalls[thdr.type])
|
|
err = "bad fcall type";
|
|
else
|
|
err = (*fcalls[thdr.type])(newfid(thdr.fid));
|
|
if(err){
|
|
if(*err == 0)
|
|
continue; /* assigned to a slave */
|
|
rhdr.type = Rerror;
|
|
rhdr.ename = err;
|
|
}else{
|
|
rhdr.type = thdr.type + 1;
|
|
rhdr.fid = thdr.fid;
|
|
}
|
|
rhdr.tag = thdr.tag;
|
|
n = convS2M(&rhdr, mdata, messagesize);
|
|
if(write(mfd[1], mdata, n) != n)
|
|
error("mount write");
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
perm(Fid *f, Dev *d, int p)
|
|
{
|
|
if((p*Pother) & d->perm)
|
|
return 1;
|
|
if(strcmp(f->user, user)==0 && ((p*Pgroup) & d->perm))
|
|
return 1;
|
|
if(strcmp(f->user, d->user)==0 && ((p*Powner) & d->perm))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
error(char *s)
|
|
{
|
|
fprint(2, "%s: %s: %r\n", argv0, s);
|
|
syslog(0, LOGFILE, "%s: %r", s);
|
|
remove("/srv/telco");
|
|
postnote(PNGROUP, getpid(), "exit");
|
|
exits(s);
|
|
}
|
|
|
|
void *
|
|
emalloc(ulong n)
|
|
{
|
|
void *p;
|
|
|
|
p = mallocz(n, 1);
|
|
if(!p)
|
|
error("out of memory");
|
|
return p;
|
|
}
|
|
|
|
void *
|
|
erealloc(void *p, ulong n)
|
|
{
|
|
p = realloc(p, n);
|
|
if(!p)
|
|
error("out of memory");
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* send bytes to modem
|
|
*/
|
|
int
|
|
send(Dev *d, char *x)
|
|
{
|
|
if(verbose)
|
|
syslog(0, LOGFILE, "->%s", x);
|
|
return write(d->data, x, strlen(x));
|
|
}
|
|
|
|
/*
|
|
* apply a string of commands to modem
|
|
*/
|
|
int
|
|
apply(Dev *d, char *s, char *substr, int secs)
|
|
{
|
|
char buf[128];
|
|
char *p;
|
|
int c, m;
|
|
|
|
p = buf;
|
|
m = Ok;
|
|
while(*s){
|
|
c = *p++ = *s++;
|
|
if(c == '\r' || *s == 0){
|
|
if(c != '\r')
|
|
*p++ = '\r';
|
|
*p = 0;
|
|
if(send(d, buf) < 0)
|
|
return Failure;
|
|
m = readmsg(d, secs, substr);
|
|
p = buf;
|
|
}
|
|
}
|
|
return m;
|
|
}
|
|
|
|
/*
|
|
* apply a command type
|
|
*/
|
|
int
|
|
applyspecial(Dev *d, int index)
|
|
{
|
|
char *cmd;
|
|
|
|
cmd = d->t->commands[index];
|
|
if(cmd == 0 && d->baset)
|
|
cmd = d->baset->commands[index];
|
|
if(cmd == 0)
|
|
return Failure;
|
|
|
|
return apply(d, cmd, 0, 2);
|
|
}
|
|
|
|
/*
|
|
* get modem into command mode if it isn't already
|
|
*/
|
|
int
|
|
attention(Dev *d)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 2; i++){
|
|
sleep(250);
|
|
if(send(d, "+") < 0)
|
|
continue;
|
|
sleep(250);
|
|
if(send(d, "+") < 0)
|
|
continue;
|
|
sleep(250);
|
|
if(send(d, "+") < 0)
|
|
continue;
|
|
sleep(250);
|
|
readmsg(d, 0, 0);
|
|
if(apply(d, "ATZH0", 0, 2) == Ok)
|
|
return Ok;
|
|
}
|
|
return Failure;
|
|
}
|
|
|
|
int portspeed[] = { 56000, 38400, 19200, 14400, 9600, 4800, 2400, 1200, 600, 300, 0 };
|
|
|
|
/*
|
|
* get the modem's type and speed
|
|
*/
|
|
char*
|
|
modemtype(Dev *d, int limit, int fax)
|
|
{
|
|
int *p;
|
|
Type *t, *bt;
|
|
char buf[28];
|
|
|
|
d->t = typetab;
|
|
d->baset = 0;
|
|
|
|
/* assume we're at a good speed, try getting attention a few times */
|
|
attention(d);
|
|
|
|
/* find a common port rate */
|
|
for(p = portspeed; *p; p++){
|
|
if(*p > limit)
|
|
continue;
|
|
setspeed(d, *p);
|
|
if(attention(d) == Ok)
|
|
break;
|
|
}
|
|
if(*p == 0)
|
|
return Eattn;
|
|
d->speed = *p;
|
|
if(verbose)
|
|
syslog(0, LOGFILE, "port speed %d", *p);
|
|
|
|
/*
|
|
* basic Hayes commands everyone implements (we hope)
|
|
* Q0 = report result codes
|
|
* V1 = full word result codes
|
|
* E0 = don't echo commands
|
|
* M1 = speaker on until on-line
|
|
* S0=0 = autoanswer off
|
|
*/
|
|
if(apply(d, "ATQ0V1E0M1S0=0", 0, 2) != Ok)
|
|
return Eattn;
|
|
|
|
/* find modem type */
|
|
for(t = typetab; t->name; t++){
|
|
if(t->ident == 0 || t->response == 0)
|
|
continue;
|
|
if(apply(d, t->ident, t->response, 2) == Found)
|
|
break;
|
|
readmsg(d, 0, 0);
|
|
}
|
|
readmsg(d, 0, 0);
|
|
if(t->name){
|
|
d->t = t;
|
|
if(t->basetype){
|
|
for(bt = typetab; bt->name; bt++)
|
|
if(strcmp(bt->name, t->basetype) == 0)
|
|
break;
|
|
if(bt->name)
|
|
d->baset = bt;
|
|
}
|
|
}
|
|
if(verbose)
|
|
syslog(0, LOGFILE, "modem %s", d->t->name);
|
|
|
|
/* try setting fax modes */
|
|
d->fclass = 0;
|
|
if(fax){
|
|
/* set up fax parameters */
|
|
if(applyspecial(d, Cfclass2) != Failure)
|
|
d->fclass = 2;
|
|
|
|
/* setup a source id */
|
|
if(srcid){
|
|
sprint(buf, "AT+FLID=\"%s\"", srcid);
|
|
apply(d, buf, 0, 2);
|
|
}
|
|
|
|
/* allow both data and fax calls in */
|
|
apply(d, "AT+FAA=1", 0, 2);
|
|
} else
|
|
applyspecial(d, Cfclass0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* a process to read input from a modem.
|
|
*/
|
|
void
|
|
monitor(Dev *d)
|
|
{
|
|
int n;
|
|
char *p;
|
|
char file[256];
|
|
int background;
|
|
|
|
background = 0;
|
|
d->ctl = d->data = -1;
|
|
|
|
for(;;){
|
|
lock(d);
|
|
sprint(file, "%sctl", d->path);
|
|
d->ctl = open(file, ORDWR);
|
|
if(d->ctl < 0)
|
|
error("opening ctl");
|
|
d->data = open(d->path, ORDWR);
|
|
if(d->data < 0)
|
|
error("opening data");
|
|
d->wp = d->rp = d->rbuf;
|
|
unlock(d);
|
|
|
|
if(!background){
|
|
background = 1;
|
|
switch(d->pid = rfork(RFPROC|RFMEM)){
|
|
case -1:
|
|
error("out of processes");
|
|
case 0:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* wait for ring or off hook */
|
|
while(d->open == 0){
|
|
d->rp = d->rbuf;
|
|
p = d->wp;
|
|
n = read(d->data, p, 1);
|
|
if(n < 1)
|
|
continue;
|
|
if(p < &d->rbuf[Nrbuf] - 2)
|
|
d->wp++;
|
|
if(*p == '\r' || *p == '\n'){
|
|
*(p+1) = 0;
|
|
if(verbose)
|
|
syslog(0, LOGFILE, "<:-%s", d->rp);
|
|
if(answer && strncmp(d->rp, "RING", 4) == 0){
|
|
receiver(d);
|
|
continue;
|
|
}
|
|
if(d->open == 0)
|
|
d->wp = d->rbuf;
|
|
}
|
|
}
|
|
|
|
/* shuttle bytes till on hook */
|
|
while(d->open){
|
|
if(d->wp >= d->rp)
|
|
n = &d->rbuf[Nrbuf] - d->wp;
|
|
else
|
|
n = d->rp - d->wp - 1;
|
|
if(n > 0)
|
|
n = read(d->data, d->wp, n);
|
|
else {
|
|
read(d->data, file, sizeof(file));
|
|
continue;
|
|
}
|
|
if(n < 0)
|
|
break;
|
|
lock(d);
|
|
if(d->wp + n >= &d->rbuf[Nrbuf])
|
|
d->wp = d->rbuf;
|
|
else
|
|
d->wp += n;
|
|
serve(d);
|
|
unlock(d);
|
|
}
|
|
|
|
close(d->ctl);
|
|
close(d->data);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get bytes input by monitor() (only routine that changes d->rp)
|
|
*/
|
|
int
|
|
getinput(Dev *d, char *buf, int n)
|
|
{
|
|
char *p;
|
|
int i;
|
|
|
|
p = buf;
|
|
while(n > 0){
|
|
if(d->wp == d->rp)
|
|
break;
|
|
if(d->wp < d->rp)
|
|
i = &d->rbuf[Nrbuf] - d->rp;
|
|
else
|
|
i = d->wp - d->rp;
|
|
if(i > n)
|
|
i = n;
|
|
memmove(p, d->rp, i);
|
|
if(d->rp + i == &d->rbuf[Nrbuf])
|
|
d->rp = d->rbuf;
|
|
else
|
|
d->rp += i;
|
|
n -= i;
|
|
p += i;
|
|
}
|
|
return p - buf;
|
|
}
|
|
|
|
/*
|
|
* fulfill a read request (we assume d is locked)
|
|
*/
|
|
void
|
|
serve(Dev *d)
|
|
{
|
|
Request *r;
|
|
int n;
|
|
Fcall rhdr;
|
|
uchar *mdata;
|
|
char *buf;
|
|
|
|
mdata = malloc(messagesize);
|
|
buf = malloc(messagesize-IOHDRSZ);
|
|
|
|
for(;;){
|
|
if(d->r == 0 || d->rp == d->wp)
|
|
break;
|
|
r = d->r;
|
|
if(r->count > sizeof(buf))
|
|
r->count = sizeof(buf);
|
|
|
|
n = getinput(d, buf, r->count);
|
|
if(n == 0)
|
|
break;
|
|
d->r = r->next;
|
|
|
|
rhdr.type = Rread;
|
|
rhdr.fid = r->fid->fid;
|
|
rhdr.tag = r->tag;
|
|
rhdr.data = buf;
|
|
rhdr.count = n;
|
|
n = convS2M(&rhdr, mdata, messagesize);
|
|
if(write(mfd[1], mdata, n) != n)
|
|
fprint(2, "telco: error writing\n");
|
|
free(r);
|
|
}
|
|
free(mdata);
|
|
free(buf);
|
|
}
|
|
|
|
/*
|
|
* dial a number
|
|
*/
|
|
char*
|
|
dialout(Dev *d, char *number)
|
|
{
|
|
int i, m, compress, rateadjust, speed, fax;
|
|
char *err;
|
|
char *field[5];
|
|
char dialstr[128];
|
|
|
|
compress = Ok;
|
|
rateadjust = Failure;
|
|
speed = maxspeed;
|
|
fax = Failure;
|
|
|
|
m = getfields(number, field, 5, 1, "!");
|
|
for(i = 1; i < m; i++){
|
|
if(field[i][0] >= '0' && field[i][0] <= '9')
|
|
speed = atoi(field[i]);
|
|
else if(strcmp(field[i], "nocompress") == 0)
|
|
compress = Failure;
|
|
else if(strcmp(field[i], "fax") == 0)
|
|
fax = Ok;
|
|
}
|
|
|
|
syslog(0, LOGFILE, "dialing %s speed=%d %s", number, speed, fax==Ok?"fax":"");
|
|
|
|
err = modemtype(d, speed, fax == Ok);
|
|
if(err)
|
|
return err;
|
|
|
|
/*
|
|
* extented Hayes commands, meaning depends on modem (VGA all over again)
|
|
*/
|
|
if(fax != Ok){
|
|
if(d->fclass != 0)
|
|
applyspecial(d, Cfclass0);
|
|
applyspecial(d, Cerrorcorrection);
|
|
if(compress == Ok)
|
|
compress = applyspecial(d, Ccompression);
|
|
if(compress != Ok)
|
|
rateadjust = applyspecial(d, Crateadjust);
|
|
}
|
|
applyspecial(d, Cflowctl);
|
|
|
|
/* dialout */
|
|
sprint(dialstr, "ATD%c%s\r", pulsed ? 'P' : 'T', number);
|
|
if(send(d, dialstr) < 0)
|
|
return Edial;
|
|
|
|
if(fax == Ok)
|
|
return 0; /* fax sender worries about the rest */
|
|
|
|
switch(readmsg(d, 120, 0)){
|
|
case Success:
|
|
break;
|
|
default:
|
|
return d->msgbuf;
|
|
}
|
|
|
|
/* change line rate if not compressing */
|
|
if(rateadjust == Ok)
|
|
setspeed(d, getspeed(d->msgbuf, d->speed));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* start a receiving process
|
|
*/
|
|
void
|
|
receiver(Dev *d)
|
|
{
|
|
int fd;
|
|
char file[256];
|
|
char *argv[8];
|
|
int argc;
|
|
int pfd[2];
|
|
char *prog;
|
|
|
|
pipe(pfd);
|
|
switch(rfork(RFPROC|RFMEM|RFFDG|RFNAMEG)){
|
|
case -1:
|
|
return;
|
|
case 0:
|
|
fd = open("/srv/telco", ORDWR);
|
|
if(fd < 0){
|
|
syslog(0, LOGFILE, "can't open telco: %r");
|
|
exits(0);
|
|
}
|
|
if(mount(fd, -1, "/net", MAFTER, "") < 0){
|
|
syslog(0, LOGFILE, "can't mount: %r");
|
|
exits(0);
|
|
}
|
|
close(fd);
|
|
|
|
/* open connection through the file system interface */
|
|
sprint(file, "/net/telco/%ld/data", d - dev);
|
|
fd = open(file, ORDWR);
|
|
if(fd < 0){
|
|
syslog(0, LOGFILE, "can't open %s: %r", file);
|
|
exits(0);
|
|
}
|
|
|
|
/* let parent continue */
|
|
close(pfd[0]);
|
|
close(pfd[1]);
|
|
|
|
/* answer the phone and see what flavor call this is */
|
|
prog = "/bin/service/telcodata";
|
|
switch(apply(d, "ATA", "+FCON", 30)){
|
|
case Success:
|
|
break;
|
|
case Found:
|
|
prog = "/bin/service/telcofax";
|
|
break;
|
|
default:
|
|
syslog(0, LOGFILE, "bad ATA response");
|
|
exits(0);
|
|
}
|
|
|
|
/* fork a receiving process */
|
|
dup(fd, 0);
|
|
dup(fd, 1);
|
|
close(fd);
|
|
argc = 0;
|
|
argv[argc++] = strrchr(prog, '/')+1;
|
|
argv[argc++] = file;
|
|
argv[argc++] = dev->t->name;
|
|
argv[argc] = 0;
|
|
exec(prog, argv);
|
|
syslog(0, LOGFILE, "can't exec %s: %r", prog);
|
|
exits(0);
|
|
default:
|
|
/* wait till child gets the device open */
|
|
close(pfd[1]);
|
|
read(pfd[0], file, 1);
|
|
close(pfd[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* hang up an connections in progress
|
|
*/
|
|
void
|
|
onhook(Dev *d)
|
|
{
|
|
write(d->ctl, "d0", 2);
|
|
write(d->ctl, "r0", 2);
|
|
sleep(250);
|
|
write(d->ctl, "r1", 2);
|
|
write(d->ctl, "d1", 2);
|
|
modemtype(d, maxspeed, 1);
|
|
}
|
|
|
|
/*
|
|
* read till we see a message or we time out
|
|
*/
|
|
int
|
|
readmsg(Dev *d, int secs, char *substr)
|
|
{
|
|
ulong start;
|
|
char *p;
|
|
int i, len;
|
|
Msg *pp;
|
|
int found = 0;
|
|
|
|
p = d->msgbuf;
|
|
len = sizeof(d->msgbuf) - 1;
|
|
for(start = time(0); time(0) <= start+secs;){
|
|
if(len && d->rp == d->wp){
|
|
sleep(100);
|
|
continue;
|
|
}
|
|
i = getinput(d, p, 1);
|
|
if(i == 0)
|
|
continue;
|
|
if(*p == '\n' || *p == '\r' || len == 0){
|
|
*p = 0;
|
|
if(verbose && p != d->msgbuf)
|
|
syslog(0, LOGFILE, "<-%s", d->msgbuf);
|
|
if(substr && strstr(d->msgbuf, substr))
|
|
found = 1;
|
|
for(pp = msgs; pp->text; pp++)
|
|
if(strncmp(pp->text, d->msgbuf, strlen(pp->text))==0)
|
|
return found ? Found : pp->type;
|
|
start = time(0);
|
|
p = d->msgbuf;
|
|
len = sizeof(d->msgbuf) - 1;
|
|
continue;
|
|
}
|
|
len--;
|
|
p++;
|
|
}
|
|
strcpy(d->msgbuf, "No response from modem");
|
|
return found ? Found : Noise;
|
|
}
|
|
|
|
/*
|
|
* get baud rate from a connect message
|
|
*/
|
|
int
|
|
getspeed(char *msg, int speed)
|
|
{
|
|
char *p;
|
|
int s;
|
|
|
|
p = msg + sizeof("CONNECT") - 1;
|
|
while(*p == ' ' || *p == '\t')
|
|
p++;
|
|
s = atoi(p);
|
|
if(s <= 0)
|
|
return speed;
|
|
else
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* set speed and RTS/CTS modem flow control
|
|
*/
|
|
void
|
|
setspeed(Dev *d, int baud)
|
|
{
|
|
char buf[32];
|
|
|
|
if(d->ctl < 0)
|
|
return;
|
|
sprint(buf, "b%d", baud);
|
|
write(d->ctl, buf, strlen(buf));
|
|
write(d->ctl, "m1", 2);
|
|
}
|