Automatically allocate one uid per session
This commit is contained in:
		
							parent
							
								
									e9ff1d92d3
								
							
						
					
					
						commit
						afad563d56
					
				| 
						 | 
					@ -38,8 +38,7 @@ EXPOSE 6120
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENTRYPOINT ["/usr/local/bin/pid1.bash"]
 | 
					ENTRYPOINT ["/usr/local/bin/pid1.bash"]
 | 
				
			||||||
COPY scripts/pid1.bash /usr/local/bin/
 | 
					COPY scripts/pid1.bash /usr/local/bin/
 | 
				
			||||||
 | 
					CMD ["yarn", "run", "server"]
 | 
				
			||||||
RUN sudo deluser docker sudo
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN mkdir /tmp/riju
 | 
					RUN mkdir /tmp/riju
 | 
				
			||||||
COPY --chown=docker:docker package.json yarn.lock /tmp/riju/
 | 
					COPY --chown=docker:docker package.json yarn.lock /tmp/riju/
 | 
				
			||||||
| 
						 | 
					@ -49,8 +48,10 @@ COPY --chown=docker:docker frontend /tmp/riju/frontend
 | 
				
			||||||
RUN cd /tmp/riju && yarn run frontend
 | 
					RUN cd /tmp/riju && yarn run frontend
 | 
				
			||||||
COPY --chown=docker:docker backend /tmp/riju/backend
 | 
					COPY --chown=docker:docker backend /tmp/riju/backend
 | 
				
			||||||
RUN cd /tmp/riju && yarn run backend
 | 
					RUN cd /tmp/riju && yarn run backend
 | 
				
			||||||
 | 
					COPY --chown=docker:docker system /tmp/riju/system
 | 
				
			||||||
 | 
					RUN cd /tmp/riju && RIJU_PRIVILEGED=1 yarn run system
 | 
				
			||||||
COPY --chown=docker:docker . /home/docker/src
 | 
					COPY --chown=docker:docker . /home/docker/src
 | 
				
			||||||
