code:
/*
    NINJAM Server - ninjamsrv.cpp
    Copyright (C) 2005-2007 Cockos Incorporated

    NINJAM 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.

    NINJAM 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 NINJAM; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*

  This file does the setup and configuration file management for the server.
  Note that the kernel of the server is basically in usercon.cpp/.h, which
  includes a User_Connection class (manages a user) and a User_Group class
  (manages a jam).

*/

#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
#include <signal.h>
#include <stdarg.h>

#include <WDL/jnetlib/jnetlib.h>
#include <WDL/jnetlib/httpget.h>
#include <ninjam/netmsg.h>
#include <ninjam/mpb.h>
#include "usercon.h"

#include <WDL/rng.h>
#include <WDL/sha.h>
#include <WDL/lineparse.h>
#include <WDL/ptrlist.h>
#include <WDL/string.h>

#define VERSION "v0.06"

const char *startupmessage="NINJAM Server " VERSION " built on " __DATE__ " at " __TIME__ " starting up...\n" "Copyright (C) 2005-2007, Cockos, Inc.\n";

int g_set_uid=-1;
int g_default_bpm,g_default_bpi;
FILE *g_logfp;
WDL_String g_pidfilename;
WDL_String g_logfilename;
WDL_String g_status_pass,g_status_user;
User_Group *m_group;
JNL_Listen *m_listener;
void onConfigChange(int argc, char **argv);
void logText(char *s, ...);

class UserPassEntry
{
public:
  UserPassEntry() {priv_flag=0;}
  ~UserPassEntry() {}
  WDL_String name, pass;
  unsigned int priv_flag;
};

#define ACL_FLAG_DENY 1
#define ACL_FLAG_RESERVE 2
typedef struct
{
  unsigned long addr;
  unsigned long mask;
  int flags;
} ACLEntry;

WDL_HeapBuf g_acllist;
void aclAdd(unsigned long addr, unsigned long mask, int flags)
{
  addr=ntohl(addr);
//  printf("adding acl entry for %08x + %08x\n",addr,mask);
  ACLEntry f={addr,mask,flags};
  int os=g_acllist.GetSize();
  g_acllist.Resize(os+sizeof(f));
  memcpy((char *)g_acllist.Get()+os,&f,sizeof(f));
}

int aclGet(unsigned long addr)
{
  addr=ntohl(addr);

  ACLEntry *p=(ACLEntry *)g_acllist.Get();
  int x=g_acllist.GetSize()/sizeof(ACLEntry);
  while (x--)
  {
  //  printf("comparing %08x to %08x\n",addr,p->addr);
    if ((addr & p->mask) == p->addr) return p->flags;
    p++;
  }
  return 0;
}

WDL_PtrList<UserPassEntry> g_userlist;
int g_config_allow_anonchat;
int g_config_port;
bool g_config_allowanonymous;
bool g_config_allowanonymous_multi;
bool g_config_anonymous_mask_ip;
int g_config_maxch_anon;
int g_config_maxch_user;
WDL_String g_config_logpath;
int g_config_log_sessionlen;

time_t next_session_update_time;

WDL_String g_config_license;

class localUserInfoLookup : public IUserInfoLookup
{
public:
  localUserInfoLookup(char *name)
  {
    username.Set(name);
  }
  ~localUserInfoLookup()
  {
  }

  int Run()
  {
    // perform lookup here

    user_valid=0;

    if (!strncmp(username.Get(),"anonymous",9) && (!username.Get()[9] || username.Get()[9] == ':'))
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      logText("got anonymous request (%s)\n",g_config_allowanonymous?"allowing":"denying");
#pragma GCC diagnostic pop
      if (!g_config_allowanonymous) return 1;

      user_valid=1;
      reqpass=0;

      WDL_String tmp(username.Get());

      if (tmp.Get()[9] == ':' && tmp.Get()[10])
      {
        username.Set(tmp.Get()+10);

        int cnt=16;
        char *p=username.Get();
        while (*p)
        {
          if (!cnt--)
          {
            *p=0;
            break;
          }
          if (*p == '@' || *p == '.') *p='_';
          p++;
        }
      }
      else username.Set("anon");

      username.Append("@");
      username.Append(hostmask.Get());

      if (g_config_anonymous_mask_ip)
      {
        char *p=username.Get();
        while (*p) p++;
        while (p > username.Get() && *p != '.' && *p != '@') p--;
        if (*p == '.' && p[1])
        {
          p[1]='x';
          p[2]=0;
        }
      }

      privs=(g_config_allow_anonchat?PRIV_CHATSEND:0) | (g_config_allowanonymous_multi?PRIV_ALLOWMULTI:0) | PRIV_VOTE;
      max_channels=g_config_maxch_anon;
    }
    else
    {
      int x;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      logText("got login request for '%s'\n",username.Get());
#pragma GCC diagnostic pop
      if (g_status_user.Get()[0] && !strcmp(username.Get(),g_status_user.Get()))
      {
        user_valid=1;
        reqpass=1;
        is_status=1;
        privs=0;
        max_channels=0;

        WDL_SHA1 shatmp;
        shatmp.add(username.Get(),strlen(username.Get()));
        shatmp.add(":",1);
        shatmp.add(g_status_pass.Get(),strlen(g_status_pass.Get()));

        shatmp.result(sha1buf_user);
      }
      else for (x = 0; x < g_userlist.GetSize(); x ++)
      {
        if (!strcmp(username.Get(),g_userlist.Get(x)->name.Get()))
        {
          user_valid=1;
          reqpass=1;

          char *pass=g_userlist.Get(x)->pass.Get();
          WDL_SHA1 shatmp;
          shatmp.add(username.Get(),strlen(username.Get()));
          shatmp.add(":",1);
          shatmp.add(pass,strlen(pass));

          shatmp.result(sha1buf_user);

          privs=g_userlist.Get(x)->priv_flag;
          max_channels=g_config_maxch_user;
          break;
        }
      }
    }

    return 1;
  }

};

static IUserInfoLookup *myCreateUserLookup(char *username)
{
  return new localUserInfoLookup(username);
}

static int ConfigOnToken(LineParser *lp)
{
  const char *t=lp->gettoken_str(0);
  if (!stricmp(t,"Port"))
  {
    if (lp->getnumtokens() != 2) return -1;
    int p=lp->gettoken_int(1);
    if (!p) return -2;
    g_config_port=p;
  }
  else if (!stricmp(t,"StatusUserPass"))
  {
    if (lp->getnumtokens() != 3) return -1;
    g_status_user.Set(lp->gettoken_str(1));
    g_status_pass.Set(lp->gettoken_str(2));
  }
  else if (!stricmp(t,"MaxUsers"))
  {
    if (lp->getnumtokens() != 2) return -1;
    int p=lp->gettoken_int(1);
    m_group->m_max_users=p;
  }
  else if (!stricmp(t,"PIDFile"))
  {
    if (lp->getnumtokens() != 2) return -1;
    g_pidfilename.Set(lp->gettoken_str(1));
  }
  else if (!stricmp(t,"LogFile"))
  {
    if (lp->getnumtokens() != 2) return -1;
    g_logfilename.Set(lp->gettoken_str(1));
  }
  else if (!stricmp(t,"SessionArchive"))
  {
    if (lp->getnumtokens() != 3) return -1;
    g_config_logpath.Set(lp->gettoken_str(1));
    g_config_log_sessionlen = lp->gettoken_int(2);
  }
  else if (!stricmp(t,"SetUID"))
  {
    if (lp->getnumtokens() != 2) return -1;
    g_set_uid = lp->gettoken_int(1);
  }
  else if (!stricmp(t,"DefaultBPI"))
  {
    if (lp->getnumtokens() != 2) return -1;
    g_default_bpi=lp->gettoken_int(1);
    if (g_default_bpi<MIN_BPI) g_default_bpi=MIN_BPI;
    else if (g_default_bpi > MAX_BPI) g_default_bpi=MAX_BPI;
  }
  else if (!stricmp(t,"DefaultBPM"))
  {
    if (lp->getnumtokens() != 2) return -1;
    g_default_bpm=lp->gettoken_int(1);
    if (g_default_bpm<MIN_BPM) g_default_bpm=MIN_BPM;
    else if (g_default_bpm > MAX_BPM) g_default_bpm=MAX_BPM;
  }
  else if (!stricmp(t,"DefaultTopic"))
  {
    if (lp->getnumtokens() != 2) return -1;
    if (!m_group->m_topictext.Get()[0])
      m_group->m_topictext.Set(lp->gettoken_str(1));
  }
  else if (!stricmp(t,"MaxChannels"))
  {
    if (lp->getnumtokens() != 2 && lp->getnumtokens() != 3) return -1;

    g_config_maxch_user=lp->gettoken_int(1);
    g_config_maxch_anon=lp->gettoken_int(lp->getnumtokens()>2?2:1);
  }
  else if (!stricmp(t,"SetKeepAlive"))
  {
    if (lp->getnumtokens() != 2) return -1;
    m_group->m_keepalive=lp->gettoken_int(1);
    if (m_group->m_keepalive < 0 || m_group->m_keepalive > 255)
      m_group->m_keepalive=0;
  }
  else if (!stricmp(t,"SetVotingThreshold"))
  {
    if (lp->getnumtokens() != 2) return -1;
    m_group->m_voting_threshold=lp->gettoken_int(1);
  }
  else if (!stricmp(t,"SetVotingVoteTimeout"))
  {
    if (lp->getnumtokens() != 2) return -1;
    m_group->m_voting_timeout=lp->gettoken_int(1);
  }
  else if (!stricmp(t,"ServerLicense"))
  {
    if (lp->getnumtokens() != 2) return -1;
    FILE *fp=fopen(lp->gettoken_str(1),"rt");
    if (!fp)
    {
      printf("Error opening license file %s\n",lp->gettoken_str(1));
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      if (g_logfp)
        logText("Error opening license file %s\n",lp->gettoken_str(1));
#pragma GCC diagnostic pop
      return -2;
    }
    g_config_license.Set("");
    for (;;)
    {
      char buf[1024];
      buf[0]=0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
      fgets(buf,sizeof(buf),fp);
#pragma GCC diagnostic pop
      if (!buf[0]) break;
      g_config_license.Append(buf);
    }

    fclose(fp);

  }
  else if (!stricmp(t,"ACL"))
  {
    if (lp->getnumtokens() != 3) return -1;
    int suc=0;
    char *v=lp->gettoken_str(1);
    char *t=strstr(v,"/");
    if (t)
    {
      *t++=0;
      unsigned long addr=JNL::ipstr_to_addr(v);
      if (addr != INADDR_NONE)
      {
        int maskbits=atoi(t);
        if (maskbits >= 0 && maskbits <= 32)
        {
          int flag=lp->gettoken_enum(2,"allow\0deny\0reserve\0");
          if (flag >= 0)
          {
            suc=1;
            unsigned long mask=~(0xffffffff>>maskbits);
            aclAdd(addr,mask,flag);
          }
        }
      }
    }

    if (!suc)
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      if (g_logfp)
        logText("Usage: ACL xx.xx.xx.xx/X [ban|allow|reserve]\n");
#pragma GCC diagnostic pop
      printf("Usage: ACL xx.xx.xx.xx/X [ban|allow|reserve]\n");
      return -2;
    }
  }
  else if (!stricmp(t,"User"))
  {
    if (lp->getnumtokens() != 3 && lp->getnumtokens() != 4) return -1;
    UserPassEntry *p=new UserPassEntry;
    p->name.Set(lp->gettoken_str(1));
    p->pass.Set(lp->gettoken_str(2));
    if (lp->getnumtokens()>3)
    {
      char *ptr=lp->gettoken_str(3);
      while (*ptr)
      {
        if (*ptr == '*') p->priv_flag|=~PRIV_HIDDEN; // everything but hidden if * used
        else if (*ptr == 'T' || *ptr == 't') p->priv_flag |= PRIV_TOPIC;
        else if (*ptr == 'B' || *ptr == 'b') p->priv_flag |= PRIV_BPM;
        else if (*ptr == 'C' || *ptr == 'c') p->priv_flag |= PRIV_CHATSEND;
        else if (*ptr == 'K' || *ptr == 'k') p->priv_flag |= PRIV_KICK;
        else if (*ptr == 'R' || *ptr == 'r') p->priv_flag |= PRIV_RESERVE;
        else if (*ptr == 'M' || *ptr == 'm') p->priv_flag |= PRIV_ALLOWMULTI;
        else if (*ptr == 'H' || *ptr == 'h') p->priv_flag |= PRIV_HIDDEN;
        else if (*ptr == 'V' || *ptr == 'v') p->priv_flag |= PRIV_VOTE;
        else
        {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
          if (g_logfp)
            logText("Warning: Unknown user priviledge flag '%c'\n",*ptr);
#pragma GCC diagnostic pop
          printf("Warning: Unknown user priviledge flag '%c'\n",*ptr);
        }
        ptr++;
      }
    }
    else p->priv_flag=PRIV_CHATSEND|PRIV_VOTE;// default privs
    g_userlist.Add(p);
  }
  else if (!stricmp(t,"AllowHiddenUsers"))
  {
    if (lp->getnumtokens() != 2) return -1;

    int x=lp->gettoken_enum(1,"no\0yes\0");
    if (x <0)
    {
      return -2;
    }
    m_group->m_allow_hidden_users=!!x;
  }
  else if (!stricmp(t,"AnonymousUsers"))
  {
    if (lp->getnumtokens() != 2) return -1;

    int x=lp->gettoken_enum(1,"no\0yes\0multi\0");
    if (x <0)
    {
      return -2;
    }
    g_config_allowanonymous=!!x;
    g_config_allowanonymous_multi=x==2;
  }
  else if (!stricmp(t,"AnonymousMaskIP"))
  {
    if (lp->getnumtokens() != 2) return -1;

    int x=lp->gettoken_enum(1,"no\0yes\0");
    if (x <0)
    {
      return -2;
    }
    g_config_anonymous_mask_ip=!!x;
  }
  else if (!stricmp(t,"AnonymousUsers"))
  {
    if (lp->getnumtokens() != 2) return -1;

    int x=lp->gettoken_enum(1,"no\0yes\0");
    if (x <0)
    {
      return -2;
    }
    g_config_allowanonymous=!!x;
  }
  else if (!stricmp(t,"AnonymousUsersCanChat"))
  {
    if (lp->getnumtokens() != 2) return -1;

    int x=lp->gettoken_enum(1,"no\0yes\0");
    if (x <0)
    {
      return -2;
    }
    g_config_allow_anonchat=!!x;
  }
  else return -3;
  return 0;

};

static int ReadConfig(char *configfile)
{
  bool comment_state=0;
  int linecnt=0;
  WDL_String linebuild;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
  if (g_logfp) logText("[config] reloading configuration file\n");
#pragma GCC diagnostic pop
  FILE *fp=strcmp(configfile,"-")?fopen(configfile,"rt"):stdin;
  if (!fp)
  {
    printf("[config] error opening configfile '%s'\n",configfile);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
    if (g_logfp) logText("[config] error opening config file (console request)\n");
#pragma GCC diagnostic pop
    return -1;
  }

  // clear user list, etc
  g_config_port=2049;
  g_config_allow_anonchat=1;
  g_config_allowanonymous=0;
  g_config_allowanonymous_multi=0;
  g_config_anonymous_mask_ip=0;
  g_config_maxch_anon=2;
  g_config_maxch_user=32;
  g_default_bpi=8;
  g_default_bpm=120;

  g_config_log_sessionlen=10; // ten minute default, tho the user will need to specify the path anyway

  m_group->m_max_users=0; // unlimited users
  g_acllist.Resize(0);
  g_config_license.Set("");
  int x;
  for(x=0;x<g_userlist.GetSize(); x++)
  {
    delete g_userlist.Get(x);
  }
  g_userlist.Empty();

  for (;;)
  {
    char buf[8192];
    buf[0]=0;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
    fgets(buf,sizeof(buf),fp);
#pragma GCC diagnostic pop
    linecnt++;
    if (!buf[0]) break;
    if (buf[strlen(buf)-1]=='\n') buf[strlen(buf)-1]=0;

    LineParser lp(comment_state);

    if (buf[0] && buf[strlen(buf)-1]=='\\')
    {
      buf[strlen(buf)-1]=0;
      linebuild.Append(buf);
      continue;
    }
    linebuild.Append(buf);

    int res=lp.parse(linebuild.Get());

    linebuild.Set("");

    if (res)
    {
      if (res==-2)
      {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
        if (g_logfp) logText("[config] warning: unterminated string parsing line %d of %s\n",linecnt,configfile);
#pragma GCC diagnostic pop
        printf("[config] warning: unterminated string parsing line %d of %s\n",linecnt,configfile);
      }
      else
      {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
        if (g_logfp) logText("[config] warning: error parsing line %d of %s\n",linecnt,configfile);
#pragma GCC diagnostic pop
        printf("[config] warning: error parsing line %d of %s\n",linecnt,configfile);
      }
    }
    else
    {
      comment_state = lp.InCommentBlock();

      if (lp.getnumtokens()>0)
      {
        int err=ConfigOnToken(&lp);
        if (err)
        {
          if (err == -1)
          {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
            if (g_logfp) logText("[config] warning: wrong number of tokens on line %d of %s\n",linecnt,configfile);
#pragma GCC diagnostic pop
            printf("[config] warning: wrong number of tokens on line %d of %s\n",linecnt,configfile);
          }
          if (err == -2)
          {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
            if (g_logfp) logText("[config] warning: invalid parameter on line %d of %s\n",linecnt,configfile);
#pragma GCC diagnostic pop
            printf("[config] warning: invalid parameter on line %d of %s\n",linecnt,configfile);
          }
          if (err == -3)
          {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
            if (g_logfp) logText("[config] warning: invalid config command \"%s\" on line %d of %s\n",lp.gettoken_str(0),linecnt,configfile);
#pragma GCC diagnostic pop
            printf("[config] warning: invalid config command \"%s\" on line %d of %s\n",lp.gettoken_str(0),linecnt,configfile);
          }
        }
      }
    }
  }

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
  if (g_logfp) logText("[config] reload complete\n");
#pragma GCC diagnostic pop

  if (fp != stdin) fclose(fp);
  return 0;
}

int g_reloadconfig;
int g_done;

void sighandler(int sig)
{
  if (sig == SIGINT)
  {
    g_done=1;
  }
#ifndef _WIN32
  if (sig == SIGHUP)
  {
    g_reloadconfig=1;
  }
#endif
}

void enforceACL()
{
  int x;
  int killcnt=0;
  for (x = 0; x < m_group->m_users.GetSize(); x ++)
  {
    User_Connection *c=m_group->m_users.Get(x);
    if (aclGet(c->m_netcon.GetConnection()->get_remote()) == ACL_FLAG_DENY)
    {
      c->m_netcon.Kill();
      killcnt++;
    }
  }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
  if (killcnt) logText("killed %d users by enforcing ACL\n",killcnt);
#pragma GCC diagnostic pop
}

void usage()
{
    printf("Usage: NINJAMserver config.cfg [options]\n"
           "Options (override config file):\n"
#ifndef _WIN32
           "  -pidfile <filename.pid>\n"
#endif
           "  -logfile <filename.log>\n"
           "  -archive <path_to_archive>\n"
           "  -port <port>\n"
#ifndef _WIN32
           "  -setuid <uid>\n"
#endif
      );
    exit(1);
}

void openLog()
{
  if (g_logfilename.Get()[0])
  {
    g_logfp=fopen(g_logfilename.Get(),"at");
    if (!g_logfp)
      printf("Error opening log file '%s'\n",g_logfilename.Get());
    else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      logText("Opened log. NINJAM Server %s built on %s at %s\n",VERSION,__DATE__,__TIME__);
#pragma GCC diagnostic pop
  }
}

void closeLog()
{
  if (g_logfp)
  {
    fclose(g_logfp);
    g_logfp=0;
  }
}

void logText(char *s, ...)
{
    if (g_logfp)
    {
      time_t tv;
      time(&tv);
      struct tm *t=localtime(&tv);
      fprintf(g_logfp,"[%04d/%02d/%02d %02d:%02d:%02d] ",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
    }

    va_list ap;
    va_start(ap,s);

    vfprintf(g_logfp?g_logfp:stdout,s,ap);

    if (g_logfp) fflush(g_logfp);

    va_end(ap);
}

int main(int argc, char **argv)
{

  if (argc < 2)
  {
    usage();
  }

  m_group=new User_Group;

  printf("%s",startupmessage);
  if (ReadConfig(argv[1]))
  {
    printf("Error loading config file!\n");
    exit(1);
  }
  int p;
  for (p = 2; p < argc; p ++)
  {
      if (!strcmp(argv[p],"-pidfile"))
      {
        if (++p >= argc) usage();
        g_pidfilename.Set(argv[p]);
      }
      else if (!strcmp(argv[p],"-logfile"))
      {
        if (++p >= argc) usage();
        g_logfilename.Set(argv[p]);
      }
      else if (!strcmp(argv[p],"-archive"))
      {
        if (++p >= argc) usage();
        g_config_logpath.Set(argv[p]);
      }
      else if (!strcmp(argv[p],"-setuid"))
      {
        if (++p >= argc) usage();
        g_set_uid=atoi(argv[p]);
      }
      else if (!strcmp(argv[p],"-port"))
      {
        if (++p >= argc) usage();
        g_config_port=atoi(argv[p]);
      }
      else usage();

  }

#ifdef _WIN32
  DWORD v=GetTickCount();
  WDL_RNG_addentropy(&v,sizeof(v));
  v=(DWORD)time(NULL);
  WDL_RNG_addentropy(&v,sizeof(v));
#else

  if (g_set_uid != -1) setuid(g_set_uid);

  time_t v=time(NULL);
  WDL_RNG_addentropy(&v,sizeof(v));
  int pid=getpid();
  WDL_RNG_addentropy(&pid,sizeof(pid));

  if (g_pidfilename.Get()[0])
  {
    FILE *fp=fopen(g_pidfilename.Get(),"w");
    if (fp)
    {
      fprintf(fp,"%d\n",pid);
      fclose(fp);
    }
    else printf("Error opening PID file '%s'\n",g_pidfilename.Get());
  }

  signal(SIGPIPE,sighandler);
  signal(SIGHUP,sighandler);
#endif
  signal(SIGINT,sighandler);

  openLog();

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
  logText("Server starting up...\n");
#pragma GCC diagnostic pop

  JNL::open_socketlib();

  {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
    logText("Port: %d\n",g_config_port);
#pragma GCC diagnostic pop
    m_listener = new JNL_Listen(g_config_port);
    if (m_listener->is_error())
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      logText("Error listening on port %d!\n",g_config_port);
#pragma GCC diagnostic pop
    }

    m_group->CreateUserLookup=myCreateUserLookup;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
    logText("Using defaults %d BPM %d BPI\n",g_default_bpm,g_default_bpi);
#pragma GCC diagnostic pop
    m_group->SetConfig(g_default_bpi,g_default_bpm);

    m_group->SetLicenseText(g_config_license.Get());

#ifdef _WIN32
    int needprompt=2;
    int esc_state=0;
#endif
    while (!g_done)
    {
      JNL_Connection *con=m_listener->get_connect(2*65536,65536);
      if (con)
      {
        char str[512];
        int flag=aclGet(con->get_remote());
        JNL::addr_to_ipstr(con->get_remote(),str,sizeof(str));
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
        logText("Incoming connection from %s!\n",str);
#pragma GCC diagnostic pop

        if (flag == ACL_FLAG_DENY)
        {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
          logText("Denying connection (via ACL)\n");
#pragma GCC diagnostic pop
          delete con;
        }
        else
        {
          m_group->AddConnection(con,flag == ACL_FLAG_RESERVE);
        }
      }

      if (m_group->Run())
      {
#ifdef _WIN32
        if (needprompt)
        {
          if (needprompt>1) printf("\nKeys:\n"
               "  [S]how user table\n"
               "  [R]eload config file\n"
               "  [K]ill user\n"
               "  [Q]uit server\n");
          printf(": ");
          needprompt=0;
        }
        if (kbhit())
        {
          int c=toupper(getch());
          printf("%c\n",isalpha(c)?c:'?');
          if (esc_state)
          {
            if (c == 'Y') break;
            printf("Exit aborted\n");
            needprompt=2;
            esc_state=0;
          }
          else if (c == 'Q')
          {
            if (!esc_state)
            {
              esc_state++;
              printf("Q pressed -- hit Y to exit, any other key to continue\n");
              needprompt=1;
            }
          }
          else if (c == 'K')
          {
            printf("(be quick, server is paused while you type!!!)\nKill username: ");
            char buf[512];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
            fgets(buf,sizeof(buf),stdin);
#pragma GCC diagnostic pop
            if (buf[0] && buf[strlen(buf)-1]=='\n') buf[strlen(buf)-1]=0;
            if (buf[0])
            {
              int x;
              int killcnt=0;
              for (x = 0; x < m_group->m_users.GetSize(); x ++)
              {
                User_Connection *c=m_group->m_users.Get(x);
                if (!strcmp(c->m_username.Get(),buf))
                {
                  char str[512];
                  JNL::addr_to_ipstr(c->m_netcon.GetConnection()->get_remote(),str,sizeof(str));
                  printf("Killing user %s on %s\n",c->m_username.Get(),str);
                  c->m_netcon.Kill();
                  killcnt++;
                }
              }
              if (!killcnt)
              {
                printf("User %s not found!\n",buf);
              }
            }
            else printf("Kill aborted with no input\n");
            needprompt=1;
          }
          else if (c == 'S')
          {
            needprompt=1;
            int x;
            for (x = 0; x < m_group->m_users.GetSize(); x ++)
            {
              User_Connection *c=m_group->m_users.Get(x);
              char str[512];
              JNL::addr_to_ipstr(c->m_netcon.GetConnection()->get_remote(),str,sizeof(str));
              printf("%s:%s\n",c->m_auth_state>0?c->m_username.Get():"<unauthorized>",str);
            }
          }
          else if (c == 'R')
          {
            if (!strcmp(argv[1],"-") || ReadConfig(argv[1]))
            {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
              if (g_logfp) logText("Error opening config file\n");
#pragma GCC diagnostic pop
              printf("Error opening config file!\n");
            }
            else
            {
//              printf("Listening on port %d...",g_config_port);

              onConfigChange(argc,argv);
            }
            needprompt=1;
          }
          else needprompt=2;

        }
        Sleep(1);
#else
	      struct timespec ts={0,1*1000*1000};
	      nanosleep(&ts,NULL);
#endif

        if (g_reloadconfig && strcmp(argv[1],"-"))
        {
          g_reloadconfig=0;
          if (!ReadConfig(argv[1]))
            onConfigChange(argc,argv);
	  closeLog();
	  openLog();
        }

        time_t now;
        time(&now);
        if (now >= next_session_update_time)
        {
          m_group->SetLogDir(NULL);

          int len=30; // check every 30 seconds if we aren't logging

          if (g_config_logpath.Get()[0])
          {
            int x;
            for (x = 0; x < m_group->m_users.GetSize() && m_group->m_users.Get(x)->m_auth_state < 1; x ++);

            if (x < m_group->m_users.GetSize())
            {
              WDL_String tmp;

              int cnt=0;
              while (cnt < 16)
              {
                char buf[512];
                struct tm *t=localtime(&now);
                sprintf(buf,"/%04d%02d%02d_%02d%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min);
                if (cnt)
                  wsprintf(buf+strlen(buf),"_%d",cnt);
                strcat(buf,".ninjam");

                tmp.Set(g_config_logpath.Get());
                tmp.Append(buf);

                #ifdef _WIN32
                if (CreateDirectory(tmp.Get(),NULL)) break;
                #else
                if (!mkdir(tmp.Get(),0755)) break;
                #endif

                cnt++;
              }

              if (cnt < 16 )
              {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
                logText("Archiving session '%s'\n",tmp.Get());
#pragma GCC diagnostic pop
                m_group->SetLogDir(tmp.Get());
              }
              else
              {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
                logText("Error creating a session archive directory! Gave up after '%s' failed!\n",tmp.Get());
#pragma GCC diagnostic pop
              }
              // if we succeded, don't check until configured time
              len=g_config_log_sessionlen*60;
              if (len < 60) len=30;
            }

          }
          next_session_update_time=now+len;

        }
      }
    }
  }

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
  logText("Shutting down server\n");
#pragma GCC diagnostic pop

  delete m_group;
  delete m_listener;

  closeLog();

  JNL::close_socketlib();
	return 0;
}

void onConfigChange(int argc, char **argv)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
  logText("reloading config...\n");
#pragma GCC diagnostic pop

  //m_group->SetConfig(g_config_bpi,g_config_bpm);
  enforceACL();
  m_group->SetLicenseText(g_config_license.Get());

  int p;
  for (p = 2; p < argc; p ++)
  {
      if (!strcmp(argv[p],"-pidfile"))
      {
        if (++p >= argc) break;
      //  g_pidfilename.Set(argv[p]);
      }
      else if (!strcmp(argv[p],"-logfile"))
      {
        if (++p >= argc) break;
//        g_logfilename.Set(argv[p]);
      }
      else if (!strcmp(argv[p],"-archive"))
      {
        if (++p >= argc) break;
        g_config_logpath.Set(argv[p]);
      }
      else if (!strcmp(argv[p],"-setuid"))
      {
        if (++p >= argc) break;
  //      g_set_uid=atoi(argv[p]);
      }
      else if (!strcmp(argv[p],"-port"))
      {
        if (++p >= argc) break;
        g_config_port=atoi(argv[p]);
      }
  }

  delete m_listener;
  m_listener = new JNL_Listen(g_config_port);

}


