/* ** Copyright (c) 1998 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License as published by the Free Software Foundation; either ** version 2 of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this program; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@acm.org ** http://www.hwaci.com/drh/ */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* ** Names of files and executables and other hard-coded ** strings. */ #define DB_DIR "/var/eznet" #define DB_FILE DB_DIR "/eznet.conf" #define PPP_DIR "/etc/ppp" #define PPP_EXE "/usr/sbin/pppd" #define SELF "/usr/bin/eznet" #define DIALD_EXE "/usr/sbin/diald" #define SESSIONLOG DB_DIR "/session.html" /* ** The following define determines what version of PPP ** this program expects by default. You can override this ** value using the pppversion= parameter. */ #define DFLT_PPP_VERSION "2.3" /* ** Each entry in the database is represented in memory by an instance ** of the following structure. */ typedef struct ConfEntry ConfEntry; struct ConfEntry { char *zLabel; /* A label for this entry */ ConfEntry *next; /* Next entry in the same record */ char *zValue; /* Value for this entry */ }; /* ** These variables record the whole database. There are nRecord ** records, and each record consists of a linked list of ConfEntry ** structures where aRecord[N] points to the head of the list for ** record number N. */ static int nRecord = 0; /* Number of records */ static ConfEntry **aRecord = 0; /* List of ConfEntries for each record */ /* ** A transcript of the CHAT session is written to this file, if ** it is open. */ static FILE *transcript = 0; /* ** If this is a set-uid or set-gid process, then release all ** privileges. */ static void Unprivileged(void){ int uid = getuid(); int gid = getgid(); setregid(gid,gid); setreuid(uid,uid); } /* ** Read the entire contents of a file into memory. Space to ** hold the file is obtained from malloc. NULL is returned if ** we can't read the file for any reason. */ static char *ReadFile(const char *zFilename){ int fd; FILE *in; int toread; int read; int n; char *zBuf; struct stat sbuf; in = fopen(zFilename,"r"); if( in==0 ) return 0; fd = fileno(in); if( fstat(fd, &sbuf)!=0 ){ fclose(in); return 0; } toread = sbuf.st_size; read = 0; zBuf = malloc( toread+1 ); if( zBuf==0 ){ fclose(in); return 0; } while( toread && (n=fread(&zBuf[read],1,toread,in))>0 ){ toread -= n; read += n; } zBuf[read] = 0; fclose(in); return zBuf; } /* Change the status for a record. The status is recorded in ** a file named ** ** /var/eznet/status.NNN ** ** where NNN is the record number. */ static void WriteStatus(int iRec, char *zStatus, ...){ FILE *out; va_list ap; char zFilename[sizeof(DB_DIR)+20]; sprintf(zFilename,"%s/status.%d",DB_DIR,iRec); out = fopen(zFilename,"w"); if( out ){ time_t now; struct tm *p; char zBuf[200]; fchown(fileno(out),0,0); fchmod(fileno(out),0644); va_start(ap, zStatus); vfprintf(out,zStatus,ap); va_end(ap); time(&now); p = localtime(&now); strftime(zBuf,sizeof(zBuf)-1," %I:%M%p %a %b %d, %Y\n", p); fprintf(out,zBuf); fclose(out); } } /* Write a status report from Chat. This is written to a file ** named ** ** /var/eznet/chatstat.NNN ** ** where NNN is the record number. */ static void WriteChatStat(int iRec, char *zStatus, ...){ FILE *out; va_list ap; char zFilename[sizeof(DB_DIR)+20]; sprintf(zFilename,"%s/chatstat.%d",DB_DIR,iRec); out = fopen(zFilename,"w"); if( out ){ va_start(ap, zStatus); vfprintf(out,zStatus,ap); va_end(ap); fclose(out); } } /* ** Write text into a file. zFile is the name of the file into ** which the text is written. zFormat is a printf-like format ** string that defines the text. The owner of the file is converted ** to root and the permissions are set to "mode". */ static void WriteToFile(char *zFile, int mode, char *zFormat, ...){ FILE *out; va_list ap; out = fopen(zFile,"w"); if( out==0 ) return; fchmod(fileno(out),mode); fchown(fileno(out),0,0); va_start(ap,zFormat); vfprintf(out,zFormat,ap); va_end(ap); fclose(out); } /* Make sure /etc/ppp/ip-up and /etc/ppp/ip-down invoke eznet ** with either the "ipup" or "ipdown" argument (as approprite.) ** This is needed so that we can adjust the status when the link ** goes up or down. */ static void SetIpUpDown(char *zUpDown){ char *zText; int i; int lineLen = 0; int patLen; char zPat[100]; char zFile[sizeof(PPP_DIR)+20]; sprintf(zFile,"%s/ip-%s",PPP_DIR,zUpDown); zText = ReadFile(zFile); if( zText==0 ){ WriteToFile(zFile, 0744, "#!/bin/sh\n" SELF " ip%s $* &\n", zUpDown); return; } for(i=0; zText[i] && zText[i]!='\n'; i++){} if( zText[i]=='\n' ){ lineLen = i; } sprintf(zPat,SELF " ip%s $* &",zUpDown); patLen = strlen(zPat); while( zText[i] && (zText[i]!='/' || strncmp(&zText[i], zPat, patLen)) ){ i++; } if( zText[i]==0 ){ WriteToFile(zFile, 0744, "#!/bin/sh\n%s\n%s", zPat, &zText[lineLen]); } } /* ** Parse a single line of the pap-secrets or chap-secrets file whose ** text begins as *pzText. The elements of the line are place in ** azArgs[0] through azArgs[3]. ** ** After parsing, *pzText is left pointing to the first character ** after then end of the parsed text. At end of file, *pzText points ** to 0. ** ** All of azArgs[0] through azArgs[3] are always filled in. Empty ** strings are inserted if necessary. A comment line is placed in ** azArgs[0] and the others are empty strings. ** ** Quotes and escapes are removed from azArgs[0] through azArgs[2], ** except for comment lines which are passed through unaltered. */ static void GetSecret(char **pzText, char **azArgs){ int i; char *z = *pzText; while( isspace(*z) ) z++; azArgs[0] = azArgs[1] = azArgs[2] = azArgs[3] = ""; if( *z=='#' ){ azArgs[0] = z; while( *z && *z!='\n' ) z++; if( *z=='\n' ){ *z = 0; z++; } *pzText = z; return; } for(i=0; i<=2; i++){ int hasEscape = 0; azArgs[i] = z; if( *z=='"' ){ azArgs[i]++; z++; while( *z && *z!='"' ){ if( *z=='\\' && z[1] ){ z++; hasEscape = 1; } z++; } if( *z=='"' ){ *z = 0; z++; } }else{ while( *z && !isspace(*z) ){ if( *z=='\\' && z[1] ){ z++; hasEscape = 1; } z++; } if( *z ){ *z = 0; z++; } } while( isspace(*z) && *z!='\n' ){ z++; } if( hasEscape ){ int j = 0, k = 0; char *zStr = azArgs[i]; while( zStr[j] ){ if( zStr[j]=='\\' && zStr[j+1] ){ j++; } zStr[k++] = zStr[j++]; } } } azArgs[i] = z; while( *z && *z!='\n' ){ if( *z=='\\' && z[1] ){ z++; } z++; } if( *z=='\n' ){ *z = 0; z++; } *pzText = z; } /* ** The elements azArgs[0] through azArgs[3] contain the text of ** a secret. This secret is written to *pzBuf. *pzBuf is updated ** to point to the null terminator at the end of the written text. ** ** Quotes and escapes are inserted on azArgs[0] through azArgs[2] ** if needed. Except if the first character of azArgs[0] is "#" ** then no quotes or escapes are inserted. */ static void AppendSecret(char **pzBuf, char **azArgs){ char *z = *pzBuf; char *zSrc; int i; if( azArgs[0][0]=='#' ){ zSrc = azArgs[0]; while( *zSrc ){ *(z++) = *(zSrc++); } *(z++) = '\n'; *z = 0; *pzBuf = z; return; } for(i=0; i<=2; i++){ zSrc = azArgs[i]; if( *zSrc==0 ) continue; while( *zSrc ){ switch( *zSrc ){ case ' ': case '\t': case '\n': case '"': case '#': case '\'': case '\\': *(z++) = '\\'; /* Fall thru */ default: *(z++) = *(zSrc++); break; } } *(z++) = ' '; } while( *zSrc ){ *(z++) = *(zSrc++); } *(z++) = '\n'; *z = 0; *pzBuf = z; } /* Make sure the correct password for the service and user is found in the ** secrets file. Add it if is not. Preserve any commands or ** unrelated secrets that are previously found in the file. ** ** Needless to say, we have to have root privileges to run ** this function successfully. The secrets files contain information ** which should not be publicly viewable. */ static void SetSecrets( char *zBase, /* Either "pap-secrets" or "chap-secrets" */ char *zService, /* Name of the service provider we are calling */ char *zUser, /* Our login name */ char *zPassword /* Our password */ ){ char *zOld, *zOldOrig; char *z; char *zNew; int extra; int seenServiceUser = 0; int seenUserService = 0; char *azArgs[4]; char zFile[sizeof(PPP_DIR)+20]; if( zPassword==0 || zUser==0 || zService==0 || zFile==0 ) return; sprintf(zFile,"%s/%.15s",PPP_DIR,zBase); zOldOrig = zOld = ReadFile(zFile); extra = (strlen(zService) + strlen(zPassword) + strlen(zUser) + 10)*4; if( zOld==0 ){ WriteToFile(zFile, 0600, "#\n%s %s %s\n%s %s %s\n", zService, zUser, zPassword, zUser, zService, zPassword ); return; } z = zNew = malloc( strlen(zOld) + extra + 100 ); if( z==0 ) return; while( zOld && *zOld ){ GetSecret(&zOld, azArgs); if( azArgs[0][0]!='#' ){ if( strcmp(azArgs[0],zService)==0 && strcmp(azArgs[1],zUser)==0 ){ seenServiceUser = 1; azArgs[2] = zPassword; azArgs[3] = ""; }else if( strcmp(azArgs[0],zUser)==0 && strcmp(azArgs[1],zService)==0 ){ seenUserService = 1; azArgs[2] = zPassword; azArgs[3] = ""; } } AppendSecret(&z, azArgs); } if( !seenServiceUser ){ azArgs[0] = zService; azArgs[1] = zUser; azArgs[2] = zPassword; azArgs[3] = ""; AppendSecret(&z, azArgs); } if( !seenUserService ){ azArgs[0] = zUser; azArgs[1] = zService; azArgs[2] = zPassword; azArgs[3] = ""; AppendSecret(&z, azArgs); } WriteToFile(zFile, 0600, "%s", zNew); free(zOldOrig); free(zNew); } /* zFile[*pI] is the first character in a line of text. Return ** a pointer to the first non-whitespace character in this line. ** Also convert the \n into a \0 and make *pI be the index of ** the first character on the next line. ** ** If zFile[*pI] is 0, then we've reached the end of input. ** Return NULL. */ static char *GetLine(char *zFile, int *pI){ int i = *pI; int iStart; if( zFile[i]==0 ) return 0; while( isspace(zFile[i]) ){ i++; } iStart = i; while( zFile[i] && zFile[i]!='\n' ){ i++; } if( zFile[i] ){ zFile[i] = 0; *pI = i+1; }else{ *pI = i; } return &zFile[iStart]; } /* This array maps hexadecimal digit values into their numeric ** value. */ static int hexval[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; /* Return a pointer to the first space-separated token past ** the character zLine[*pI]. Return NULL if there are no more ** tokens. *pI is left pointing to the first character after ** the token. ** ** The HTTP-style encoding is removed from the token and the ** token is null-terminated. */ static char *GetToken(char *zLine, int *pI){ int i = *pI; int iStart; int j; while( isspace(zLine[i]) ){ i++; } if( zLine[i]==0 ) return 0; iStart = i; while( zLine[i] && !isspace(zLine[i]) ){ i++; } if( zLine[i] ){ *pI = i + 1; zLine[i] = 0; }else{ *pI = i; } for(i=j=iStart; zLine[i]; i++){ switch( zLine[i] ){ case '+': zLine[j++] = ' '; break; case '%': if( zLine[i+1] && zLine[i+2] ){ zLine[j++] = hexval[0x7f & zLine[i+1]]*16 + hexval[0x7f & zLine[i+2]]; i += 2; }else{ zLine[j++] = '%'; } break; default: zLine[j++] = zLine[i]; break; } } zLine[j] = 0; return &zLine[iStart]; } /* ** This function inserts a new entry into the database (or replaces ** the value of an existing entry). iRec is the record number. ** New records are created as necessary. zField is the entry label, ** and zValue is its value. Both are plain text. ** ** If zValue==0 or *zValue==0, then the entry is deleted. ** ** Memory to hold the new entry is obtained from malloc. */ static void InsertEntry(int iRec, char *zLabel, char *zValue){ ConfEntry *p; if( zValue==0 || *zValue==0 ){ if( iRec>=0 && iReczLabel,zLabel)==0 ) break; pp = &p->next; } if( p ){ *pp = p->next; free( p->zValue ); free( p ); } } return; } if( iRec>=nRecord ){ if( nRecord==0 ){ aRecord = malloc( sizeof(ConfEntry*)*(iRec+1) ); }else{ aRecord = realloc( aRecord, sizeof(ConfEntry*)*(iRec+1) ); } if( aRecord==0 ){ nRecord = 0; return; } while( nRecord<=iRec ){ aRecord[nRecord++] = 0; } } for(p=aRecord[iRec]; p; p=p->next){ if( strcmp(p->zLabel,zLabel)==0 ) break; } if( p==0 ){ p = malloc( sizeof(ConfEntry) + strlen(zLabel) + 1 ); if( p==0 ) return; p->zLabel = (char*)&p[1]; strcpy(p->zLabel, zLabel); p->next = aRecord[iRec]; p->zValue = 0; aRecord[iRec] = p; } if( p ){ if( p->zValue ) free(p->zValue); p->zValue = malloc( strlen(zValue) + 1 ); if( p->zValue ) strcpy(p->zValue, zValue); } } /* ** The set of characters that need to be excaped are coded as "1". ** Safe characters are 0. */ static unsigned char NeedEsc[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; /* Do an HTTP-style encoding of zToken and write it on the ** output file given. */ static void WriteToken(FILE *out, char *zToken){ int i = 0; while( zToken[i] ){ if( !NeedEsc[(unsigned)zToken[i]&0xff] ){ i++; }else{ if( i>0 ){ fwrite(zToken,1,i,out); } if( zToken[i]==' ' ){ fwrite("+",1,1,out); }else{ char zBuf[4]; zBuf[0] = '%'; zBuf[1] = "0123456789ABCDEF"[(zToken[i]>>4)&0xf]; zBuf[2] = "0123456789ABCDEF"[zToken[i]&0xf]; zBuf[3] = 0; fwrite(zBuf,1,3,out); } zToken += i + 1; i = 0; } } if( i>0 ){ fwrite(zToken,1,i,out); } } /* ** The set of characters that need to be excaped like "\012" are ** are coded as 1. Safe characters are 0. Characters that need ** a backslash in front are coded with 2. Another code generates ** a symbolic escape by putting a backslash in front of the value. */ static unsigned char NeedTclEsc[] = { 1, 1, 1, 1, 1, 1, 1, 'a', 'b','t','n', 1, 'f','r', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; /* Write a string as a single TCL token. */ static void WriteTcl(FILE *out, char *z){ register c; while( (c=*(z++))!=0 ){ if( NeedTclEsc[c] ){ int e = NeedTclEsc[c]; if( e==1 ){ fprintf(out,"\\%03o",0xff&c); }else if( e==2 ){ fprintf(out,"\\%c",c); }else{ fprintf(out,"\\%c",e); } }else{ putc(c,out); } } } /* ** The set of characters that need to be excaped like "\012" are ** are coded as 1. Safe characters are 0. Characters that need ** a backslash in front are coded with 2. Another code generates ** a symbolic escape by putting a backslash in front of the value. */ static unsigned char NeedHumanEsc[] = { 1, 1, 1, 1, 1, 1, 1, 'a', 'b','t','n', 1, 'f','r', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; /* Write a string as a single TCL token. */ static void WriteHumanReadable(FILE *out, char *z){ register c; while( (c=*(z++))!=0 ){ if( NeedHumanEsc[c] ){ int e = NeedHumanEsc[c]; if( e==1 ){ fprintf(out,"\\%03o",0xff&c); }else if( e==2 ){ fprintf(out,"\\%c",c); }else{ fprintf(out,"\\%c",e); } }else{ putc(c,out); } } } /* ** Convert a non-negative number from its ASCII form into a binary ** form. Return -1 if the input string is not a non-negative number. */ static int GetInt(char *z){ int v = 0; while( *z ){ if( *z<'0' || *z>'9' ) return -1; v = v*10 + *(z++) - '0'; } return v; } /* ** Read the whole database into memory. */ static void ReadDb(void){ char *zDb; int i,j; char *zLine; char *zToken; int iRec; char *zField; char *zValue; zDb = ReadFile(DB_FILE); if( zDb==0 ) return; i = 0; while( (zLine=GetLine(zDb,&i))!=0 ){ if( *zLine=='#' ) continue; j = 0; zToken = GetToken(zLine,&j); if( zToken==0 ) continue; iRec = GetInt(zToken); if( iRec<0 ) continue; zField = GetToken(zLine,&j); zValue = GetToken(zLine,&j); if( zValue==0 ) continue; InsertEntry(iRec,zField,zValue); } free( zDb ); } /* Given two lists of ConfEntry structures both of which are ** sorted and either or both of which can be empty, combine the ** two lists into a single sorted list. Return a pointer to ** the head of the new list. ** ** This is part of the merge-sort algorithm. */ static ConfEntry *merge(ConfEntry *left, ConfEntry *right){ ConfEntry *head = 0, *tail = 0; while( left && right ){ int c = strcmp(left->zLabel, right->zLabel); if( c<0 ){ if( tail ){ tail->next = left; tail = tail->next; }else{ head = tail = left; } left = left->next; tail->next = 0; }else{ if( tail ){ tail->next = right; tail = tail->next; }else{ head = tail = right; } right = right->next; tail->next = 0; } } if( left ){ if( tail ){ tail->next = left; }else{ head = tail = left; } }else if( right ){ if( tail ){ tail->next = right; }else{ head = tail = right; } } return head; } /* Sort a list of ConfEntry structures. Return a pointer to ** the sorted list. ** ** The algorithm is merge-sort. */ static ConfEntry *sort(ConfEntry *p){ int i; ConfEntry *pNext; ConfEntry *a[32]; for(i=0; inext; p->next = 0; for(i=0; a[i] && inext){ fprintf(out,"%d %s ",i,p->zLabel); WriteToken(out,p->zValue); fprintf(out,"\n"); } } fclose(out); unlink(DB_FILE); link(zFilename,DB_FILE); unlink(zFilename); } /* ** Given a sequence of strings of the form ** ** LABEL=VALUE ** ** parse the strings up into LABEL and VALUE and add entries ** for each string to the database as record iRec. */ static void AddEntries(int iRec, int argc, char **argv){ int i, j; char *zLabel; char *zValue; for(i=0; i=nRecord ) M = nRecord-1; if( M<0 ) M = 0; if( N>=nRecord ) N = nRecord-1; if( N<0 ) N = 0; if( M==N ) return; if( MN; i--){ aRecord[i] = aRecord[i-1]; } aRecord[N] = pTemp; } } /* ** Return TRUE if the given string is an IP address composed of ** 4 numbers between 0 and 255 separated by dots. If the string ** is an IP address, return the value in *pIp. */ static int IsIpAddr(char *z, int *pIp){ int a1, a2, a3, a4, rc; if( sscanf(z,"%d.%d.%d.%d",&a1,&a2,&a3,&a4)==4 && a1>=0 && a1<=255 && a2>=0 && a2<=255 && a3>=0 && a3<=255 && a4>=0 && a4<=255 ){ *pIp = (a1<<24) | (a2<<16) | (a3<<8) | a4; rc = 1; }else{ rc = 0; } return rc; } /* ** Look up a specific field in a specific record and return its ** value. Return the given default value if it is not found. */ static char *Lookup(int iRec, char *zLabel, char *zDflt){ ConfEntry *p; if( iRec<0 || iRec>=nRecord ) return zDflt; for(p=aRecord[iRec]; p; p=p->next){ if( strcmp(zLabel,p->zLabel)==0 ) return p->zValue; } return zDflt; } /* ** Given a command-line parameter that might be a record number, ** or an IP address, or a modem tty name, or the keyword "all", ** return a record number. The special code FIND_ERROR is returned ** if we can't find a match. FIND_ALL is returned if the keyword ** "all" appears or for a NULL string. */ #define FIND_ALL -1 #define FIND_ERROR -2 static int FindRecord(char *z){ int i; int ip; if( z==0 ){ if( nRecord==1 ) return 0; return FIND_ALL; } if( strcmp(z,"all")==0 ){ return FIND_ALL; } i = GetInt(z); if( i>=0 ){ if( i>=nRecord ) i = nRecord-1; if( i<0 ) i = 0; return i; } if( IsIpAddr(z,&ip) ){ int t, mask, iRec; char *zVal; for(iRec=0; iRec0 ){ int i = strlen(z); while( i>0 ){ fprintf(transcript,"%.*s",i,"**********" "**********" "***********"); i -= 30; } }else{ WriteHumanReadable(transcript,z); } } n = strlen(z); while( n ){ w = write(1, z, n); if( w>0 ){ n -= w; z += w; } } } /* ** Send output to the modem. ** ** Delay for "delay" microseconds before actually doing the write. ** This is for modems that get fouled up by input that follows to ** closely after their own output. ** ** The transmitted text is normally written to the transcript file. ** But if "shroud" is one, then "*" characters are subsitituted. ** This is used when writting the password, to keep it from view. */ static void ChatSend(int delay, int shroud, char *z, ...){ va_list ap; if( z==0 ) return; if( delay>0 ){ usleep(delay); } if( transcript ){ WriteTimestamp(); fprintf(transcript," Send: \""); } va_start(ap, z); ChatWrite(z, shroud); while( (z = va_arg(ap,char*))!=0 ){ ChatWrite(z, shroud); shroud--; } if( transcript ){ fprintf(transcript,"\"\n"); fflush(transcript); } } /* ** Read text from the input and put it into zBuf. Reading continues ** until: ** ** * No characters are received after waiting for "timeout" ** seconds, or ** ** * Some characters have been received but no characters have ** appeared for 500 milliseconds or more. ** ** Only characters since the last new-line are returned in zBuf. ** But all received characters are written to the transcript. ** ** Characters in zBuf[] when this routine is called are not erased ** or overwritten. Newly read characters are appended. Unless ** a new-line or carriage routine is seen, when all characters are ** deleted. ** ** The function returns the number of characters that were received ** during the current call to this function. This number will be ** zero if a timeout occurs. */ static int ChatRecv(char *zBuf, int nBuf, int timeout){ int n; /* Number of bytes returned from a single read() */ int i; /* Loop counter */ int idle = 0; /* Number of 1/10ths of second since last read() */ int got; /* How many characters previously read */ int nChar = 0; /* Total number of characters read */ time_t start; time_t now; /* Remember when we began looking... */ time(&start); /* Figure out how much text is already in zBuf[]. New ** text will be appended. */ for(got=0; zBuf[got]; got++){} if( got>0 && (zBuf[got-1]=='\n' || zBuf[got-1]=='\r') ){ got = 0; } zBuf[got] = 0; /* Start reading input... */ while( got". */ static int IsPrompt(char *zLine){ int i; for(i=0; zLine[i]!=0 && zLine[i]!='\n' && zLine[i]!='\r'; i++){} while( i>0 && isspace(zLine[i-1]) ){ i--; } i--; if( i<=0 ) return 0; if( zLine[i]==':' ) return 1; if( zLine[i]=='?' ) return 1; if( zLine[i]=='>' ) return 1; if( zLine[i]=='$' ) return 1; return 0; } #define MAX_TIME 70 /* Maximum time to connect */ #define DIAL_TIMEOUT "60" /* How long to wait for dialing to complete */ #define CHAT_TIMEOUT "3" /* How long to wait for responses after dialing */ /* This routine is pretty much the whole point. Assuming a ** modem is connected on file descriptors 0 and 1, initialize ** the modem, dial the phone number, supply user ID and password ** (if requested). Return successfully (0) if we see the start of ** PPP communication and return an error (1) if anything goes ** wrong. ** ** This routine runs as root so that it will have permission ** to open the output file associated with WriteChatStat(). */ static int Chat(int iRec){ struct termios tio_0_saved; struct termios tio_1_saved; struct termios tio_new; int flags; int i; char *zVal; int timeout; int dial_timeout; int nTimeout = 0; time_t start, now; int rc = 4; int chatDelay = 0; char *zChat; char *zPhone; char *zExpect[10]; char *zReply[10]; char zLine[1000]; tcgetattr(0,&tio_0_saved); tcgetattr(1,&tio_1_saved); tio_new = tio_0_saved; cfmakeraw(&tio_new); tcsetattr(0,TCSANOW,&tio_new); tio_new = tio_1_saved; cfmakeraw(&tio_new); tcsetattr(1,TCSANOW,&tio_new); flags = fcntl(0, F_GETFL); fcntl(0, F_SETFL, O_NONBLOCK); sprintf(zLine,"%s/transcript.%d",DB_DIR,iRec); transcript = fopen(zLine,"a"); if( transcript ){ fchmod(fileno(transcript),0644); fchown(fileno(transcript),0,0); } zChat = Lookup(iRec,"chat","yes"); chatDelay = atoi(Lookup(iRec,"delay","0"))*1000; for(i=0; i<=9; i++){ static char *dfltInit[10] = { "atz", "at&d3", }; sprintf(zLine,"init%d",i); zVal = Lookup(iRec,zLine,dfltInit[i]); if( zVal ){ ChatSend(chatDelay,0,zVal,"\r",0); zLine[0] = 0; ChatRecv(zLine,sizeof(zLine),1000); if( !Match(zLine,"OK") ){ ChatRecv(zLine,sizeof(zLine),1000); } } sprintf(zLine,"expect%d",i); zExpect[i] = Lookup(iRec,zLine,0); sprintf(zLine,"reply%d",i); zReply[i] = Lookup(iRec,zLine,0); } zPhone = Lookup(iRec,"phone",0); if( zPhone==0 ){ WriteChatStat(iRec,"No phone number specified"); return 1; } ChatSend(chatDelay,0,"atd", zPhone, "\r", 0); timeout = dial_timeout = atoi(Lookup(iRec,"dialtimeout",DIAL_TIMEOUT)); time(&start); zLine[0] = 0; nTimeout = 0; while( 1 ){ int nChar = ChatRecv(zLine,sizeof(zLine),timeout); time(&now); if( now-start > MAX_TIME ){ WriteChatStat(iRec,"Login timeout at"); break; } if( zLine[0]=='a' && strncmp(zLine,"atd",3)==0 && zLine[3]==zPhone[0] && strncmp(&zLine[3],zPhone,strlen(zPhone))==0 ){ /* Skip the echo of the dial command to the modem */ zLine[0] = 0; continue; } if( nChar==0 ){ nTimeout++; if( timeout==dial_timeout ){ WriteChatStat(iRec,"Modem could not connect at"); break; }else if( (nTimeout>2 && *Lookup(iRec,"autostart","yes")!='n') || zChat[0]=='n' ){ rc = 0; break; }else{ ChatSend(chatDelay,0,"\r",0); continue; } } timeout = atoi(Lookup(iRec,"chattimeout",CHAT_TIMEOUT)); nTimeout = 0; for(i=0; i<=9; i++){ if( zExpect[i]==0 || zReply[i]==0 ) continue; if( Match7(zLine,zExpect[i]) ){ zLine[0] = 0; if( strcmp(zReply[i],"ACCEPT")==0 ){ rc = 0; goto chat_done; }else if( strcmp(zReply[i],"FAIL")==0 ){ goto chat_done; }else{ ChatSend(chatDelay,0,zReply[i],"\r",0); nTimeout = 0; timeout = 3; break; } } } if( Match(zLine,"BUSY") ){ WriteChatStat(iRec,"Busy signal at"); break; }else if( Match(zLine,"NO CARRIER") ){ WriteChatStat(iRec,"No carrier at"); break; }else if( Match(zLine,"NO DIALTONE") ){ WriteChatStat(iRec,"No dialtone at"); break; }else if( Match(zLine,"NO ANSWER") ){ WriteChatStat(iRec,"Ringing but no answer at"); break; }else if( Match7(zLine,"ogin:") || Match7(zLine,"rname:") || Match7(zLine,"-on:") || Match7(zLine,"serid:") ){ zVal = Lookup(iRec,"user","anonymous"); ChatSend(chatDelay,0,zVal,"\r",0); zLine[0] = 0; }else if( Match7(zLine,"ssword:") ){ zVal = Lookup(iRec,"password",""); ChatSend(chatDelay,1,zVal,"\r",0); zLine[0] = 0; }else if( Match(zLine,"~\377}#") || Match(zLine,"\300!}!") ){ rc = 0; break; }else if( Match(zLine,"CONNECT") && zChat[0]=='n' ){ rc = 0; break; #if 0 }else if( Match7(zLine,"destination:") || Match7(zLine,"response:") ){ ChatSend(chatDelay,0,"ppp\r",0); zLine[0] = 0; #endif }else if( IsPrompt(zLine) ){ ChatSend(chatDelay,0,"ppp\r",0); zLine[0] = 0; }else{ /* Ignore it */ } } chat_done: if( transcript ){ WriteTimestamp(); fprintf(transcript, rc ? " Fail\n" : " Accept\n"); fclose(transcript); } fcntl(0,F_SETFL,flags); tcsetattr(0,TCSANOW,&tio_0_saved); tcsetattr(1,TCSANOW,&tio_1_saved); return rc; } /* Print status information for a given service. */ static void PrintStatus(int iRec){ char *zService; char *zStatus; char *zDiald; int pidDiald; char zBuf[30]; char zFile[sizeof(DB_DIR)+100]; if( iRec<0 || iRec>=nRecord ) return; sprintf(zFile,"%s/diald.%d",DB_DIR,iRec); zDiald = ReadFile(zFile); if( zDiald==0 || (pidDiald=atoi(zDiald))<=0 || kill(pidDiald,0)!=0 ){ zDiald = ""; }else{ zDiald = "(diald running) "; } sprintf(zBuf,"service #%d",iRec); zService = Lookup(iRec,"service",zBuf); sprintf(zFile,"%s/status.%d",DB_DIR,iRec); zStatus = ReadFile(zFile); if( zStatus==0 ){ printf("%s: %sStatus Unknown\n",zService, zDiald); }else{ printf("%s: %s%s",zService, zDiald, zStatus); free( zStatus ); } } /* ** Return true if the given string contains a space or $ character ** and therefore needs to be quoted for the shell. */ static int NeedQuote(char *z){ while( *z ){ if( *z==' ' || *z=='$' || *z=='"' ) return 1; z++; } return 0; } /* ** Open the transcript for record N and write the given argv[] string ** to it. */ static void WriteArgvToTranscript(int iRec, char **argv){ int size; int i; char *zStr, *z; char zFile[sizeof(DB_DIR)+20]; size = 0; for(i=0; argv[i]; i++){ size += strlen(argv[i]) + 3; } zStr = z = malloc( size ); if( zStr ){ for(i=0; argv[i]; i++){ if( NeedQuote(argv[i]) ){ *z++ = '\''; strcpy(z,argv[i]); z += strlen(z); *z++ = '\''; }else{ strcpy(z,argv[i]); z += strlen(z); } if( argv[i+1] ){ *z++ = ' '; } } *z = 0; sprintf(zFile,"%s/transcript.%d", DB_DIR, iRec); WriteToFile(zFile, 0600, "%s\n", zStr); } } /* Check to see if diald is already running. Return a process ID ** for diald if it is running. Or return 0 if there is no diald ** running. */ static int DialdPid(int iRec){ char *zDiald; int pidDiald; char zDialdFile[sizeof(DB_DIR)+100]; sprintf(zDialdFile, "%s/diald.%d", DB_DIR, iRec); zDiald = ReadFile(zDialdFile); if( zDiald==0 ) return 0; pidDiald = atoi(zDiald); free(zDiald); if( pidDiald==0 ){ unlink(zDialdFile); return 0; } if( kill(pidDiald,0) ){ unlink(zDialdFile); return 0; } return pidDiald; } /* ** Shutdown the diald process */ static int StopDiald(int iRec){ int pidDiald; if( (pidDiald = DialdPid(iRec))!=0 ){ kill(pidDiald,SIGTERM); } return 0; } /* Attempt to hang up the given port. */ static void Hangup(int iRec){ char *zDev; char *zPpp; int pidPpp = -1; int pidDiald = -1; int fd; int tries; struct termios ios; char zFile[sizeof(DB_DIR)+20]; WriteStatus(iRec,"hangup at"); /* Send a SIGINT to any diald process that is running. ** This is suppose to make diald hangup the line. */ pidDiald = DialdPid(iRec); if( pidDiald ){ kill(pidDiald,SIGINT); return; } /* We only reach this point if diald is not running. ** Send a SIGTERM to any running ppp process. This is suppose ** to make ppp hang up the line and exit */ sprintf(zFile,"%s/ppp.%d", DB_DIR, iRec); zPpp = ReadFile(zFile); if( zPpp && (pidPpp=atoi(zPpp))>0 ){ kill(pidPpp,SIGTERM); sleep(1); } /* Try to open the TTY. Try as many as three times waiting for ** one second between each attempt. After the second failed attempt, ** send a SIGKILL to any diald or ppp process out there. */ zDev = Lookup(iRec,"tty","/dev/modem"); tries = 0; while( (fd=open(zDev,O_RDWR|O_NONBLOCK))<0 && tries<2 ){ if( tries==1 ){ if( pidPpp>0 ){ kill(pidPpp, SIGKILL); }else if( pidDiald>0 ){ kill(pidDiald, SIGKILL); } } tries++; sleep(1); } /* Change the buad rate of the TTY to 0. This is suppose to make ** the DTR line go low, which should make the modem hangup. */ if( fd>=0 ){ tcgetattr(fd, &ios); cfsetospeed(&ios, B0); tcsetattr(fd, TCSANOW, &ios); close(fd); } } /* ** Write into the given buffer the name of a file that will contain ** the PID of the PPP process that is calling on serial device zDev. ** Assume the size of the buffer into which we write is at least ** sizeof(DB_DIR)+100. ** ** The zDev probably contains '/' characters. These must be converted ** to something different. */ static void MakeDeviceFile(char *zFile, char *zDev){ int i; sprintf(zFile,"%s/device%.80s", DB_DIR, zDev); i = strlen(DB_DIR) + 5; while( zFile[i] ){ if( zFile[i]=='/' ){ zFile[i] = '.'; } i++; } } /* ** Given a modem device name, find the service number. */ static int DeviceToRec(char *zDev){ char *z; int iRec; char zFile[sizeof(DB_DIR)+100]; MakeDeviceFile(zFile, zDev); z = ReadFile(zFile); if( z==0 || (iRec=atoi(z))<0 || iRec>=nRecord ){ for(iRec=0; iRecnRecord ) iRec = FIND_ERROR; } return iRec; } /* This routine is called when "pppd" executes "ip-up". */ static void IpUp(char **argv){ int iRec; int i; char zFile[sizeof(DB_DIR)+20]; iRec = DeviceToRec(argv[1]); if( iRec<0 ) return; WriteStatus(iRec,"%s %s to %s up since",argv[0],argv[3],argv[4]); sprintf(zFile,"%s/pid.%d", DB_DIR, iRec); unlink(zFile); for(i=0; i<=9; i++){ char *zNet; char zRoute[1000]; char zLabel[20]; sprintf(zLabel,"route%d",i); zNet = Lookup(iRec,zLabel,0); if( zNet==0 ) continue; sprintf(zRoute,"/sbin/route add -net %.100s %.100s", zNet, argv[0]); system(zRoute); } } /* This routine is called when "pppd" executes "ip-down". */ static void IpDown(char **argv){ int iRec; char zFile[sizeof(DB_DIR)+80]; iRec = DeviceToRec(argv[1]); if( iRec<0 ) return; WriteStatus(iRec,"down since"); MakeDeviceFile(zFile, argv[1]); unlink(zFile); sprintf(zFile,"%s/ppp.%d", DB_DIR, iRec); unlink(zFile); } /* ** This routine creates a session log for the most recent login ** attempt. The session log consists of two data sources: ** ** 1. The modem dialing and logon transcript. ** ** 2. All messages written to the /var/log/messages file by pppd. ** ** The session log is overwritten after every logon attempt. */ static void MakeSessionLog(time_t startTime, int pid, int iRec){ FILE *out; char *z; struct tm *pTm; char zFile[sizeof(DB_DIR)+100]; char zCmd[sizeof(DB_DIR)+100]; out = fopen(SESSIONLOG,"w"); if( out==0 ) return; fchmod(fileno(out),0644); fprintf(out,"This is a diagnostic log of the most recent login\n" "attempt by eznet.\n\n"); z = Lookup(iRec,"service",0); if( z ){ fprintf(out, " ISP Name: \"%s\"\n", z); } pTm = localtime(&startTime); fprintf(out," Call Initiated: %s\n", asctime(pTm)); sprintf(zFile,"%s/transcript.%d", DB_DIR, iRec); z = ReadFile(zFile); if( z ){ int i; for(i=0; z[i] && z[i]!='\n'; i++){} fprintf(out, "The UNIX command used to initiate the connection attempt was: \n\n" "%.*s\n\n", i, z); if( z[i] ){ i++; } fprintf(out,"The interaction with the modem was as follows:\n\n" "%s\n", &z[i]); free(z); }else{ fprintf(out,"The modem transcript could not be located!\n\n"); } fprintf(out, "The following messages were written to the system log\n" "(in /var/log/messages) by pppd as it attempted to negotiate\n" "a PPP connection."); z = Lookup(iRec,"debug","n"); if( z && *z=='y' ){ fprintf(out," Details may be omitted by clearing the\n" "\"debug=%s\" property.\n\n", z); }else{ fprintf(out," To see more detail, set the \"debug=yes\"\n" "property and reattempt the logon.\n\n"); } fclose(out); sprintf(zCmd,"tail -200 /var/log/messages | grep 'pppd.%d.' >>%s", pid, SESSIONLOG); system(zCmd); } /* This routine is called in order to attempt to establish ** a connection. Return 0 if successful and non-zero if we fail. ** ** The process ID of the PPP process is written to *piChild if piChild!=0. */ static int Dial(int iRec, int *piChild){ char *zPw; char *zDevice; char *zUser; char *zPpp; char *zService; char *zIdle; int pidPpp; int pidDiald; int rc; int isup = 1; time_t startTime; char zDeviceFile[sizeof(DB_DIR)+100]; char zChatStat[sizeof(DB_DIR)+100]; char zPidFile[sizeof(DB_DIR)+100]; char zConnect[200]; char zServiceBuf[50]; /* Check to see if we are already running directly. */ startTime = time(0); sprintf(zPidFile, "%s/pid.%d", DB_DIR, iRec); zPpp = ReadFile(zPidFile); if( zPpp && (pidPpp=atoi(zPpp))>0 && kill(pidPpp,0)==0 ){ return 0; } /* Check to see if diald is running. If it is, then ** just send it a SIGUSR1 to bring the link up. */ if( (pidDiald = DialdPid(iRec))>0 ){ kill(pidDiald,SIGUSR1); return 0; } /* Initialize the ipup, ipdown, chap-secrets and pap-secrets files. */ SetIpUpDown("up"); SetIpUpDown("down"); sprintf(zServiceBuf,"x%d",iRec); zService = Lookup(iRec,"service",zServiceBuf); zUser = Lookup(iRec,"user","anonymous"); zPw = Lookup(iRec, "password", "*"); if( zPw ){ SetSecrets("pap-secrets",zService,zUser,zPw); SetSecrets("chap-secrets",zService,zUser,zPw); } zDevice = Lookup(iRec, "tty", "/dev/modem"); /* Set the device file to the current ordinal number. This is needed ** by the ipup and ipdown scripts. */ MakeDeviceFile(zDeviceFile, zDevice); WriteToFile(zDeviceFile, 0644, "%d\n", iRec); WriteToFile(zPidFile, 0644, "%d\n", getpid()); /* Set the status to dialing. */ WriteStatus(iRec, "dialing since"); /* The child does the work. The parent waits for it to finish. */ rc = fork(); if( rc<0 ){ WriteStatus(iRec,"fork failed"); return 1; } if( rc==0 ){ /* The child process */ int argc = 0; char *z; int i; char zLabel[100]; char *argv[200]; argv[argc++] = Lookup(iRec, "pppd", PPP_EXE); argv[argc++] = zDevice; argv[argc++] = Lookup(iRec,"baud","115200"); argv[argc++] = "connect"; sprintf(zConnect,SELF " chat %d",iRec); argv[argc++] = zConnect; z = Lookup(iRec, "defaultroute", "y"); if( *z=='y' ){ argv[argc++] = "defaultroute"; } argv[argc++] = "lock"; argv[argc++] = "modem"; argv[argc++] = "mtu"; argv[argc++] = Lookup(iRec,"mtu","552"); argv[argc++] = "mru"; argv[argc++] = Lookup(iRec,"mru","552"); z = Lookup(iRec,"debug",0); if( z && *z=='y' ){ argv[argc++] = "debug"; } argv[argc++] = "crtscts"; argv[argc++] = "-detach"; z = Lookup(iRec,"persist","no"); if( *z=='y' ){ argv[argc++] = "persist"; }else{ zIdle = Lookup(iRec,"idle","300"); if( atoi(zIdle)>0 ){ z = Lookup(iRec,"pppversion",DFLT_PPP_VERSION); argv[argc++] = strcmp(z,"2.3")<0 ? "idle-disconnect" : "idle"; argv[argc++] = zIdle; } } argv[argc++] = "user"; argv[argc++] = zUser; argv[argc++] = "remotename"; argv[argc++] = zService; for(i=0; i<10; i++){ sprintf(zLabel,"pppopt%d",i); z = Lookup(iRec,zLabel,0); if( z==0 ) break; argv[argc++] = z; } argv[argc] = 0; WriteArgvToTranscript(iRec,argv); execv(argv[0],argv); exit(0); }else{ /* The parent process */ char zPppFile[sizeof(DB_DIR)+20]; if( piChild ) *piChild = rc; sprintf(zPppFile,"%s/ppp.%d", DB_DIR, iRec); WriteToFile(zPppFile,0644,"%d\n",rc); while( access(zPidFile,0)==0 ){ int status; if( waitpid(rc,&status,WNOHANG)>0 ){ isup = 0; break; } sleep(1); } sprintf(zChatStat,"%s/chatstat.%d", DB_DIR, iRec); if( !isup ){ char *z = ReadFile(zChatStat); if( z ){ WriteStatus(iRec,z); }else{ WriteStatus(iRec,"pppd failure at"); } unlink(zDeviceFile); unlink(zPppFile); } unlink(zPidFile); unlink(zChatStat); MakeSessionLog(startTime, rc,iRec); PrintStatus(iRec); } return !isup; } /* This routine is called in order to active the diald daemon. */ static int StartDiald(int iRec){ char *zPpp; char *zDiald; char *zPw; char *zUser; char *zService; char *z; int pidPpp; int pidDiald; int rc; int i; FILE *out; char zConfFile[sizeof(DB_DIR)+100]; char zPidFile[sizeof(DB_DIR)+20]; char zDialdFile[sizeof(DB_DIR)+100]; char zServiceBuf[50]; /* Check to see if diald is already running. */ sprintf(zDialdFile, "%s/diald.%d", DB_DIR, iRec); zDiald = ReadFile(zDialdFile); if( zDiald && (pidDiald=atoi(zDiald))>0 && kill(pidDiald,0)==0 ){ return 0; } /* Check to see if we are already running directly. If so, then ** do a hangup before continuing. */ sprintf(zPidFile, "%s/pid.%d", DB_DIR, iRec); zPpp = ReadFile(zPidFile); if( zPpp && (pidPpp=atoi(zPpp))>0 && kill(pidPpp,0)==0 ){ Hangup(iRec); } /* Initialize the chap-secrets and pap-secrets files. */ sprintf(zServiceBuf,"x%d",iRec); zService = Lookup(iRec,"service",zServiceBuf); zUser = Lookup(iRec,"user","anonymous"); zPw = Lookup(iRec, "password", "*"); if( zPw ){ SetSecrets("pap-secrets",zService,zUser,zPw); SetSecrets("chap-secrets",zService,zUser,zPw); } /* Create the diald configuration script. */ sprintf(zConfFile, "%s/diald.conf.%d", DB_DIR, iRec); out = fopen(zConfFile,"w"); if( out==0 ){ WriteStatus(iRec, "configuration failed"); return 1; } fprintf(out,"mode ppp\n"); fprintf(out,"device %s\n", Lookup(iRec,"tty","/dev/modem")); fprintf(out,"connect '" SELF " chat %d'\n", iRec); fprintf(out,"speed %s\n", Lookup(iRec,"baud","115200")); z = Lookup(iRec,"defaultroute","y"); if( *z=='y' ){ fprintf(out,"defaultroute\n"); } fprintf(out,"local %s\n", Lookup(iRec,"local","127.0.0.100")); fprintf(out,"remote %s\n", Lookup(iRec,"remote","127.0.0.101")); fprintf(out, "lock\n" "modem\n" "crtscts\n" "-reroute\n" "dynamic\n" "-daemon\n" ); fclose(out); /* Fire up the diald process */ rc = fork(); if( rc<0 ){ WriteStatus(iRec,"fork failed"); return 1; } if( rc==0 ){ /* The child process */ int argc = 0; char *argv[30]; argv[argc++] = Lookup(iRec, "diald", DIALD_EXE); argv[argc++] = "-f"; argv[argc++] = zConfFile; argv[argc++] = "--"; argv[argc++] = "remotename"; argv[argc++] = zService; argv[argc++] = "user"; argv[argc++] = zUser; for(i=0; i<10; i++){ char zLabel[100]; sprintf(zLabel,"pppopt%d",i); z = Lookup(iRec,zLabel,0); if( z==0 ) break; argv[argc++] = z; } argv[argc] = 0; WriteArgvToTranscript(iRec,argv); close(0); close(1); close(2); open("/dev/null",O_RDWR); open("/dev/null",O_RDWR); open("/dev/null",O_RDWR); execv(argv[0],argv); exit(0); } /* The parent process */ WriteToFile(zDialdFile,0644,"%d\n",rc); sleep(1); if( kill(rc,0) ){ WriteStatus(iRec,"diald startup failed at"); rc = 1; }else{ WriteStatus(iRec,"diald started at"); rc = 0; } return rc; } /* ** Given a configuration file entry, return a "safe" value for ** this entry. If the entry is the password and the real user ** is anything other than root, return "*********". */ static char *SafeValue(ConfEntry *p){ char *zVal; if( strcmp(p->zLabel,"password")==0 && getuid()!=0 ){ zVal = "**********"; }else{ zVal = p->zValue; } return zVal; } /* Print a usage comment and die. A call to this ** routine never returns. */ static void usage(char *argv0){ fprintf(stderr, "User commands:\n" " %s add ORDINAL FIELD=VALUE...\n" " %s change ORDINAL|SERVICE ?FIELD=VALUE?...\n" " %s delete ORDINAL|SERVICE|IP\n" " %s dialdoff ORDINAL|IP|DEVICE\n" " %s dialdon ORDINAL|IP|DEVICE\n" " %s down ORDINAL|IP|DEVICE|all\n" " %s list ?ORDINAL|SERVICE?\n" " %s log\n" " %s move ORDINAL ORDINAL\n" " %s names\n" " %s status\n" " %s up SERVICE|IP|ORDINAL\n" " %s wait SERVICE|IP|ORDINAL\n" "\n" "Commands for internal use only:\n" " %s chat ORDINAL\n" " %s ipdown INTERFACE DEVICE SPEED LOCAL-IP REMOTE-IP\n" " %s ipup INTERFACE DEVICE SPEED LOCAL-IP REMOTE-IP\n" "\n" "Field names:\n" " phone = phone number for modem to dial\n" " user = user login name\n" " password = login password\n" " tty = serial port for the modem\n" " baud = baud rate\n" " service = name of ISP\n" " chat = \"no\" to ignore \"login:\" prompts, etc.\n" " autostart = \"no\" to prevent starting pppd if no \"login:\" seen\n" " pppversion = what version of pppd is being used\n" " debug = \"yes\" to turn on pppd debugging\n" " expectN = Nth extra expect string (N between 1 and 9)\n" " replyN = Reply to issue when expectN is seen\n" " initN = Nth modem initialization string (N between 1 and 9)\n" " diald = name of diald executable\n" " pppd = name of pppd executable\n" " local = local IP address for diald slip link\n" " remote = remote IP address for diald slip link\n" " idle = number of seconds for idle timeout\n" " persist = \"yes\" for a permanent connection\n" " defaultroute = \"no\" to prevent creating a default route\n" " pppoptN = Nth extra parameter to pppd\n" " routeN = Add a route to the specified network when pppd comes up\n" " ip = network that this service gives access to\n" " netmask = netmask for the ip= network\n" " dialtimeout = time allowed for the modem to connect\n" " chattimeout = time allowed for chat responses\n" " errordelay = \"wait\" command waits this long after an error\n", argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0 ); exit(1); } int main(int argc, char **argv){ int iRec; int rc = 0; if( argc<2 ) usage(argv[0]); if( access(DB_DIR,0) ){ mkdir(DB_DIR,0755); chown(DB_DIR,0,0); if( access(DB_DIR,0) ){ fprintf(stderr,"%s: missing directory \"%s\"\n", argv[0], DB_DIR); exit(1); } } ReadDb(); if( (argc==2 || argc==3) && strcmp(argv[1],"list")==0 ){ int iRec = FindRecord(argv[2]); Unprivileged(); if( iRec<0 || iRec>=nRecord ){ int i; for(i=0; inext){ printf(" %s = %s\n",p->zLabel, SafeValue(p)); } } }else{ ConfEntry *p = aRecord[iRec] = sort(aRecord[iRec]); for(; p; p=p->next){ printf(" %s = %s\n",p->zLabel, SafeValue(p)); } } }else if( argc==2 && strcmp(argv[1],"log")==0 ){ char *z; Unprivileged(); z = ReadFile(SESSIONLOG); if( z ){ printf("%s",z); } }else if( (argc==2 || argc==3 ) && strcmp(argv[1],"tcllist")==0 ){ int i; Unprivileged(); for(i=0; inext){ printf("set eznet(%d:%s) ",i,p->zLabel); WriteTcl(stdout,SafeValue(p)); printf("\n"); } } }else if( argc==2 && strcmp(argv[1],"names")==0 ){ int i; Unprivileged(); for(i=0; i=3 && strcmp(argv[1],"add")==0 ){ int iRec; Unprivileged(); iRec = FindRecord(argv[2]); if( iRec==FIND_ERROR ){ AddEntries(nRecord, argc-2, argv+2); }else if( iRec>=nRecord ){ AddEntries(nRecord, argc-3, argv+3); }else{ AddEntries(nRecord, argc-3, argv+3); MoveRecord(nRecord, iRec); } WriteDb(); }else if( argc==3 && strcmp(argv[1],"delete")==0 ){ int iRec = FindRecord(argv[2]); Unprivileged(); if( iRec==FIND_ALL ){ nRecord = 0; }else if( iRec==FIND_ERROR ){ usage(argv[0]); }else{ MoveRecord(iRec,nRecord-1); aRecord[nRecord-1] = 0; } WriteDb(); }else if( argc>=4 && strcmp(argv[1],"change")==0 ){ int iRec; Unprivileged(); iRec = FindRecord(argv[2]); if( iRec==FIND_ALL ){ usage(argv[0]); }else{ if( iRec==FIND_ERROR ){ iRec = nRecord==1 ? 0 : nRecord; } AddEntries(iRec, argc-3, argv+3); } WriteDb(); }else if( argc==4 && strcmp(argv[1],"move")==0 ){ int from, to; Unprivileged(); if( argc!=4 ) usage(argv[0]); from = GetInt(argv[2]); to = GetInt(argv[3]); if( from>=0 && to>=0 ) MoveRecord(from,to); WriteDb(); }else if( argc==3 && strcmp(argv[1],"chat")==0 ){ iRec = FindRecord(argv[2]); if( iRec<0 ) usage(argv[0]); rc = Chat(iRec); }else if( (argc==2 || argc==3) && strcmp(argv[1],"status")==0 ){ iRec = FindRecord(argv[2]); Unprivileged(); if( iRec<0 ){ for(iRec=0; iRec/dev/null 2>&1"); system("/bin/kill -TERM `/sbin/pidof diald` >/dev/null 2>&1"); for(iRec=0; iRec0 ) sleep(delay); } }else if( (argc==3 || argc==2) && strcmp(argv[1],"dialdon")==0 ){ if( access(SELF,X_OK)!=0 ){ fprintf(stderr,"eznet must be installed as \"" SELF "\"\n"); exit(1); } iRec = FindRecord(argv[2]); if( iRec<0 ){ usage(argv[0]); } rc = StartDiald(iRec); }else if( (argc==3 || argc==2) && strcmp(argv[1],"dialdoff")==0 ){ iRec = FindRecord(argv[2]); if( iRec<0 ){ usage(argv[0]); } rc = StopDiald(iRec); }else if( argc==3 && strcmp(argv[1],"delete")==0 ){ Unprivileged(); iRec = FindRecord(argv[2]); if( iRec>=0 && iRec