#include "bbs.h"

void do_talk(const char params[], confrecordtyp *confrecord)
{
  int n;

  if (*params == '\0')  return;
  n = 0;
  n = atoi(params);
  if (n < 1) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_talk","wrong pid %s",
             params);
    return;
  }
  iniconnecttouser(n,confrecord);
}


void do_sprache(const char params[], const char *sprachennamen[],
                confrecordtyp *confrecord)
{
  int lang;
  char c;
  
  c = params[0];
  tolower(c);
  if (c=='d') {
    confrecord->userrecord.lang = GERMAN;
  }
  else if (c=='e') {
    confrecord->userrecord.lang = ENGLISH;
  }
  lang = confrecord->userrecord.lang;
  writetouser(confrecord,msg("do_sprache",0,lang),sprachennamen[lang]);
}


void do_autozmodem(const char params[], confrecordtyp *confrecord)
{
  char c;
  
  c = params[0];
  tolower(c);
  if (c=='y' || c=='j') {
    confrecord->userrecord.autozmodem = TRUE;
  }
  else if (c=='n') {
    confrecord->userrecord.autozmodem = FALSE;
  }
  
  if (confrecord->userrecord.autozmodem) {
    writetouser(confrecord,msg("do_autozmodem",0,confrecord->userrecord.lang));
  }
  else {
    writetouser(confrecord,msg("do_autozmodem",1,confrecord->userrecord.lang));
  }
}


void do_fullist(const char params[], confrecordtyp *confrecord)
{
  char c;
  
  c = params[0];
  tolower(c);
  if (c=='y' || c=='j') {
    confrecord->userrecord.fullist_on_cd = TRUE;
  }
  else if (c=='n') {
    confrecord->userrecord.fullist_on_cd = FALSE;
  }
  
  if (confrecord->userrecord.fullist_on_cd) {
    writetouser(confrecord,msg("do_fullist",0,confrecord->userrecord.lang));
  }
  else {
    writetouser(confrecord,msg("do_fullist",1,confrecord->userrecord.lang));
  }
}


void do_cd(const char params[], char chrootcwd[], confrecordtyp *confrecord)
{
  char *args[MAXARGS+1], str[PATH_MAX+1];

  strmaxcpy(str,params,PATH_MAX);
  splitparams(args,str);
  if (args[0]==(char *)NULL) {
    strcpy(str,confrecord->userrecord.home);
    args[0] = str;
  }
  chrootpath(args[0],chrootcwd,confrecord->rootdir);
  if (chdir(args[0]) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_cd","chdir %s: %m",
             getchrootpath(str,confrecord->rootdir));
  }
  getchrootcwd(chrootcwd,confrecord->rootdir);
#ifdef AUTOLISTFILE
  if (checkfileperms(AUTOLISTFILE,geteuid(),getegid()) == 0) {
    do_showfile(AUTOLISTFILE,chrootcwd,confrecord);
  }
#endif
}


