diff --git a/reviewboard/scmtools/admin.py b/reviewboard/scmtools/admin.py
--- a/reviewboard/scmtools/admin.py
+++ b/reviewboard/scmtools/admin.py
@@ -39,7 +39,10 @@ class RepositoryAdmin(admin.ModelAdmin):
             'classes': ('wide',),
         }),
         (_('Advanced'), {
-            'fields': ('encoding',),
+            'fields': (
+                'encoding',
+                'ticket_based_auth',
+            ),
             'classes': ('wide',),
         }),
     )
diff --git a/reviewboard/scmtools/core.py b/reviewboard/scmtools/core.py
--- a/reviewboard/scmtools/core.py
+++ b/reviewboard/scmtools/core.py
@@ -49,6 +49,13 @@ class SCMTool(object):
     supports_authentication = False
     supports_raw_file_urls = False
 
+    # A list of supported metadata.
+    # The Repository model includes a JSON field to store metadata.
+    # This list here is used to determine which of the stored variables is
+    # used by the particular SCMTool. The Repository admin form will show
+    # additional input fields depending on these settings.
+    supported_metadata = []
+
     # A list of dependencies for this SCMTool. This should be overridden
     # by subclasses. Python module names go in dependencies['modules'] and
     # binary executables go in dependencies['executables'] (but without
diff --git a/reviewboard/scmtools/evolutions/__init__.py b/reviewboard/scmtools/evolutions/__init__.py
--- a/reviewboard/scmtools/evolutions/__init__.py
+++ b/reviewboard/scmtools/evolutions/__init__.py
@@ -3,4 +3,5 @@ SEQUENCE = [
     'repository_raw_file_url',
     'repository_visible',
     'repository_path_length_255',
+    'repository_metadata',
 ]
diff --git a/reviewboard/scmtools/evolutions/repository_metadata.py b/reviewboard/scmtools/evolutions/repository_metadata.py
--- /dev/null
+++ b/reviewboard/scmtools/evolutions/repository_metadata.py
@@ -0,0 +1,8 @@
+from django_evolution.mutations import AddField
+from django.db import models
+from djblets.util.fields import JSONField
+
+
+MUTATIONS = [
+    AddField('Repository', 'metadata', JSONField, initial='')
+]
diff --git a/reviewboard/scmtools/forms.py b/reviewboard/scmtools/forms.py
--- a/reviewboard/scmtools/forms.py
+++ b/reviewboard/scmtools/forms.py
@@ -352,6 +352,12 @@ class RepositoryForm(forms.ModelForm):
                     "an advanced setting and should only be used if you're "
                     "sure you need it."))
 
+    ticket_based_auth = forms.BooleanField(
+        label=_("Ticket-based authentication"),
+        required=False,
+        help_text=_("Some Perforce servers require ticket base authentication "
+                    "due to high security settings."))
+
 
     def __init__(self, *args, **kwargs):
         super(RepositoryForm, self).__init__(*args, **kwargs)
@@ -362,6 +368,11 @@ class RepositoryForm(forms.ModelForm):
         self._populate_hosting_service_fields()
         self._populate_bug_tracker_fields()
 