code:
/*
    NINJAM Server - usercon.cpp
    Copyright (C) 2005-2007 Cockos Incorporated

    NINJAM 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.

    NINJAM 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 NINJAM; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*

  This header provides the implementations of User_Group and User_Connection etc.
  These is where the core logic of the server (message routing and upload/download
  management) happens.

*/

#ifdef _WIN32
#include <windows.h>
#else
#include <stdlib.h>
#include <string.h>
#endif

#include <ctype.h>

#include "usercon.h"
#include <ninjam/mpb.h>

#include <WDL/rng.h>
#include <WDL/sha.h>

#ifdef _WIN32
#define strncasecmp strnicmp
#endif

static void guidtostr(unsigned char *guid, char *str)
{
  int x;
  for (x = 0; x < 16; x ++) wsprintf(str+x*2,"%02x",guid[x]);
}

static int is_type_char_valid(int c)
{
  c&=0xff;
  return (c >= 'a' && c <= 'z') ||
         (c >= 'A' && c <= 'Z') ||
         (c >= '0' && c <= '9') ||
         c == ' ' || c == '-' ||
         c == '.' || c == '_';
}

static int is_type_valid(unsigned int t)
{
  return (t&0xff) != ' ' &&
          is_type_char_valid(t>>24) &&
          is_type_char_valid(t>>16) &&
          is_type_char_valid(t>>8) &&
          is_type_char_valid(t);
}

static void type_to_string(unsigned int t, char *out)
{
  if (is_type_valid(t))
  {
    out[0]=(t)&0xff;
    out[1]=(t>>8)&0xff;
    out[2]=(t>>16)&0xff;
    out[3]=' ';//(t>>24)&0xff;
    out[4]=0;
    int x=3;
    while (out[x]==' ' && x > 0) out[x--]=0;
  }
  else *out=0;
}

extern void logText(char *s, ...);

#define MAX_NICK_LEN 128 // not including null term

#define TRANSFER_TIMEOUT 8

User_Connection::User_Connection(JNL_Connection *con, User_Group *grp) : m_auth_state(0), m_clientcaps(0), m_auth_privs(0), m_reserved(0), m_max_channels(0),
      m_vote_bpm(0), m_vote_bpm_lasttime(0), m_vote_bpi(0), m_vote_bpi_lasttime(0)
{
  m_netcon.attach(con);

  WDL_RNG_bytes(m_challenge,sizeof(m_challenge));

  mpb_server_auth_challenge ch;
  memcpy(ch.challenge,m_challenge,sizeof(ch.challenge));

  ch.protocol_version = PROTO_VER_CUR;
  int ka=grp->m_keepalive;

  if (ka < 0)ka=0;
  else if (ka > 255) ka=255;
  ch.server_caps=ka<<8;

  if (grp->m_licensetext.Get()[0])
  {
    m_netcon.SetKeepAlive(45); // wait a max of 45s * 3
    ch.license_agreement=grp->m_licensetext.Get();
  }
  else m_netcon.SetKeepAlive(grp->m_keepalive);
  Send(ch.build());

  time(&m_connect_time);

  m_lookup=0;
}

void User_Connection::Send(Net_Message *msg)
{
  if (m_netcon.Send(msg))
  {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
    logText("Error sending message to user '%s', type %d, queue full!\n",m_username.Get(),msg->get_type());
#pragma GCC diagnostic pop
  }
}

User_Connection::~User_Connection()
{

  int x;
  for (x = 0; x < m_sublist.GetSize(); x ++)
    delete m_sublist.Get(x);
  m_sublist.Empty();
  for (x = 0; x < m_recvfiles.GetSize(); x ++)
    delete m_recvfiles.Get(x);
  m_recvfiles.Empty();
  for (x = 0; x < m_sendfiles.GetSize(); x ++)
    delete m_sendfiles.Get(x);
  m_sendfiles.Empty();

  delete m_lookup;
  m_lookup=0;
}

void User_Connection::SendConfigChangeNotify(int bpm, int bpi)
{
  if (m_auth_state > 0)
  {
    mpb_server_config_change_notify mk;
    mk.beats_interval=bpi;
    mk.beats_minute=bpm;
    Send(mk.build());
  }
}

int User_Connection::OnRunAuth(User_Group *group)
{
  char addrbuf[256];
  JNL::addr_to_ipstr(m_netcon.GetConnection()->get_remote(),addrbuf,sizeof(addrbuf));

  {
    WDL_SHA1 shatmp;

    shatmp.add(m_lookup->sha1buf_user,sizeof(m_lookup->sha1buf_user));

    shatmp.add(m_challenge,sizeof(m_challenge));

    char buf[WDL_SHA1SIZE];
    shatmp.result(buf);

    if ((m_lookup->reqpass && memcmp(buf,m_lookup->sha1buf_request,WDL_SHA1SIZE)) || !m_lookup->user_valid)
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      logText("%s: Refusing user, invalid login/password\n",addrbuf);
#pragma GCC diagnostic pop
      mpb_server_auth_reply bh;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      bh.errmsg="invalid login/password";
#pragma GCC diagnostic pop
      Send(bh.build());
      return 0;
    }
  }

  if (m_lookup->is_status)
  {
    SendUserList(group);

    {
      mpb_server_config_change_notify mk;
      mk.beats_interval=group->m_last_bpi;
      mk.beats_minute=group->m_last_bpm;
      Send(mk.build());
    }

    {
      mpb_chat_message newmsg;
      newmsg.parms[0]="TOPIC";
      newmsg.parms[1]="";
      newmsg.parms[2]=group->m_topictext.Get();
      Send(newmsg.build());
    }

    {
      int cnt=0;
      int user;
      for (user = 0; user < group->m_users.GetSize(); user ++)
      {
        User_Connection *u=group->m_users.Get(user);
        if (u != this && u->m_auth_state > 0 && !(u->m_auth_privs & PRIV_HIDDEN))
          cnt++;
      }
      char buf[64],buf2[64];
      sprintf(buf,"%d",cnt);
      sprintf(buf2,"%d",group->m_max_users);

      mpb_chat_message newmsg;
      newmsg.parms[0]="USERCOUNT";
      newmsg.parms[1]=buf;;
      newmsg.parms[2]=buf2;
      Send(newmsg.build());
    }

    return 0;
  }

  m_auth_privs=m_lookup->privs;
  m_max_channels = m_lookup->max_channels;

  {
    // fix any invalid characters in username
    char *p=m_lookup->username.Get();
    int l=MAX_NICK_LEN;
    while (*p)
    {
      char c=*p;
      if (!isalnum(c) && c != '-' && c != '_' && c != '@' && c != '.') c='_';
      *p++=c;

      if (!--l) *p=0;
    }
  }

  m_username.Set(m_lookup->username.Get());
  // disconnect any user by the same name
  // in allowmulti mode, append -<idx>
  {
    int user=0;
    int uw_pos=0;

    while (user < group->m_users.GetSize())
    {
      User_Connection *u=group->m_users.Get(user);
      if (u != this && !strcasecmp(u->m_username.Get(),m_username.Get()))
      {

        if ((m_auth_privs & PRIV_ALLOWMULTI) && ++uw_pos < 16)
        {
          char buf[64];
          sprintf(buf,".%d",uw_pos+1);
          m_username.Set(m_lookup->username.Get());
          m_username.Append(buf);
          user=0;
          continue; // start over
        }
        else
        {
          delete u;
          group->m_users.Delete(user);
          break;
        }
      }
      user++;
    }
  }

  if (group->m_max_users && !m_reserved && !(m_auth_privs & PRIV_RESERVE))
  {
    int user;
    int cnt=0;
    for (user = 0; user < group->m_users.GetSize(); user ++)
    {
      User_Connection *u=group->m_users.Get(user);
      if (u != this && u->m_auth_state > 0 && !(u->m_auth_privs & PRIV_HIDDEN))
        cnt++;
    }
    if (cnt >= group->m_max_users)
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      logText("%s: Refusing user %s, server full\n",addrbuf,m_username.Get());
#pragma GCC diagnostic pop
      // sorry, gotta kill this connection
      mpb_server_auth_reply bh;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      bh.errmsg="server full";
#pragma GCC diagnostic pop
      Send(bh.build());
      return 0;
    }
  }

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
  logText("%s: Accepted user: %s\n",addrbuf,m_username.Get());
