source: code/trunk/partage.go@ 37

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

Keep setuid/setgid syscalls in main()

File size: 7.0 KB
Line 
1package main
2
3import (
4 "encoding/json"
5 "flag"
6 "fmt"
7 "html/template"
8 "io"
9 "io/ioutil"
10 "log"
11 "net/http"
12 "os"
13 "os/user"
14 "path"
15 "path/filepath"
16 "strconv"
17 "syscall"
18 "time"
19
20 "github.com/dustin/go-humanize"
21 "gopkg.in/ini.v1"
22)
23
24type templatedata struct {
25 Links []string
26 Size string
27 Maxsize string
28}
29
30type metadata struct {
31 Filename string
32 Size int64
33 Expiry int64
34}
35
36var conf struct {
37 user string
38 group string
39 chroot string
40 bind string
41 baseuri string
42 rootdir string
43 tmplpath string
44 filepath string
45 metapath string
46 filectx string
47 metactx string
48 maxsize int64
49 expiry int64
50}
51
52var verbose bool
53
54func writefile(f *os.File, s io.ReadCloser, contentlength int64) error {
55 buffer := make([]byte, 4096)
56 eof := false
57 sz := int64(0)
58
59 defer f.Sync()
60
61 for !eof {
62 n, err := s.Read(buffer)
63 if err != nil && err != io.EOF {
64 return err
65 } else if err == io.EOF {
66 eof = true
67 }
68
69 /* ensure we don't write more than expected */
70 r := int64(n)
71 if sz+r > contentlength {
72 r = contentlength - sz
73 eof = true
74 }
75
76 _, err = f.Write(buffer[:r])
77 if err != nil {
78 return err
79 }
80 sz += r
81 }
82
83 return nil
84}
85
86func writemeta(filename string, expiry int64) error {
87
88 f, _ := os.Open(filename)
89 stat, _ := f.Stat()
90 size := stat.Size()
91 f.Close()
92
93 meta := metadata{
94 Filename: filepath.Base(filename),
95 Size: size,
96 Expiry: time.Now().Unix() + expiry,
97 }
98
99 if verbose {
100 log.Printf("Saving metadata for %s in %s", meta.Filename, conf.metapath+"/"+meta.Filename+".json")
101 }
102
103 f, err := os.Create(conf.metapath + "/" + meta.Filename + ".json")
104 if err != nil {
105 return err
106 }
107 defer f.Close()
108
109 j, err := json.Marshal(meta)
110 if err != nil {
111 return err
112 }
113
114 _, err = f.Write(j)
115
116 return err
117}
118
119func servetemplate(w http.ResponseWriter, f string, d templatedata) {
120 t, err := template.ParseFiles(conf.tmplpath + "/" + f)
121 if err != nil {
122 http.Error(w, "Internal error", http.StatusInternalServerError)
123 return
124 }
125
126 if verbose {
127 log.Printf("Serving template %s", t.Name())
128 }
129
130 err = t.Execute(w, d)
131 if err != nil {
132 fmt.Println(err)
133 }
134}
135
136func uploaderPut(w http.ResponseWriter, r *http.Request) {
137 /* limit upload size */
138 if r.ContentLength > conf.maxsize {
139 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
140 }
141
142 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(r.URL.Path))
143 f, err := os.Create(tmp.Name())
144 if err != nil {
145 fmt.Println(err)
146 return
147 }
148 defer f.Close()
149
150 if verbose {
151 log.Printf("Writing %d bytes to %s", r.ContentLength, tmp.Name())
152 }
153
154 if err = writefile(f, r.Body, r.ContentLength); err != nil {
155 http.Error(w, "Internal error", http.StatusInternalServerError)
156 defer os.Remove(tmp.Name())
157 return
158 }
159 writemeta(tmp.Name(), conf.expiry)
160
161 resp := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
162 w.Write([]byte(resp))
163}
164
165func uploaderPost(w http.ResponseWriter, r *http.Request) {
166 /* read 32Mb at a time */
167 r.ParseMultipartForm(32 << 20)
168
169 links := []string{}
170 for _, h := range r.MultipartForm.File["uck"] {
171 if h.Size > conf.maxsize {
172 http.Error(w, "File is too big", http.StatusRequestEntityTooLarge)
173 return
174 }
175
176 post, err := h.Open()
177 if err != nil {
178 http.Error(w, "Internal error", http.StatusInternalServerError)
179 return
180 }
181 defer post.Close()
182
183 tmp, _ := ioutil.TempFile(conf.filepath, "*"+path.Ext(h.Filename))
184 f, err := os.Create(tmp.Name())
185 if err != nil {
186 http.Error(w, "Internal error", http.StatusInternalServerError)
187 return
188 }
189 defer f.Close()
190
191 if err = writefile(f, post, h.Size); err != nil {
192 http.Error(w, "Internal error", http.StatusInternalServerError)
193 defer os.Remove(tmp.Name())
194 return
195 }
196
197 writemeta(tmp.Name(), conf.expiry)
198
199 link := conf.baseuri + conf.filectx + filepath.Base(tmp.Name())
200 links = append(links, link)
201 }
202
203 if r.PostFormValue("output") == "html" {
204 data := templatedata{Links: links}
205 servetemplate(w, "/upload.html", data)
206 return
207 } else {
208 for _, link := range links {
209 w.Write([]byte(link + "\r\n"))
210 }
211 }
212}
213
214func uploaderGet(w http.ResponseWriter, r *http.Request) {
215 // r.URL.Path is sanitized regarding "." and ".."
216 filename := r.URL.Path
217 if r.URL.Path == "/" || r.URL.Path == "/index.html" {
218 data := templatedata{Maxsize: humanize.IBytes(uint64(conf.maxsize))}
219 servetemplate(w, "/index.html", data)
220 return
221 }
222
223 if verbose {
224 log.Printf("Serving file %s", conf.rootdir+filename)
225 }
226
227 http.ServeFile(w, r, conf.rootdir+filename)
228}
229
230func uploader(w http.ResponseWriter, r *http.Request) {
231 if verbose {
232 log.Printf("%s: <%s> %s %s %s", r.Host, r.RemoteAddr, r.Method, r.RequestURI, r.Proto)
233 }
234
235 switch r.Method {
236 case "POST":
237 uploaderPost(w, r)
238 case "PUT":
239 uploaderPut(w, r)
240 case "GET":
241 uploaderGet(w, r)
242 }
243}
244
245func parseconfig(file string) error {
246 cfg, err := ini.Load(file)
247 if err != nil {
248 return err
249 }
250
251 conf.bind = cfg.Section("").Key("bind").String()
252 conf.user = cfg.Section("").Key("user").String()
253 conf.group = cfg.Section("").Key("group").String()
254 conf.baseuri = cfg.Section("").Key("baseuri").String()
255 conf.filepath = cfg.Section("").Key("filepath").String()
256 conf.metapath = cfg.Section("").Key("metapath").String()
257 conf.filectx = cfg.Section("").Key("filectx").String()
258 conf.metactx = cfg.Section("").Key("metactx").String()
259 conf.rootdir = cfg.Section("").Key("rootdir").String()
260 conf.chroot = cfg.Section("").Key("chroot").String()
261 conf.tmplpath = cfg.Section("").Key("tmplpath").String()
262 conf.maxsize, _ = cfg.Section("").Key("maxsize").Int64()
263 conf.expiry, _ = cfg.Section("").Key("expiry").Int64()
264
265 return nil
266}
267
268func usergroupids(username string, groupname string) (int, int, error) {
269 u, err := user.Lookup(username)
270 if err != nil {
271 return -1, -1, err
272 }
273
274 uid, _ := strconv.Atoi(u.Uid)
275 gid, _ := strconv.Atoi(u.Gid)
276
277 if conf.group != "" {
278 g, err := user.LookupGroup(groupname)
279 if err != nil {
280 return uid, -1, err
281 }
282 gid, _ = strconv.Atoi(g.Gid)
283 }
284
285 return uid, gid, nil
286}
287
288func main() {
289 var configfile string
290
291 /* default values */
292 conf.bind = "0.0.0.0:8080"
293 conf.baseuri = "http://127.0.0.1:8080"
294 conf.rootdir = "/htdocs"
295 conf.tmplpath = "/htdocs/templates"
296 conf.filepath = "/htdocs/files"
297 conf.metapath = "/htdocs/meta"
298 conf.filectx = "/f/"
299 conf.metactx = "/m/"
300 conf.maxsize = 34359738368
301 conf.expiry = 86400
302
303 flag.StringVar(&configfile, "f", "", "Configuration file")
304 flag.BoolVar(&verbose, "v", false, "Verbose logging")
305 flag.Parse()
306
307 if configfile != "" {
308 if verbose {
309 log.Printf("Reading configuration %s", configfile)
310 }
311 parseconfig(configfile)
312 }
313
314 if conf.chroot != "" {
315 if verbose {
316 log.Printf("Changing root to %s", conf.chroot)
317 }
318 syscall.Chroot(conf.chroot)
319 }
320
321 if conf.user != "" {
322 if verbose {
323 log.Printf("Dropping privileges to %s", conf.user)
324 }
325 uid, gid, err := usergroupids(conf.user, conf.group)
326 if err != nil {
327 log.Fatal(err)
328 }
329 syscall.Setuid(uid)
330 syscall.Setgid(gid)
331 }
332
333 http.HandleFunc("/", uploader)
334 http.Handle(conf.filectx, http.StripPrefix(conf.filectx, http.FileServer(http.Dir(conf.filepath))))
335
336 if verbose {
337 log.Printf("Listening on %s", conf.bind)
338 }
339
340 http.ListenAndServe(conf.bind, nil)
341}
Note: See TracBrowser for help on using the repository browser.