+        if kwargs.has_key('instance'):
+            instance = kwargs['instance']
+            for meta_field in instance.get_scmtool().supported_metadata:
+                self.initial[meta_field] = instance.metadata[meta_field]
+
     def _populate_hosting_service_fields(self):
         if (not self.instance or
             not self.instance.path or
@@ -568,6 +579,17 @@ class RepositoryForm(forms.ModelForm):
 
         return tool
 
+    def save(self, commit=True):
+        model = super(RepositoryForm, self).save(commit=False)
+
+        for meta_field in model.get_scmtool().supported_metadata:
+            model.metadata[meta_field] = self.cleaned_data[meta_field]
+
+        if commit:
+            model.save()
+
+        return model
+
     def is_valid(self):
         """
         Returns whether or not the form is valid.
diff --git a/reviewboard/scmtools/models.py b/reviewboard/scmtools/models.py
--- a/reviewboard/scmtools/models.py
+++ b/reviewboard/scmtools/models.py
@@ -1,5 +1,7 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from djblets.util.fields import JSONField
 
 
 class Tool(models.Model):
@@ -10,6 +12,8 @@ class Tool(models.Model):
         lambda x: x.get_scmtool_class().supports_authentication)
     supports_raw_file_urls = property(
         lambda x: x.get_scmtool_class().supports_raw_file_urls)
+    supported_metadata = property(
+        lambda x: x.get_scmtool_class().supported_metadata)
 
     def __unicode__(self):
         return self.name
@@ -46,6 +50,8 @@ class Repository(models.Model):
     bug_tracker = models.CharField(max_length=256, blank=True)
     encoding = models.CharField(max_length=32, blank=True)
     visible = models.BooleanField(default=True)
+    metadata = JSONField(_("metadata"))
+
 
     def get_scmtool(self):
         cls = self.tool.get_scmtool_class()
diff --git a/reviewboard/scmtools/perforce.py b/reviewboard/scmtools/perforce.py
--- a/reviewboard/scmtools/perforce.py
+++ b/reviewboard/scmtools/perforce.py
@@ -15,11 +15,14 @@ from reviewboard.scmtools.errors import SCMError, EmptyChangeSetError
 class PerforceTool(SCMTool):
     name = "Perforce"
     uses_atomic_revisions = True
+    uses_ticket_based_auth = False
     supports_authentication = True
     dependencies = {
         'modules': ['P4'],
     }
 
+    supported_metadata = ['ticket_based_auth',]
+
     def __init__(self, repository):
         SCMTool.__init__(self, repository)
 
@@ -30,6 +33,15 @@ class PerforceTool(SCMTool):
         self.p4.password = str(repository.password)
         self.p4.exception_level = 1
 
+        # Some servers require the use of ticket based authentication
+        # Will raise an exception if called with empty user/password
+        if (repository.metadata.get('ticket_based_auth', False) and
+           self.p4.user and self.p4.password):
+            uses_ticket_based_auth = True
+            self.p4.connect()
+            self.p4.run_login()
+            self.p4.disconnect()
+
         # We defer actually connecting until just before we do some operation
         # that requires an active connection to the perforce depot.  This
         # connection is then left open as long as possible.
@@ -81,29 +93,47 @@ class PerforceTool(SCMTool):
         else:
             file = '%s#%s' % (path, revision)
 
-        cmdline = ['p4', '-p', self.p4.port]
-        if self.p4.user:
-            cmdline.extend(['-u', self.p4.user])
-        if self.p4.password:
-            cmdline.extend(['-P', self.p4.password])
-        cmdline.extend(['print', '-q', file])
-
-        p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        # We could simply use the perforce APIs run_print to get file content.
+        # However there have been blank lines issues with that command in the past.
+        # As we don't want to break things, run_print is only used with ticket based
+        # authentication at this point in time as realizing this without using the API
+        # would enforce another command line call for "p4 login" that gets the password
+        # as piped input.
+        # This should be reconsidered in future releases!
+        if uses_ticket_based_auth == True:
+            self._connect()
+            res = self.p4.run_print(file)
+            self._disconnect()
 
-        (res, errdata) = p.communicate()
-        failure = p.poll()
+            if res:
+                return res[-1]
 
-        if failure:
-            error = errdata.splitlines()
-            # The command-line output is the same as the contents of a P4Error
-            # except they're prefixed with a line that says "Perforce client
-            # error:", and the lines of the error are indented with tabs.
-            if error[0].startswith("Perforce client error:"):
-                error = error[1:]
+                return None
 
-            raise SCMError('\n'.join(line.lstrip("\t") for line in error))
         else:
-            return res
+            cmdline = ['p4', '-p', self.p4.port]
+            if self.p4.user:
+                cmdline.extend(['-u', self.p4.user])
+            if self.p4.password:
+                cmdline.extend(['-P', self.p4.password])
+            cmdline.extend(['print', '-q', file])
+
+            p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+            (res, errdata) = p.communicate()
+            failure = p.poll()
+
+            if failure:
+                error = errdata.splitlines()
+                # The command-line output is the same as the contents of a P4Error
+                # except they're prefixed with a line that says "Perforce client
+                # error:", and the lines of the error are indented with tabs.
+                if error[0].startswith("Perforce client error:"):
+                    error = error[1:]
+
+                raise SCMError('\n'.join(line.lstrip("\t") for line in error))
+            else:
+                return res
 
     def parse_diff_revision(self, file_str, revision_str):
         # Perforce has this lovely idiosyncracy that diffs show revision #1 both
diff --git a/reviewboard/templates/admin/scmtools/repository/change_form.html b/reviewboard/templates/admin/scmtools/repository/change_form.html
--- a/reviewboard/templates/admin/scmtools/repository/change_form.html
+++ b/reviewboard/templates/admin/scmtools/repository/change_form.html
@@ -40,12 +40,15 @@
   }{% endspaceless %}
 
   var TOOLS_FIELDS = { {% spaceless %}
-      "none": [ "raw_file_url", "username", "password" ],
+      "none": [ "raw_file_url", "username", "password", "ticket_based_auth" ],
 {% for tool in adminform.form.tool.field.queryset %}
       "{{tool.id}}": [ {% spaceless %}
 {%  if tool.supports_raw_file_urls %}
            "raw_file_url",
 {%  endif %}
+{%  for field in tool.supported_metadata %}
+		   "{{field}}",
+{%  endfor %}
            "username", "password"
       {% endspaceless %} ]{% if not forloop.last %},{% endif %}
 {% endfor %}
