View | Details | Raw Unified | Return to bug 31923 | Differences between
and this patch

Collapse All | Expand All

(-)univention-management-console-frontend/univention-management-console-web-server (-59 / +20 lines)
 Lines 40-50    Link Here 
40
import simplejson
40
import simplejson
41
import signal
41
import signal
42
import sys
42
import sys
43
import tempfile
44
import time
43
import time
45
import threading
44
import threading
46
import uuid
45
import uuid
47
import datetime
46
import datetime
47
import base64
48
48
49
import notifier
49
import notifier
50
from daemon.runner import DaemonRunner, DaemonRunnerStopFailureError, DaemonRunnerStartFailureError
50
from daemon.runner import DaemonRunner, DaemonRunnerStopFailureError, DaemonRunnerStartFailureError
 Lines 257-290    Link Here 
257
		except KeyError, e:
257
		except KeyError, e:
258
			CORE.info( 'Session %s not found' % sessionid )
258
			CORE.info( 'Session %s not found' % sessionid )
259
259
260
class UploadManager( dict ):
261
	def add( self, request_id, store ):
262
		tmpfile = tempfile.NamedTemporaryFile( prefix = request_id, dir = umcp.TEMPUPLOADDIR, delete = False )
263
		if hasattr(store, 'file') and store.file is None:
264
			tmpfile.write( store.value )
265
		else:
266
			tmpfile.write( store.file.read() )
267
		tmpfile.close()
268
		if request_id in self:
269
			self[ request_id ].append( tmpfile.name )
270
		else:
271
			self[ request_id ] = [ tmpfile.name ]
272
273
		return tmpfile.name
274
275
	def cleanup( self, request_id ):
276
		if request_id in self:
277
			filenames = self[ request_id ]
278
			for filename in filenames:
279
				if os.path.isfile( filename ):
280
					os.unlink( filename )
281
			del self[ request_id ]
282
			return True
283
284
		return False
285
286
_upload_manager = UploadManager()
287
288
class QueueRequest(object):
260
class QueueRequest(object):
289
	"""Element for the request queue containing the assoziated session
261
	"""Element for the request queue containing the assoziated session
290
	ID, the request object, a response queue and the request ip address."""
262
	ID, the request object, a response queue and the request ip address."""
 Lines 457-502    Link Here 
457
class CPUpload( CPgeneric ):
429
class CPUpload( CPgeneric ):
458
	def get_request(self, path, args ):
430
	def get_request(self, path, args ):
459
		self._log( 'info', 'Handle upload command' )
431
		self._log( 'info', 'Handle upload command' )
460
		global _upload_manager
461
		req = umcp.Request( 'UPLOAD', arguments = [ path ] )
432
		req = umcp.Request( 'UPLOAD', arguments = [ path ] )
462
433
463
		options = []
434
		options = {}
464
		body = {}
435
		for name, ifield in args.iteritems():
465
		for iid, ifield in args.iteritems():
466
			if isinstance(ifield, cherrypy._cpcgifs.FieldStorage):
436
			if isinstance(ifield, cherrypy._cpcgifs.FieldStorage):
467
				# field is a FieldStorage object
437
				# lowercase all headers # TODO: check if not anymore required, yet
468
				store = ifield
438
				ifield.headers = dict((key.lower(), val) for key, val in ifield.headers.items())
469
				tmpfile = _upload_manager.add( req.id, store )
470
439
471
				# check if filesize is allowed
440
				content = ifield.file.read()
472
				st = os.stat( tmpfile )
441
				mimetype = ifield.headers.get('content-type')
473
				max_size = int( configRegistry.get( 'umc/server/upload/max', 64 ) ) * 1024
442
				encoding = None
474
				if st.st_size > max_size:
443
				if mimetype not in ('text/plain',):
475
					self._log('warn', 'file of size %d could not be uploaded' % (st.st_size))
444
					encoding = 'base64'
476
					raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'The size of the uploaded file is too large')
445
					content = base64.encodestring(content)
477
446
478
				# check if enough free space is available
447
				options[name] = {
479
				min_size = int( configRegistry.get( 'umc/server/upload/min_free_space', 51200 ) ) # kilobyte
448
					'filename': ifield.filename,
480
				s = os.statvfs(tmpfile)
449
					'name': ifield.name,
481
				free_disk_space = s.f_bavail * s.f_frsize / 1024 # kilobyte
450
					'content': content,
482
				if free_disk_space < min_size:
451
					'mimetype': mimetype,
483
					self._log('error', 'there is not enough free space to upload files')
452
					'encoding': encoding
484
					raise cherrypy.HTTPError(httplib.BAD_REQUEST, 'There is not enough free space on disk')
453
				}