void do_get(const char params[], const char *chrootcwd,
            const char *protokollnamen[], const int protokoll,
	    confrecordtyp *confrecord)
{
  UID_T euid;
  GID_T egid;
  int k, p;
  char *args[MAXARGS+1], *inargs[MAXARGS+1], *files[MAXARGS+1],
       str[PATH_MAX+1], path[PATH_MAX+1];

  strmaxcpy(str,params,PATH_MAX);
  euid = geteuid();
  egid = getegid();
  bbslog(LOG_FILE,confrecord,"%s: %s",protokollnamen[protokoll],str);
  splitparams(inargs,str);
  p = 0;
  for (k=0;inargs[k]!=NULL;k++) {
    if (*inargs[k]!='-') {
      strcpy(path,inargs[k]);
      chrootpath(path,chrootcwd,confrecord->rootdir);
      switch (checkfileperms(path,euid,egid)) {
      case -EACCES:
      case -ENOENT:
	errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_get",
	         msg("do_get",0,confrecord->userrecord.lang),
		     getchrootpath(path,confrecord->rootdir));
	break;
      case EFAULT:
	errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_get",
	         msg("do_get",1,confrecord->userrecord.lang),
		     getchrootpath(path,confrecord->rootdir));
	break;
      case EACCES:
	errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_get",
	         msg("do_get",2,confrecord->userrecord.lang),
		     getchrootpath(path,confrecord->rootdir));
	break;
      default:
        files[p] = (char *)malloc((SIZE_T)PATH_MAX);
	strcpy(files[p],path);
        p++;
      }
    }
  }
  files[p] = NULL;
  if (p == 0)  return;
  
  strcpy(path,".");
  chrootpath(path,chrootcwd,confrecord->rootdir);
  p = 0;
  switch (protokoll) {
    case KERMITPROTO:
      if ((errno=checkfileperms(confrecord->kermrcpath,euid,egid)) != 0) {
        if (errno < 0)  errno = -errno;
        errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_get",
	         "cannot access kermit configuration file\n%s:\n%m",
		 confrecord->kermrcpath);
	break;
      }
      args[p++] = "kermit";
      args[p++] = "-y";
      args[p++] = confrecord->kermrcpath;
      args[p++] = "-s";
      for (k=0;files[k]!=NULL;k++) {
        getchrootpath(files[k],path);
  	args[p++] = &(files[k][1]);	/* wegen dem fuehrenden '/' */
      }
      args[p] = NULL;
      execute(KERMIT,args,confrecord->userrecord.loglevel,confrecord);
      break;
    case ZMODEMPROTO:
      args[p++] = "sz";
      args[p++] = "-b";
      for (k=0;files[k]!=NULL;k++) {
        getchrootpath(files[k],path);
	args[p++] = &(files[k][1]);	/* wegen dem fuehrenden '/' */
      }
      args[p] = NULL;
      execute(SZ,args,confrecord->userrecord.loglevel,confrecord);
      break;
  }
  for (k=0;files[k]!=NULL;k++) {
    free((void *)files[k]);
  }
}


void do_home(const char params[], const char *chrootcwd,
             userrecordtyp *userrecord, confrecordtyp *confrecord)
{
  char *args[MAXARGS+1], path[PATH_MAX+1], fullpath[PATH_MAX+1],
       wdir[PATH_MAX+1];
  
  strmaxcpy(path,params,PATH_MAX);
  splitparams(args,path);
  if (args[0]!=(char *)NULL) {
    strcpy(fullpath,args[0]);
    chrootpath(fullpath,chrootcwd,confrecord->rootdir);
    getcwd(wdir,PATH_MAX);
    if (chdir(fullpath)<0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_home",
               msg("do_home",0,confrecord->lang));
    }
    else if (userrecord->seclevel == HIGHSECURITY) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_home",
               msg("do_home",1,confrecord->lang));
    }
    else {
      mdefenv("HOME",fullpath);
      chrootpath(args[0],chrootcwd,(char *)NULL);
      strcpy(userrecord->home,args[0]);
    }
    chdir(wdir);
  }
  writetouser(confrecord,"Home: %s\n",userrecord->home);
}