#pragma GCC diagnostic pop

  {
    mpb_server_auth_reply bh;
    bh.flag=1;
    int ch=m_max_channels;
    if (ch > MAX_USER_CHANNELS) ch=MAX_USER_CHANNELS;
    if (ch < 0) ch = 0;

    bh.maxchan = ch;

    bh.errmsg=m_username.Get();
    Send(bh.build());
  }

  m_auth_state=1;

  SendConfigChangeNotify(group->m_last_bpm,group->m_last_bpi);

  SendUserList(group);

  {
    mpb_chat_message newmsg;
    newmsg.parms[0]="TOPIC";
    newmsg.parms[1]="";
    newmsg.parms[2]=group->m_topictext.Get();
    Send(newmsg.build());
  }
  {
    mpb_chat_message newmsg;
    newmsg.parms[0]="JOIN";
    newmsg.parms[1]=m_username.Get();
    group->Broadcast(newmsg.build(),this);
  }

  return 1;
}

// send user list to user
void User_Connection::SendUserList(User_Group *group)
{
  mpb_server_userinfo_change_notify bh;

  int user;
  for (user = 0; user < group->m_users.GetSize(); user++)
  {
    User_Connection *u=group->m_users.Get(user);
    int channel;
    if (u && u->m_auth_state>0 && u != this)
    {
      int acnt=0;
      for (channel = 0; channel < u->m_max_channels && channel < MAX_USER_CHANNELS; channel ++)
      {
        if (u->m_channels[channel].active)
        {
          bh.build_add_rec(1,channel,u->m_channels[channel].volume,u->m_channels[channel].panning,u->m_channels[channel].flags,
                            u->m_username.Get(),u->m_channels[channel].name.Get());
          acnt++;
        }
      }
      if (!acnt && !group->m_allow_hidden_users && u->m_max_channels && !(u->m_auth_privs & PRIV_HIDDEN)) // give users at least one channel
      {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
          bh.build_add_rec(1,0,0,0,0,u->m_username.Get(),"");
#pragma GCC diagnostic pop
      }
    }
  }
  Send(bh.build());
}

int User_Connection::Run(User_Group *group, int *wantsleep)
{
  Net_Message *msg=m_netcon.Run(wantsleep);
  if (m_netcon.GetStatus())
  {
    delete msg;
    return m_netcon.GetStatus();
  }

  if (!msg)
  {
    if (m_auth_state < 0)
    {
      if (!m_lookup || m_lookup->Run())
      {
        if (!m_lookup || !OnRunAuth(group))
        {
          m_netcon.Run();
          m_netcon.Kill();
        }
        delete m_lookup;
        m_lookup=0;
      }
    }
    else if (!m_auth_state)
    {
      if (time(NULL) > m_connect_time+120) // if we haven't gotten an auth reply in 120s, disconnect. The reason this is so long is to give
                                        // the user time to potentially read the license agreement.
      {
        char buf[256];
        JNL::addr_to_ipstr(m_netcon.GetConnection()->get_remote(),buf,sizeof(buf));
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
        logText("%s: Got an authorization timeout\n",buf);
#pragma GCC diagnostic pop
        m_connect_time=time(NULL)+120;
        mpb_server_auth_reply bh;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
        bh.errmsg="authorization timeout";
#pragma GCC diagnostic pop
        Send(bh.build());
        m_netcon.Run();

        m_netcon.Kill();
      }
    }
    return 0;
  }

  msg->addRef();

  if (!m_auth_state)
  {
    mpb_client_auth_user authrep;

    // verify everything
    int          err_st = ( msg->get_type() != MESSAGE_CLIENT_AUTH_USER || authrep.parse(msg) || !authrep.username || !authrep.username[0] ) ? 1 : 0;
    if (!err_st) err_st = ( authrep.client_version < PROTO_VER_MIN || authrep.client_version > PROTO_VER_MAX ) ? 2 : 0;
    if (!err_st) err_st = ( group->m_licensetext.Get()[0] && !(authrep.client_caps & 1) ) ? 3 : 0;

    if (err_st)
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      static char *tab[] = { "invalid authorization reply", "incorrect client version", "license not agreed to" };
#pragma GCC diagnostic pop
      mpb_server_auth_reply bh;
      bh.errmsg=tab[err_st-1];

      char addrbuf[256];
      JNL::addr_to_ipstr(m_netcon.GetConnection()->get_remote(),addrbuf,sizeof(addrbuf));

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
      logText("%s: Refusing user, %s\n",addrbuf,bh.errmsg);
#pragma GCC diagnostic pop

      Send(bh.build());
      m_netcon.Run();

      m_netcon.Kill();
      msg->releaseRef();

      return 0;
    }

    m_netcon.SetKeepAlive(group->m_keepalive); // restore default keepalive behavior since we got a response

    m_clientcaps=authrep.client_caps;

    delete m_lookup;
    m_lookup=group->CreateUserLookup?group->CreateUserLookup(authrep.username):NULL;

    if (m_lookup)
    {
      char addrbuf[256];
      JNL::addr_to_ipstr(m_netcon.GetConnection()->get_remote(),addrbuf,sizeof(addrbuf));
      m_lookup->hostmask.Set(addrbuf);
      memcpy(m_lookup->sha1buf_request,authrep.passhash,sizeof(m_lookup->sha1buf_request));
    }

    m_auth_state=-1;

  } // !m_auth_state

  if (m_auth_state > 0)
  {
    switch (msg->get_type())
    {
      case MESSAGE_CLIENT_SET_CHANNEL_INFO:
        {
          mpb_client_set_channel_info chi;
          if (!chi.parse(msg))
          {
            // update our channel list

            mpb_server_userinfo_change_notify mfmt;
            int mfmt_changes=0;

            int offs=0;
            short v;
            int p,f;
            int whichch=0;
            char *chnp=0;
            while ((offs=chi.parse_get_rec(offs,&chnp,&v,&p,&f))>0 && whichch < MAX_USER_CHANNELS && whichch < m_max_channels)
            {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
              if (!chnp) chnp="";
#pragma GCC diagnostic pop

              int doactive=!(f&0x80);

              // only if something changes, do we add it to the rec
              int hadch=!m_channels[whichch].active != !doactive;
              if (!hadch) hadch = strcmp(chnp,m_channels[whichch].name.Get());
              if (!hadch) hadch = m_channels[whichch].volume!=v;
              if (!hadch) hadch = m_channels[whichch].panning!=p;
              if (!hadch) hadch = m_channels[whichch].flags!=f;

              m_channels[whichch].active=doactive;
              m_channels[whichch].name.Set(chnp);
              m_channels[whichch].volume=v;
              m_channels[whichch].panning=p;
              m_channels[whichch].flags=f;

              if (hadch)
              {
                mfmt_changes++;
                mfmt.build_add_rec(m_channels[whichch].active,whichch,
                                  m_channels[whichch].volume,
                                  m_channels[whichch].panning,
                                  m_channels[whichch].flags,
                                  m_username.Get(),
                                  m_channels[whichch].name.Get());
              }

              whichch++;
            }
            while (whichch < MAX_USER_CHANNELS)
            {
              m_channels[whichch].name.Set("");

              if (m_channels[whichch].active) // only send deactivate if it was previously active
              {
                m_channels[whichch].active=0;
                mfmt_changes++;
                mfmt.build_add_rec(0,whichch,
                                  m_channels[whichch].volume,
                                  m_channels[whichch].panning,
                                  m_channels[whichch].flags,
                                  m_username.Get(),
                                  m_channels[whichch].name.Get());
              }

              whichch++;
            }
            if (!group->m_allow_hidden_users && m_max_channels && !(m_auth_privs&PRIV_HIDDEN))
            {
              for (whichch = 0; whichch < MAX_USER_CHANNELS && !m_channels[whichch].active; whichch ++);

              if (whichch == MAX_USER_CHANNELS) // if empty, tell the user about one channel
              {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
                mfmt.build_add_rec(1,0,0,0,0,m_username.Get(),"");
#pragma GCC diagnostic pop
                mfmt_changes++;
              }
            }

            if (mfmt_changes) group->Broadcast(mfmt.build(),this);
          }
        }
      break;
      case MESSAGE_CLIENT_SET_USERMASK:
        {
          mpb_client_set_usermask umi;
          if (!umi.parse(msg))
          {
            int offs=0;
            char *unp=0;
            unsigned int fla=0;
            while ((offs=umi.parse_get_rec(offs,&unp,&fla))>0)
            {
              if (unp)
              {
                int x;
                for (x = 0; x < m_sublist.GetSize() && strcasecmp(unp,m_sublist.Get(x)->username.Get()); x ++);
                if (x == m_sublist.GetSize()) // add new
                {
                  if (fla) // only add if we need to subscribe
                  {
                    User_SubscribeMask *n=new User_SubscribeMask;
                    n->username.Set(unp);
                    n->channelmask = fla;
                    m_sublist.Add(n);
                  }
                }
                else
                {
                  if (fla) // update flag
                  {
                    m_sublist.Get(x)->channelmask=fla;
                  }
                  else // remove
                  {
                    delete m_sublist.Get(x);
                    m_sublist.Delete(x);
                  }
                }
              }
            }
          }
        }
      break;
      case MESSAGE_CLIENT_UPLOAD_INTERVAL_BEGIN:
        {
          mpb_client_upload_interval_begin mp;
          if (!mp.parse(msg) && mp.chidx < m_max_channels)
          {
            char *myusername=m_username.Get();

            mpb_server_download_interval_begin nmb;
            nmb.chidx=mp.chidx;
            nmb.estsize=mp.estsize;
            nmb.fourcc=mp.fourcc;
            memcpy(nmb.guid,mp.guid,sizeof(nmb.guid));
            nmb.username = myusername;

            Net_Message *newmsg=nmb.build();
            newmsg->addRef();

            static unsigned char zero_guid[16];

            if (mp.fourcc && memcmp(mp.guid,zero_guid,sizeof(zero_guid))) // zero = silence, so simply rebroadcast
            {
              User_TransferState *newrecv=new User_TransferState;
              newrecv->bytes_estimated=mp.estsize;
              newrecv->fourcc=mp.fourcc;
              memcpy(newrecv->guid,mp.guid,sizeof(newrecv->guid));

              if (group->m_logdir.Get()[0])
              {
                char fn[512];
                char guidstr[64];
                guidtostr(mp.guid,guidstr);

                char ext[8];
                type_to_string(mp.fourcc,ext);
                sprintf(fn,"%c/%s.%s",guidstr[0],guidstr,ext);

                WDL_String tmp(group->m_logdir.Get());
                tmp.Append(fn);

                newrecv->fp = fopen(tmp.Get(),"wb");

                if (group->m_logfp)
                {
                  // decide when to write new interval
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
                  char *chn="?";
#pragma GCC diagnostic pop
                  if (mp.chidx >= 0 && mp.chidx < MAX_USER_CHANNELS) chn=m_channels[mp.chidx].name.Get();
                  fprintf(group->m_logfp,"user %s \"%s\" %d \"%s\"\n",guidstr,myusername,mp.chidx,chn);
                }
              }

              m_recvfiles.Add(newrecv);
            }

            int user;
            for (user=0;user<group->m_users.GetSize(); user++)
            {
              User_Connection *u=group->m_users.Get(user);
              if (u && u != this)
              {
                int i;
                for (i=0; i < u->m_sublist.GetSize(); i ++)
                {
                  User_SubscribeMask *sm=u->m_sublist.Get(i);
                  if (!strcasecmp(sm->username.Get(),myusername))
                  {
                    if (sm->channelmask & (1<<mp.chidx))
                    {
                      if (memcmp(mp.guid,zero_guid,sizeof(zero_guid))) // zero = silence, so simply rebroadcast
                      {
                        // add entry in send list
                        User_TransferState *nt=new User_TransferState;
                        memcpy(nt->guid,mp.guid,sizeof(nt->guid));
                        nt->bytes_estimated = mp.estsize;
                        nt->fourcc = mp.fourcc;
                        u->m_sendfiles.Add(nt);
                      }

                      u->Send(newmsg);
                    }
                    break;
                  }
                }
              }
            }
            newmsg->releaseRef();
          }
        }
        //m_recvfiles
      break;
      case MESSAGE_CLIENT_UPLOAD_INTERVAL_WRITE:
        {
          mpb_client_upload_interval_write mp;
          if (!mp.parse(msg))
          {
            time_t now;
            time(&now);
            msg->set_type(MESSAGE_SERVER_DOWNLOAD_INTERVAL_WRITE); // we rely on the fact that the upload/download write messages are identical
                                                                   // though we may need to update this at a later date if we change things.

            int user,x;

            for (x = 0; x < m_recvfiles.GetSize(); x ++)
            {
              User_TransferState *t=m_recvfiles.Get(x);
              if (!memcmp(t->guid,mp.guid,sizeof(mp.guid)))
              {
                t->last_acttime=now;

                if (t->fp) fwrite(mp.audio_data,1,mp.audio_data_len,t->fp);

                t->bytes_sofar+=mp.audio_data_len;
                if (mp.flags & 1)
                {
                  delete t;
                  m_recvfiles.Delete(x);
                }
                break;
              }
              if (now-t->last_acttime > TRANSFER_TIMEOUT)
              {
                delete t;
                m_recvfiles.Delete(x--);
              }
            }

            for (user=0;user<group->m_users.GetSize(); user++)
            {
              User_Connection *u=group->m_users.Get(user);
              if (u && u != this)
              {
                int i;
                for (i=0; i < u->m_sendfiles.GetSize(); i ++)
                {
                  User_TransferState *t=u->m_sendfiles.Get(i);
                  if (t && !memcmp(t->guid,mp.guid,sizeof(t->guid)))
                  {
                    t->last_acttime=now;
                    t->bytes_sofar += mp.audio_data_len;
                    u->Send(msg);
                    if (mp.flags & 1)
                    {
                      delete t;
                      u->m_sendfiles.Delete(i);
                      // remove from transfer list
                    }
                    break;
                  }
                  if (now-t->last_acttime > TRANSFER_TIMEOUT)
                  {
                    delete t;
                    u->m_sendfiles.Delete(i--);
                  }
                }
              }
            }
          }
        }
      break;

      case MESSAGE_CHAT_MESSAGE:
        {
          mpb_chat_message poo;
          if (!poo.parse(msg))
          {
            group->onChatMessage(this,&poo);
          }
        }
      break;

      default:
      break;
    }
  } // m_auth_state > 0
  msg->releaseRef();

  return 0;
}