485
486
				filename = store.filename
487
				# some security
488
				for c in ('<>/'):
489
					filename = filename.replace(c, '_')
490
491
				options.append( { 'filename' : filename, 'name' : store.name, 'tmpfile' : tmpfile } )
492
			elif isinstance(ifield, basestring):
454
			elif isinstance(ifield, basestring):
493
				# field is a string :)
455
				# field is a string :)
494
				body[iid] = ifield
456
				options[name] = dict(name=name, content=ifield)
495
			else:
457
			else:
496
				# we cannot handle any other type
458
				# we cannot handle any other type
497
				CORE.warn( 'Unknown type of multipart/form entry: %s=%s' % (iid, ifield) )
459
				CORE.warn( 'Unknown type of multipart/form-data entry: %s=%s' % (name, ifield) )
498
460
499
		req.body = body
500
		req.body['options'] = options
461
		req.body['options'] = options
501
		return req
462
		return req
502
463
(-)univention-management-console/scripts/univention-management-console-client (+2 lines)
 Lines 108-113    Link Here 
108
						value = eval( "%s('%s')" % ( typ, value ) )
108
						value = eval( "%s('%s')" % ( typ, value ) )
109
					except NameError:
109
					except NameError:
110
						print >>sys.stderr, "Invalid type for option: %s" % typ
110
						print >>sys.stderr, "Invalid type for option: %s" % typ
111
				elif value.startswith('@'):
112
					value = open(value[1:]).read()
111
				msg.options[ key ] = value
113
				msg.options[ key ] = value
112
		return msg
114
		return msg
113
115
(-)univention-management-console/src/univention/management/console/protocol/session.py (-42 / +117 lines)
 Lines 39-44    Link Here 
39
import os
39
import os
40
import time
40
import time
41
import json
41
import json
42
import tempfile
43
import os.path
42
44
43
import notifier
45
import notifier
44
import notifier.signals as signals
46
import notifier.signals as signals
 Lines 70-75    Link Here 
70
72
71
TEMPUPLOADDIR = '/var/tmp/univention-management-console-frontend'
73
TEMPUPLOADDIR = '/var/tmp/univention-management-console-frontend'
72
74
75
class UploadManager(dict):
76
	def add(self, request_id, data):
77
		tmpfile = tempfile.NamedTemporaryFile(prefix=request_id, dir=TEMPUPLOADDIR, delete=False)
78
		tmpfile.write(data)
79
		tmpfile.close()
80
		if request_id in self:
81
			self[request_id].append(tmpfile.name)
82
		else:
83
			self[request_id] = [tmpfile.name]
84
85
		return tmpfile.name
86
87
	def cleanup(self, request_id):
88
		if request_id in self:
89
			filenames = self[request_id]
90
			for filename in filenames:
91
				if os.path.isfile(filename):
92
					os.unlink(filename)
93
			del self[request_id]
94
			return True
95
96
		return False
97
98
_upload_manager = UploadManager()
99
73
class State( signals.Provider ):
100
class State( signals.Provider ):
74
	"""Holds information about the state of an active session
101
	"""Holds information about the state of an active session
75
102
 Lines 165-170    Link Here 
165
		if msg.command == 'EXIT' and 'internal' in msg.arguments:
192
		if msg.command == 'EXIT' and 'internal' in msg.arguments:
166
			return
193
			return
167
194
195
		if msg.command == 'UPLOAD':
196
			# remove temporary uploaded files
197
			_upload_manager.cleanup(msg.id)
198
168
		self.signal_emit( 'result', msg )
199
		self.signal_emit( 'result', msg )
169
200
170
	def pid( self ):
201
	def pid( self ):
 Lines 522-587    Link Here 
522
		module._inactivity_counter = MODULE_INACTIVITY_TIMER
553
		module._inactivity_counter = MODULE_INACTIVITY_TIMER
523
554
524
	def handle_request_upload( self, msg ):
555
	def handle_request_upload( self, msg ):
525
		"""Handles an UPLOAD request. The command is used for the HTTP
556
		"""Handles an UPLOAD request.
