30 | Run Dyalog APL is an online sandbox environment for running code in an ever-growing
31 | list of programming languages, both practical and recreational. ATO was originally
32 | conceived as a replacement for the increasingly out-of-date
33 | {' '}
34 | Try It Online
35 | {' '}
36 | service.
37 |
38 |
39 | To get started, click the button below to run some code.
40 |
We don't advertise or use any tracking technologies
74 |
Regularly maintained: new languages and features are added by request all the time
75 |
76 | The interface is customisable (see the
77 | {' '}
78 |
79 | Preferences
80 |
81 | {' '}
82 | page)
83 |
84 |
85 |
Give Feedback
86 |
87 | If you have a feature suggestion, bug report, or request for a new or updated language,
88 | open an issue in the
89 | {' '}
90 | GitHub repository
91 | .
92 | Feel free to implement it yourself and
93 | {' '}
94 | submit a pull request
95 | !
96 |
97 |
98 | You can also discuss ATO in the dedicated
99 | {' '}
100 | Stack Exchange chatroom
101 | .
102 |
36 | The principal purpose of dyalog.run is to support other websites hosted by Dyalog Ltd.
37 | In order to access dyalog.run directly you must agree to the Terms of Use.
39 |
40 |
41 |
42 |
Privacy Policy
43 |
44 | When you visit the dyalog.run, the following information is collected:
45 |
46 |
47 |
48 | Your IP address. This is used to implement rate-limiting to prevent exhaustion of resources.
49 | A hashed version of your IP address may be stored for up to 1 month, and is never associated
50 | with any data except the amount of resources you use. Your IP address in an unhashed form
51 | may be stored for up to 1 year for security reasons.
52 |
53 |
54 | Any data you submit in the Run form (code, input, etc.). These are only used to process your
55 | code execution request, and immediately deleted after execution has completed.
56 |
57 |
58 |
59 | Any information you save on the Preferences page is stored locally in your browser and never
60 | shared with anyone
61 |
71 | "The Service" refers to the website at https://dyalog.run and all services provided at that URL.
73 | This does not include the Software itself, but only the instance of the Software made available
74 | at https://dyalog.run.
75 |
76 |
77 | Unless Dyalog Ltd grants you explicit written or electronic permission, you must not use the
78 | Service, except directly through the web page and as a result of your manual user interaction with it.
79 |
80 |
81 | The Software includes an "API", which is a programmatic interface for interacting with the
82 | Service, but this is for use only by the Service internally, and by
83 | other websites hosted by Dyalog Ltd. You are not permitted to use the API otherwise.
84 |
85 |
86 |
87 |
88 |
89 | >
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | ## Adding Languages
3 | If you're not familiar with the typical GitHub (fork-edit-PR) workflow, please read the [GitHub
4 | guide](https://guides.github.com/introduction/flow/) on the matter.
5 |
6 | 1. Create a [Docker image](https://hub.docker.com) with the toolchain for the language, and submit it to the
7 | [languages](https://github.com/attempt-this-online/languages) repository, based on our common base image
8 | `attemptthisonline/base` (which uses Arch Linux)
9 | - in rare cases (such as for non-open-source languages), an existing Docker image that isn't based on
10 | `attemptthisonline/base` may be used
11 | - for languages that require other languages, base the new language on an `attemptthisonline/` image for the required
12 | language. For example, Jelly's interpreter is written in Python, so Jelly's image is based on
13 | `attemptthisonline/python`
14 | - the build process of the Docker image should follow the general pattern of:
15 | - declare a Docker build argument corresponding to the language's version; this can be used as an evironment
16 | variable throughout the Dockerfile thereafter. This allows fast updating of the Docker images
17 | - install any dependencies necessary to compile the language from source
18 | - download and extract the source code of the language
19 | - compile the language's source code
20 | - install the compiled language (if just a few binaries are provided, they should go in `/usr/local/bin/`; if the
21 | installation is more complex, put it into a directory in `/opt/`)
22 | - clean up any downloaded or cached files that are no longer needed
23 | - a fairly easy-to-follow example of this is [Zsh](https://github.com/attempt-this-online/languages/blob/main/languages/zsh/Dockerfile)
24 | - if the language is particularly complex (or just slow) to build from source, a pre-built version can be used
25 | (example: [Java](https://github.com/attempt-this-online/languages/blob/main/languages/java/Dockerfile))
26 | - make a pull request to add it to the repository (from where it will be built and pushed to Docker Hub automatically)
27 | 2. Add the language's metadata to `ato/languages.go`; set the *key* to an identifier-safe name for the
28 | language (avoid special characters); in the value, set these fields:
29 | - name (should be human-readable - this is what will be presented to the user in the UI)
30 | - image (name of the Docker image used to run the code)
31 | - version (set this to whatever boundary we guarantee to the user, not just the version it currently uses)
32 | - URL (homepage of the language)
33 | - SBCS (set to `true` if the language's code uses a single-byte character set; this will change the behaviour of the
34 | byte counter in the frontend to assume all characters comprise one byte (rather than using UTF-8))
35 | - SE_class (provide this only if StackExchange has built-in syntax highlighting for the language; this will be added
36 | when a CGCC post template is generated. See [here](https://meta.stackexchange.com/q/184108) for details)
37 | 3. Create a runner script in `runners/`. Here is an example showing the general idea:
38 |
39 | ```sh
40 | #!/bin/sh
41 | # There are no minimal requirements for the Docker image, as long as it doesn't contain a /ATO directory. If the Docker
42 | # image you're using doesn't have a POSIX shell, it is always available as `/ATO/bash`. If you need to use it, make sure
43 | # to change the `#!` (shebang) line above to match that.
44 |
45 | # Do whatever is necessary to compile and run the code.
46 | # - code is saved in /ATO/code
47 | # - input provided by the user is saved in /ATO/input
48 | # - options to pass to the compiler or interpreter are stored in /ATO/options, null-terminated
49 | # - options to pass to the program itself are stored in /ATO/arguments
50 |
51 | # Use /ATO/yargs to substitute in the command-line options: the first argument is the replacement string, the second is
52 | # the intput file for the arguments, and after is the program and its arguments. The replacement string indicates the
53 | # position of the substitution.
54 | /ATO/yargs % /ATO/options gcc % /ATO/code -o /ATO/compiled
55 |
56 | # Note that, while the script will always start in /ATO/, you should always use absolute paths.
57 |
58 | # The code itself should always be run in the working directory /ATO/context
59 | cd /ATO/context
60 |
61 | # Pass arguments to the compiled file. Also, make sure you give the program input from /ATO/input.
62 | /ATO/yargs % /ATO/arguments /ATO/compiled % < /ATO/input
63 |
64 | # Make sure you retain the status code of the program! If you need to do any cleanup for whatever reason, make sure to
65 | # store a copy of the exit code and use it again.
66 | stored_status="$?"
67 | do_cleanup
68 | exit "$stored_status"
69 | ```
70 |
71 | Here is another example runner for an interpreted language instead:
72 |
73 | ```sh
74 | #!/bin/sh
75 |
76 | cd /ATO/context
77 |
78 | # Use two levels of yargs to substitute in multiple sets of arguments:
79 | /ATO/yargs %1 /ATO/options /ATO/yargs %2 /ATO/arguments python %1 /ATO/code %2 < /ATO/input
80 | ```
81 | - Make sure you've made the runner script executable (`chmod +x runners/path`)
82 | - Test your runner! It's unhelpful if you submit a broken runner
83 | - Make a [Pull Request](https://github.com/attempt-this-online/attempt-this-online/pulls) to add the runner for
84 |
85 | ## Making Releases
86 | - Update version numbers in `frontend/package.json` and `setup/setup`
87 | - Upgrade dependencies (`cd frontend; npm update; cd ..`)
88 | - Stage and commit changes
89 | - Tag version in git, e.g. `v0.1.2`
90 | - Push `main` **and the new tag** to GitHub
91 | - Build package `./build`
92 | - Upload `setup/setup` and `dist/attempt_this_online.tar.gz` to GitHub release
93 | - Set description etc. on GitHub release
94 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # API Reference
2 | The official instance uses the base url `https://ato.pxeger.com`. Note that use of the API on the
3 | official instance must abide by the [Terms of Use](https://ato.pxeger.com/legal#terms-of-use):
4 | **you must have explicit permission from me to use the API**.
5 |
6 | ## Websocket connect `/api/v0/ws/execute`
7 | Socket flow is pretty simple; the sequence of messages looks like this:
8 |
9 | - connect to websocket
10 | - client sends message 1 (as a binary message)
11 | - server sends message 2 (as a binary message)
12 | - server closes connection
13 |
14 | Websocket close codes are:
15 | - Normal closure (1000): everything was ok
16 | - Unsupported data (1003): received a text message instead of a binary message
17 | - Policy violation (1008): request was otherwise invalid. The close reason may contain additional details
18 | - For full details of potential causes of this message, consult [`ato/api.go`](https://github.com/attempt-this-online/attempt-this-online/blob/main/ato/api.go)
19 | - Message too big (1009): request exceeded the maximum size, which is 65536 bytes
20 | - Internal server error (1011): something went wrong inside ATO
21 |
22 | ### Message 1
23 | A [msgpack]-encoded payload - a map with the following string keys:
24 | - `language`: the identifier of the language interpreter or compiler to use. The identifier is a filename from the
25 | [`runners/` directory]
26 | - `code`: a binary containing the program data
27 | - `input`: a binary containing the data to be passed to the standard input of the program
28 | - `options`: an array of binaries - command-line arguments to be passed to the **interpreter or compiler**
29 | - `arguments`: an array of binaries - command-line arguments to be passed to the **program itself**
30 | - `timeout`: (optional) an integer which specifies the duration in seconds for which the program is allowed to run. Must
31 | be less than or equal to 60. If not specified, 60 is used.
32 |
33 | Typing is fairly lax; strings will be accepted in place of binaries (they will be encoded in UTF-8).
34 |
35 | ### Message 2
36 | A [msgpack]-encoded payload - a map with the following string keys:
37 | - `stdout`: the standard output from the program and compilation (limited to 128 KiB)
38 | - `stderr`: the standard error from the program and compilation (limited to 32 KiB)
39 | - `status_type`: the reason the process ended - one of:
40 | - `exited`: terminated normally by returning from `main` or calling `exit`
41 | - `killed`: terminated by a signal; only happens on timeout or if the process killed itself for some reason
42 | - `core_dumped`: core dumped, e.g. due to a segmentation fault
43 | - `unknown`: meaning of the value is not known; should never normally happen
44 | - `status_value`: the status code of the end of the process. Its exact meaning depends on `status_type`:
45 | - `exited`: the exit code that the program returned
46 | - `killed`: the number of the signal that killed the process (see [`signal(7)`])
47 | - `core_dumped`: the number of the signal that caused the process to dump its core (see [`signal(7)`], [`core(5)`])
48 | - `unknown`: always `-1`
49 | - `timed_out`: whether the process had to be killed because it overran its 60 second timeout. If this is the case,
50 | the process will have been killed by `SIGKILL` (ID 9)
51 | - `real`: real elapsed time in nanoseconds
52 | - `kernel`: CPU nanoseconds spent in kernel mode
53 | - `user`: CPU nanoseconds spent in user mode
54 | - `max_mem`: total maximum memory usage at any one time, in kilobytes
55 | - `waits`: number of voluntary context switches
56 | - `preemptions`: number of involuntary context switches
57 | - `major_page_faults`: number of major page faults (where a memory page needed to be brought from the disk)
58 | - `minor_page_faults`: number of minor page faults
59 | - `input_ops`: number of input operations
60 | - `output_ops`: number of output operations
61 |
62 | ## GET `/api/v0/metadata`
63 | ### Request
64 | No parameters required.
65 |
66 | ### Response
67 | A [msgpack]-encoded payload - a map from language IDs to a dictionary of their properties. For example (in JSON):
68 |
69 | ```json
70 | { "python": { "name": "Python", "image": "python:3-buster" } }
71 | ```
72 |
73 | The language ID is the filename of a script in the [`runners/` directory], to be passed to the execute endpoint.
74 |
75 | Currently recognised proprties are:
76 | - `name` (string): human-readable name of the language
77 | - `image` (string): Docker image used for execution of the language
78 | - `version` (string): What version constraints ATO places on this language. (Not a guarantee)
79 | - `url` (string): homepage URL for the language
80 | - `sbcs` (boolean): true if the language's byte-counter should assume all characters are one byte long
81 | - `SE_class` (string, optional): language ID used for syntax highlighting when a StackExchange post is generated. If
82 | empty or not present, then language will have no syntax highlighting
83 |
84 | [msgpack]: https://msgpack.org
85 | [`runners/` directory]: https://github.com/attempt-this-online/attempt-this-online/tree/main/runners
86 | [`signal(7)`]: https://man.archlinux.org/man/core/man-pages/signal.7.en
87 | [`core(5)`]: https://man.archlinux.org/man/core/man-pages/core.5.en
88 |
89 |
90 | ## WebSocket API Example
91 | Simple example of using WebSocket API from JavaScript. It corresponds to this [ATO link](https://ato.pxeger.com/run?1=m700OT49OXlVtJKuS6KtiZFS7IKlpSVpuhbbcxMz8zQ0qwuKMvNK0jSUVFOUdBI1rWshslBFC6A0AA).
92 | It prints `stdout: 42` to the JavaScript console.
93 |
94 | ```javascript
95 | // get `msgpack` from https://www.npmjs.com/package/@msgpack/msgpack
96 | // use NPM or something
97 |
98 | function ato_run()
99 | {
100 | let socket = new WebSocket("wss://ato.pxeger.com/api/v0/ws/execute");
101 | socket.onopen = () => socket.send(msgpack.encode({
102 | language: "c_gcc", // get this from https://github.com/attempt-this-online/attempt-this-online/tree/main/runners
103 | code: 'main(){printf("%d",a);}',
104 | input: "",
105 | options: ["-Da=42"],
106 | arguments: [],
107 | timeout: 60,
108 | }));
109 | socket.onmessage = async event => {
110 | let response = await msgpack.decodeAsync(event.data.stream());
111 | console.log("stdout:", new TextDecoder().decode(response.stdout));
112 | }
113 | }
114 | ```
115 |
--------------------------------------------------------------------------------
/setup/nginx.conf:
--------------------------------------------------------------------------------
1 | ## Adapted from a configuration file generated by https://nginxconfig.io
2 |
3 | user http;
4 | worker_processes auto;
5 | worker_rlimit_nofile 65535;
6 |
7 | events {
8 | multi_accept on;
9 | worker_connections 65535;
10 | }
11 |
12 | http {
13 | # Connection header for WebSocket reverse proxy
14 | map $http_upgrade $connection_upgrade {
15 | default upgrade;
16 | '' close;
17 | }
18 |
19 | # General
20 | charset utf-8;
21 | sendfile on;
22 | tcp_nopush on;
23 | tcp_nodelay on;
24 | server_tokens off;
25 | log_not_found off;
26 | types_hash_max_size 4096;
27 | types_hash_bucket_size 64;
28 | client_max_body_size 16M;
29 |
30 | # MIME
31 | include mime.types;
32 | default_type application/octet-stream;
33 |
34 | # Logging
35 | access_log /var/log/nginx/access.log;
36 | error_log /var/log/nginx/error.log warn;
37 |
38 | # SSL
39 | ssl_session_timeout 1d;
40 | ssl_session_cache shared:SSL:10m;
41 | ssl_session_tickets off;
42 |
43 | # Diffie-Hellman parameter for DHE ciphersuites
44 | ssl_dhparam /etc/nginx/dhparam.pem;
45 |
46 | # Mozilla Intermediate configuration
47 | ssl_protocols TLSv1.2 TLSv1.3;
48 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
49 |
50 | # OCSP Stapling
51 | ssl_stapling on;
52 | ssl_stapling_verify on;
53 | resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
54 | resolver_timeout 2s;
55 |
56 | # Rate limit - TODO: investigate whether this is sufficient
57 | limit_req_zone $binary_remote_addr zone=limit_api:8M rate=20r/m;
58 |
59 | server {
60 | listen 443 ssl http2;
61 | listen [::]:443 ssl http2;
62 | server_name ato.pxeger.com; #### CHANGE ####
63 | root /usr/local/share/ATO/public;
64 |
65 | # SSL
66 | ssl_certificate /etc/letsencrypt/live/ato.pxeger.com/fullchain.pem; #### CHANGE ###
67 | ssl_certificate_key /etc/letsencrypt/live/ato.pxeger.com/privkey.pem; #### CHANGE ###
68 | ssl_trusted_certificate /etc/letsencrypt/live/ato.pxeger.com/chain.pem; #### CHANGE ###
69 |
70 | # Security headers
71 | add_header X-Frame-Options "SAMEORIGIN" always;
72 | add_header X-XSS-Protection "1; mode=block" always;
73 | add_header X-Content-Type-Options "nosniff" always;
74 | add_header Referrer-Policy "no-referrer-when-downgrade" always;
75 | add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
76 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
77 |
78 | # .files
79 | location ~ /\.(?!well-known) {
80 | deny all;
81 | }
82 |
83 | error_page 404 /404.html;
84 | index index.html;
85 | location / {
86 | try_files $uri $uri.html $uri/ =404;
87 | }
88 |
89 | # Reverse proxy
90 | location /api {
91 | limit_req zone=limit_api burst=100 nodelay;
92 |
93 | proxy_pass http://127.0.0.1:4568;
94 | proxy_http_version 1.1;
95 | proxy_cache_bypass $http_upgrade;
96 |
97 | # Proxy headers
98 | proxy_set_header Upgrade $http_upgrade;
99 | proxy_set_header Connection $connection_upgrade;
100 | proxy_set_header Host $host;
101 | proxy_set_header X-Real-IP $remote_addr;
102 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
103 | proxy_set_header X-Forwarded-Proto $scheme;
104 | proxy_set_header X-Forwarded-Host $host;
105 | proxy_set_header X-Forwarded-Port $server_port;
106 |
107 | # Proxy timeouts
108 | proxy_connect_timeout 90s;
109 | proxy_send_timeout 90s;
110 | proxy_read_timeout 90s;
111 | }
112 |
113 | # favicon.ico
114 | location = /favicon.ico {
115 | log_not_found off;
116 | access_log off;
117 | }
118 |
119 | # robots.txt
120 | location = /robots.txt {
121 | log_not_found off;
122 | access_log off;
123 | }
124 |
125 | # Assets
126 | location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {
127 | expires 7d;
128 | access_log off;
129 | }
130 |
131 | # SVG, Fonts
132 | location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {
133 | add_header Access-Control-Allow-Origin "*";
134 | expires 7d;
135 | access_log off;
136 | }
137 |
138 | # GZIP
139 | gzip on;
140 | gzip_vary on;
141 | gzip_proxied any;
142 | gzip_comp_level 6;
143 | gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
144 |
145 | }
146 |
147 | # www. redirect
148 | server {
149 | listen 443 ssl http2 default_server;
150 | listen [::]:443 ssl http2 default_server;
151 | server_name www.ato.pxeger.com;
152 |
153 | # SSL
154 | ssl_certificate /etc/letsencrypt/live/ato.pxeger.com/fullchain.pem; #### CHANGE ###
155 | ssl_certificate_key /etc/letsencrypt/live/ato.pxeger.com/privkey.pem; #### CHANGE ###
156 | ssl_trusted_certificate /etc/letsencrypt/live/ato.pxeger.com/chain.pem; #### CHANGE ###
157 | return 301 https://ato.pxeger.com$request_uri; #### CHANGE ###
158 | }
159 |
160 | # HTTP redirect
161 | server {
162 | listen 80 default_server;
163 | listen [::]:80 default_server;
164 | # TODO: is this neccessary when we use default_server?
165 | # server_name "" *.ato.pxeger.com ato.pxeger.com;
166 |
167 | location ^~ /.well-known/acme-challenge/ {
168 | root /var/www/_letsencrypt;
169 | }
170 |
171 | location / {
172 | return 308 https://ato.pxeger.com$request_uri; #### CHANGE ###
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/frontend/pages/preferences.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowLeftIcon } from '@heroicons/react/outline';
2 | import Head from 'next/head';
3 | import { useRouter } from 'next/router';
4 | import { useSelector, useDispatch } from 'react-redux';
5 |
6 | import useSystemThemePreference from 'lib/useSystemThemePreference';
7 | import Footer from 'components/footer';
8 | import ResizeableText from 'components/resizeableText';
9 |
10 | const BIG_TEXT_BOXES_EXPLANATION: Record = {
11 | false: 'Demo of small text box (always minimum possible height)',
12 | true: 'Demo of big text box (always at least 3 lines high)',
13 | };
14 |
15 | export default function Preferences() {
16 | const router = useRouter();
17 | const dispatch = useDispatch();
18 | const systemThemePreference = useSystemThemePreference();
19 | const theme = useSelector((state: any) => state.theme);
20 | const handleThemeChange = async (event: any) => {
21 | dispatch({ type: 'setTheme', theme: event.target.value });
22 | };
23 | const fontLigaturesEnabled = useSelector((state: any) => state.fontLigaturesEnabled);
24 | const handleFontLigaturesChange = async (event: any) => {
25 | dispatch({ type: 'setFontLigaturesEnabled', fontLigaturesEnabled: event.target.checked });
26 | };
27 | const fullWidthMode = useSelector((state: any) => state.fullWidthMode);
28 | const handleFullWidthModeChange = async (event: any) => {
29 | dispatch({ type: 'setFullWidthMode', fullWidthMode: event.target.checked });
30 | };
31 | const bigTextBoxes = useSelector((state: any) => state.bigTextBoxes);
32 | const handleBigTextBoxesChange = async (event: any) => {
33 | dispatch({ type: 'setBigTextBoxes', bigTextBoxes: event.target.checked });
34 | };
35 | const tabBehaviour = useSelector((state: any) => state.tabBehaviour);
36 | const handleTabBehaviourChange = (event: any) => {
37 | dispatch({ type: 'setTabBehaviour', tabBehaviour: event.target.value });
38 | };
39 | return (
40 | <>
41 |
42 | Prefences – Run Dyalog APL
43 |
44 |
45 |
49 |
50 |
53 |
54 | Preferences
55 |
56 |
57 |
58 | Run Dyalog APL stores your preferences locally in your browser, and they are never
59 | shared with anyone.
60 |
61 |
117 |
130 |
131 |
132 |
133 | >
134 | );
135 | }
136 |
--------------------------------------------------------------------------------
/setup/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | VERSION="0.1.16"
6 |
7 | error() {
8 | echo "$@" >&2; exit 2
9 | }
10 |
11 | # make sure this is arch linux
12 | # shellcheck disable=SC1091
13 | [ -f /etc/arch-release ] || error this script only works on Arch Linux
14 |
15 | # make sure a valid domain name was provided (this isn't perfect validation, but it's good enough to check that it's not
16 | # just garbage)
17 | echo "$1" | grep -xEq '([0-9A-Za-z\-]+\.)+[0-9A-Za-z]+' || error you must provide a valid domain name as the first command-line argument
18 |
19 | whoami | grep -xFq root || error you must run this script as root
20 |
21 | # check for apparmor kernel parameter TODO
22 | # [ -e /sys/kernel/security/apparmor/profiles ] || error you must enable apparmor in the kernel - see 'https://wiki.archlinux.org/index.php/AppArmor'
23 |
24 | # install dependencies
25 | # - bubblewrap: primary sandboxing mechanism
26 | # - certbot: generate SSL certificates for nginx
27 | # - cronie: automate renewal of certbot certificates, cache cleaning, etc.
28 | # - nginx: web server
29 | # - nginx-mod-modsecurity: web application firewall
30 | # - python: runtime for API
31 | # - sed: configuration editing on installation
32 | # - skopeo: for docker image extraction
33 | # - sudo: privilege management for sandboxing
34 | # - zsh: for running the runner scripts
35 | [ -z "$ATO_NO_DEPS" ] && pacman -Syu --noconfirm --needed \
36 | bubblewrap \
37 | certbot \
38 | cronie \
39 | nginx \
40 | nginx-mod-modsecurity \
41 | python \
42 | sed \
43 | skopeo \
44 | sudo \
45 | zsh
46 |
47 | # don't use /tmp because it has weird permissions
48 | mkdir -p /var/cache/ATO
49 |
50 | tar -xf "dist/attempt_this_online.tar.gz" -C /var/cache/ATO
51 |
52 | cd /var/cache/ATO/attempt_this_online
53 |
54 | mkdir -p \
55 | /usr/local/lib/ATO \
56 | /usr/local/share/ATO
57 |
58 | # configure ATO user
59 | # useradd -rs /usr/bin/nologin -md /var/lib/ATO_home ato
60 |
61 | # install backend
62 | install -m 500 -o ato -g ato server /usr/local/lib/ATO/
63 |
64 | # install runners
65 | cp -RT runners /usr/local/share/ATO/runners
66 | chown -R ato:ato /usr/local/share/ATO/runners
67 | chmod -R a+rX-w /usr/local/share/ATO/runners
68 |
69 | # setup apparmor TODO
70 | # systemctl enable --now apparmor.service
71 |
72 | install -m 500 -o ato -g ato sandbox /usr/local/bin/ATO_sandbox
73 | install -m 500 -o ato -g ato wrapper /usr/local/bin/ATO_wrapper
74 |
75 | # install static files
76 | cp -RT public /usr/local/share/ATO/public
77 | for format in woff woff2 ttf; do
78 | curl -sSL "https://raw.githubusercontent.com/tonsky/FiraCode/5.2/distr/$format/FiraCode-Regular.$format" -o "/usr/local/share/ATO/public/FiraCode-Regular.$format"
79 | done
80 | for format in woff woff2 otf; do
81 | for variant in Bold Regular; do
82 | curl -sSL "https://github.com/pxeger/Cantarell/raw/v0.301-2/Cantarell$variant.$format" -o "/usr/local/share/ATO/public/Cantarell-$variant.$format"
83 | done
84 | done
85 | chown -R http:http /usr/local/share/ATO/public
86 | chmod -R a+rX-w /usr/local/share/ATO/public
87 |
88 | # configure nginx
89 | sed -i "s/ato.pxeger.com/$1/g" setup/nginx.conf
90 | install -m 644 -o root -g root setup/nginx.conf /etc/nginx/
91 | install -m 644 -o root -g root setup/modsecurity.conf /etc/nginx/
92 | # don't waste time generating DH params unless necessary
93 | [ -f /etc/nginx/dhparam.pem ] || openssl dhparam -out /etc/nginx/dhparam.pem 2048
94 | systemctl enable nginx.service
95 |
96 | # configure Let's Encrypt
97 | install -dm 750 -o root -g http /var/www/_letsencrypt
98 | # we can't start nginx because the certificate isn't there, but we can't obtain a certificate if nginx isn't running
99 | # so start a temporary Python web server
100 | # TODO(pxeger): work around for letsencrypt issues without needing a temp Python server (certbot standalone mode?)
101 | python -m http.server --directory /var/www/_letsencrypt 80 & PYSERVERPID="$!"
102 | # TODO get option for email
103 | certbot certonly -n --webroot -w /var/www/_letsencrypt -d "$1" -d "www.$1" --agree-tos --register-unsafely-without-email
104 | # end that Python web server
105 | # kill -TERM "$PYSERVERPID"
106 | # configure cronie to automatically renew certificates
107 | # (every day at 10:07)
108 | echo "7 10 * * * certbot renew && systemctl reload nginx" >> /var/spool/cron/root
109 | systemctl enable --now cronie.service
110 |
111 | # install yargs
112 | install -m 555 -o root -g root yargs /usr/local/bin/ATO_yargs
113 |
114 | # steal a statically linked bash from Debian
115 | curl -L https://github.com/attempt-this-online/static-bash/releases/download/v5.1-6/bash > /usr/local/bin/ATO_bash
116 | chmod 555 /usr/local/bin/ATO_bash
117 |
118 | # configure service
119 | install -m 555 -o root -g root setup/ATO /usr/local/bin/
120 | mkdir -p /usr/local/lib/systemd/system/
121 | install -m 644 -o root -g root setup/ATO.service /usr/local/lib/systemd/system/
122 | systemctl enable ATO.service
123 |
124 | # generate flags to be used as proof of compromise
125 | flag() {
126 | # shellcheck disable=SC2018
127 | tr < /dev/urandom -dc a-z | head -c 32 > "$1"
128 | chown "$2" "$1"
129 | chmod 400 "$1"
130 | }
131 | flag /root/flag root:root
132 | flag /var/lib/ATO_home/flag ato:ato
133 |
134 | # Create upper layer filesystem for overlayfs which contains the mount point /ATO pre-made for bwrap - see
135 | # https://github.com/containers/bubblewrap/issues/413
136 | mkdir -p /usr/local/share/ATO/overlayfs_upper/ATO /usr/local/share/ATO/overlayfs_upper/proc /usr/local/share/ATO/overlayfs_upper/dev
137 |
138 | echo Finished system setup.
139 | echo Now extracting Docker images - this will take a long time...
140 |
141 | mkdir -p /usr/local/lib/ATO/env /usr/local/lib/ATO/layers
142 | mkdir -p /var/cache/ATO/images
143 |
144 | [ -z "$ATO_NO_IMAGES" ] && \
145 | while read -r image
146 | do
147 | # ignore blank lines or comments
148 | [ -z "$image" ] || [ "$image" = "#" ] && continue
149 | echo "$image"
150 |
151 | skopeo copy docker://"$image" docker-archive:>(tar -xC /var/cache/ATO/images)
152 | # shellcheck disable=SC2010
153 | ls /var/cache/ATO/images |
154 | grep -Ex '[0-9a-f]+\.tar' |
155 | while read -r filename
156 | do
157 | # skip duplicate layers
158 | layer_id="${filename%.tar}"
159 | [ -d "/usr/local/lib/ATO/layers/$layer_id" ] && continue
160 | mkdir "/usr/local/lib/ATO/layers/$layer_id"
161 | tar -xf "/var/cache/ATO/images/$layer_id.tar" -C \
162 | "/usr/local/lib/ATO/layers/$layer_id"
163 | done
164 |
165 | # replace slash with plus so that it can be used as an individual filename
166 | image_pathsafe="$(echo "$image" | tr '/' '+')"
167 | setup/overlayfs_genfstab "$image_pathsafe" >> /etc/fstab
168 |
169 | # extract environment variables from the image
170 | skopeo inspect docker://"$image" | setup/parse_env > "/usr/local/lib/ATO/env/$image_pathsafe"
171 |
172 | rm -rf /var/cache/ATO/images/*
173 |
174 | done < images.txt
175 |
176 | echo Finished extracting images.
177 | echo Clearing up...
178 |
179 | cd /
180 | rm -rf /var/cache/ATO
181 |
182 | echo Starting up services...
183 | # mount all overlayfs
184 | mount -a
185 | systemctl start nginx.service ATO.service
186 |
187 | echo Finished!
188 |
--------------------------------------------------------------------------------
/setup/setup.ubuntu:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | VERSION="0.1.16"
6 |
7 |
8 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
9 | cd ${SCRIPT_DIR}/..
10 | PROJROOT=`pwd`
11 |
12 |
13 | error() {
14 | echo "$@" >&2; exit 2
15 | }
16 |
17 | # make sure this is ubuntu linux
18 | [ "$(cat /etc/os-release | awk -F'=' '/^NAME/ {print $2}' | sed 's/"//g')" = "Ubuntu" ] || error this script only works on Ubuntu Linux
19 |
20 |
21 | # make sure a valid domain name was provided (this isn't perfect validation, but it's good enough to check that it's not
22 | # just garbage)
23 | echo "$1" | grep -xEq '([0-9A-Za-z\-]+\.)+[0-9A-Za-z]+' || error you must provide a valid domain name as the first command-line argument
24 |
25 | whoami | grep -xFq root || error you must run this script as root
26 |
27 | # check for apparmor kernel parameter TODO
28 | # [ -e /sys/kernel/security/apparmor/profiles ] || error you must enable apparmor in the kernel - see 'https://wiki.archlinux.org/index.php/AppArmor'
29 |
30 | # install dependencies
31 | # - bubblewrap: primary sandboxing mechanism
32 | # - certbot: generate SSL certificates for nginx
33 | # - cronie: automate renewal of certbot certificates, cache cleaning, etc.
34 | # - nginx: web server
35 | # - nginx-mod-modsecurity: web application firewall
36 | # - python: runtime for API
37 | # - sed: configuration editing on installation
38 | # - skopeo: for docker image extraction
39 | # - sudo: privilege management for sandboxing
40 | # - zsh: for running the runner scripts
41 | [ -z "$ATO_NO_DEPS" ] && apt install -y \
42 | bubblewrap \
43 | certbot \
44 | cron \
45 | golang-go \
46 | npm \
47 | nginx \
48 | python3 \
49 | python-is-python3 \
50 | sed \
51 | skopeo \
52 | sudo \
53 | zsh
54 |
55 | # nginx-mod-modsecurity \
56 |
57 | ./build
58 |
59 | # don't use /tmp because it has weird permissions
60 | mkdir -p /var/cache/ATO
61 |
62 | tar -xf "dist/attempt_this_online.tar.gz" -C /var/cache/ATO
63 |
64 | cd /var/cache/ATO/attempt_this_online
65 |
66 | mkdir -p \
67 | /usr/local/lib/ATO \
68 | /usr/local/share/ATO
69 |
70 | # configure ATO user
71 | if ! cat /etc/passwd | grep ato ; then
72 | useradd -rs /usr/bin/nologin -md /var/lib/ATO_home ato
73 | fi
74 |
75 | # install backend
76 | install -m 500 -o ato -g ato server /usr/local/lib/ATO/
77 |
78 | # install runners
79 | cp -RT runners /usr/local/share/ATO/runners
80 | chown -R ato:ato /usr/local/share/ATO/runners
81 | chmod -R a+rX-w /usr/local/share/ATO/runners
82 |
83 | # setup apparmor TODO
84 | # systemctl enable --now apparmor.service
85 |
86 | install -m 500 -o ato -g ato sandbox /usr/local/bin/ATO_sandbox
87 | install -m 500 -o ato -g ato wrapper /usr/local/bin/ATO_wrapper
88 |
89 | # install static files
90 | cp -RT public /usr/local/share/ATO/public
91 | for format in woff woff2 ttf; do
92 | curl -sSL "https://raw.githubusercontent.com/tonsky/FiraCode/5.2/distr/$format/FiraCode-Regular.$format" -o "/usr/local/share/ATO/public/FiraCode-Regular.$format"
93 | done
94 | for format in woff woff2 otf; do
95 | for variant in Bold Regular; do
96 | curl -sSL "https://github.com/pxeger/Cantarell/raw/v0.301-2/Cantarell$variant.$format" -o "/usr/local/share/ATO/public/Cantarell-$variant.$format"
97 | done
98 | done
99 | chown -R www-data:www-data /usr/local/share/ATO/public
100 | chmod -R a+rX-w /usr/local/share/ATO/public
101 |
102 | # configure nginx
103 | sed -i "s/user http;/user www-data;/g" setup/nginx.conf
104 | sed -i "s/ato.pxeger.com/$1/g" setup/nginx.conf
105 | install -m 644 -o root -g root setup/nginx.conf /etc/nginx/
106 | # don't waste time generating DH params unless necessary
107 | [ -f /etc/nginx/dhparam.pem ] || openssl dhparam -out /etc/nginx/dhparam.pem 2048
108 | systemctl enable nginx.service
109 |
110 | # configure Let's Encrypt
111 | install -dm 750 -o root -g www-data /var/www/_letsencrypt
112 | # we can't start nginx because the certificate isn't there, but we can't obtain a certificate if nginx isn't running
113 | # so start a temporary Python web server
114 | # TODO(pxeger): work around for letsencrypt issues without needing a temp Python server (certbot standalone mode?)
115 | python -m http.server --directory /var/www/_letsencrypt 80 & PYSERVERPID="$!"
116 | # TODO get option for email
117 | #certbot certonly -n --webroot -w /var/www/_letsencrypt -d "$1" -d "www.$1" --agree-tos --register-unsafely-without-email
118 |
119 |
120 | # end that Python web server
121 | kill -TERM "$PYSERVERPID"
122 | # configure cronie to automatically renew certificates
123 | # (every day at 10:07)
124 | echo "7 10 * * * certbot renew && systemctl reload nginx" >> /var/spool/cron/root
125 | systemctl enable --now cron.service
126 |
127 | # install yargs
128 | install -m 555 -o root -g root yargs /usr/local/bin/ATO_yargs
129 |
130 | # steal a statically linked bash from Debian
131 | curl -L https://github.com/attempt-this-online/static-bash/releases/download/v5.1-6/bash > /usr/local/bin/ATO_bash
132 | chmod 555 /usr/local/bin/ATO_bash
133 |
134 | # configure service
135 | install -m 555 -o root -g root setup/ATO /usr/local/bin/
136 | mkdir -p /usr/local/lib/systemd/system/
137 | install -m 644 -o root -g root setup/ATO.service /usr/local/lib/systemd/system/
138 | systemctl enable ATO.service
139 |
140 | # generate flags to be used as proof of compromise
141 | flag() {
142 | # shellcheck disable=SC2018
143 | tr < /dev/urandom -dc a-z | head -c 32 > "$1"
144 | chown "$2" "$1"
145 | chmod 400 "$1"
146 | }
147 | flag /root/flag root:root
148 | flag /var/lib/ATO_home/flag ato:ato
149 |
150 | # Create upper layer filesystem for overlayfs which contains the mount point /ATO pre-made for bwrap - see
151 | # https://github.com/containers/bubblewrap/issues/413
152 | mkdir -p /usr/local/share/ATO/overlayfs_upper/ATO /usr/local/share/ATO/overlayfs_upper/proc /usr/local/share/ATO/overlayfs_upper/dev
153 |
154 | echo Finished system setup.
155 | echo Now extracting Docker images - this will take a long time...
156 |
157 | mkdir -p /usr/local/lib/ATO/env /usr/local/lib/ATO/layers
158 | mkdir -p /var/cache/ATO/images
159 |
160 | [ -z "$ATO_NO_IMAGES" ] && \
161 | while read -r image
162 | do
163 | # ignore blank lines or comments
164 | [ -z "$image" ] || [ "$image" = "#" ] && continue
165 | echo "$image"
166 |
167 | skopeo copy docker://"$image" docker-archive:>(tar -xC /var/cache/ATO/images)
168 | # shellcheck disable=SC2010
169 | ls /var/cache/ATO/images |
170 | grep -Ex '[0-9a-f]+\.tar' |
171 | while read -r filename
172 | do
173 | # skip duplicate layers
174 | layer_id="${filename%.tar}"
175 | [ -d "/usr/local/lib/ATO/layers/$layer_id" ] && continue
176 | mkdir "/usr/local/lib/ATO/layers/$layer_id"
177 | tar -xf "/var/cache/ATO/images/$layer_id.tar" -C \
178 | "/usr/local/lib/ATO/layers/$layer_id"
179 | done
180 |
181 | # replace slash with plus so that it can be used as an individual filename
182 | image_pathsafe="$(echo "$image" | tr '/' '+')"
183 | setup/overlayfs_genfstab "$image_pathsafe" >> /etc/fstab
184 |
185 | # extract environment variables from the image
186 | skopeo inspect docker://"$image" | setup/parse_env > "/usr/local/lib/ATO/env/$image_pathsafe"
187 |
188 | rm -rf /var/cache/ATO/images/*
189 |
190 | done < images.txt
191 |
192 | echo Finished extracting images.
193 | echo Clearing up...
194 |
195 | cd /
196 | rm -rf /var/cache/ATO
197 |
198 | echo Starting up services...
199 | # mount all overlayfs
200 | mount -a
201 | systemctl start nginx.service ATO.service
202 |
203 | echo Finished!
204 |
--------------------------------------------------------------------------------
/wrapper.c:
--------------------------------------------------------------------------------
1 | /* timeout -- run a command with bounded time
2 |
3 | Modified by Patrick Reader from the original from GNU Coreutils, which is:
4 | Copyright (C) 2008-2021 Free Software Foundation, Inc.
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see . */
18 |
19 | /* timeout - Start a command, and kill it if the specified timeout expires
20 |
21 | We try to behave like a shell starting a single (foreground) job,
22 | and will kill the job if we receive the alarm signal we setup.
23 | The exit status of the job is returned, or one of these errors:
24 | EXIT_TIMEDOUT 124 job timed out
25 | EXIT_CANCELED 125 internal error
26 | EXIT_CANNOT_INVOKE 126 error executing job
27 | EXIT_ENOENT 127 couldn't find job to exec
28 |
29 | Caveats:
30 | If user specifies the KILL (9) signal is to be sent on timeout,
31 | the monitor is killed and so exits with 128+9 rather than 124.
32 |
33 | If you start a command in the background, which reads from the tty
34 | and so is immediately sent SIGTTIN to stop, then the timeout
35 | process will ignore this so it can timeout the command as expected.
36 | This can be seen with 'timeout 10 dd&' for example.
37 | However if one brings this group to the foreground with the 'fg'
38 | command before the timer expires, the command will remain
39 | in the stop state as the shell doesn't send a SIGCONT
40 | because the timeout process (group leader) is already running.
41 | To get the command running again one can Ctrl-Z, and do fg again.
42 | Note one can Ctrl-C the whole job when in this state.
43 | I think this could be fixed but I'm not sure the extra
44 | complication is justified for this scenario.
45 |
46 | Written by Pádraig Brady. */
47 |
48 | #include
49 | #include
50 | #include
51 | #include
52 | #include
53 | #include
54 | #include
55 | #include
56 | #include
57 | #include
58 | #include
59 | #include
60 | #include
61 | #include
62 |
63 | /* NonStop circa 2011 lacks both SA_RESTART and siginterrupt. */
64 | #ifndef SA_RESTART
65 | #define SA_RESTART 0
66 | #endif
67 |
68 | #define PROGRAM_NAME "timeout"
69 |
70 | // #define AUTHORS proper_name("Padraig Brady")
71 |
72 | #define MAX_TIMEOUT_SECS 60
73 |
74 | #define DPRINTF(d, f, ...) do { \
75 | int _result; \
76 | _result = dprintf(d, f, __VA_ARGS__); \
77 | if (_result < 0) { \
78 | perror("dprintf"); \
79 | return 1; \
80 | } \
81 | } while (0)
82 |
83 | // converts to nanoseconds, using (long long) because the number of nanoseconds in a minute might exceed 2**31
84 | #define TIMESPEC(ts) ((long long)ts.tv_sec * 1000000000LL + (long long)ts.tv_nsec)
85 | #define TIMEVAL(tv) ((long long)tv.tv_sec * 1000000000LL + (long long)tv.tv_usec * 1000LL)
86 |
87 | static int timed_out;
88 | static int term_signal = SIGKILL; /* same default as kill command. */
89 | static int timeout_secs = MAX_TIMEOUT_SECS;
90 | static pid_t monitored_pid;
91 | static bool foreground; /* whether to use another program group. */
92 | static bool preserve_status; /* whether to use a timeout status or not. */
93 |
94 | /* Start the timeout after which we'll receive a SIGALRM. */
95 | static void
96 | settimeout(bool warn)
97 | {
98 | struct timespec ts = { timeout_secs, 0 };
99 | struct itimerspec its = { { 0, 0 }, ts };
100 | timer_t timerid;
101 | if (timer_create(CLOCK_REALTIME, NULL, &timerid) == 0) {
102 | if (timer_settime(timerid, 0, &its, NULL) == 0)
103 | return;
104 | else {
105 | if (warn)
106 | perror("warning: timer_settime");
107 | timer_delete(timerid);
108 | }
109 | } else if (warn && errno != ENOSYS)
110 | perror("warning: timer_create");
111 |
112 | /* fallback to single second resolution provided by alarm(). */
113 | alarm(timeout_secs);
114 | }
115 |
116 | /* send SIG avoiding the current process. */
117 |
118 | static int
119 | send_sig(pid_t where, int sig)
120 | {
121 | /* If sending to the group, then ignore the signal,
122 | so we don't go into a signal loop. Note that this will ignore any of the
123 | signals registered in install_cleanup(), that are sent after we
124 | propagate the first one, which hopefully won't be an issue. Note this
125 | process can be implicitly multithreaded due to some timer_settime()
126 | implementations, therefore a signal sent to the group, can be sent
127 | multiple times to this process. */
128 | if (where == 0)
129 | signal(sig, SIG_IGN);
130 | return kill(where, sig);
131 | }
132 |
133 | /* Signal handler which is required for sigsuspend() to be interrupted
134 | whenever SIGCHLD is received. */
135 | static void
136 | chld(int sig)
137 | {
138 | }
139 |
140 | static void
141 | cleanup(int sig)
142 | {
143 | if (sig == SIGALRM) {
144 | timed_out = 1;
145 | sig = term_signal;
146 | }
147 | if (monitored_pid) {
148 | /* Send the signal directly to the monitored child,
149 | in case it has itself become group leader,
150 | or is not running in a separate group. */
151 | send_sig(monitored_pid, sig);
152 | } else /* we're the child or the child is not exec'd yet. */
153 | _exit(128 + sig);
154 | }
155 |
156 | static void
157 | unblock_signal(int sig)
158 | {
159 | sigset_t unblock_set;
160 | sigemptyset(&unblock_set);
161 | sigaddset(&unblock_set, sig);
162 | if (sigprocmask(SIG_UNBLOCK, &unblock_set, NULL) != 0)
163 | perror("warning: sigprocmask");
164 | }
165 |
166 | static void
167 | install_sigchld(void)
168 | {
169 | struct sigaction sa;
170 | sigemptyset(&sa.sa_mask); /* Allow concurrent calls to handler */
171 | sa.sa_handler = chld;
172 | sa.sa_flags = SA_RESTART; /* Restart syscalls if possible, as that's
173 | more likely to work cleanly. */
174 |
175 | sigaction(SIGCHLD, &sa, NULL);
176 |
177 | /* We inherit the signal mask from our parent process,
178 | so ensure SIGCHLD is not blocked. */
179 | unblock_signal(SIGCHLD);
180 | }
181 |
182 | static void
183 | handle_usr1(int sig, siginfo_t *info, void *_ucontext) {
184 | assert(sig == SIGUSR1);
185 | union sigval sigval = info->si_value;
186 | int signal = sigval.sival_int;
187 | kill(monitored_pid, signal);
188 | }
189 |
190 | static void
191 | install_cleanup(int sigterm)
192 | {
193 | struct sigaction sa;
194 | sigemptyset(&sa.sa_mask); /* Allow concurrent calls to handler */
195 | sa.sa_handler = cleanup;
196 | sa.sa_flags = SA_RESTART; /* Restart syscalls if possible, as that's
197 | more likely to work cleanly. */
198 |
199 | sigaction(SIGALRM, &sa, NULL); /* our timeout. */
200 | sigaction(SIGINT, &sa, NULL); /* Ctrl-C at terminal for example. */
201 | sigaction(SIGQUIT, &sa, NULL); /* Ctrl-\ at terminal for example. */
202 | sigaction(SIGHUP, &sa, NULL); /* terminal closed for example. */
203 | sigaction(SIGTERM, &sa, NULL); /* if we're killed, stop monitored proc. */
204 | sigaction(sigterm, &sa, NULL); /* user specified termination signal. */
205 | }
206 |
207 | /* Block all signals which were registered with cleanup() as the signal
208 | handler, so we never kill processes after waitpid() returns.
209 | Also block SIGCHLD to ensure it doesn't fire between
210 | waitpid() polling and sigsuspend() waiting for a signal.
211 | Return original mask in OLD_SET. */
212 | static void
213 | block_cleanup_and_chld(int sigterm, sigset_t* old_set)
214 | {
215 | sigset_t block_set;
216 | sigemptyset(&block_set);
217 |
218 | sigaddset(&block_set, SIGALRM);
219 | sigaddset(&block_set, SIGINT);
220 | sigaddset(&block_set, SIGQUIT);
221 | sigaddset(&block_set, SIGHUP);
222 | sigaddset(&block_set, SIGTERM);
223 | sigaddset(&block_set, sigterm);
224 |
225 | sigaddset(&block_set, SIGCHLD);
226 |
227 | if (sigprocmask(SIG_BLOCK, &block_set, old_set) != 0)
228 | perror("warning: sigprocmask");
229 | }
230 |
231 | int parse_int(char* string) {
232 | int value = 0;
233 | if (string[0] < '1') {
234 | // invalid integer (must be >= 0)
235 | exit(2);
236 | }
237 | for (int i = 0; string[i]; i++) {
238 | if (string[i] < '0' || string[i] > '9') {
239 | // invalid integer
240 | exit(2);
241 | }
242 | value *= 10;
243 | value += string[i] - '0';
244 | }
245 | return value;
246 | }
247 |
248 | int main(int argc, char** argv)
249 | {
250 | if (argc != 3) {
251 | // file descriptor and timeout must be given as argument
252 | return 2;
253 | }
254 | int fd = parse_int(argv[1]);
255 | timeout_secs = parse_int(argv[2]);
256 | if (timeout_secs < 1 || timeout_secs > MAX_TIMEOUT_SECS) {
257 | return 2;
258 | }
259 |
260 | errno = 0;
261 | fcntl(fd, F_GETFD);
262 | if (errno) {
263 | perror("wrapper");
264 | return errno;
265 | }
266 |
267 | preserve_status = true;
268 |
269 | /* Ensure we're in our own group so all subprocesses can be killed.
270 | Note we don't just put the child in a separate group as
271 | then we would need to worry about foreground and background groups
272 | and propagating signals between them. */
273 | if (!foreground)
274 | setpgid(0, 0);
275 |
276 | /* Setup handlers before fork() so that we
277 | handle any signals caused by child, without races. */
278 | install_cleanup(term_signal);
279 | signal(SIGTTIN, SIG_IGN); /* Don't stop if background child needs tty. */
280 | signal(SIGTTOU, SIG_IGN); /* Don't stop if background child needs tty. */
281 | install_sigchld(); /* Interrupt sigsuspend() when child exits. */
282 |
283 | struct timespec start_time;
284 | int result = clock_gettime(CLOCK_MONOTONIC, &start_time);
285 | if (result == -1) {
286 | perror("clock_gettime");
287 | return 1;
288 | }
289 |
290 | monitored_pid = fork();
291 | if (monitored_pid == -1) {
292 | perror("fork system call failed");
293 | return 2;
294 | } else if (monitored_pid == 0) { /* child */
295 | /* exec doesn't reset SIG_IGN -> SIG_DFL. */
296 | signal(SIGTTIN, SIG_DFL);
297 | signal(SIGTTOU, SIG_DFL);
298 |
299 | close(fd);
300 | execlp("/ATO/runner", "/ATO/runner", (char*)NULL);
301 | perror("execlp");
302 | return 1;
303 | } else {
304 | pid_t wait_result;
305 | int status;
306 | struct rusage rusage;
307 | char* status_type = "unknown";
308 |
309 | /* We configure timers so that SIGALRM is sent on expiry.
310 | Therefore ensure we don't inherit a mask blocking SIGALRM. */
311 | unblock_signal(SIGALRM);
312 |
313 | /* setup handler for kill-child-process signal */
314 | struct sigaction sa;
315 | sigemptyset(&sa.sa_mask);
316 | sa.sa_sigaction = handle_usr1;
317 | sa.sa_flags = SA_SIGINFO;
318 | int err = sigaction(SIGUSR1, &sa, NULL);
319 | if (err < 0) {
320 | perror("sigaction");
321 | }
322 |
323 | settimeout(true);
324 |
325 | /* Ensure we don't cleanup() after waitpid() reaps the child,
326 | to avoid sending signals to a possibly different process. */
327 | sigset_t cleanup_set;
328 | block_cleanup_and_chld(term_signal, &cleanup_set);
329 |
330 | while ((wait_result = waitpid(monitored_pid, &status, WNOHANG)) == 0)
331 | sigsuspend(&cleanup_set); /* Wait with cleanup signals unblocked. */
332 |
333 | struct timespec end_time;
334 | result = clock_gettime(CLOCK_MONOTONIC, &end_time);
335 |
336 | if (wait_result < 0) {
337 | /* shouldn't happen. */
338 | perror("error waiting for command");
339 | status = -1;
340 | } else {
341 | if (WIFEXITED(status)) {
342 | status_type = "exited";
343 | status = WEXITSTATUS(status);
344 | } else if (WIFSIGNALED(status)) {
345 | status_type = "killed";
346 | if (WCOREDUMP(status))
347 | status_type = "core_dump";
348 | status = WTERMSIG(status);
349 | } else {
350 | /* shouldn't happen. */
351 | status = -1;
352 | }
353 | }
354 |
355 | result = getrusage(RUSAGE_CHILDREN, &rusage);
356 | if (result == -1) {
357 | perror("getrusage");
358 | return 1;
359 | }
360 |
361 | DPRINTF(fd, "%s", "{");
362 | DPRINTF(fd, "\"timed_out\":%s,", timed_out ? "true" : "false");
363 | DPRINTF(fd, "\"status_type\":\"%s\",", status_type);
364 | DPRINTF(fd, "\"status_value\":%d,", status);
365 | DPRINTF(fd, "\"user\":%lld,", TIMEVAL(rusage.ru_utime));
366 | DPRINTF(fd, "\"kernel\":%lld,", TIMEVAL(rusage.ru_stime));
367 | DPRINTF(fd, "\"real\":%lld,", TIMESPEC(end_time) - TIMESPEC(start_time));
368 | DPRINTF(fd, "\"max_mem\":%ld,", rusage.ru_maxrss);
369 | DPRINTF(fd, "\"major_page_faults\":%ld,", rusage.ru_majflt);
370 | DPRINTF(fd, "\"minor_page_faults\":%ld,", rusage.ru_minflt);
371 | DPRINTF(fd, "\"input_ops\":%ld,", rusage.ru_inblock);
372 | DPRINTF(fd, "\"output_ops\":%ld,", rusage.ru_oublock);
373 | DPRINTF(fd, "\"waits\":%ld,", rusage.ru_nvcsw);
374 | DPRINTF(fd, "\"preemptions\":%ld", rusage.ru_nivcsw);
375 | DPRINTF(fd, "%s\n", "}");
376 |
377 | return 0;
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/frontend/pages/run.tsx:
--------------------------------------------------------------------------------
1 | // import localforage from 'localforage';
2 | import Head from 'next/head';
3 | import { useRouter } from 'next/router';
4 | import { connect } from 'react-redux';
5 | import {
6 | SyntheticEvent, useMemo, useRef, useState, useEffect,
7 | } from 'react';
8 | import { escape, throttle } from 'lodash';
9 | import { ExclamationCircleIcon, ClipboardCopyIcon, XIcon } from '@heroicons/react/solid';
10 |
11 | import CollapsibleText from 'components/collapsibleText';
12 | import ResizeableText from 'components/resizeableText';
13 | import LanguageSelector from 'components/languageSelector';
14 | import Footer from 'components/footer';
15 | import Navbar from 'components/navbar';
16 | import Notification from 'components/notification';
17 | import { ArgvList, parseList } from 'components/argvList';
18 | import * as API from 'lib/api';
19 | import { save, load } from 'lib/urls';
20 | import { ENCODERS, DECODERS } from 'lib/encoding';
21 | import stringLength from 'lib/stringLength';
22 |
23 | const NEWLINE = '\n'.charCodeAt(0);
24 |
25 | const EMPTY_BUFFER = new Uint8Array([]);
26 |
27 | const SIGNALS: Record = {
28 | 1: 'SIGHUP',
29 | 2: 'SIGINT',
30 | 3: 'SIGQUIT',
31 | 4: 'SIGILL',
32 | 5: 'SIGTRAP',
33 | 6: 'SIGABRT',
34 | 7: 'SIGBUS',
35 | 8: 'SIGFPE',
36 | 9: 'SIGKILL',
37 | 10: 'SIGUSR1',
38 | 11: 'SIGSEGV',
39 | 12: 'SIGUSR2',
40 | 13: 'SIGPIPE',
41 | 14: 'SIGALRM',
42 | 15: 'SIGTERM',
43 | // 16 unused
44 | 17: 'SIGCHLD',
45 | 18: 'SIGCONT',
46 | 19: 'SIGSTOP',
47 | 20: 'SIGTSTP',
48 | 21: 'SIGTTIN',
49 | 22: 'SIGTTOU',
50 | 23: 'SIGURG',
51 | 24: 'SIGXCPU',
52 | 25: 'SIGXFSZ',
53 | 26: 'SIGVTALRM',
54 | 27: 'SIGPROF',
55 | 28: 'SIGWINCH',
56 | 29: 'SIGIO',
57 | 30: 'SIGPWR',
58 | 31: 'SIGSYS',
59 | };
60 |
61 | const statusToString = (type: 'exited' | 'killed' | 'core_dumped' | 'unknown', value: number) => {
62 | switch (type) {
63 | case 'exited':
64 | return `Exited with status code ${value}`;
65 | case 'killed':
66 | return `Killed by ${SIGNALS[value] || 'unknown signal'}`;
67 | case 'core_dumped':
68 | return `Killed by ${SIGNALS[value] || 'unknown signal'} and dumped core`;
69 | default:
70 | return 'Unknown status';
71 | }
72 | };
73 |
74 | const pluralise = (string: string, n: number) => (n === 1 ? string : `${string}s`);
75 |
76 | function _Run(
77 | { languages, fullWidthMode }: {
78 | languages: Record,
79 | fullWidthMode: boolean
80 | },
81 | ) {
82 | const router = useRouter();
83 |
84 | let [language, setLanguage] = useState(null);
85 | const [header, setHeader] = useState('');
86 | const [headerEncoding, setHeaderEncoding] = useState('utf-8');
87 | const [code, setCode] = useState('');
88 | // default code encoding depends on language selected
89 | const [codeEncoding, setCodeEncoding] = useState(null);
90 | const [footer, setFooter] = useState('');
91 | const [footerEncoding, setFooterEncoding] = useState('utf-8');
92 | const [input, setInput] = useState('');
93 | const [inputEncoding, setInputEncoding] = useState('utf-8');
94 | const [stdout, setStdout] = useState(EMPTY_BUFFER);
95 | const [stdoutEncoding, setStdoutEncoding] = useState('utf-8');
96 | const [stderr, setStderr] = useState(EMPTY_BUFFER);
97 | const [stderrEncoding, setStderrEncoding] = useState('utf-8');
98 |
99 | const [statusType, setStatusType] = useState<'exited' | 'killed' | 'core_dumped' | 'unknown' | null>(null);
100 | const [statusValue, setStatusValue] = useState(null);
101 | const [timing, setTiming] = useState('');
102 | const [timingOpen, setTimingOpen] = useState(false);
103 |
104 | const [languageSelectorOpen, setLanguageSelectorOpen] = useState(false);
105 |
106 | const [[optionsString, options], setOptions] = useState<[string, string[] | null]>(['', []]);
107 | const [[argsString, programArguments], setProgramArguments] = useState<[string, string[] | null]>(['', []]);
108 |
109 | const [submitting, setSubmitting] = useState(false);
110 | const [notifications, setNotifications] = useState<{ id: number, text: string }[]>([]);
111 | const dismissNotification = (target: number) => {
112 | setNotifications(notifications.filter(({ id }) => id !== target));
113 | };
114 | const notify = (text: string) => {
115 | // avoid double notifications
116 | if (notifications.find(n => n.text === text) === undefined) {
117 | setNotifications([{ id: Math.random(), text }, ...notifications]);
118 | }
119 | };
120 |
121 | // when language changes, set encoding to sbcs if it uses it by default
122 | useEffect(() => {
123 | if (!languages) {
124 | // not loaded yet
125 | return;
126 | } else if (!language) {
127 | // not chosen yet
128 | return;
129 | }
130 | setCodeEncoding(languages[language].sbcs ? 'sbcs' : 'utf-8');
131 | }, [language, languages]);
132 |
133 | const submit = async (event: SyntheticEvent) => {
134 | event.preventDefault();
135 | setSubmitting(true);
136 |
137 | const headerBytes = ENCODERS[headerEncoding](header);
138 | const footerBytes = ENCODERS[footerEncoding](footer);
139 | const codeBytes = ENCODERS[codeEncoding!](code);
140 | const combined = new Uint8Array([
141 | ...headerBytes,
142 | ...(headerBytes.length === 0 ? [] : [NEWLINE]),
143 | ...codeBytes,
144 | ...(footerBytes.length === 0 ? [] : [NEWLINE]),
145 | ...footerBytes,
146 | ]);
147 | const inputBytes = ENCODERS[inputEncoding](input);
148 |
149 | let data;
150 |
151 | try {
152 | data = await API.runWs({
153 | language: language!,
154 | input: inputBytes,
155 | code: combined,
156 | options: options!,
157 | programArguments: programArguments!,
158 | timeout: 60, // TODO
159 | });
160 | } catch (e) {
161 | console.error(e);
162 | notify('An error occurred; see the console for details');
163 | setSubmitting(false);
164 | return;
165 | }
166 |
167 | setStdout(data.stdout);
168 | setStderr(data.stderr);
169 |
170 | setStatusType(data.status_type);
171 | setStatusValue(data.status_value);
172 |
173 | setTiming(
174 | `
175 | Real time: ${data.real / 1e9} s
176 | Kernel time: ${data.kernel / 1e9} s
177 | User time: ${data.kernel / 1e9} s
178 | Maximum lifetime memory usage: ${data.max_mem} KiB
179 | Waits (voluntary context switches): ${data.waits}
180 | Preemptions (involuntary context switches): ${data.preemptions}
181 | Minor page faults: ${data.minor_page_faults}
182 | Major page faults: ${data.major_page_faults}
183 | Input operations: ${data.input_ops}
184 | Output operations: ${data.output_ops}
185 | `.trim().split('\n').map(s => s.trim()).join('\n'),
186 | );
187 |
188 | if (data.timed_out) {
189 | notify('The program ran for over 60 seconds and timed out');
190 | }
191 | if (data.stdout_truncated) {
192 | notify('stdout exceeded 128KiB and was truncated');
193 | }
194 | if (data.stderr_truncated) {
195 | notify('stderr exceeded 32KiB and was truncated');
196 | }
197 |
198 | setSubmitting(false);
199 | };
200 |
201 | const encodedStdout = useMemo(() => DECODERS[stdoutEncoding](stdout), [stdout, stdoutEncoding]);
202 | const encodedStderr = useMemo(() => DECODERS[stderrEncoding](stderr), [stderr, stderrEncoding]);
203 |
204 | const [shouldLoad, setShouldLoad] = useState(true);
205 | useEffect(() => {
206 | if (!shouldLoad || !router.isReady) {
207 | return;
208 | }
209 | const loadedData = load(router.query);
210 | if (loadedData === null) {
211 | // TODO: if loaded data is invalid rather than simply not present, don't open the selector?
212 | const loadedLanguage = router.query.L || router.query.l;
213 | if (loadedLanguage) {
214 | setLanguage(loadedLanguage as string);
215 | } else {
216 | setLanguage("dyalog_apl");
217 | }
218 | } else {
219 | const loadedLanguage = router.query.L || router.query.l || loadedData.language;
220 | setLanguage(loadedLanguage);
221 | setOptions([loadedData.options, parseList(loadedData.options)]);
222 | setHeader(loadedData.header);
223 | setHeaderEncoding(loadedData.headerEncoding);
224 | setCode(loadedData.code);
225 | if (loadedData.codeEncoding !== null) {
226 | // avoid overwriting codeEncoding while it is half way through being set automatically
227 | setCodeEncoding(loadedData.codeEncoding);
228 | }
229 | setFooter(loadedData.footer);
230 | setFooterEncoding(loadedData.footerEncoding);
231 | setProgramArguments([loadedData.programArguments, parseList(loadedData.programArguments)]);
232 | setInput(loadedData.input);
233 | setInputEncoding(loadedData.inputEncoding);
234 | // further updates the router.query should not trigger updates
235 | setShouldLoad(false);
236 | }
237 | }, [router, shouldLoad]);
238 |
239 | if (languages && language && !languages[language]) {
240 | alert(`Unknown language:\n${language}`);
241 | setLanguage(null);
242 | setLanguageSelectorOpen(true);
243 | language = null;
244 | }
245 |
246 | // combination of useRef and useMemo: useRef ignores its argument after the second call, but the
247 | // argument still has to be computed which is a bit of a waste. useMemo here is not being used to
248 | // maintain the same stored throttle function (that's the job of useMemo), but as an optimisation
249 | // only.
250 | const updateURL = useRef(useMemo(
251 | // we throttle calls for 2 reasons:
252 | // - saving might take a while due to several layers of encoding and compression;
253 | // we don't want it to block the main thread too much
254 | // - some browsers error on too many frequent history pushState/replaceState calls
255 | () => throttle(
256 | // the debounced function cannot have any closure variable dependencies because otherwise the
257 | // function would have to be recreated every render and the debouncing wouldn't work properly,
258 | // so what would otherwise be closure variables are passed as arguments upon call.
259 | (router2, data) => {
260 | router2.replace(`/run?${save(data)}`, null, { scroll: false });
261 | },
262 | 200, // milliseconds
263 | ),
264 | [],
265 | ));
266 |
267 | const charLength = stringLength(code);
268 |
269 | let byteLength: number = 0;
270 | if (codeEncoding !== null) {
271 | if (codeEncoding === 'sbcs') {
272 | byteLength = stringLength(code);
273 | } else {
274 | byteLength = ENCODERS[codeEncoding](code).length;
275 | }
276 | }
277 |
278 | // save data in URL on data change
279 | useEffect(
280 | () => {
281 | if (!router.isReady) {
282 | return;
283 | } else if (!language) {
284 | // not chosen
285 | return;
286 | }
287 | updateURL.current(router, {
288 | language,
289 | options: optionsString,
290 | header,
291 | headerEncoding,
292 | code,
293 | codeEncoding,
294 | footer,
295 | footerEncoding,
296 | programArguments: argsString,
297 | input,
298 | inputEncoding,
299 | });
300 | },
301 | // update when these values change:
302 | [
303 | language,
304 | optionsString,
305 | header,
306 | headerEncoding,
307 | code,
308 | codeEncoding,
309 | footer,
310 | footerEncoding,
311 | argsString,
312 | input,
313 | inputEncoding,
314 | ],
315 | );
316 |
317 | const getCurrentURL = () => {
318 | const saveCode = save({
319 | language,
320 | options: optionsString,
321 | header,
322 | headerEncoding,
323 | code,
324 | codeEncoding,
325 | footer,
326 | footerEncoding,
327 | programArguments: argsString,
328 | input,
329 | inputEncoding,
330 | });
331 | const baseUrl = window.location.host + window.location.pathname;
332 | return `https://${baseUrl}?${saveCode}`
333 | };
334 |
335 | const [clipboardCopyModalOpen, setClipboardCopyModalOpen] = useState(false);
336 | const clipboardCopyModal = useRef(null);
337 | const clipboardCopyButton = useRef(null);
338 |
339 | const copyCGCCPost = () => {
340 | if (!language) {
341 | notify('Please select a language first!');
342 | return;
343 | }
344 | setClipboardCopyModalOpen(false);
345 | let syntaxHighlightingClass: string;
346 | if (languages[language].SE_class) {
347 | syntaxHighlightingClass = ` class="lang-${escape(languages[language].SE_class)}"`;
348 | } else {
349 | syntaxHighlightingClass = '';
350 | }
351 | let title: string;
352 | if (languages[language].url) {
353 | title = `[${languages[language].name}](${languages[language].url})`;
354 | } else {
355 | title = languages[language].name;
356 | }
357 | navigator.clipboard.writeText(`# ${title}, ${byteLength} ${pluralise('byte', byteLength)}
358 |
359 |
635 | >
636 | );
637 | }
638 |
639 | const Run = connect((state: any) => ({
640 | fullWidthMode: state.fullWidthMode,
641 | languages: state.metadata,
642 | }))(_Run);
643 | export default Run;
644 |
--------------------------------------------------------------------------------
/LICENCE.txt:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published by
637 | the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------