void do_ls(const char params[], const char *chrootcwd,
           confrecordtyp *confrecord)
{
  UID_T euid;
  GID_T egid;
  int n, anz;
  char *args[MAXARGS+1], dir[PATH_MAX+1], str[PATH_MAX+1], tstr[ASCTIMESTRLEN],
       *sp, *sp2;
  DIR *dp;
  struct dirent *dirp;
  struct stat stats;
  tlistetyp *tliste;

  strmaxcpy(str,params,PATH_MAX);
  anz = splitparams(args,str);
  if (anz == 0) {
    strcpy(dir,".");
  }
  else {
    strcpy(dir,args[0]);
  }
  chrootpath(dir,chrootcwd,confrecord->rootdir);
  
  if (stat(dir,&stats) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_ls","stat %s: %m",dir);
    return;
  }
  if (! S_ISDIR(stats.st_mode)) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_ls",
             "%s is not a directory",dir);
    return;
  }
  if ((dp=opendir(dir)) == NULL) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_ls","opendir %s: %m",dir);
    return;
  }

  euid = geteuid();
  egid = getegid();
  tliste = (tlistetyp *)0;
  addstrtoliste(&tliste,TRUE,0,TL_BLOCKLEN,"",confrecord);
  anz = 0;
  while ((dirp=readdir(dp)) != NULL) {
    if (strcmp(dirp->d_name,".") == 0) continue;
    if (strcmp(dirp->d_name,"..") == 0) continue;
    if (stat(dirp->d_name,&stats) < 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_ls","stat %s: %m",
               dirp->d_name);
      continue;
    }
    n = getperms(&stats,euid,egid);
    if (!(PERM_R & n)) continue;
    if (S_ISREG(stats.st_mode)) {
      strcpy(tstr,ctime(&(stats.st_mtime)));
      tstr[7] = '\0';
      tstr[10] = '\0';
      tstr[19] = '\0';
      tstr[24] = '\0';
      sprintf(str,"%s\t%s %s %s %8ld",dirp->d_name,
        &(tstr[8]),&(tstr[4]),&(tstr[20]),(long)stats.st_size);
      addstrtoliste(&tliste,TRUE,anz,TL_BLOCKLEN,str,confrecord);
      anz++;
    }
    else if (S_ISDIR(stats.st_mode) && (PERM_X & n)) {
      strcpy(str,dirp->d_name);
      strcat(str,"\t      (Directory)   ");
      addstrtoliste(&tliste,TRUE,anz,TL_BLOCKLEN,str,confrecord);
      anz++;
    }
    else {
      continue;
    }
  }
  sortliste(tliste,TL_BLOCKLEN,anz,confrecord);
  for (n=0; n<anz; n++) {
    readliste(&tliste,FALSE,n,TL_BLOCKLEN,&sp,confrecord);
    sp2 = sp;
    for (; *sp!='\t'; sp++) ;
    *sp++ = '\0';
    sprintf(str,"%s  %s",sp,sp2);
    addstrtoliste(&tliste,TRUE,n,TL_BLOCKLEN,str,confrecord);
  }
  tlpager(tliste,anz,"     Datum       Bytes    Name",confrecord);
  removeliste(tliste,TRUE,TL_BLOCKLEN);
  
  return;
}


void do_protokoll(const char params[], const char *protokollnamen[],
                  int *protokoll, confrecordtyp *confrecord)
{
  char c;
  
  c = params[0];
  tolower(c);
  if (c=='k') {
    if (confrecord->userrecord.kermitok) {
      *protokoll = KERMITPROTO;
      mputenv("PROTOKOLL=KERMIT");
    }
    else {
      writetouser(confrecord,msg("do_protokoll",0,
                  confrecord->userrecord.lang));
      return;
    }
  }
  else if (c=='z') {
    *protokoll = ZMODEMPROTO;
    mputenv("PROTOKOLL=ZMODEM");
  }
  confrecord->userrecord.protokoll = *protokoll;
  writetouser(confrecord,"Protokoll: %s\n",protokollnamen[*protokoll]);
}