RUN cp -R /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju
 | 
					RUN cp -R /tmp/riju/* /home/docker/src/ && rm -rf /tmp/riju
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /home/docker/src
 | 
					WORKDIR /home/docker/src
 | 
				
			||||||
CMD ["yarn", "run", "server"]
 | 
					RUN sudo deluser docker sudo
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								README.md
								
								
								
								
							
							
						
						
									
										12
									
								
								README.md
								
								
								
								
							| 
						 | 
					@ -20,6 +20,7 @@ usual to install dependencies. For production, it's:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $ yarn backend
 | 
					    $ yarn backend
 | 
				
			||||||
    $ yarn frontend
 | 
					    $ yarn frontend
 | 
				
			||||||
 | 
					    $ yarn system
 | 
				
			||||||
    $ yarn server
 | 
					    $ yarn server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For development with file watching and automatic server rebooting and
 | 
					For development with file watching and automatic server rebooting and
 | 
				
			||||||
| 
						 | 
					@ -27,15 +28,18 @@ all that, it's:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $ yarn backend-dev
 | 
					    $ yarn backend-dev
 | 
				
			||||||
    $ yarn frontend-dev
 | 
					    $ yarn frontend-dev
 | 
				
			||||||
 | 
					    $ yarn system-dev
 | 
				
			||||||
    $ yarn server-dev
 | 
					    $ yarn server-dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The webserver listens on `localhost:6119`. Now, although the server
 | 
					The webserver listens on `localhost:6119`. Now, although the server
 | 
				
			||||||
itself will work, the only languages that will work are the ones that
 | 
					itself will work, the only languages that will work are the ones that
 | 
				
			||||||
happen to be installed on your machine. (I'm sure you can find a few
 | 
					happen to be installed on your machine. (I'm sure you can find a few
 | 
				
			||||||
that are already.) If you want to test with *all* the languages (or
 | 
					that are already.) Also, sandboxing using UNIX filesystem permissions
 | 
				
			||||||
you're working on adding a new language), then you need to use Docker.
 | 
					will be disabled, because that requires root privileges. If you want
 | 
				
			||||||
Running the app is exactly the same as before, you just have to jump
 | 
					to test with *all* the languages plus sandboxing (or you're working on
 | 
				
			||||||
into the container first:
 | 
					adding a new language), then you need to use Docker. Running the app
 | 
				
			||||||
 | 
					is exactly the same as before, you just have to jump into the
 | 
				
			||||||
 | 
					container first:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $ make docker
 | 
					    $ make docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,64 +1,88 @@
 | 
				
			||||||
import * as fs from "fs";
 | 
					import * as fs from "fs";
 | 
				
			||||||
import * as mkdirp from "mkdirp";
 | 
					 | 
				
			||||||
import * as pty from "node-pty";
 | 
					 | 
				
			||||||
import { IPty } from "node-pty";
 | 
					 | 
				
			||||||
import * as path from "path";
 | 
					import * as path from "path";
 | 
				
			||||||
import * as tmp from "tmp";
 | 
					 | 
				
			||||||
import * as WebSocket from "ws";
 | 
					import * as WebSocket from "ws";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as mkdirp from "mkdirp";
 | 
				
			||||||
 | 
					import * as nodeCleanup from "node-cleanup";
 | 
				
			||||||
 | 
					import * as pty from "node-pty";
 | 
				
			||||||
 | 
					import { IPty } from "node-pty";
 | 
				
			||||||
 | 
					import * as tmp from "tmp";
 | 
				
			||||||
 | 
					import { v4 as getUUID } from "uuid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { LangConfig, langs } from "./langs";
 | 
					import { LangConfig, langs } from "./langs";
 | 
				
			||||||
 | 
					import { borrowUser } from "./users";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Session {
 | 
					export class Session {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
  code: string;
 | 
					  code: string;
 | 
				
			||||||
  config: LangConfig;
 | 
					  config: LangConfig;
 | 
				
			||||||
  term: { pty: IPty | null; live: boolean };
 | 
					  term: { pty: IPty | null; live: boolean };
 | 
				
			||||||
  ws: WebSocket;
 | 
					  ws: WebSocket;
 | 
				
			||||||
  tmpdir: string | null;
 | 
					  tmpdir: string | null;
 | 
				
			||||||
  tmpdirCleanup: (() => void) | null;
 | 
					  tmpdirCleanup: (() => void) | null;
 | 
				
			||||||
 | 
					  uid: number | null;
 | 
				
			||||||
 | 
					  uidCleanup: (() => Promise<void>) | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  log = (msg: string) => console.log(`[${this.id}] ${msg}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(ws: WebSocket, lang: string) {
 | 
					  constructor(ws: WebSocket, lang: string) {
 | 
				
			||||||
 | 
					    this.id = getUUID();
 | 
				
			||||||
 | 
					    this.log(`Creating session, language ${lang}`);
 | 
				
			||||||
    this.ws = ws;
 | 
					    this.ws = ws;
 | 
				
			||||||
    this.config = langs[lang];
 | 
					    this.config = langs[lang];
 | 
				
			||||||
    this.term = { pty: null, live: false };
 | 
					    this.term = { pty: null, live: false };
 | 
				
			||||||
    this.code = "";
 | 
					    this.code = "";
 | 
				
			||||||
    this.tmpdir = null;
 | 
					    this.tmpdir = null;
 | 
				
			||||||
    this.tmpdirCleanup = null;
 | 
					    this.tmpdirCleanup = null;
 | 
				
			||||||
 | 
					    this.uid = null;
 | 
				
			||||||
 | 
					    this.uidCleanup = null;
 | 
				
			||||||
    ws.on("message", this.handleClientMessage);
 | 
					    ws.on("message", this.handleClientMessage);
 | 
				
			||||||
    ws.on("close", this.cleanup);
 | 
					    ws.on("close", () =>
 | 
				
			||||||
    this.run().catch(console.error);
 | 
					      this.cleanup().catch((err) =>
 | 
				
			||||||
 | 
					        this.log(`Error during session cleanup: ${err}`)
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    nodeCleanup();
 | 
				
			||||||
 | 
					    this.run().catch((err) => this.log(`Error while running: ${err}`));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  handleClientMessage = (event: string) => {
 | 
					  handleClientMessage = (event: string) => {
 | 
				
			||||||
    let msg: any;
 | 
					    let msg: any;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      msg = JSON.parse(event);
 | 
					      msg = JSON.parse(event);
 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
      console.error(`failed to parse client message: ${msg}`);
 | 
					      this.log(`Failed to parse client message: ${msg}`);
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    switch (msg?.event) {
 | 
					    switch (msg?.event) {
 | 
				
			||||||
      case "terminalInput":
 | 
					      case "terminalInput":
 | 
				
			||||||
        if (!this.term) {
 | 
					        if (!this.term) {
 | 
				
			||||||
          console.error(`terminalInput: no terminal`);
 | 
					          this.log(`Got terminal input before pty was started`);
 | 
				
			||||||
        } else if (typeof msg.input !== "string") {
 | 
					        } else if (typeof msg.input !== "string") {
 | 
				
			||||||
          console.error(`terminalInput: missing or malformed input field`);
 | 
					          this.log(`Got malformed terminal input message`);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          this.term.pty!.write(msg.input);
 | 
					          this.term.pty!.write(msg.input);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case "runCode":
 | 
					      case "runCode":
 | 
				
			||||||
        if (typeof msg.code !== "string") {
 | 
					        if (typeof msg.code !== "string") {
 | 
				
			||||||
          console.error(`runCode: missing or malformed code field`);
 | 
					          this.log(`Got malformed run message`);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          this.code = msg.code;
 | 
					          this.code = msg.code;
 | 
				
			||||||
          this.run();
 | 
					          this.run();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        console.error(`unknown client message type: ${msg.event}`);
 | 
					        this.log(`Got unknown message type: ${msg.event}`);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  run = async () => {
 | 
					  run = async () => {
 | 
				
			||||||
 | 
					    if (this.uid === null) {
 | 
				
			||||||
 | 
					      ({ uid: this.uid, cleanup: this.uidCleanup } = await borrowUser(
 | 
				
			||||||
 | 
					        this.log
 | 
				
			||||||
 | 
					      ));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.log(`Borrowed uid ${this.uid}`);
 | 
				
			||||||
    const { name, repl, main, suffix, compile, run, hacks } = this.config;
 | 
					    const { name, repl, main, suffix, compile, run, hacks } = this.config;
 | 
				
			||||||
    if (this.term.pty) {
 | 
					    if (this.term.pty) {
 | 
				
			||||||
      this.term.pty.kill();
 | 
					      this.term.pty.kill();
 | 
				
			||||||
| 
						 | 
					@ -72,13 +96,16 @@ export class Session {
 | 
				
			||||||
    if (this.tmpdir == null) {
 | 
					    if (this.tmpdir == null) {
 | 
				
			||||||
      ({ path: this.tmpdir, cleanup: this.tmpdirCleanup } = await new Promise(
 | 
					      ({ path: this.tmpdir, cleanup: this.tmpdirCleanup } = await new Promise(
 | 
				
			||||||
        (resolve, reject) =>
 | 
					        (resolve, reject) =>
 | 
				
			||||||
          tmp.dir({ unsafeCleanup: true }, (err, path, cleanup) => {
 | 
					          tmp.dir(
 | 
				
			||||||
            if (err) {
 | 
					            { unsafeCleanup: true, dir: "riju" },
 | 
				
			||||||
              reject(err);
 | 
					            (err, path, cleanup) => {
 | 
				
			||||||
            } else {
 | 
					              if (err) {
 | 
				
			||||||
              resolve({ path, cleanup });
 | 
					                reject(err);
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                resolve({ path, cleanup });
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          })
 | 
					          )
 | 
				
			||||||
      ));
 | 
					      ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    let cmdline: string;
 | 
					    let cmdline: string;
 | 
				
			||||||
| 
						 | 
					@ -157,9 +184,13 @@ export class Session {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  cleanup = () => {
 | 
					  cleanup = async () => {
 | 
				
			||||||
 | 
					    this.log(`Cleaning up session`);
 | 
				
			||||||
    if (this.tmpdirCleanup) {
 | 
					    if (this.tmpdirCleanup) {
 | 
				
			||||||
      this.tmpdirCleanup();
 | 
					      this.tmpdirCleanup();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.uidCleanup) {
 | 
				
			||||||
 | 
					      await this.uidCleanup();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,100 @@
 | 
				
			||||||
 | 
					import { spawn } from "child_process";
 | 
				
			||||||
 | 
					import * as fs from "fs";
 | 
				
			||||||
 | 
					import * as process from "process";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as AsyncLock from "async-lock";
 | 
				
			||||||
 | 
					import * as _ from "lodash";
 | 
				
			||||||
 | 
					import * as parsePasswd from "parse-passwd";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Keep in sync with system/src/riju-system-privileged.c
 | 
				
			||||||
 | 
					const MIN_UID = 2000;
 | 
				
			||||||
 | 
					const MAX_UID = 65000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PRIVILEGED = process.env.RIJU_PRIVILEGED ? true : false;
 | 
				
			||||||
 | 
					const CUR_UID = parseInt(process.env.UID || "") || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let availIds: number[] | null = null;
 | 
				
			||||||
 | 
					let nextId: number | null = null;
 | 
				
			||||||
 | 
					let lock = new AsyncLock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function readExistingUsers(log: (msg: string) => void) {
 | 
				
			||||||
 | 
					  availIds = parsePasswd(
 | 
				
			||||||
 | 
					    await new Promise((resolve, reject) =>
 | 
				
			||||||
 | 
					      fs.readFile("/etc/passwd", "utf-8", (err, data) => {
 | 
				
			||||||
 | 
					        if (err) {
 | 
				
			||||||
 | 
					          reject(err);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          resolve(data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					    .filter(({ username }) => username.startsWith("riju_user"))
 | 
				
			||||||
 | 
					    .map(({ uid }) => parseInt(uid))
 | 
				
			||||||
 | 
					    .filter((uid) => !isNaN(uid) && uid >= MIN_UID && uid < MAX_UID);
 | 
				
			||||||
 | 
					  nextId = (_.max(availIds) || MIN_UID - 1) + 1;
 | 
				
			||||||
 | 
					  log(`Found ${availIds.length} existing users, next ID is ${nextId}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function createUser(log: (msg: string) => void): Promise<number> {
 | 
				
			||||||
 | 
					  if (nextId! >= MAX_UID) {
 | 
				
			||||||
 | 
					    throw new Error("too many users");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return await new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					    const uid = nextId!;
 | 
				
			||||||
 | 
					    const useradd = spawn("system/out/riju-system-privileged", [
 | 
				
			||||||
 | 
					      "useradd",
 | 
				
			||||||
 | 
					      `${uid}`,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    let output = "";
 | 
				
			||||||
 | 
					    useradd.stdout.on("data", (data) => {
 | 
				
			||||||
 | 
					      output += `${data}`;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    useradd.stderr.on("data", (data) => {
 | 
				
			||||||
 | 
					      output += `${data}`;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    useradd.on("close", (code) => {
 | 
				
			||||||
 | 
					      output = output.trim();
 | 
				
			||||||
 | 
					      if (output) {
 | 
				
			||||||
 | 
					        log("Output from useradd:\n" + output);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (code === 0) {
 | 
				
			||||||
 | 
					        log(`Created new user with ID ${uid}`);
 | 
				
			||||||
 | 
					        nextId! += 1;
 | 
				
			||||||
 | 
					        resolve(uid);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        reject(`useradd failed with error code ${code}`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function borrowUser(log: (msg: string) => void) {
 | 
				
			||||||
 | 
					  if (!PRIVILEGED) {
 | 
				
			||||||
 | 
					    if (CUR_UID === null) {
 | 
				
			||||||
 | 
					      throw new Error("unable to determine current UID");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return { uid: CUR_UID, cleanup: async () => {} };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return await lock.acquire("key", async () => {
 | 
				
			||||||
 | 
					      if (availIds === null || nextId === null) {
 | 
				
			||||||
 | 
					        await readExistingUsers(log);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      let uid: number;
 | 
				
			||||||
 | 
					      if (availIds!.length > 0) {
 | 
				
			||||||
 | 
					        uid = availIds!.pop()!;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        uid = await createUser(log);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        uid,
 | 
				
			||||||
 | 
					        cleanup: async () => {
 | 
				
			||||||
 | 
					          await lock.acquire("key", () => {
 | 
				
			||||||
 | 
					            availIds!.push(uid);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								package.json
								
								
								
								
							
							
						
						
									
										13
									
								
								package.json
								
								
								
								
							| 
						 | 
					@ -5,12 +5,16 @@
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@types/app-root-path": "^1.2.4",
 | 
					    "@types/app-root-path": "^1.2.4",
 | 
				
			||||||
 | 
					    "@types/async-lock": "^1.1.2",
 | 
				
			||||||
    "@types/express": "^4.17.6",
 | 
					    "@types/express": "^4.17.6",
 | 
				
			||||||
    "@types/express-ws": "^3.0.0",
 | 
					    "@types/express-ws": "^3.0.0",
 | 
				
			||||||
    "@types/lodash": "^4.14.155",
 | 
					    "@types/lodash": "^4.14.155",
 | 
				
			||||||
    "@types/mkdirp": "^1.0.1",
 | 
					    "@types/mkdirp": "^1.0.1",
 | 
				
			||||||
 | 
					    "@types/parse-passwd": "^1.0.0",
 | 
				
			||||||
    "@types/tmp": "^0.2.0",
 | 
					    "@types/tmp": "^0.2.0",
 | 
				
			||||||
 | 
					    "@types/uuid": "^8.0.0",
 | 
				
			||||||
    "app-root-path": "^3.0.0",
 | 
					    "app-root-path": "^3.0.0",
 | 
				
			||||||
 | 
					    "async-lock": "^1.2.4",
 | 
				
			||||||
    "css-loader": "^3.5.3",
 | 
					    "css-loader": "^3.5.3",
 | 
				
			||||||
    "ejs": "^3.1.3",
 | 
					    "ejs": "^3.1.3",
 | 
				
			||||||
    "express": "^4.17.1",
 | 
					    "express": "^4.17.1",
 | 
				
			||||||
| 
						 | 
					@ -19,11 +23,14 @@
 | 
				
			||||||
    "lodash": "^4.17.15",
 | 
					    "lodash": "^4.17.15",
 | 
				
			||||||
    "mkdirp": "^1.0.4",
 | 
					    "mkdirp": "^1.0.4",
 | 
				
			||||||
    "monaco-editor": "^0.20.0",
 | 
					    "monaco-editor": "^0.20.0",
 | 
				
			||||||
 | 
					    "node-cleanup": "^2.1.2",
 | 
				
			||||||
    "node-pty": "^0.9.0",
 | 
					    "node-pty": "^0.9.0",
 | 
				
			||||||
 | 
					    "parse-passwd": "^1.0.0",
 | 
				
			||||||
    "style-loader": "^1.2.1",
 | 
					    "style-loader": "^1.2.1",
 | 
				
			||||||
    "tmp": "^0.2.1",
 | 
					    "tmp": "^0.2.1",
 | 
				
			||||||
    "ts-loader": "^7.0.5",
 | 
					    "ts-loader": "^7.0.5",
 | 
				
			||||||
    "typescript": "^3.9.5",
 | 
					    "typescript": "^3.9.5",
 | 
				
			||||||
 | 
					    "uuid": "^8.1.0",
 | 
				
			||||||
    "webpack": "^4.43.0",
 | 
					    "webpack": "^4.43.0",
 | 
				
			||||||
    "webpack-cli": "^3.3.11",
 | 
					    "webpack-cli": "^3.3.11",
 | 
				
			||||||
    "xterm": "^4.6.0",
 | 
					    "xterm": "^4.6.0",
 | 
				
			||||||
| 
						 | 
					@ -34,7 +41,9 @@
 | 
				
			||||||
    "backend-dev": "tsc --watch",
 | 
					    "backend-dev": "tsc --watch",
 | 
				
			||||||
    "frontend": "webpack --production",
 | 
					    "frontend": "webpack --production",
 | 
				
			||||||
    "frontend-dev": "webpack --development --watch",
 | 
					    "frontend-dev": "webpack --development --watch",
 | 
				
			||||||
    "server": "node backend/out/server.js",
 | 
					    "server": "scripts/setup.bash && node backend/out/server.js",
 | 
				
			||||||
    "server-dev": "watchexec -w backend/out -r -n node backend/out/server.js"
 | 
					    "server-dev": "watchexec -w backend/out -r 'scripts/setup.bash && node backend/out/server.js'",
 | 
				
			||||||
 | 
					    "system": "scripts/compile-system.bash",
 | 
				
			||||||
 | 
					    "system-dev": "watchexec -w system/src -n scripts/compile-system.bash"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					set -o pipefail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ ! -d system/src ]]; then
 | 
				
			||||||
 | 
					    echo "compile-system.bash: no system/src directory" >&2
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function verbosely {
 | 
				
			||||||
 | 
					    echo "$@"
 | 
				
			||||||
 | 
					    "$@"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p system/out
 | 
				
			||||||
 | 
					rm -f system/out/*
 | 
				
			||||||
 | 
					for src in system/src/*.c; do
 | 
				
			||||||
 | 
					    out="${src/src/out}"
 | 
				
			||||||
 | 
					    out="${out/.c}"
 | 
				
			||||||
 | 
					    verbosely clang -Wall -Wextra -Werror -std=c11 "${src}" -o "${out}"
 | 
				
			||||||
 | 
					    if [[ "${out}" == *-privileged && -n "${RIJU_PRIVILEGED}" ]]; then
 | 
				
			||||||
 | 
					        sudo chown root:docker "${out}"
 | 
				
			||||||
 | 
					        sudo chmod a=,g=rx,u=rwxs "${out}"
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ export LC_ALL=C.UTF-8
 | 
				
			||||||
export SHELL="$(which bash)"
 | 
					export SHELL="$(which bash)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export HOST=0.0.0.0
 | 
					export HOST=0.0.0.0
 | 
				
			||||||
 | 
					export RIJU_PRIVILEGED=yes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if [[ -d /home/docker/src ]]; then
 | 
					if [[ -d /home/docker/src ]]; then
 | 
				
			||||||
    cd /home/docker/src
 | 
					    cd /home/docker/src
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					set -o pipefail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p /tmp/riju
 | 
				
			||||||
 | 
					rm -rf /tmp/riju/*
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					#define _GNU_SOURCE
 | 
				
			||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include <stdlib.h>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Keep in sync with backend/src/users.ts
 | 
				
			||||||
 | 
					const int MIN_UID = 2000;
 | 
				
			||||||
 | 
					const int MAX_UID = 65000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void die(const char *msg)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  fprintf(stderr, "%s\n", msg);
 | 
				
			||||||
 | 
					  exit(1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void die_with_usage()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  die("usage:\n"
 | 
				
			||||||
 | 
					      "  riju-system-privileged useradd UID");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void useradd(int uid)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  char *cmdline;
 | 
				
			||||||
 | 
					  if (asprintf(&cmdline, "useradd -M -N -l -r -u %1$d riju_user%1$d", uid) < 0) {
 | 
				
			||||||
 | 
					    die("asprintf failed");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  int status = system(cmdline);
 | 
				
			||||||
 | 
					  if (status) {
 | 
				
			||||||
 | 
					    die("useradd failed");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main(int argc, char **argv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  setuid(0);
 | 
				
			||||||
 | 
					  if (argc < 2)
 | 
				
			||||||
 | 
					    die_with_usage();
 | 
				
			||||||
 | 
					  if (!strcmp(argv[1], "useradd")) {
 | 
				
			||||||
 | 
					    if (argc != 3)
 | 
				
			||||||
 | 
					      die_with_usage();
 | 
				
			||||||
 | 
					    char *endptr;
 | 
				
			||||||
 | 
					    long uid = strtol(argv[2], &endptr, 10);
 | 
				
			||||||
 | 
					    if (!argv[2] || *endptr) {
 | 
				
			||||||
 | 
					      die("uid must be an integer");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (uid < MIN_UID || uid >= MAX_UID) {
 | 
				
			||||||
 | 
					      die("uid is out of range");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    useradd(uid);
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  die_with_usage();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								yarn.lock
								
								
								
								
							
							
						
						
									
										30
									
								
								yarn.lock
								
								
								
								
							| 
						 | 
					@ -7,6 +7,11 @@
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.4.tgz#a78b703282b32ac54de768f5512ecc3569919dc7"
 | 
					  resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.4.tgz#a78b703282b32ac54de768f5512ecc3569919dc7"
 | 
				
			||||||
  integrity sha1-p4twMoKzKsVN52j1US7MNWmRncc=
 | 
					  integrity sha1-p4twMoKzKsVN52j1US7MNWmRncc=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/async-lock@^1.1.2":
 | 
				
			||||||
 | 
					  version "1.1.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.2.tgz#cbc26a34b11b83b28f7783a843c393b443ef8bef"
 | 
				
			||||||
 | 
					  integrity sha512-j9n4bb6RhgFIydBe0+kpjnBPYumDaDyU8zvbWykyVMkku+c2CSu31MZkLeaBfqIwU+XCxlDpYDfyMQRkM0AkeQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@types/body-parser@*":
 | 
					"@types/body-parser@*":
 | 
				
			||||||
  version "1.19.0"
 | 
					  version "1.19.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
 | 
					  resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
 | 
				
			||||||
| 
						 | 
					@ -77,6 +82,11 @@
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3"
 | 
					  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3"
 | 
				
			||||||
  integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg==
 | 
					  integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/parse-passwd@^1.0.0":
 | 
				
			||||||
 | 
					  version "1.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/parse-passwd/-/parse-passwd-1.0.0.tgz#72fc50feb14d484547f40ba7da54dfcfc711dcf1"
 | 
				
			||||||
 | 
					  integrity sha512-+kETlH3XJMQKUpJ4dYfU4MlfYqyjKwsOC9PLJcGiUhPcHrNEgvcEbllSJ5HlsvflDLswTtyDoKJEUcCQg2l9PA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@types/qs@*":
 | 
					"@types/qs@*":
 | 
				
			||||||
  version "6.9.3"
 | 
					  version "6.9.3"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.3.tgz#b755a0934564a200d3efdf88546ec93c369abd03"
 | 
					  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.3.tgz#b755a0934564a200d3efdf88546ec93c369abd03"
 | 
				
			||||||
| 
						 | 
					@ -100,6 +110,11 @@
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c"
 | 
					  resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c"
 | 
				
			||||||
  integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ==
 | 
					  integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/uuid@^8.0.0":
 | 
				
			||||||
 | 
					  version "8.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0"
 | 
				
			||||||
 | 
					  integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@types/ws@*":
 | 
					"@types/ws@*":
 | 
				
			||||||
  version "7.2.5"
 | 
					  version "7.2.5"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.5.tgz#513f28b04a1ea1aa9dc2cad3f26e8e37c88aae49"
 | 
					  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.5.tgz#513f28b04a1ea1aa9dc2cad3f26e8e37c88aae49"
 | 
				
			||||||
| 
						 | 
					@ -390,6 +405,11 @@ async-limiter@~1.0.0:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
 | 
					  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
 | 
				
			||||||
  integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
 | 
					  integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async-lock@^1.2.4:
 | 
				
			||||||
 | 
					  version "1.2.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.2.4.tgz#80d0d612383045dd0c30eb5aad08510c1397cb91"
 | 
				
			||||||
 | 
					  integrity sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async@0.9.x:
 | 
					async@0.9.x:
 | 
				
			||||||
  version "0.9.2"
 | 
					  version "0.9.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
 | 
					  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
 | 
				
			||||||
| 
						 | 
					@ -2190,6 +2210,11 @@ nice-try@^1.0.4:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
 | 
					  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
 | 
				
			||||||
  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 | 
					  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					node-cleanup@^2.1.2:
 | 
				
			||||||
 | 
					  version "2.1.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c"
 | 
				
			||||||
 | 
					  integrity sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-libs-browser@^2.2.1:
 | 
					node-libs-browser@^2.2.1:
 | 
				
			||||||
  version "2.2.1"
 | 
					  version "2.2.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
 | 
					  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
 | 
				
			||||||
| 
						 | 
					@ -3299,6 +3324,11 @@ utils-merge@1.0.1:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
 | 
					  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
 | 
				
			||||||
  integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
 | 
					  integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uuid@^8.1.0:
 | 
				
			||||||
 | 
					  version "8.1.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
 | 
				
			||||||
 | 
					  integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
v8-compile-cache@2.0.3:
 | 
					v8-compile-cache@2.0.3:
 | 
				
			||||||
  version "2.0.3"
 | 
					  version "2.0.3"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
 | 
					  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue