source: code/trunk/partage.go@ 28

Last change on this file since 28 was 28, checked in by dev, 4 years ago

Provide ability to drop privileges on start

File size: 6.2 KB
Line 
1package main
2
3import (
4 "fmt"
5 "flag"
6 "io"
7 "io/ioutil"
8 "net/http"
9 "os"
10 "os/user"
11 "time"
12 "path"
13 "syscall"
14 "strconv"
15 "path/filepath"
16 "html/template"
17 "encoding/json"
18
19 "github.com/dustin/go-humanize"
20 "github.com/vharitonsky/iniflags"
21)
22
23type templatedata struct {
24 Links []string
25 Size string
26 Maxsize string
27}
28
29type metadata struct {
30 Filename string
31 Size int64
32 Expiry int64
33}
34
35var conf struct {
36 bind string
37 user string
38 group string
39 baseuri string
40 filepath string
41 metapath string
42 rootdir string
43 chroot string
44 templatedir string
45 filectx string
46 metactx string
47 maxsize int64
48 expiry int64
49}
50
51func writefile(f *os.File, s io.ReadCloser, contentlength int64) error {
52 buffer := make([]byte, 4096)
53 eof := false
54 sz := int64(0)
55
56 defer f.Sync()
57
58 for !eof {
59 n, err := s.Read(buffer)
60 if err != nil && err != io.EOF {
61 return err
62 } else if err == io.EOF {
63 eof = true
64 }
65
66 /* ensure we don't write more than expected */
67 r := int64(n)
68 if sz+r > contentlength {
69 r = contentlength - sz
70 eof = true
71 }
72
73 _, err = f.Write(buffer[:r])
74 if err != nil {
75 return err
76 }
77 sz += r
78 }
79
80 return nil
81}
82
83func writemeta(filename string, expiry int64) error {
84
85 f, _ := os.Open(filename)
86 stat, _ := f.Stat()
87 size := stat.Size()
88 f.Close()
89
90 meta := metadata{
91 Filename: filepath.Base(filename),
92 Size: size,
93 Expiry: time.Now().Unix() + expiry,
94 }
95
96 f, err := os.Create(conf.metapath + "/" + meta.Filename + ".json")
97 if err != nil {
98 return err
99 }
100 defer f.Close()
101
102 j, err := json.Marshal(meta)
103 if err != nil {
104 return err
105 }
106
107 _, err = f.Write(j)
108
109 return err
110}
111
112func servetemplate(w http.ResponseWriter, f string, d templatedata) {
113 t, err := template.ParseFiles(conf.templatedir + "/" + f)
114 if err != nil {
115 http.Error(w, "Internal error", http.StatusInternalServerError)
116 return
117 }
118
119 err = t.Execute(w, d)
120 if err != nil {
121 fmt.Println(err)
122 }
123}
124
125func uploaderPut(w http.ResponseWriter, r *http.Request) {
126 /* limit upload size */
127 if r.ContentLength > conf.maxsize {
128 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
129 }
130
131 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(r.URL.Path))
132 f, err := os.Create(tmp.Name())
133 if err != nil {
134 fmt.Println(err)
135 return
136 }
137 defer f.Close()
138
139 if err = writefile(f, r.Body, r.ContentLength); err != nil {
140 http.Error(w, "Internal error", http.StatusInternalServerError)
141 defer os.Remove(tmp.Name())
142 return
143 }
144 writemeta(tmp.Name(), conf.expiry)
145
146 resp := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
147 w.Write([]byte(resp))
148}
149
150func uploaderPost(w http.ResponseWriter, r *http.Request) {
151 /* read 32Mb at a time */
152 r.ParseMultipartForm(32 << 20)
153
154 links := []string{}
155 for _, h := range r.MultipartForm.File["uck"] {
156 if h.Size > conf.maxsize {
157 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
158 return
159 }
160
161 post, err := h.Open()
162 if err != nil {
163 http.Error(w, "Internal error", http.StatusInternalServerError)
164 return
165 }
166 defer post.Close()
167
168 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(h.Filename))
169 f, err := os.Create(tmp.Name())
170 if err != nil {
171 http.Error(w, "Internal error", http.StatusInternalServerError)
172 return
173 }
174 defer f.Close()
175
176 if err = writefile(f, post, h.Size); err != nil {
177 http.Error(w, "Internal error", http.StatusInternalServerError)
178 defer os.Remove(tmp.Name())
179 return
180 }
181
182 writemeta(tmp.Name(), conf.expiry)
183
184
185 link := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
186 links = append(links, link)
187 }
188
189 if (r.PostFormValue("output") == "html") {
190 data := templatedata{ Links: links }
191 servetemplate(w, "/upload.html", data)
192 return
193 } else {
194 for _, link := range links {
195 w.Write([]byte(link + "\r\n"))
196 }
197 }
198}
199
200func uploaderGet(w http.ResponseWriter, r *http.Request) {
201 // r.URL.Path is sanitized regarding "." and ".."
202 filename := r.URL.Path
203 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
204 data := templatedata{ Maxsize: humanize.IBytes(uint64(conf.maxsize))}
205 servetemplate(w, "/index.html", data)
206 return
207 }
208
209 http.ServeFile(w, r, conf.rootdir + filename)
210}
211
212func uploader(w http.ResponseWriter, r *http.Request) {
213 switch r.Method {
214 case "POST":
215 uploaderPost(w, r)
216 case "PUT":
217 uploaderPut(w, r)
218 case "GET":
219 uploaderGet(w, r)
220 }
221}
222
223func main() {
224 flag.StringVar(&conf.bind, "bind", "0.0.0.0:8080", "Address to bind to (default: 0.0.0.0:8080)")
225 flag.StringVar(&conf.user, "user", "", "User to drop privileges to on startup (default: current user)")
226 flag.StringVar(&conf.group, "group", "", "Group to drop privileges to on startup (default: user's group)")
227 flag.StringVar(&conf.baseuri, "baseuri", "http://127.0.0.1:8080", "Base URI to use for links (default: http://127.0.0.1:8080)")
228 flag.StringVar(&conf.filepath, "filepath", "./files", "Path to save files to (default: ./files)")
229 flag.StringVar(&conf.metapath, "metapath", "./meta", "Path to save metadata to (default: ./meta)")
230 flag.StringVar(&conf.filectx, "filectx", "/f/", "Context to serve files from (default: /f/)")
231 flag.StringVar(&conf.metactx, "metactx", "/m/", "Context to serve metadata from (default: /m/)")
232 flag.StringVar(&conf.rootdir, "rootdir", "./static", "Root directory (default: ./static)")
233 flag.StringVar(&conf.chroot, "chroot", "", "Directory to chroot into upon starting (default: no chroot)")
234 flag.StringVar(&conf.templatedir, "templatedir", "./templates", "Templates directory (default: ./templates)")
235 flag.Int64Var(&conf.maxsize, "maxsize", 30064771072, "Maximum file size (default: 28Gib)")
236 flag.Int64Var(&conf.expiry, "expiry", 86400, "Link expiration time (default: 24h)")
237
238 iniflags.Parse()
239
240 if (conf.chroot != "") {
241 syscall.Chroot(conf.chroot)
242 }
243
244 if conf.user != "" {
245 u, err := user.Lookup(conf.user)
246 if err != nil {
247 fmt.Println(err)
248 return
249 }
250
251 uid, _ := strconv.Atoi(u.Uid)
252 gid, _ := strconv.Atoi(u.Gid)
253
254 if conf.group != "" {
255 g, err := user.LookupGroup(conf.group)
256 if err != nil {
257 fmt.Println(err)
258 return
259 }
260 gid, _ = strconv.Atoi(g.Gid)
261 }
262
263 syscall.Setuid(uid)
264 syscall.Setgid(gid)
265 }
266
267 http.HandleFunc("/", uploader)
268 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
269 http.ListenAndServe(conf.bind, nil)
270}
Note: See TracBrowser for help on using the repository browser.