void do_put(const char params[], const char *chrootcwd, const int protokoll,
            confrecordtyp *confrecord)
{
  int p;
  char *args[MAXARGS+1], *inargs[MAXARGS+1], str[PATH_MAX+1], path[PATH_MAX+1];
  
  getcwd(path,PATH_MAX);
  strmaxcpy(str,confrecord->userrecord.uploaddir,PATH_MAX);
  chrootpath(str,chrootcwd,confrecord->rootdir);
  if (chdir(str) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_put","chdir %s: %m",
             confrecord->userrecord.uploaddir);
    return;
  }
  if (access(".",W_OK) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_put",
             msg("do_put",0,confrecord->lang), chrootcwd);
    if (chdir(path) < 0) {
      errormsg(E_LOGFILE,confrecord,"bbs","do_put","chdir (r) %s: %m",path);
    }
    return;
  }

  strcpy(str,params);
  splitparams(inargs,str);
  p = 0;
  switch (protokoll) {
    case KERMITPROTO:
      if ((errno=checkfileperms(confrecord->kermrcpath,geteuid(),getegid())) != 0) {
        if (errno < 0)  errno = -errno;
        errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_put",
                 "cannot access kermit configuration file\n%s:\n%m",
                 confrecord->kermrcpath);
        break;
      }
      args[p++] = "kermit";
      args[p++] = "-y";
      args[p++] = confrecord->kermrcpath;
      args[p++] = "-r";
      args[p] = NULL;
      execute(KERMIT,args,confrecord->userrecord.loglevel,confrecord);
      break;
    case ZMODEMPROTO:
      args[p++] = "rz";
#ifndef NO_RZ_BFLAG
      args[p++] = "-b";
#endif
      args[p] = NULL;
      execute(RZ,args,confrecord->userrecord.loglevel,confrecord);
      break;
  }
  if (chdir(path) < 0) {
    errormsg(E_LOGFILE,confrecord,"bbs","do_put","chdir (r) %s: %m",path);
  }
  return;
}


void do_pwd(const char *chrootcwd, confrecordtyp *confrecord)
{
  writetouser(confrecord,"Directory: %s",chrootcwd);
}


void do_showenv(userrecordtyp *userrecord, confrecordtyp *confrecord)
{
  char str[STRLEN+1], str1[STRLEN+1];

  sprintf(str1,"Username: %s\n", userrecord->name);
  strmaxcpy(str,str1,STRLEN);
  sprintf(str1,"Securitylevel: %d\n", userrecord->seclevel);
  strmaxcat(str,str1,STRLEN);
  sprintf(str1,"Protokoll: %s\n", protokollnamen[userrecord->protokoll]);
  strmaxcat(str,str1,STRLEN);
  sprintf(str1,"Auto-Z-Modem: %s\n", userrecord->autozmodem ? "on":"off");
  strmaxcat(str,str1,STRLEN);
  sprintf(str1,"Fullist on cd: %s\n", userrecord->fullist_on_cd ? "yes":"no");
  strmaxcat(str,str1,STRLEN);
  sprintf(str1,"Home: %s\n", userrecord->home);
  strmaxcat(str,str1,STRLEN);
  if (strcmp(userrecord->shell,NOSHELL)==0) {
    sprintf(str1,"No shell\n");
  }
  else if (strcmp(userrecord->shell,RESHELL)==0) {
    sprintf(str1,"'restricted' shell (%s)\n", RESHELL);
  }
  else {
    sprintf(str1,"Shell: %s\n", userrecord->shell);
  }
  strmaxcat(str,str1,STRLEN);
  if (userrecord->seclevel>=MEDIUMSECURITY) {
    sprintf(str1,"Path: %s\n", userrecord->path);
    strmaxcat(str,str1,STRLEN);
  }
  sprintf(str1,"Term: %s\n", userrecord->term);
  strmaxcat(str,str1,STRLEN);
  sprintf(str1,"Lines: %d\n", userrecord->lines);
  strmaxcat(str,str1,STRLEN);
  sprintf(str1,"Columns: %d\n", userrecord->columns);
  strmaxcat(str,str1,STRLEN);
  sprintf(str1,"Language: %s\n", sprachennamen[userrecord->lang]);
  strmaxcat(str,str1,STRLEN);
  writetouser(confrecord,str);
}