User_Group::User_Group() : m_max_users(0), m_last_bpm(120), m_last_bpi(32), m_keepalive(0),
  m_voting_threshold(110), m_voting_timeout(120),
  m_loopcnt(0), m_run_robin(0), m_allow_hidden_users(0), m_logfp(0)
{
  CreateUserLookup=0;
  memset(&m_next_loop_time,0,sizeof(m_next_loop_time));
}

User_Group::~User_Group()
{
  int x;
  for (x = 0; x < m_users.GetSize(); x ++)
  {
    delete m_users.Get(x);
  }
  m_users.Empty();
  if (m_logfp) fclose(m_logfp);
  m_logfp=0;
}

void User_Group::SetLogDir(char *path) // NULL to not log
{
  m_loopcnt=0;
  if (!path || !*path)
  {
    if (m_logfp) fclose(m_logfp);
    m_logfp=0;
    m_logdir.Set("");
    return;
  }

#ifdef _WIN32
    CreateDirectory(path,NULL);
#else
    mkdir(path,0755);
#endif

  m_logdir.Set(path);
  m_logdir.Append("/");

  WDL_String cl(path);
  cl.Append("/clipsort.log");
  m_logfp=fopen(cl.Get(),"at");

  int a;
  for (a = 0; a < 16; a ++)
  {
    WDL_String tmp(path);
    char buf[5];
    sprintf(buf,"/%x",a);
    tmp.Append(buf);
#ifdef _WIN32
    CreateDirectory(tmp.Get(),NULL);
#else
    mkdir(tmp.Get(),0755);
#endif
  }
}

void User_Group::Broadcast(Net_Message *msg, User_Connection *nosend)
{
  if (msg)
  {
    msg->addRef(); // we do this so that if nobody actually gets to send it, we delete it

    int x;
    for (x = 0; x < m_users.GetSize(); x ++)
    {
      User_Connection *p=m_users.Get(x);
      if (p && p->m_auth_state > 0 && p != nosend)
      {
        p->Send(msg);
      }
    }

    msg->releaseRef();
  }
}

int User_Group::Run()
{
    int wantsleep=1;
    int x;

    // track bpm/bpi stuff
#ifdef _WIN32
    DWORD now=GetTickCount();
    if (now >= m_next_loop_time)
    {
      m_next_loop_time = now + (60*1000*m_last_bpi) / (m_last_bpm?m_last_bpm:120);
#else
    struct timeval now;
    gettimeofday(&now,NULL);
    if (now.tv_sec > m_next_loop_time.tv_sec ||
        (now.tv_sec == m_next_loop_time.tv_sec && now.tv_usec >= m_next_loop_time.tv_usec))
    {
      int len_ms = ((60*1000*m_last_bpi) / (m_last_bpm?m_last_bpm:120));
      int len_s = len_ms/1000;
      len_ms %= 1000;
      m_next_loop_time.tv_sec = now.tv_sec + len_s;
      m_next_loop_time.tv_usec = now.tv_usec + len_ms*1000;
      if (m_next_loop_time.tv_usec >= 1000000)
      {
        m_next_loop_time.tv_sec += 1;
        m_next_loop_time.tv_usec -= 1000000;
      }
#endif

      m_loopcnt++;
      if (m_logfp)
      {
        fprintf(m_logfp,"interval %d %d %d\n",m_loopcnt,m_last_bpm,m_last_bpi);
        fflush(m_logfp);
      }
    }

    for (x = 0; x < m_users.GetSize(); x ++)
    {
      int thispos=(x+m_run_robin)%m_users.GetSize();
      User_Connection *p=m_users.Get(thispos);
      if (p)
      {
        int ret=p->Run(this,&wantsleep);
        if (ret)
        {
          // broadcast to other users that this user is no longer present
          if (p->m_auth_state>0)
          {
            mpb_chat_message newmsg;
            newmsg.parms[0]="PART";
            newmsg.parms[1]=p->m_username.Get();
            Broadcast(newmsg.build(),p);

            mpb_server_userinfo_change_notify mfmt;
            int mfmt_changes=0;

            int whichch=0;
            while (whichch < MAX_USER_CHANNELS)
            {
              p->m_channels[whichch].name.Set("");

              if (!whichch || p->m_channels[whichch].active) // only send deactivate if it was previously active
              {
                p->m_channels[whichch].active=0;
                mfmt_changes++;
                mfmt.build_add_rec(0,whichch,
                                  p->m_channels[whichch].volume,
                                  p->m_channels[whichch].panning,
                                  p->m_channels[whichch].flags,
                                  p->m_username.Get(),
                                  p->m_channels[whichch].name.Get());
              }

              whichch++;
            }

            if (mfmt_changes) Broadcast(mfmt.build(),p);
          }

          char addrbuf[256];
          JNL::addr_to_ipstr(p->m_netcon.GetConnection()->get_remote(),addrbuf,sizeof(addrbuf));
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
          logText("%s: disconnected (username:'%s', code=%d)\n",addrbuf,p->m_auth_state>0?p->m_username.Get():"",ret);
#pragma GCC diagnostic pop

          delete p;
          m_users.Delete(thispos);
          x--;
        }
      }
    }
    m_run_robin++;

    return wantsleep;
}

void User_Group::SetConfig(int bpi, int bpm)
{
  m_last_bpi=bpi;
  m_last_bpm=bpm;
  mpb_server_config_change_notify mk;
  mk.beats_interval=bpi;
  mk.beats_minute=bpm;
  Broadcast(mk.build());
}

void User_Group::AddConnection(JNL_Connection *con, int isres)
{
  User_Connection *p=new User_Connection(con,this);
  if (isres) p->m_reserved=1;
  m_users.Add(p);
}

void User_Group::onChatMessage(User_Connection *con, mpb_chat_message *msg)
{
  if (!strcmp(msg->parms[0],"MSG")) // chat message
  {
    WDL_PtrList<Net_Message> need_bcast;
    if (msg->parms[1] && !strncmp(msg->parms[1],"!vote",5)) // chat message
    {
      if (!(con->m_auth_privs & PRIV_VOTE) || m_voting_threshold > 100 || m_voting_threshold < 1)
      {
        mpb_chat_message newmsg;
        newmsg.parms[0]="MSG";
        newmsg.parms[1]="";
        newmsg.parms[2]=(char *)(m_voting_threshold > 100 || m_voting_threshold < 1? "[voting system] Voting not enabled" : "[voting system] No vote permission");
        con->Send(newmsg.build());
        return;
      }
      const char *p=msg->parms[1];
      while (*p && *p != ' ') p++;
      while (*p == ' ') p++;
      const char *pn=p;
      while (*p && *p != ' ') p++;
      while (*p == ' ') p++;

      if (*p && !strncmp(pn,"bpm",3) && atoi(p) >= MIN_BPM && atoi(p) <= MAX_BPM)
      {
        con->m_vote_bpm=atoi(p);
        con->m_vote_bpm_lasttime=time(NULL);
      }
      else if (*p && !strncmp(pn,"bpi",3) && atoi(p) >= MIN_BPI && atoi(p) <= MAX_BPI)
      {
        con->m_vote_bpi=atoi(p);
        con->m_vote_bpi_lasttime=time(NULL);
      }
      else
      {
        mpb_chat_message newmsg;
        newmsg.parms[0]="MSG";
        newmsg.parms[1]="";
        newmsg.parms[2]="[voting system] !vote requires <bpm|bpi> <value> parameters";
        con->Send(newmsg.build());
        return;
      }
      // print voting stats
      {
        int bpis[1+MAX_BPI-MIN_BPI]={0,};
        int bpms[1+MAX_BPM-MIN_BPM]={0,};
        int x;
        int maxbpi=0, maxbpm=0;
        int vucnt=0;

        time_t now=time(NULL);
        for (x = 0; x < m_users.GetSize(); x ++)
        {
          User_Connection *p=m_users.Get(x);
          if (p->m_auth_state<=0) continue;
          if (!(p->m_auth_privs & PRIV_HIDDEN)) vucnt++;
          if (p->m_vote_bpi_lasttime >= now-m_voting_timeout && p->m_vote_bpi >= MIN_BPI && p->m_vote_bpi <= MAX_BPI)
          {
              int v=++bpis[p->m_vote_bpi-MIN_BPI];
              if (v > bpis[maxbpi]) maxbpi=p->m_vote_bpi-MIN_BPI;
          }
          if (p->m_vote_bpm_lasttime >= now-m_voting_timeout && p->m_vote_bpm >= MIN_BPM && p->m_vote_bpm <= MAX_BPM)
          {
              int v=++bpms[p->m_vote_bpm-MIN_BPM];
              if (v > bpms[maxbpm]) maxbpm=p->m_vote_bpm-MIN_BPM;
          }
        }
        if (bpms[maxbpm] > 0 && !strncmp(pn,"bpm",3))
        {
          if (bpms[maxbpm] >= (vucnt * m_voting_threshold + 50)/100)
          {
            m_last_bpm=maxbpm+MIN_BPM;
            char buf[512];
            sprintf(buf,"[voting system] setting BPM to %d",m_last_bpm);

            mpb_chat_message newmsg;
            newmsg.parms[0]="MSG";
            newmsg.parms[1]="";
            newmsg.parms[2]=buf;
            need_bcast.Add(newmsg.build());

            mpb_server_config_change_notify mk;
            mk.beats_interval=m_last_bpi;
            mk.beats_minute=m_last_bpm;
            need_bcast.Add(mk.build());
            for (x = 0; x < m_users.GetSize(); x ++)
              m_users.Get(x)->m_vote_bpm_lasttime = 0;
          }
          else
          {
            char buf[512];
            sprintf(buf,"[voting system] leading candidate: %d/%d votes for %d BPM [each vote expires in %ds]",bpms[maxbpm],(vucnt * m_voting_threshold + 50)/100,maxbpm+MIN_BPM,m_voting_timeout);

            mpb_chat_message newmsg;
            newmsg.parms[0]="MSG";
            newmsg.parms[1]="";
            newmsg.parms[2]=buf;
            need_bcast.Add(newmsg.build());
          }
        }
        if (bpis[maxbpi] > 0 && !strncmp(pn,"bpi",3))
        {
          if (bpis[maxbpi] >= (vucnt * m_voting_threshold + 50)/100)
          {
            m_last_bpi=maxbpi+MIN_BPI;
            char buf[512];
            sprintf(buf,"[voting system] setting BPI to %d",m_last_bpi);

            mpb_chat_message newmsg;
            newmsg.parms[0]="MSG";
            newmsg.parms[1]="";
            newmsg.parms[2]=buf;
            need_bcast.Add(newmsg.build());

            mpb_server_config_change_notify mk;
            mk.beats_interval=m_last_bpi;
            mk.beats_minute=m_last_bpm;
            need_bcast.Add(mk.build());
            for (x = 0; x < m_users.GetSize(); x ++)
              m_users.Get(x)->m_vote_bpi_lasttime = 0;
          }
          else
          {
            char buf[512];
            sprintf(buf,"[voting system] leading candidate: %d/%d votes for %d BPI [each vote expires in %ds]",bpis[maxbpi],(vucnt * m_voting_threshold + 50)/100,maxbpi+MIN_BPI,m_voting_timeout);

            mpb_chat_message newmsg;
            newmsg.parms[0]="MSG";
            newmsg.parms[1]="";
            newmsg.parms[2]=buf;
            need_bcast.Add(newmsg.build());
          }
        }
      }

    }

    if (!(con->m_auth_privs & PRIV_CHATSEND))
    {
      mpb_chat_message newmsg;
      newmsg.parms[0]="MSG";
      newmsg.parms[1]="";
      newmsg.parms[2]="No MSG permission";
      con->Send(newmsg.build());
    }
    else if (msg->parms[1] && *msg->parms[1])
    {
      mpb_chat_message newmsg;
      newmsg.parms[0]="MSG";
      newmsg.parms[1]=con->m_username.Get();
      newmsg.parms[2]=msg->parms[1];
      Broadcast(newmsg.build());
    }
    int x;
    for (x = 0; x < need_bcast.GetSize(); x ++)
      Broadcast(need_bcast.Get(x));
    need_bcast.Empty();
  }
  else if (!strcmp(msg->parms[0],"SESSION")) // session block descriptor message
  {
    mpb_chat_message newmsg;
    newmsg.parms[0]="SESSION";
    newmsg.parms[1]=con->m_username.Get();
    newmsg.parms[2]=msg->parms[1]; // guid
    newmsg.parms[3]=msg->parms[2]; // index
    newmsg.parms[4]=msg->parms[3]; // offset, length
    Broadcast(newmsg.build(),con);
  }
  else if (!strcmp(msg->parms[0],"PRIVMSG")) // chat message
  {
    if (!(con->m_auth_privs & PRIV_CHATSEND))
    {
      mpb_chat_message newmsg;
      newmsg.parms[0]="MSG";
      newmsg.parms[1]="";
      newmsg.parms[2]="No PRIVMSG permission";
      con->Send(newmsg.build());
    }
    else if (msg->parms[1] && *msg->parms[1] && msg->parms[2] && *msg->parms[2])
    {
      // send a privmsg to user in parm1, and if they don't
      int x;
      for (x = 0; x < m_users.GetSize(); x ++)
      {
        if (!strcasecmp(msg->parms[1],m_users.Get(x)->m_username.Get()))
        {
          mpb_chat_message newmsg;
          newmsg.parms[0]="PRIVMSG";
          newmsg.parms[1]=con->m_username.Get();
          newmsg.parms[2]=msg->parms[2];
          m_users.Get(x)->Send(newmsg.build());

          return;
        }
      }

      // send a privmsg back to sender, saying shit aint there
      WDL_String buf("No such user: ");
      buf.Append(msg->parms[1]);
      mpb_chat_message newmsg;
      newmsg.parms[0]="MSG";
      newmsg.parms[1]="";
      newmsg.parms[2]=buf.Get();
      con->Send(newmsg.build());
    }
  }
  else if (!strcmp(msg->parms[0],"ADMIN")) // admin message
  {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wwrite-strings"
    char *adminerr="ADMIN requires valid parameter, i.e. topic, kick, bpm, bpi";
#pragma GCC diagnostic pop
    if (msg->parms[1] && *msg->parms[1])
    {
      if (!strncasecmp(msg->parms[1],"topic ",6))
      {
        if (!(con->m_auth_privs & PRIV_TOPIC))
        {
          mpb_chat_message newmsg;
          newmsg.parms[0]="MSG";
          newmsg.parms[1]="";
          newmsg.parms[2]="No TOPIC permission";
          con->Send(newmsg.build());
        }
        else
        {
          // set topic, notify everybody of topic change
          const char *p=msg->parms[1]+6;
          while (*p == ' ') p++;
          if (*p)
          {
            m_topictext.Set(p);
            mpb_chat_message newmsg;
            newmsg.parms[0]="TOPIC";
            newmsg.parms[1]=con->m_username.Get();
            newmsg.parms[2]=m_topictext.Get();
            Broadcast(newmsg.build());
          }
        }

      }
      else if (!strncasecmp(msg->parms[1],"kick ",5))
      {
        if (!(con->m_auth_privs & PRIV_KICK))
        {
          mpb_chat_message newmsg;
          newmsg.parms[0]="MSG";
          newmsg.parms[1]="";
          newmsg.parms[2]="No KICK permission";
          con->Send(newmsg.build());
        }
        else
        {
          // set topic, notify everybody of topic change
          const char *p=msg->parms[1]+5;
          while (*p == ' ') p++;
          if (*p)
          {
            // try to kick user
            int x;
            int killcnt=0;
            int pl=strlen(p);
            for (x = 0; x < m_users.GetSize(); x ++)
            {
              User_Connection *c=m_users.Get(x);
              if ((p[pl-1] == '*' && !strncasecmp(c->m_username.Get(),p,pl-1)) || !strcasecmp(c->m_username.Get(),p))
              {
                if (c != con)
                {
                  WDL_String buf("User ");
                  buf.Append(c->m_username.Get());
                  buf.Append(" kicked by ");
                  buf.Append(con->m_username.Get());

                  mpb_chat_message newmsg;
                  newmsg.parms[0]="MSG";
                  newmsg.parms[1]="";
                  newmsg.parms[2]=buf.Get();
                  Broadcast(newmsg.build());

                  c->m_netcon.Kill();
                }
                killcnt++;
              }
            }
            if (!killcnt)
            {
              WDL_String tmp;
              tmp.Set("User \"");
              tmp.Append(p);
              tmp.Append("\" not found!\n");

              mpb_chat_message newmsg;
              newmsg.parms[0]="MSG";
              newmsg.parms[1]="";
              newmsg.parms[2]=tmp.Get();
              con->Send(newmsg.build());
            }

          }
        }

      }
      else if (!strncasecmp(msg->parms[1],"bpm ",4) || !strncasecmp(msg->parms[1],"bpi ",4))
      {
        if (!(con->m_auth_privs & PRIV_BPM))
        {
          mpb_chat_message newmsg;
          newmsg.parms[0]="MSG";
          newmsg.parms[1]="";
          newmsg.parms[2]="No BPM/BPI permission";
          con->Send(newmsg.build());
        }
        else
        {
          int isbpm=tolower(msg->parms[1][2])=='m';

          const char *p=msg->parms[1]+4;
          while (*p == ' ') p++;
          int v=atoi(p);
          if (isbpm && (v < 20 || v > 400))
          {
            mpb_chat_message newmsg;
            newmsg.parms[0]="MSG";
            newmsg.parms[1]="";
            newmsg.parms[2]="BPM parameter must be between 20 and 400";
            con->Send(newmsg.build());
          }
          else if (!isbpm && (v < 2 || v > 1024))
          {
            mpb_chat_message newmsg;
            newmsg.parms[0]="MSG";
            newmsg.parms[1]="";
            newmsg.parms[2]="BPI parameter must be between 2 and 1024";
            con->Send(newmsg.build());
          }
          else
          {
            if (isbpm) m_last_bpm=v;
            else m_last_bpi=v;

            mpb_server_config_change_notify mk;
            mk.beats_interval=m_last_bpi;
            mk.beats_minute=m_last_bpm;
            Broadcast(mk.build());

            WDL_String str(con->m_username.Get());
            str.Append(" sets ");
            if (isbpm) str.Append("BPM"); else str.Append("BPI");
            str.Append(" to ");
            char buf[64];
            sprintf(buf,"%d",v);
            str.Append(buf);

            mpb_chat_message newmsg;
            newmsg.parms[0]="MSG";
            newmsg.parms[1]="";
            newmsg.parms[2]=str.Get();
            Broadcast(newmsg.build());
          }
        }

      }
      else
      {
        mpb_chat_message newmsg;
        newmsg.parms[0]="MSG";
        newmsg.parms[1]="";
        newmsg.parms[2]=adminerr;
        con->Send(newmsg.build());
      }
    }
    else
    {
      mpb_chat_message newmsg;
      newmsg.parms[0]="MSG";
      newmsg.parms[1]="";
      newmsg.parms[2]=adminerr;
      con->Send(newmsg.build());
    }
  }
  else // unknown message
  {
  }
}

repo_name: ninjam/libninjam

license: mit


code:
/*
    NINJAM Server - usercon.h
    Copyright (C) 2005-2007 Cockos Incorporated

    NINJAM 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.

    NINJAM 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 NINJAM; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*

  This header provides the class declarations for the User_Group (which the server uses)
  and other classes it is built upon. Additionally, it provides the abstract declaration
  of IUserInfoLookup, which the server needs to override to provide user authentication
  lookup.

*/

#ifndef _USERCON_H_
#define _USERCON_H_

#include <ninjam/netmsg.h>
#include <WDL/string.h>
#include <WDL/sha.h>
#include <WDL/ptrlist.h>
#include <ninjam/mpb.h>

#define MAX_USER_CHANNELS 32
#define MAX_USERS 64
#define MAX_UPLOADS 32
#define MAX_DOWNLOADS 128

#define PRIV_TOPIC 1
#define PRIV_CHATSEND 2
#define PRIV_BPM 4
#define PRIV_KICK 8
#define PRIV_RESERVE 16
#define PRIV_ALLOWMULTI 32 // allows multiple users by the same name (subsequent users append -X to them)
#define PRIV_HIDDEN 64   // hidden user, doesn't count for a slot, too
#define PRIV_VOTE 128

#define MAX_BPM 400
#define MAX_BPI 64
#define MIN_BPM 40
#define MIN_BPI 2

class IUserInfoLookup // abstract base class, overridden by server
{
public:
  IUserInfoLookup() { is_status=0; user_valid=0; reqpass=1; privs=0; max_channels=0; }
  virtual ~IUserInfoLookup() { }

  virtual int Run()=0; // return 1 if run is complete, 0 if still needs to run more

  int user_valid; // 1 if valid
  int reqpass; // password required, 1 is default
  unsigned int privs;
  int max_channels;
  int is_status;
  unsigned char sha1buf_user[WDL_SHA1SIZE];

  WDL_String hostmask;
  WDL_String username; // can modify this to change the username

  unsigned char sha1buf_request[WDL_SHA1SIZE]; // don't use, internal for User_Connection
};

class User_Connection;

class User_Group
{
  public:
    User_Group();
    ~User_Group();

    void AddConnection(JNL_Connection *con, int isres=0);

    int Run(); // return 1 if safe to sleep
    void SetConfig(int bpi, int bpm);
    void SetLicenseText(char *text) { m_licensetext.Set(text); }
    void Broadcast(Net_Message *msg, User_Connection *nosend=0);

    void SetLogDir(char *path); // NULL to not log

    // sends a message to the people subscribing to a channel of a user
    void BroadcastToSubs(Net_Message *msg, User_Connection *src, int channel);

    IUserInfoLookup *(*CreateUserLookup)(char *username);

    void onChatMessage(User_Connection *con, mpb_chat_message *msg);

    WDL_PtrList<User_Connection> m_users;

    int m_max_users;
    int m_last_bpm, m_last_bpi;
    int m_keepalive;

    int m_voting_threshold; // can be 1-100, or >100 to disable
    int m_voting_timeout; // seconds

    int m_loopcnt;

    unsigned int m_run_robin;

    int m_allow_hidden_users;

    WDL_String m_licensetext;
    WDL_String m_topictext;

    WDL_String m_logdir;
    FILE *m_logfp;

#ifdef _WIN32
    DWORD m_next_loop_time;
#else
    struct timeval m_next_loop_time;
#endif
};

class User_SubscribeMask
{
public:
  User_SubscribeMask()  {}
  ~User_SubscribeMask() {}
  WDL_String username;
  unsigned int channelmask;
};

class User_Channel
{
public:
  User_Channel() { active=0; volume=0; panning=128; flags=0; }
  ~User_Channel() { }
  int active;
  WDL_String name;
  int volume;
  int panning;
  int flags;
};

class User_TransferState
{
public:
  User_TransferState() : fourcc(0), bytes_estimated(0), bytes_sofar(0), fp(0)
  {
    time(&last_acttime);
    memset(guid,0,sizeof(guid));
  }
  ~User_TransferState()
  {
    if (fp) fclose(fp);
    fp=0;
  }

  time_t last_acttime;
  unsigned char guid[16];
  unsigned int fourcc;
  unsigned int bytes_estimated;

  unsigned int bytes_sofar;

  FILE *fp;
};

class User_Connection
{
  public:
    User_Connection(JNL_Connection *con, User_Group *grp);
    ~User_Connection();

    int Run(User_Group *group, int *wantsleep=0); // returns 1 if disconnected, -1 if error in data. 0 if ok.
    void SendConfigChangeNotify(int bpm, int bpi);

    void Send(Net_Message *msg);

    int OnRunAuth(User_Group *group);

    void SendUserList(User_Group *group);

    Net_Connection m_netcon;
    WDL_String m_username;

    // auth info
    time_t m_connect_time;
    int m_auth_state;      // 1 if authorized, 0 if not yet, -1 if auth pending
    unsigned char m_challenge[8];
    int m_clientcaps;

    int m_auth_privs;

    int m_reserved;

    int m_max_channels;

    int m_vote_bpm;
    time_t m_vote_bpm_lasttime;
    int m_vote_bpi;
    time_t m_vote_bpi_lasttime;

    User_Channel m_channels[MAX_USER_CHANNELS];

    WDL_PtrList<User_SubscribeMask> m_sublist; // people+channels we subscribe to

    WDL_PtrList<User_TransferState> m_recvfiles;
    WDL_PtrList<User_TransferState> m_sendfiles;

    IUserInfoLookup *m_lookup;
};

#endif//_USERCON_H_


code:
<?

require_once("njproto.php");

function show_status($host, $port, $user, $pass)
{
  $fp = fsockopen($host, $port, $errno,$errstr,30);
  if (!$fp) {  echo "$errstr ($errno)
\n"; return; }

  $poo = nj_getmsg($fp);
  if ($poo && $poo['type'] == 0) // this is a challenge/license message
  {
    $res = nj_parseauthmsg($poo['data']);
    if ($res)
    {
      if ($res['license'])
      {
      echo "Server license:
\n" . $res['license'] . "\n\n
";
      }
      echo "Querying auth...
";
      nj_sendauthmsg($fp, $user, $pass, $res['challenge']);

      while (!feof($fp))
      {
        $poo=nj_getmsg($fp);
        if (!$poo) break;

        if ($poo['type'] == 1)
        {
          echo "invalid login to server!
";
          break;
        }

        switch ($poo['type'])
        {
          case 192: // chat message
            $ret=nj_parsechatmsg($poo['data']);
            if ($ret && !strcasecmp($ret[0],"TOPIC"))
            {
               if ($ret[2] != "") echo "Topic: '" .  $ret[2] . "'
";
            }
          break;
          case 2: // beat change etc
        $ret=nj_parsebpmbpimsg($poo['data']);
            if ($ret)
            {
              echo "BPM: " . $ret['bpm'] . ", BPI: " . $ret['bpi'] . "
";
            }
          break;
          case 3:
            $ret = nj_parseuserinfolist($poo['data']);
            if ($ret)
            {
              reset($ret);
              echo count($ret) . " users:
\n";
              while ($f = key($ret))
              {
                $p=$ret[$f];
                $cnt=$p['cnt'];
                echo "  $f ($cnt channels)
\n";
                if (0) for ($x = 0; $x < $cnt; $x ++)
                {
                  echo "  channel " . ($p[$x]['id']|0) . " '" . $p[$x]['name'] . "'
\n";
                }
                next($ret);
              }
            }
            else echo "Empty server
\n";
          break;
          default:
            printf("got unknown message, type=%d, len=%d
",$poo['type'],$poo['length']);
          break;
        }
      }
    }
    else echo "invalid auth challenge message\n";
  }
  else echo "didn't get a valid message from server!\n";

  fclose($fp);
}

$user="user"; // set up the server with a l/p for your status user
$pass="pass";
$port=2051;
$host="localhost";

show_status($host,$port,$user,$pass);

?>


code:
<?
/// messaging code

function nj_sendmsg($fp, $type, $data)
{
  $len = strlen($data);
  $hdr = chr($type&0xff);
  $hdr .= chr($len&0xff);
  $hdr .= chr(($len>>8)&0xff);
  $hdr .= chr(($len>>16)&0xff);
  $hdr .= chr(($len>>24)&0xff);
  fwrite($fp,$hdr);

  fwrite($fp,$data);
}

function nj_getmsg($fp)
{
  $buf=fread($fp,5);
  if (strlen($buf) != 5) return 0;

  $ret['type']=ord($buf);
  $len = ord(substr($buf,1));
  $len |= ord(substr($buf,2)) << 8;
  $len |= ord(substr($buf,3)) << 16;
  $len |= ord(substr($buf,4)) << 24;

  if ($len < 0 || $len > 16384) return 0;

  if ($len) $buf = fread($fp,$len);
  else $buf="";

  if (strlen($buf) != $len) return 0;

  $ret['length'] = $len;
  $ret['data'] = $buf;
  return $ret;
}

function nj_makepasshash($user, $pass, $challenge)
{
  if (phpversion () < "5") {
    $s = pack("H*", sha1($user . ":" . $pass));
    return (pack("H*", sha1($s . $challenge)));
  } else {
    $s=sha1($user . ":" . $pass,TRUE);
    return sha1($s . $challenge,TRUE);
  }
}

function nj_sendauthmsg($fp, $user, $pass, $challenge)
{

  $msg = nj_makepasshash($user,$pass,$challenge);
  $msg .= $user . chr(0);

  $caps = 1;
  $ver = 0x00020000;

  $msg .= chr($caps & 0xff);
  $msg .= chr(($caps>>8) & 0xff);
  $msg .= chr(($caps>>16) & 0xff);
  $msg .= chr(($caps>>24) & 0xff);

  $msg .= chr($ver & 0xff);
  $msg .= chr(($ver>>8) & 0xff);
  $msg .= chr(($ver>>16) & 0xff);
  $msg .= chr(($ver>>24) & 0xff);

  nj_sendmsg($fp,0x80,$msg);
}

function nj_parseauthmsg($data)
{
  if (strlen($data) < 4+4+8) { return 0;}
  $ret['challenge'] = substr($data,0,8);

  $ret['servercaps'] = ord(substr($data,8));
  $ret['servercaps'] |= ord(substr($data,9)) << 8;
  $ret['servercaps'] |= ord(substr($data,10)) << 16;
  $ret['servercaps'] |= ord(substr($data,11)) << 24;

  $ret['protover'] = ord(substr($data,12))&0xff;
  $ret['protover'] |= ord(substr($data,13)) << 8;
  $ret['protover'] |= ord(substr($data,14)) << 16;
  $ret['protover'] |= ord(substr($data,15)) << 24;

  if ($ret['protover'] < 0x00020000 || $ret['protover'] > 0x0002ffff) { return 0;  }

  $ret['license']="";
  if ($ret['servercaps'] & 1)
  {
    $pos=16;
    while ($pos < strlen($data))
    {
      if (!ord(substr($data,$pos)))
      {
        $ret['license']=substr($data,16);
    break;
      }
      $pos++;
    }
  }

  return $ret;
}

function nj_parsebpmbpimsg($data)
{
  if (strlen($data) < 4) return 0;
  $ret['bpm']=ord(substr($data,0));
  $ret['bpm']|=ord(substr($data,1))<<8;
  $ret['bpi']=ord(substr($data,2));
  $ret['bpi']|=ord(substr($data,3))<<8;
  return $ret;
}

function nj_parsechatmsg($data)
{
  $len=strlen($data);
  if ($len < 1) return 0;
  $pos=0;
  for ($cnt = 0; $cnt < 16; $cnt ++)
  {
    $lpos=$pos;
    while ($pos < $len && ord(substr($data,$pos,1))) $pos++;
    if ($pos > $lpos) $ret[$cnt] = substr($data,$lpos,$pos-$lpos);
    else $ret[$cnt] = "";
    $pos++;
    if ($pos >= $len) break;
  }
  return $ret;
}

function nj_parseuserinfolist($data)
{
  $len = strlen($data);
  if ($len < 1) return 0;

  $pos = 0;
  $hdrsize=6;
  while ($pos < $len)
  {
    $act=ord(substr($data,$pos,1));
    $idx=ord(substr($data,$pos+1,1));
    $pos += $hdrsize;

    $lpos=$pos;
    while ($pos < $len && ord(substr($data,$pos,1))) $pos++;
    if ($pos > $lpos) $usn = substr($data,$lpos,$pos-$lpos);
    else $usn="";
    $pos++;
    $lpos=$pos;
    while ($pos < $len && ord(substr($data,$pos,1))) $pos++;
    if ($pos > $lpos) $chn = substr($data,$lpos,$pos-$lpos);
    else $chn="";
    $pos++;

    if ($pos <= $len && $act)
    {
      $ret[$usn][$ret[$usn]['cnt']|0]['id'] = $idx;
      $ret[$usn][$ret[$usn]['cnt']|0]['name'] = $chn;
      $ret[$usn]['cnt']++;
    }

  }
  return $ret;
}
?>


code:
*~
*.pyc

# virtualenv
bin/
build/
dist/
include/
lib/
local/
*.egg-info/

path: .gitignore


code:
jantwisted <janith@member.fsf.org>
rsiddharth <rsd@gnu.org>

path: CONTRIBUTORS


code:
# LibrePlanet schedule generator

## On GNU/Linux

Install `virtualenv` and python dev. package.

On a Debian based distribution, do:

    # aptitude install virtualenv python-dev

## Set up environment

    $ git clone git://notabug.org/rsd/lpschedule-generator.git

Setup virtual environment:

    $ cd lpschedule-generator
    $ virtualenv .

Activate the virtual environment:

    $ source bin/activate

Install `lpschedule-generator`:

    $ python setup.py install

This will install all the dependencies.


code:
# -*- coding: utf-8 -*-
#
#    Copyright (C) 2015  lpschedule-generator contributors. See CONTRIBUTORS.
#
#    This file is part of lpschedule-generator.
#
#   lpschedule-generator 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 3 of
#   the License, or (at your option) any later version.
#
#   lpschedule-generator 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 lpschedule-generator (see COPYING).  If not, see
#   <http://www.gnu.org/licenses/>.

import json
import jinja2

from argparse import ArgumentParser
from collections import OrderedDict
from os import path

from mistune import Renderer, Markdown

# Python dictionary that will contain the lp schedule.
lps_dict = OrderedDict()

def read_file(filename):
    """Read file and return it as a string.

    filename: Absolute pathname of the file.
    """
    content = ''

    with open(filename, 'rb') as f:
        for line in f:
            content = content + line

    return content

class LPSRenderer(Renderer):
    """Helps in converting Markdown version of LP schedule to a dictionary.
    """
    def __init__(self, **kwargs):
        super(LPSRenderer, self).__init__(**kwargs)
        self.last_day = None
        self.last_time_slot = None
        self.last_session = None

        # Denotes the no. of the paragraph under a session; this
        # information will be helpful in identifying the "speaker",
        # "room" and session "description".
        self.no_paragraph = None

    def header(self, text, level, raw=None):
        global lps_dict

        if level == 2:
            # Add new day.
            lps_dict[text] = OrderedDict()
            self.last_day = text
        elif level == 3:
            # Add new timeslot
            lps_dict[self.last_day][text] = OrderedDict()
            self.last_time_slot = text
        elif level == 4:
            # Add new session
            lps_dict[self.last_day][self.last_time_slot][text] = OrderedDict()
            self.last_session = text
            # We found a new session; set no of paragraphs processed
            # to 0.
            self.no_paragraph = 0

        return super(LPSRenderer, self).header(text, level, raw)

    def paragraph(self, text):
        global lps_dict

        p = super(LPSRenderer, self).paragraph(text)

        if self.no_paragraph == 0:
            # Speaker
            if len(text.split(', ')) == 1:
                speaker_text = text
            else:
                speaker_text = text.split(', ')

            lps_dict[self.last_day][self.last_time_slot][
                self.last_session]['speaker'] = speaker_text
            self.no_paragraph = self.no_paragraph + 1
        elif self.no_paragraph == 1:
            # Room
            lps_dict[self.last_day][self.last_time_slot][
                self.last_session]['room'] = text
            # Initialize description
            lps_dict[self.last_day][self.last_time_slot][
                self.last_session]['desc'] = []
            self.no_paragraph = self.no_paragraph + 1
        elif self.no_paragraph > 1:
            lps_dict[self.last_day][self.last_time_slot][
                self.last_session]['desc'].append(text)

        return p

class LPSMarkdown(Markdown):
    """Converts MD LP schedule to a dictionary.

    Returns the Markdown version of LP schedule as a dictionary.
    """
    def __init__(self, inline=None, block=None, **kwargs):
        """
        Initialize with LPSRenderer as the renderer.
        """
        super(LPSMarkdown, self).__init__(renderer=LPSRenderer(),
                                          inline=None, block=None,
                                          **kwargs)

    def parse(self, text):
        global lps_dict

        lps_dict = OrderedDict()
        html = super(LPSMarkdown, self).parse(text)
        return lps_dict

def HTMLRender(_dict):
    """Render html from a dictionary and returns a string.

    Uses jinja2 and HTML template file.
    """
    templateLoader = jinja2.FileSystemLoader( searchpath="./" )
    templateEnv = jinja2.Environment( loader=templateLoader )
    TEMPLATE_FILE = "tests/files/index.html"
    template = templateEnv.get_template( TEMPLATE_FILE )
    _data  = {'res':_dict}
    lps_output = template.render( _data )
    return lps_output

def main():
    parser = ArgumentParser()
    parser.add_argument("lps_md",
                        help="Path to the markdown version of LP Schedule.")
    args = parser.parse_args()

    lps_md_content = read_file(path.abspath(args.lps_md))

    markdown = LPSMarkdown()
    lps_dict = markdown(lps_md_content)
    print HTMLRender(lps_dict)

    #print json.dumps(lps_dict, indent=4)

if __name__ == "__main__":
    main()


code:
#    Copyright (C) 2015  lpschedule-generator contributors. See CONTRIBUTORS.
#
#    This file is part of lpschedule-generator.
#
#   lpschedule-generator 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 3 of
#   the License, or (at your option) any later version.
#
#   lpschedule-generator 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 lpschedule-generator (see COPYING).  If not, see
#   <http://www.gnu.org/licenses/>.

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

config = {
    'description': 'LibrePlanet schedule generator',
    'author': 'rsiddharth',
    'url': 'https://notabug.org/rsd/lpschedule-generator/',
    'author_email': 'rsd@gnu.org',
    'version': '0.0',
    'install_requires': ['nose', 'mistune'],
    'py_modules': ['lps_gen'],
    'name': 'lpschedule-generator'
    }

setup(**config)


code:
{% for key, value in res.iteritems() %}

        <hgroup>

{{ key }}

              ↓

        </hgroup>

  {% for key2, value2 in value.iteritems() %}

    <hgroup>

{{ key2 }}

    </hgroup>

    {# conditional content #}
    {% if value2|count > 0 %}

      {% for key3, value3 in value2.iteritems() %}

{{ key3 }}

    {{ value3['speaker'] }}

<button class="btn btn-default btn-xs"
data-toggle="collapse" aria-expanded="false"
aria-controls="sat-ts0-s0-collapse"
data-target="#sat-ts0-s0-collapse">
Details
</button>

{{ value3['room'] }}

{{ value3['desc'][0].decode('utf-8') }}

      {% endfor %}
    {% endif %}

  {% endfor %}
{% endfor %}


code:
## Saturday, March 19

### 09:00 - 09:45: Registration and Breakfast

### 09:45 - 10:45: Opening Keynote: Richard Stallman

#### Free software, free hardware, and other things

Richard Stallman

Room 32-123

Preceded by a welcome address from John Sullivan, FSF executive
director.

### 10:55 - 11:40: Session Block 1A

#### Federation and GNU

Christopher Webber

Room 32-123

The effort to re-decentralize the web has been under way for a number
of years, but what's really happening under the hood? Various projects
like Diaspora, GNU social, [GNU MediaGoblin][gmg-home], Friendica Red, and Pump.IO
all exist, but not all these projects can talk to each other. How can
we fix that? A demo of PyPump will be given, as well as a rundown on
the progress of the W3C Social Working Group.

[gmg-home]: http://mediagoblin.org/

#### Dr. Hyde and Mr. Jekyll: advocating for free software in nonfree academic contexts

ginger coons

Room 32-141

What if the classic horror trope of the good doctor who becomes a
monster at night were reversed? Instead of the good Dr. Jekyll
transforming into the rampaging Mr. Hyde, advocates of free who work
in nonfree environments can feel as if they only get to put on their
altruistic persona at night. For academics advocating free software
and free culture in particular, libre ethics are often at odds with
both administrative structures and expected teaching outcomes. This
session explores the struggles of advocating free in both research and
teaching.

#### TAFTA, CETA, TISA: traps and threats to Free Software Everywhere

Marianne Corvellec, Jonathan Le Lous

Room 32-155

TAFTA, CETA, and TISA are far-reaching trade agreements posing major
threats to online freedom and creating legal uncertainty for all
Internet players. They set forth an ever stronger protection of
copyright and patents. They 'recycle' the most toxic parts of ACTA,
the anti-counterfeiting trade agreement which was rejected
in 2012. The presentation focuses on the software aspects of TAFTA,
CETA, TISA. We will call for action against these global treaty
projects and offer alternative proposals, which favour Free Software
Everywhere.

### 11:40 - 11:50: Break

### 11:50 - 12:35: Session Block 2A

#### Let's encrypt!

Seth Schoen

Room 32-123

This year a robotic certificate authority will start issuing
publicly-trusted certificates, at no charge, by the millions. Called
Let's Encrypt, this CA is an initiative of several organizations. Our
free software and protocol will let sysadmins run a single command to
turn on HTTPS on their servers in about a minute, helping eliminate
obstacles to activating encryption for every Web server. I'll describe
how it all works and give a demo. We need lots of testing and
integration help!

#### Attribution revolution -- turning copyright upside-down

Jonas Öberg

Room 32-141

Reusing works licensed under free licenses seems pretty simple, but it
can often be quite time consuming. One image or a few lines of source
code might be okay, but keeping track of the license and attribution
of a thousand different pieces, or when quoting from massive data sets
such as Wikipedia? Whoah! Don’t we have computers to do that for us!?
We do, but there’s no widespread support for including licensing or
author information when sharing or reusing digital works. This session
will discuss how this should work in a free knowledge environment, and
could it be that many problems regarding copyright and "piracy" in our
digital society could be solved with free software?

In order to relate effectively to the digital works we see online,
attribution (who made or built something) matters. Proper attribution
is the start of being able to explore digital works online in their
right context. This talk will focus on the philosophical background of
why attribution matters, the benefits that free software can bring to
the way we work with pieces of art (lolcats and Shakespeare alike),
and where we're heading in the future.

## Sunday, March 20

### 09:00 - 09:45: Registration and breakfast

### 09:45 - 10:30: Keynote: Benjamin Mako Hill

#### Access without empowerment

Benjamin Mako Hill

Room 32-123

The free software movement has twin goals: promoting access to
software through users' freedom to share, and empowering users by
giving them control over their technology. For all our movement's
success, we have been much more successful at the former. I will use
data from free software and from several related movements to explain
why promoting empowerment is systematically more difficult than
promoting access and I will explore how our movement might address the
second challenge in the future.

### 10:30 - 10:40: Break

### 10:40 - 11:25: Session Block 1B

#### Fork and ignore: fighting a GPL violation by coding instead

Bradley Kuhn

Room 32-123

Typically, GPL enforcement activity involves copyright infringement
actions which compel license violators to correct errors in their GPL
compliance, defending the policy goals of the GPL: the rights of
developers and users to copy, share, modify and redistribute.

While traditional enforcement is often undeniably necessary for
embedded electronics products, novel approaches to GPL violations are
often possible and even superior for more traditional software
distributions.

Recently, Software Freedom Conservancy engaged in an enforcement
action whereby, rather than fight the violator in court, we instead
provided resources and assistance to a vetted GPL-compliant fork of a
violating codebase.

This talk discusses which scenarios make this remedy optimal and the
lessons learned. The talk includes some licensing and technical
content about vetting the licensing information of codebases.

#### Who did this? Just wait until your father gets home

Ken Starks

Room 32-141

What's going on in here? Computer parts laying all over the
place... screws and ribbon cables scattered cross heaven's half
acre. And who left this power supply in the refrigerator? Is that your
dad's new impact drive? Don't you dare let me get up in the middle of
the night and step on that motherboard in my bare feet. Just what in
the name of Michael Dell is going on here?


code:
# -*- coding: utf-8 -*-
#
#    Copyright (C) 2015  lpschedule-generator contributors. See CONTRIBUTORS.
#
#    This file is part of lpschedule-generator.
#
#   lpschedule-generator 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 3 of
#   the License, or (at your option) any later version.
#
#   lpschedule-generator 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 lpschedule-generator (see COPYING).  If not, see
#   <http://www.gnu.org/licenses/>.

import json
import pprint

import mistune

from os import path

from nose.tools import *

from lps_gen import *

class TestLpsGen(object):
    """
    Class that tests the lps_gen.py module.
    """
    @classmethod
    def setup_class(self):
        """Runs before running any tests in this class."""

        self.MD_FILE = path.join('tests', 'files', 'lp-sch.md')
        self.MD_FILE_CONTENT = read_file(self.MD_FILE)

        self.markdown = LPSMarkdown()
        self.lps_dict = self.markdown(self.MD_FILE_CONTENT)

    def setup(self):
        """Runs before each test in this class."""
        pass

    def test_LPSMarkdown_day(self):
        """
        Testing `LPSMarkdown` class - Day.
        """
        days = ['Saturday, March 19',
                'Sunday, March 20']
        i = 0
        for day in self.lps_dict.keys():
            assert_equal(day, days[i])
            i = i + 1

    def test_LPSMarkdown_timeslot(self):
        """
        Testing `LPSMarkdown` class - Timeslot.
        """
        timeslots = [
            '09:00 - 09:45: Registration and Breakfast',
            '09:45 - 10:45: Opening Keynote: Richard Stallman',
            '10:55 - 11:40: Session Block 1A',
            '11:40 - 11:50: Break',
            '11:50 - 12:35: Session Block 2A',
            '09:00 - 09:45: Registration and breakfast',
            '09:45 - 10:30: Keynote: Benjamin Mako Hill',
            '10:30 - 10:40: Break',
            '10:40 - 11:25: Session Block 1B',
            ]

        i = 0
        for lps_timeslots in self.lps_dict.values():
            for timeslot in lps_timeslots.keys():
                assert_equal(timeslot, timeslots[i])
                i = i + 1

    def test_LPSMarkdown_session(self):
        """
        Testing `LPSMarkdown` class - Session.
        """
        sessions = [
            'Free software, free hardware, and other things',
            'Federation and GNU',
            'Dr. Hyde and Mr. Jekyll: advocating for free software in nonfree academic contexts',
            'TAFTA, CETA, TISA: traps and threats to Free Software Everywhere',
            'Let\'s encrypt!',
            'Attribution revolution -- turning copyright upside-down',
            'Access without empowerment',
            'Fork and ignore: fighting a GPL violation by coding instead',
            'Who did this? Just wait until your father gets home',
            ]

        i = 0
        for lps_timeslots in self.lps_dict.values():
            for lps_sessions in lps_timeslots.values():
                for session in lps_sessions.keys():
                    assert_equal(session, sessions[i])
                    i = i + 1

    def test_LPSMarkdown_speaker(self):
        """
        Testing `LPSMarkdown` class - Speaker
        """
        speakers = [
            'Richard Stallman',
            'Christopher Webber',
            'ginger coons',
            ['Marianne Corvellec', 'Jonathan Le Lous'],
            'Seth Schoen',
            'Jonas Öberg',
            'Benjamin Mako Hill',
            'Bradley Kuhn',
            'Ken Starks',
            ]

        i = 0
        for lps_timeslots in self.lps_dict.values():
            for lps_sessions in lps_timeslots.values():
                for session_info in lps_sessions.values():
                    assert_equal(session_info['speaker'], speakers[i])
                    i = i + 1

    def test_LPSMarkdown_room(self):
        """
        Testing `LPSMarkdown` class - Room
        """
        rooms = [
            'Room 32-123',
            'Room 32-123',
            'Room 32-141',
            'Room 32-155',
            'Room 32-123',
            'Room 32-141',
            'Room 32-123',
            'Room 32-123',
            'Room 32-141',
            ]
        i = 0
        for lps_timeslots in self.lps_dict.values():
            for lps_sessions in lps_timeslots.values():
                for session_info in lps_sessions.values():
                    assert_equal(session_info['room'], rooms[i])
                    i = i + 1

    def teardown(self):
        """Cleans up things after each test in this class."""
        pass

    @classmethod
    def teardown_class(self):
        """Purge the mess created by this test."""
        pass


code:
# samllmi
# 接口不定时更新嫖吧
# 哈哈哈

repo_name: qazwsx/hhh

license: unknown