526
		access to the UMC server. Incoming HTTP requests that send a
557
		The request options is a dictionary of dictionaries which
527
		list of files are passed on to the UMC server by storing the
558
		can either be treaten as files or as normal strings
528
		files in temporary files and passing the information about the
529
		files to the UMC server in the options of the request. The
530
		request options must be a list of dictionaries. Each dictionary
531
		must contain the following keys:
532
559
560
533
		* *filename* -- the original name of the file
561
		* *filename* -- the original name of the file
534
		* *name* -- name of the form field
562
		* *name* -- name of the form field
535
		* *tmpfile* -- filename of the temporary file
563
		* *content* -- the content of the file
564
		* *mimetype* -- the file mimetype
565
		* *enconding* -- encoding of the content if the content is encoded
536
566
537
		:param Request msg: UMCP request
567
		:param Request msg: UMCP request
538
		"""
568
		"""
539
		# request.options = ( { 'filename' : store.filename, 'name' : store.name, 'tmpfile' : tmpfile } )
540
569
541
		if not isinstance( msg.options, ( list, tuple ) ):
570
		if isinstance(msg.options, list):
571
			# backward compatibility
572
			try:
573
				msg.options = dict((val['name'], val) for val in msg.options)
574
			except (TypeError, KeyError):
575
				raise InvalidOptionsError
576
577
		if not isinstance(msg.options, dict):
542
			raise InvalidOptionsError
578
			raise InvalidOptionsError
543
579
544
		for file_obj in msg.options:
580
		def sanitize_item(item):
545
			# check if required options exists and file_obj is a dict
581
			if not isinstance(item, dict):
546
			try:
582
				raise InvalidOptionsError
547
				tmpfilename, filename, name = file_obj['tmpfile'], file_obj['filename'], file_obj['name']
548
			except:
549
				raise InvalidOptionsError('required options "tmpfile", "filename" or "name" is missing')
550
583
551
			# limit files to tmpdir
584
			res = dict((key, item.get(key, '')) for key in ('filename', 'name', 'content', 'mimetype'))
552
			if not os.path.realpath(tmpfilename).startswith(TEMPUPLOADDIR):
585
			# make sure some fields are strings
553
				raise InvalidOptionsError('invalid file: invalid path')
586
			if not all(isinstance(res[k], str) for k in ('filename', 'name', 'mimetype')):
587
				raise InvalidOptionsError
554
588
555
			# check if file exists
589
			# if the field is a file the content must be a string
556
			if not os.path.isfile( tmpfilename ):
590
			if res['filename'] and not isinstance(res['content'], str):
557
				raise InvalidOptionsError('invalid file: file does not exists')
591
				raise InvalidOptionsError
558
592
559
			# don't accept files bigger than umc/server/upload/max
593
			# remove the possibility of XSS and path overwriting
560
			st = os.stat( tmpfilename )
594
			for c in '<>/':
561
			max_size = int( ucr.get( 'umc/server/upload/max', 64 ) ) * 1024
595
				res['filename'] = res['filename'].replace(c, '_')
562
			if st.st_size > max_size:
563
				raise InvalidOptionsError('filesize is too large, maximum allowed filesize is %d' % (max_size,))
564
596
565
		if msg.arguments and msg.arguments[0] not in ('', '/'):
597
			return res
566
			# The request has arguments, so it will be treaten as COMMAND
598
567
			self.handle_request_command(msg)
599
		if not msg.arguments or msg.arguments[0] in ('', '/'):
600
			# The request is an generic UPLOAD command ('/upload')
601
			result = {}
602
			for item in msg.options.values():
603
				# read tmpfile and convert to base64
604
				res = sanitize_item(item)
605
				name = res['name']
606
607
				# bring content into base64 (!)
608
				if item.get('encoding') != 'base64':
609
					res['content'] = base64.encodestring(res['content'])
610
				result[name] = res
611
612
			response = Response( msg )
613
			response.result = result
614
			response.status = SUCCESS
615
616
			self.signal_emit( 'response', response )
568
			return
617
			return
569
618
570
		# The request is an generic UPLOAD command (/upload)
619
		ucr.load()
571
		result = []
620
		max_upload_size = int(ucr.get('umc/server/upload/max', 64)) * 1024
572
		for file_obj in msg.options:
621
		min_free_space = int(ucr.get('umc/server/upload/min_free_space', 51200)) # kilobyte