void do_showfile(const char *filepath, const char *chrootcwd,
                 confrecordtyp *confrecord)
{
  int fd, maxz, k, offset, zeilen, zstep, lang;
  char str[PATH_MAX+1], prompt[S_STRLEN+1], *mmp, *cp, key;
  char cset[] = {SPACE_KEY,BACKSPACE_KEY,DELETE_KEY,QUIT_KEY,'\0'};
  struct stat stats;
  tlistetyp *tliste;
  
  lang = confrecord->userrecord.lang;
  strmaxcpy(str,filepath,PATH_MAX);
  if (chrootcwd != NULL) {
    chrootpath(str,chrootcwd,confrecord->rootdir);
  }
  switch (checkfileperms(str,geteuid(),getegid())) {
  case -EACCES:
  case -ENOENT:
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             msg("do_showfile",0,lang),filepath);
    return;
    break;
  case EFAULT:
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             msg("do_showfile",1,lang),filepath);
    return;
    break;
  case EACCES:
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             msg("do_showfile",2,lang),filepath);
    return;
    break;
  }

  if ((fd=open(str,O_RDONLY)) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             "open %s: %m",str);
    return;
  }
  if (fstat(fd,&stats) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             "fstat %s: %m",str);
    return;
  }
#ifdef NO_MMAP
  if ((mmp=(char *)malloc(stats.st_size)) == NULL) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             "malloc: %m");
    return;
  }
  if (readn(fd,(void *)mmp,stats.st_size) != stats.st_size) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             "read %s: %m",str);
    return;
  }
#else
  if ((mmp=(char *)mmap(0,stats.st_size,PROT_READ,MAP_FILE|MAP_SHARED,fd,0)) == (CADDR_T) -1) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_showfile",
             "mmap %s: %m",str);
    return;
  }
#endif
  
  cp = mmp;
  tliste = (tlistetyp *)0;
  addstrtoliste(&tliste,FALSE,0,TL_BLOCKLEN,"",confrecord);
  zeilen = 0;
  do {
    addstrtoliste(&tliste,FALSE,zeilen++,TL_BLOCKLEN,cp,confrecord);
    for (; *cp!='\n' && (OFF_T)(cp-mmp)<stats.st_size; cp++) ;
    if (*cp=='\n' && (OFF_T)(cp-mmp)<stats.st_size) cp++;
  } while ((OFF_T)(cp-mmp)<stats.st_size);
  maxz = confrecord->userrecord.lines -1;
  zstep = maxz - 2;
  offset = 0;
  do {
    move(0,0);
    clear();
    for (k=offset; k<offset+maxz && k<zeilen; k++) {
      readliste(&tliste,FALSE,k,TL_BLOCKLEN,&cp,confrecord);
      while (*cp!='\n' && (OFF_T)(cp-mmp)<stats.st_size) addch(*cp++);
      addch('\n');
    }
    if (zeilen > maxz) {
      if (offset+maxz < zeilen) {
        if (offset == 0) {
          strcpy(prompt,msg("do_showfile",4,lang));
	}
	else {
	  sprintf(prompt,msg("do_showfile",5,lang),offset);
	}
      }
      else {
        strcpy(prompt,msg("do_showfile",3,lang));
      }
    }
    else {
      strcpy(prompt,msg("do_showfile",6,lang));
    }
    key = getkeyonprompt(prompt,cset,TRUE,FALSE,confrecord);
    if (pagerstep(key) < 0) {
      offset -= zstep;
    }
    else if (pagerstep(key) > 0) {
      offset += zstep;
    }
    if (offset > zeilen-maxz) offset = zeilen-maxz;
    if (offset < 0) offset = 0;
  } while (pagerquit(key) == 0);

  removeliste(tliste,FALSE,TL_BLOCKLEN);

#ifdef NO_MMAP
  free(mmp);
#else
  munmap(mmp,stats.st_size);
#endif
  close(fd);
  return;
}


int do_term(const char params[], userrecordtyp *userrecord,
             confrecordtyp *confrecord)
{
  int n;
  boolean size_changed=FALSE;
  char *args[MAXARGS+1], str[PATH_MAX+1], tgetentbuf[TGETENTBUF];
  struct winsize termsize;
  
  strmaxcpy(str,params,PATH_MAX);
  splitparams(args,str);
  if (args[0]!=(char *)NULL) {
    if (strcmp(args[0],"*")!=0) {
      lowercases(args[0]);
      if (! termexists(args[0]))  return(-1);
      strmaxcpy(userrecord->term,args[0],S_STRLEN);
      mdefenv("TERM",args[0]);
    }
    if (args[1]!=(char *)NULL) {
      if (isdigit(args[1][0])) {
        n = atoi(args[1]);
        if (n>0 && n<1000) {
          userrecord->lines = n;
       	  mdefenv("LINES","%d",n);
	  size_changed = TRUE;
        }
      }
      if (args[2]!=(char *)NULL) {
        if (isdigit(args[2][0])) {
          n = atoi(args[2]);
          if (n>0 && n<1000) {
            userrecord->columns = n;
 	    mdefenv("COLUMNS","%d",n);
	    size_changed = TRUE;
          }
	}
      }
    }
  }
  if (confrecord->curses_on) {
    if (size_changed) {
      ioctl(0,TIOCGWINSZ, (char *)&termsize);
      termsize.ws_row = userrecord->lines;
      termsize.ws_col = userrecord->columns;
      ioctl(0,TIOCSWINSZ, (char *)&termsize);
      tgetent(tgetentbuf,userrecord->term);
      endwin();
      confrecord->curses_on = FALSE;
      window_on(confrecord);
    }
    sprintf(str,"Term: %s\nLines: %d\nColumns: %d\n", userrecord->term,
            userrecord->lines, userrecord->columns);
    writetouser(confrecord,str);
  }
  return(0);
}


int do_newpasswd(const char params[], userrecordtyp *userrecord,
                 confrecordtyp *confrecord)
{
  boolean passwdchanged=FALSE;
  char *args[MAXARGS+1], str[S_STRLEN+PASS_MAX+2], salt[9];
  userrecordtyp urec;
#ifndef NO_CRYPT
  void to64(char *, long, int);
#endif
  
  strmaxcpy(str,params,PATH_MAX);
  if (splitparams(args,str) != 2) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_newpasswd",
             "wrong number of args");
    return(-1);
  }
  if (strlen(args[1]) > PASS_MAX) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_newpasswd",
             "password too long");
    return(-1);
  }

  if (userrecord->seclevel != HIGHSECURITY) {
#ifndef NO_CRYPT
    SRANDOM(time((TIME_T)0));
# ifdef NEWSALT
    salt[0] = _PASSWORD_EFMT1;
    to64(&salt[1], (long)(29 * 25), 4);
    to64(&salt[5], RANDOM(), 4);
# else
    to64(&salt[0], RANDOM(), 2);
# endif
#endif
    if (strcmp(userrecord->name,args[0]) == 0) {
      strmaxcpy(userrecord->passwd,CRYPTFUNC(args[1],salt),PASSCRYPTED_LEN);
      saveuserrecord(userrecord,confrecord);
      passwdchanged = TRUE;
    }
    else if (strcmp(userrecord->name,confrecord->sysop) == 0) {
      if (! getuserrecord(&urec,args[0],confrecord)) {
        errormsg(E_LOGFILE|E_USER,confrecord,"bbs","do_newpasswd",
	         msg("do_newpasswd",0,userrecord->lang),args[0]);
        return(-1);
      }
      strmaxcpy(urec.passwd,CRYPTFUNC(args[1],salt),PASSCRYPTED_LEN);
      saveuserrecord(&urec,confrecord);
      passwdchanged = TRUE;
    }
  }
  
  if (passwdchanged) {
    bbslog(LOG_FILE,confrecord,msg("do_newpasswd",1,userrecord->lang),
           args[0],userrecord->name);
    writetouser(confrecord,msg("do_newpasswd",2,userrecord->lang));
  }
  else {
    writetouser(confrecord,msg("do_newpasswd",3,userrecord->lang));
  }
  return(0);
}

#ifndef NO_CRYPT
void to64(char *s, long v, int n)
{
  U_CHAR itoa64[] =
  "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  while (--n >= 0) {
    *s++ = itoa64[v&0x3f];
    v >>= 6;
  }
}
#endif