573
			# read tmpfile and convert to base64
574
			tmpfilename, filename, name = file_obj['tmpfile'], file_obj['filename'], file_obj['name']
575
			with open( tmpfilename ) as buf:
576
				b64buf = base64.b64encode( buf.read() )
577
			result.append( { 'filename' : filename, 'name' : name, 'content' : b64buf } )
578
622
579
		response = Response( msg )
623
		# check if enough free space is available
580
		response.result = result
624
		s = os.statvfs(TEMPUPLOADDIR)
581
		response.status = SUCCESS
625
		free_disk_space = s.f_bavail * s.f_frsize / 1024 # kilobyte
626
		if free_disk_space < min_free_space:
627
			CORE.error('There is not enough free space on disk to upload files')
628
			raise InvalidOptionsError('There is not enough free space on disk to upload files')
582
629
583
		self.signal_emit( 'response', response )
630
		options = {}
631
		for item in msg.options.values():
632
			res = sanitize_item(item)
633
			name, filename = res['name'], res['filename']
584
634
635
			if not filename:
636
				# field is not a file, it's a string
637
				res = res['content']
638
			else:
639
				content = res.pop('content')
640
641
				# don't accept files bigger than umc/server/upload/max
642
				clen = len(content)
643
				if clen > max_upload_size:
644
					CORE.warn('File of size %d could not be uploaded' % (clen))
645
					raise InvalidOptionsError('Filesize is too large, maximum allowed filesize is %d' % (max_upload_size,))
646
647
				# decode the file
648
				if item.get('encoding') == 'base64':
649
					content = base64.decodestring(content)
650
651
				# write content to disk
652
				tmpfile = _upload_manager.add(msg.id, content)
653
				res['tmpfile'] = tmpfile
654
655
			options[name] = res
656
		msg.options = options
657
658
		self.handle_request_command(msg)
659
585
	def handle_request_command( self, msg ):
660
	def handle_request_command( self, msg ):
586
		"""Handles a COMMAND request. The request must contain a valid
661
		"""Handles a COMMAND request. The request must contain a valid
587
		and known command that can be accessed by the current user. If
662
		and known command that can be accessed by the current user. If
(-)univention-management-console/src/univention/management/console/protocol/message.py (-4 lines)
 Lines 245-254    Link Here 
245
				PARSER.process( 'Error parsing UMCP message body' )
245
				PARSER.process( 'Error parsing UMCP message body' )
246
				raise ParseError( UMCP_ERR_UNPARSABLE_BODY, _( 'error parsing UMCP message body' ) )
246
				raise ParseError( UMCP_ERR_UNPARSABLE_BODY, _( 'error parsing UMCP message body' ) )
247
247
248
			for key in ( 'options', ):
249
				if key in self.body:
250
					setattr( self, key[ 1 : ], self.body[ key ] )
251
252
		PARSER.info( 'UMCP %(type)s %(id)s parsed successfully' % groups )
248
		PARSER.info( 'UMCP %(type)s %(id)s parsed successfully' % groups )
253
249
254
		return remains
250
		return remains
(-)univention-management-console-module-udm/umc/python/udm/__init__.py (-7 / +5 lines)
 Lines 223-235    Link Here 
223
223
224
	@LDAP_Connection
224
	@LDAP_Connection
225
	def license_import( self, request, ldap_connection = None, ldap_position = None ):
225
	def license_import( self, request, ldap_connection = None, ldap_position = None ):
226
		filename = None
226
		if request.command == 'UPLOAD':
227
		if isinstance(request.options, (list, tuple)) and request.options:
227
			key = 'licenseUpload' # FIXME: in umc.widgets.Uploader the name of the form field get overwritten into 'uploadedfile'
228
			# file upload
228
			key = 'uploadedfile'
229
			filename = request.options[ 0 ][ 'tmpfile' ]
229
			self.required_options(request, key)
230
			if not os.path.realpath(filename).startswith(TEMPUPLOADDIR):
230
			filename = request.options[key]['tmpfile']
231
				self.finished(request.id, [{'success': False, 'message': 'invalid file path'}])
232
				return
233
		else:
231
		else:
234
			self.required_options( request, 'license' )
232
			self.required_options( request, 'license' )
235
			lic = request.options[ 'license' ]
233
			lic = request.options[ 'license' ]

Return to bug 31923