// Copyright 2017 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package models import ( "bytes" "container/list" "crypto" "encoding/base64" "fmt" "hash" "io" "strings" "time" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "github.com/keybase/go-crypto/openpgp" "github.com/keybase/go-crypto/openpgp/armor" "github.com/keybase/go-crypto/openpgp/packet" "xorm.io/xorm" ) // GPGKey represents a GPG key. type GPGKey struct { ID int64 `xorm:"pk autoincr"` OwnerID int64 `xorm:"INDEX NOT NULL"` KeyID string `xorm:"INDEX CHAR(16) NOT NULL"` PrimaryKeyID string `xorm:"CHAR(16)"` Content string `xorm:"TEXT NOT NULL"` CreatedUnix timeutil.TimeStamp `xorm:"created"` ExpiredUnix timeutil.TimeStamp AddedUnix timeutil.TimeStamp SubsKey []*GPGKey `xorm:"-"` Emails []*EmailAddress CanSign bool CanEncryptComms bool CanEncryptStorage bool CanCertify bool } //GPGKeyImport the original import of key type GPGKeyImport struct { KeyID string `xorm:"pk CHAR(16) NOT NULL"` Content string `xorm:"TEXT NOT NULL"` } // BeforeInsert will be invoked by XORM before inserting a record func (key *GPGKey) BeforeInsert() { key.AddedUnix = timeutil.TimeStampNow() } // AfterLoad is invoked from XORM after setting the values of all fields of this object. func (key *GPGKey) AfterLoad(session *xorm.Session) { err := session.Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey) if err != nil { log.Error("Find Sub GPGkeys[%s]: %v", key.KeyID, err) } } // ListGPGKeys returns a list of public keys belongs to given user. func ListGPGKeys(uid int64, listOptions ListOptions) ([]*GPGKey, error) { sess := x.Where("owner_id=? AND primary_key_id=''", uid) if listOptions.Page != 0 { sess = listOptions.setSessionPagination(sess) } keys := make([]*GPGKey, 0, 2) return keys, sess.Find(&keys) } // GetGPGKeyByID returns public key by given ID. func GetGPGKeyByID(keyID int64) (*GPGKey, error) { key := new(GPGKey) has, err := x.ID(keyID).Get(key) if err != nil { return nil, err } else if !has { return nil, ErrGPGKeyNotExist{keyID} } return key, nil } // GetGPGKeysByKeyID returns public key by given ID. func GetGPGKeysByKeyID(keyID string) ([]*GPGKey, error) { keys := make([]*GPGKey, 0, 1) return keys, x.Where("key_id=?", keyID).Find(&keys) } // GetGPGImportByKeyID returns the import public armored key by given KeyID. func GetGPGImportByKeyID(keyID string) (*GPGKeyImport, error) { key := new(GPGKeyImport) has, err := x.ID(keyID).Get(key) if err != nil { return nil, err } else if !has { return nil, ErrGPGKeyImportNotExist{keyID} } return key, nil } // checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key. // The function returns the actual public key on success func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) { list, err := openpgp.ReadArmoredKeyRing(strings.NewReader(content)) if err != nil { return nil, ErrGPGKeyParsing{err} } return list[0], nil } //addGPGKey add key, import and subkeys to database func addGPGKey(e Engine, key *GPGKey, content string) (err error) { //Add GPGKeyImport if _, err = e.Insert(GPGKeyImport{ KeyID: key.KeyID, Content: content, }); err != nil { return err } // Save GPG primary key. if _, err = e.Insert(key); err != nil { return err } // Save GPG subs key. for _, subkey := range key.SubsKey { if err := addGPGSubKey(e, subkey); err != nil { return err } } return nil } //addGPGSubKey add subkeys to database func addGPGSubKey(e Engine, key *GPGKey) (err error) { // Save GPG primary key. if _, err = e.Insert(key); err != nil { return err } // Save GPG subs key. for _, subkey := range key.SubsKey { if err := addGPGSubKey(e, subkey); err != nil { return err } } return nil } // AddGPGKey adds new public key to database. func AddGPGKey(ownerID int64, content string) (*GPGKey, error) { ekey, err := checkArmoredGPGKeyString(content) if err != nil { return nil, err } // Key ID cannot be duplicated. has, err := x.Where("key_id=?", ekey.PrimaryKey.KeyIdString()). Get(new(GPGKey)) if err != nil { return nil, err } else if has { return nil, ErrGPGKeyIDAlreadyUsed{ekey.PrimaryKey.KeyIdString()} } //Get DB session sess := x.NewSession() defer sess.Close() if err = sess.Begin(); err != nil { return nil, err } key, err := parseGPGKey(ownerID, ekey) if err != nil { return nil, err } if err = addGPGKey(sess, key, content); err != nil { return nil, err } return key, sess.Commit() } //base64EncPubKey encode public key content to base 64 func base64EncPubKey(pubkey *packet.PublicKey) (string, error) { var w bytes.Buffer err := pubkey.Serialize(&w) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(w.Bytes()), nil } //base64DecPubKey decode public key content from base 64 func base64DecPubKey(content string) (*packet.PublicKey, error) { b, err := readerFromBase64(content) if err != nil { return nil, err } //Read key p, err := packet.Read(b) if err != nil { return nil, err } //Check type pkey, ok := p.(*packet.PublicKey) if !ok { return nil, fmt.Errorf("key is not a public key") } return pkey, nil } //GPGKeyToEntity retrieve the imported key and the traducted entity func GPGKeyToEntity(k *GPGKey) (*openpgp.Entity, error) { impKey, err := GetGPGImportByKeyID(k.KeyID) if err != nil { return nil, err } return checkArmoredGPGKeyString(impKey.Content) } //parseSubGPGKey parse a sub Key func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) { content, err := base64EncPubKey(pubkey) if err != nil { return nil, err } return &GPGKey{ OwnerID: ownerID, KeyID: pubkey.KeyIdString(), PrimaryKeyID: primaryID, Content: content, CreatedUnix: timeutil.TimeStamp(pubkey.CreationTime.Unix()), ExpiredUnix: timeutil.TimeStamp(expiry.Unix()), CanSign: pubkey.CanSign(), CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(), CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(), CanCertify: pubkey.PubKeyAlgo.CanSign(), }, nil } //getExpiryTime extract the expire time of primary key based on sig func getExpiryTime(e *openpgp.Entity) time.Time { expiry := time.Time{} //Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165 var selfSig *packet.Signature for _, ident := range e.Identities { if selfSig == nil { selfSig = ident.SelfSignature } else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId { selfSig = ident.SelfSignature break } } if selfSig.KeyLifetimeSecs != nil { expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second) } return expiry } //parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature) func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) { pubkey := e.PrimaryKey expiry := getExpiryTime(e) //Parse Subkeys subkeys := make([]*GPGKey, len(e.Subkeys)) for i, k := range e.Subkeys { subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry) if err != nil { return nil, ErrGPGKeyParsing{ParseError: err} } subkeys[i] = subs } //Check emails userEmails, err := GetEmailAddresses(ownerID) if err != nil { return nil, err } emails := make([]*EmailAddress, 0, len(e.Identities)) for _, ident := range e.Identities { if ident.Revocation != nil { continue } email := strings.ToLower(strings.TrimSpace(ident.UserId.Email)) for _, e := range userEmails { if e.Email == email { emails = append(emails, e) break } } } //In the case no email as been found if len(emails) == 0 { failedEmails := make([]string, 0, len(e.Identities)) for _, ident := range e.Identities { failedEmails = append(failedEmails, ident.UserId.Email) } return nil, ErrGPGNoEmailFound{failedEmails} } content, err := base64EncPubKey(pubkey) if err != nil { return nil, err } return &GPGKey{ OwnerID: ownerID, KeyID: pubkey.KeyIdString(), PrimaryKeyID: "", Content: content, CreatedUnix: timeutil.TimeStamp(pubkey.CreationTime.Unix()), ExpiredUnix: timeutil.TimeStamp(expiry.Unix()), Emails: emails, SubsKey: subkeys, CanSign: pubkey.CanSign(), CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(), CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(), CanCertify: pubkey.PubKeyAlgo.CanSign(), }, nil } // deleteGPGKey does the actual key deletion func deleteGPGKey(e *xorm.Session, keyID string) (int64, error) { if keyID == "" { return 0, fmt.Errorf("empty KeyId forbidden") //Should never happen but just to be sure } //Delete imported key n, err := e.Where("key_id=?", keyID).Delete(new(GPGKeyImport)) if err != nil { return n, err } return e.Where("key_id=?", keyID).Or("primary_key_id=?", keyID).Delete(new(GPGKey)) } // DeleteGPGKey deletes GPG key information in database. func DeleteGPGKey(doer *User, id int64) (err error) { key, err := GetGPGKeyByID(id) if err != nil { if IsErrGPGKeyNotExist(err) { return nil } return fmt.Errorf("GetPublicKeyByID: %v", err) } // Check if user has access to delete this key. if !doer.IsAdmin && doer.ID != key.OwnerID { return ErrGPGKeyAccessDenied{doer.ID, key.ID} } sess := x.NewSession() defer sess.Close() if err = sess.Begin(); err != nil { return err } if _, err = deleteGPGKey(sess, key.KeyID); err != nil { return err } return sess.Commit() } // CommitVerification represents a commit validation of signature type CommitVerification struct { Verified bool Warning bool Reason string SigningUser *User CommittingUser *User SigningEmail string SigningKey *GPGKey TrustStatus string } // SignCommit represents a commit with validation of signature. type SignCommit struct { Verification *CommitVerification *UserCommit } const ( // BadSignature is used as the reason when the signature has a KeyID that is in the db // but no key that has that ID verifies the signature. This is a suspicious failure. BadSignature = "gpg.error.probable_bad_signature" // BadDefaultSignature is used as the reason when the signature has a KeyID that matches the // default Key but is not verified by the default key. This is a suspicious failure. BadDefaultSignature = "gpg.error.probable_bad_default_signature" // NoKeyFound is used as the reason when no key can be found to verify the signature. NoKeyFound = "gpg.error.no_gpg_keys_found" ) func readerFromBase64(s string) (io.Reader, error) { bs, err := base64.StdEncoding.DecodeString(s) if err != nil { return nil, err } return bytes.NewBuffer(bs), nil } func populateHash(hashFunc crypto.Hash, msg []byte) (hash.Hash, error) { h := hashFunc.New() if _, err := h.Write(msg); err != nil { return nil, err } return h, nil } // readArmoredSign read an armored signature block with the given type. https://sourcegraph.com/github.com/golang/crypto/-/blob/openpgp/read.go#L24:6-24:17 func readArmoredSign(r io.Reader) (body io.Reader, err error) { block, err := armor.Decode(r) if err != nil { return } if block.Type != openpgp.SignatureType { return nil, fmt.Errorf("expected '" + openpgp.SignatureType + "', got: " + block.Type) } return block.Body, nil } func extractSignature(s string) (*packet.Signature, error) { r, err := readArmoredSign(strings.NewReader(s)) if err != nil { return nil, fmt.Errorf("Failed to read signature armor") } p, err := packet.Read(r) if err != nil { return nil, fmt.Errorf("Failed to read signature packet") } sig, ok := p.(*packet.Signature) if !ok { return nil, fmt.Errorf("Packet is not a signature") } return sig, nil } func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error { //Check if key can sign if !k.CanSign { return fmt.Errorf("key can not sign") } //Decode key pkey, err := base64DecPubKey(k.Content) if err != nil { return err } return pkey.VerifySignature(h, s) } func hashAndVerify(sig *packet.Signature, payload string, k *GPGKey, committer, signer *User, email string) *CommitVerification { //Generating hash of commit hash, err := populateHash(sig.Hash, []byte(payload)) if err != nil { //Skipping failed to generate hash log.Error("PopulateHash: %v", err) return &CommitVerification{ CommittingUser: committer, Verified: false, Reason: "gpg.error.generate_hash", } } if err := verifySign(sig, hash, k); err == nil { return &CommitVerification{ //Everything is ok CommittingUser: committer, Verified: true, Reason: fmt.Sprintf("%s <%s> / %s", signer.Name, signer.Email, k.KeyID), SigningUser: signer, SigningKey: k, SigningEmail: email, } } return nil } func hashAndVerifyWithSubKeys(sig *packet.Signature, payload string, k *GPGKey, committer, signer *User, email string) *CommitVerification { commitVerification := hashAndVerify(sig, payload, k, committer, signer, email) if commitVerification != nil { return commitVerification } //And test also SubsKey for _, sk := range k.SubsKey { commitVerification := hashAndVerify(sig, payload, sk, committer, signer, email) if commitVerification != nil { return commitVerification } } return nil } func hashAndVerifyForKeyID(sig *packet.Signature, payload string, committer *User, keyID, name, email string) *CommitVerification { if keyID == "" { return nil } keys, err := GetGPGKeysByKeyID(keyID) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ CommittingUser: committer, Verified: false, Reason: "gpg.error.failed_retrieval_gpg_keys", } } if len(keys) == 0 { return nil } for _, key := range keys { var primaryKeys []*GPGKey if key.PrimaryKeyID != "" { primaryKeys, err = GetGPGKeysByKeyID(key.PrimaryKeyID) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ CommittingUser: committer, Verified: false, Reason: "gpg.error.failed_retrieval_gpg_keys", } } } activated := false if len(email) != 0 { for _, e := range key.Emails { if e.IsActivated && strings.EqualFold(e.Email, email) { activated = true email = e.Email break } } if !activated { for _, pkey := range primaryKeys { for _, e := range pkey.Emails { if e.IsActivated && strings.EqualFold(e.Email, email) { activated = true email = e.Email break } } if activated { break } } } } else { for _, e := range key.Emails { if e.IsActivated { activated = true email = e.Email break } } if !activated { for _, pkey := range primaryKeys { for _, e := range pkey.Emails { if e.IsActivated { activated = true email = e.Email break } } if activated { break } } } } if !activated { continue } signer := &User{ Name: name, Email: email, } if key.OwnerID != 0 { owner, err := GetUserByID(key.OwnerID) if err == nil { signer = owner } else if !IsErrUse<?php $TRANSLATIONS = array( "Unable to load list from App Store" => "Kan de lijst niet van de App store laden", "Authentication error" => "Authenticatie fout", "Your display name has been changed." => "Uw weergavenaam is gewijzigd.", "Unable to change display name" => "Kon de weergavenaam niet wijzigen", "Group already exists" => "Groep bestaat al", "Unable to add group" => "Niet in staat om groep toe te voegen", "Could not enable app. " => "Kan de app. niet activeren", "Email saved" => "E-mail bewaard", "Invalid email" => "Ongeldige e-mail", "Unable to delete group" => "Niet in staat om groep te verwijderen", "Unable to delete user" => "Niet in staat om gebruiker te verwijderen", "Language changed" => "Taal aangepast", "Invalid request" => "Ongeldige aanvraag", "Admins can't remove themself from the admin group" => "Admins kunnen zichzelf niet uit de admin groep verwijderen", "Unable to add user to group %s" => "Niet in staat om gebruiker toe te voegen aan groep %s", "Unable to remove user from group %s" => "Niet in staat om gebruiker te verwijderen uit groep %s", "Couldn't update app." => "Kon de app niet bijwerken.", "Update to {appversion}" => "Bijwerken naar {appversion}", "Disable" => "Uitschakelen", "Enable" => "Activeer", "Please wait...." => "Even geduld aub....", "Error" => "Fout", "Updating...." => "Bijwerken....", "Error while updating app" => "Fout bij bijwerken app", "Updated" => "Bijgewerkt", "Decrypting files... Please wait, this can take some time." => "Bestanden worden gedecodeerd... Even geduld alstublieft, dit kan even duren.", "Saving..." => "Opslaan", "deleted" => "verwijderd", "undo" => "ongedaan maken", "Unable to remove user" => "Kon gebruiker niet verwijderen", "Groups" => "Groepen", "Group Admin" => "Groep beheerder", "Delete" => "Verwijder", "add group" => "toevoegen groep", "A valid username must be provided" => "Er moet een geldige gebruikersnaam worden opgegeven", "Error creating user" => "Fout bij aanmaken gebruiker", "A valid password must be provided" => "Er moet een geldig wachtwoord worden opgegeven", "__language_name__" => "Nederlands", "Security Warning" => "Beveiligingswaarschuwing", "Your data directory and your files are probably accessible from the internet. The .htaccess file is not working. We strongly suggest that you configure your webserver in a way that the data directory is no longer accessible or you move the data directory outside the webserver document root." => "Uw data folder en uw bestanden zijn waarschijnlijk vanaf het internet bereikbaar. Het .htaccess-bestand werkt niet. We raden ten zeerste aan aan om uw webserver zodanig te configureren, dat de datamap niet bereikbaar is vanaf het internet of om uw datamap te verplaatsen naar een locatie buiten de document root van de webserver.", "Setup Warning" => "Instellingswaarschuwing", "Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Uw webserver is nog niet goed ingesteld voor bestandssynchronisatie omdat de WebDAV interface verbroken lijkt.", "Please double check the <a href=\"%s\">installation guides</a>." => "Conntroleer de <a href='%s'>installatie handleiding</a> goed.", "Module 'fileinfo' missing" => "Module 'fileinfo' ontbreekt", "The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." => "De PHP module 'fileinfo' ontbreekt. We adviseren met klem om deze module te activeren om de beste resultaten te bereiken voor mime-type detectie.", "Locale not working" => "Taalbestand werkt niet", "System locale can't be set to %s. This means that there might be problems with certain characters in file names. We strongly suggest to install the required packages on your system to support %s." => "De systeemtaal kan niet worden ingesteld op %s. Hierdoor kunnen er problemen optreden met bepaalde karakters in bestandsnamen. Het wordt sterk aangeraden om de vereiste pakketen op uw systeem te installeren, zodat %s ondersteund wordt.", "Internet connection not working" => "Internet verbinding werkt niet", "This server has no working internet connection. This means that some of the features like mounting of external storage, notifications about updates or installation of 3rd party apps don´t work. Accessing files from remote and sending of notification emails might also not work. We suggest to enable internet connection for this server if you want to have all features." => "Deze server heeft geen actieve internetverbinding. Dat betekent dat sommige functies, zoals aankoppelen van externe opslag, notificaties over updates of installatie van apps van 3e partijen niet werken. Ook het benaderen van bestanden vanaf een remote locatie en het versturen van notificatie emails kan mislukken. We adviseren om de internetverbinding voor deze server in te schakelen als u alle functies wilt gebruiken.", "Cron" => "Cron", "Execute one task with each page loaded" => "Bij laden van elke pagina één taak uitvoeren", "cron.php is registered at a webcron service to call cron.php once a minute over http." => "cron.php is geregistreerd bij een webcron service om cron.php eens per minuut aan te roepen via http.", "Use systems cron service to call the cron.php file once a minute." => "Gebruik de systeem cron service om het bestand cron.php eens per minuut aan te roepen.", "Sharing" => "Delen", "Enable Share API" => "Activeren Share API", "Allow apps to use the Share API" => "Apps toestaan de Share API te gebruiken", "Allow links" => "Toestaan links", "Allow users to share items to the public with links" => "Toestaan dat gebruikers objecten met links delen met anderen", "Allow public uploads" => "Sta publieke uploads toe", "Allow users to enable others to upload into their publicly shared folders" => "Sta gebruikers toe anderen in hun publiek gedeelde mappen bestanden te uploaden", "Allow resharing" => "Toestaan opnieuw delen", "Allow users to share items shared with them again" => "Toestaan dat gebruikers objecten die anderen met hun gedeeld hebben zelf ook weer delen met anderen", "Allow users to share with anyone" => "Toestaan dat gebruikers met iedereen delen", "Allow users to only share with users in their groups" => "Instellen dat gebruikers alleen met leden binnen hun groepen delen", "Security" => "Beveiliging", "Enforce HTTPS" => "Afdwingen HTTPS", "Forces the clients to connect to %s via an encrypted connection." => "Dwingt de clients om een versleutelde verbinding te maken met %s", "Please connect to your %s via HTTPS to enable or disable the SSL enforcement." => "Maak verbinding naar uw %s via HTTPS om een geforceerde versleutelde verbinding in- of uit te schakelen.", "Log" => "Log", "Log level" => "Log niveau", "More" => "Meer", "Less" => "Minder", "Version" => "Versie", "Developed by the <a href=\"http://ownCloud.org/contact\" target=\"_blank\">ownCloud community</a>, the <a href=\"https://github.com/owncloud\" target=\"_blank\">source code</a> is licensed under the <a href=\"http://www.gnu.org/licenses/agpl-3.0.html\" target=\"_blank\"><abbr title=\"Affero General Public License\">AGPL</abbr></a>." => "Ontwikkeld door de <a href=\"http://ownCloud.org/contact\" target=\"_blank\">ownCloud gemeenschap</a>, de <a href=\"https://github.com/owncloud\" target=\"_blank\">bron code</a> is gelicenseerd onder de <a href=\"http://www.gnu.org/licenses/agpl-3.0.html\" target=\"_blank\"><abbr title=\"Affero General Public License\">AGPL</abbr></a>.", "Add your App" => "App toevoegen", "More Apps" => "Meer apps", "Select an App" => "Selecteer een app", "See application page at apps.owncloud.com" => "Zie de applicatiepagina op apps.owncloud.com", "<span class=\"licence\"></span>-licensed by <span class=\"author\"></span>" => "<span class=\"licence\"></span>-Gelicenseerd door <span class=\"author\"></span>", "Update" => "Bijwerken", "User Documentation" => "Gebruikersdocumentatie", "Administrator Documentation" => "Beheerdersdocumentatie", "Online Documentation" => "Online documentatie", "Forum" => "Forum", "Bugtracker" => "Bugtracker", "Commercial Support" => "Commerciële ondersteuning", "Get the apps to sync your files" => "Download de apps om bestanden te syncen", "Show First Run Wizard again" => "Toon de Eerste start Wizard opnieuw", "You have used <strong>%s</strong> of the available <strong>%s</strong>" => "Je hebt <strong>%s</strong> gebruikt van de beschikbare <strong>%s<strong>", "Password" => "Wachtwoord", "Your password was changed" => "Je wachtwoord is veranderd", "Unable to change your password" => "Niet in staat om uw wachtwoord te wijzigen", "Current password" => "Huidig wachtwoord", "New password" => "Nieuw", "Change password" => "Wijzig wachtwoord", "Display Name" => "Weergavenaam", "Email" => "E-mailadres", "Your email address" => "Uw e-mailadres", "Fill in an email address to enable password recovery" => "Vul een mailadres in om je wachtwoord te kunnen herstellen", "Language" => "Taal", "Help translate" => "Help met vertalen", "WebDAV" => "WebDAV", "Use this address to <a href=\"%s/server/5.0/user_manual/files/files.html\" target=\"_blank\">access your Files via WebDAV</a>" => "Gebruik dit adres <a href=\"%s/server/5.0/user_manual/files/files.html\" target=\"_blank\">toegang tot uw bestanden via WebDAV</a>", "Encryption" => "Versleuteling", "The encryption app is no longer enabled, decrypt all your file" => "De encryptie-appplicatie is niet meer aanwezig, decodeer al uw bestanden", "Log-in password" => "Inlog-wachtwoord", "Decrypt all Files" => "Decodeer alle bestanden", "Login Name" => "Inlognaam", "Create" => "Creëer", "Admin Recovery Password" => "Beheer herstel wachtwoord", "Enter the recovery password in order to recover the users files during password change" => "Voer het herstel wachtwoord in om de gebruikersbestanden terug te halen bij wachtwoordwijziging", "Default Storage" => "Standaard Opslaglimiet", "Unlimited" => "Ongelimiteerd", "Other" => "Anders", "Username" => "Gebruikersnaam", "Storage" => "Opslaglimiet", "change display name" => "wijzig weergavenaam", "set new password" => "Instellen nieuw wachtwoord", "Default" => "Standaard" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
<?php $TRANSLATIONS = array( "Unable to load list from App Store" => "Kan de lijst niet van de App store laden", "Authentication error" => "Authenticatie fout", "Your display name has been changed." => "Uw weergavenaam is gewijzigd.", "Unable to change display name" => "Kon de weergavenaam niet wijzigen", "Group already exists" => "Groep bestaat al", "Unable to add group" => "Niet in staat om groep toe te voegen", "Could not enable app. " => "Kan de app. niet activeren", "Email saved" => "E-mail bewaard", "Invalid email" => "Ongeldige e-mail", "Unable to delete group" => "Niet in staat om groep te verwijderen", "Unable to delete user" => "Niet in staat om gebruiker te verwijderen", "Language changed" => "Taal aangepast", "Invalid request" => "Ongeldige aanvraag", "Admins can't remove themself from the admin group" => "Admins kunnen zichzelf niet uit de admin groep verwijderen", "Unable to add user to group %s" => "Niet in staat om gebruiker toe te voegen aan groep %s", "Unable to remove user from group %s" => "Niet in staat om gebruiker te verwijderen uit groep %s", "Couldn't update app." => "Kon de app niet bijwerken.", "Update to {appversion}" => "Bijwerken naar {appversion}", "Disable" => "Uitschakelen", "Enable" => "Activeer", "Please wait...." => "Even geduld aub....", "Error" => "Fout", "Updating...." => "Bijwerken....", "Error while updating app" => "Fout bij bijwerken app", "Updated" => "Bijgewerkt", "Decrypting files... Please wait, this can take some time." => "Bestanden worden gedecodeerd... Even geduld alstublieft, dit kan even duren.", "Saving..." => "Opslaan", "deleted" => "verwijderd", "undo" => "ongedaan maken", "Unable to remove user" => "Kon gebruiker niet verwijderen", "Groups" => "Groepen", "Group Admin" => "Groep beheerder", "Delete" => "Verwijder", "add group" => "toevoegen groep", "A valid username must be provided" => "Er moet een geldige gebruikersnaam worden opgegeven", "Error creating user" => "Fout bij aanmaken gebruiker", "A valid password must be provided" => "Er moet een geldig wachtwoord worden opgegeven", "__language_name__" => "Nederlands", "Security Warning" => "Beveiligingswaarschuwing", "Your data directory and your files are probably accessible from the internet. The .htaccess file is not working. We strongly suggest that you configure your webserver in a way that the data directory is no longer accessible or you move the data directory outside the webserver document root." => "Uw data folder en uw bestanden zijn waarschijnlijk vanaf het internet bereikbaar. Het .htaccess-bestand werkt niet. We raden ten zeerste aan aan om uw webserver zodanig te configureren, dat de datamap niet bereikbaar is vanaf het internet of om uw datamap te verplaatsen naar een locatie buiten de document root van de webserver.", "Setup Warning" => "Instellingswaarschuwing", "Your web server is not yet properly setup to allow files synchronization because the WebDAV interface seems to be broken." => "Uw webserver is nog niet goed ingesteld voor bestandssynchronisatie omdat de WebDAV interface verbroken lijkt.", "Please double check the <a href=\"%s\">installation guides</a>." => "Conntroleer de <a href='%s'>installatie handleiding</a> goed.", "Module 'fileinfo' missing" => "Module 'fileinfo' ontbreekt", "The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." => "De PHP module 'fileinfo' ontbreekt. We adviseren met klem om deze module te activeren om de beste resultaten te bereiken voor mime-type detectie.", "Locale not working" => "Taalbestand werkt niet", "System locale can't be set to %s. This means that there might be problems with certain characters in file names. We strongly suggest to install the required packages on your system to support %s." => "De systeemtaal kan niet worden ingesteld op %s. Hierdoor kunnen er problemen optreden met bepaalde karakters in bestandsnamen. Het wordt sterk aangeraden om de vereiste pakketen op uw systeem te installeren, zodat %s ondersteund wordt.", "Internet connection not working" => "Internet verbinding werkt niet", "This server has no working internet connection. This means that some of the features like mounting of external storage, notifications about updates or installation of 3rd party apps don´t work. Accessing files from remote and sending of notification emails might also not work. We suggest to enable internet connection for this server if you want to have all features." => "Deze server heeft geen actieve internetverbinding. Dat betekent dat sommige functies, zoals aankoppelen van externe opslag, notificaties over updates of installatie van apps van 3e partijen niet werken. Ook het benaderen van bestanden vanaf een remote locatie en het versturen van notificatie emails kan mislukken. We adviseren om de internetverbinding voor deze server in te schakelen als u alle functies wilt gebruiken.", "Cron" => "Cron", "Execute one task with each page loaded" => "Bij laden van elke pagina één taak uitvoeren", "cron.php is registered at a webcron service to call cron.php once a minute over http." => "cron.php is geregistreerd bij een webcron service om cron.php eens per minuut aan te roepen via http.", "Use systems cron service to call the cron.php file once a minute." => "Gebruik de systeem cron service om het bestand cron.php eens per minuut aan te roepen.", "Sharing" => "Delen", "Enable Share API" => "Activeren Share API", "Allow apps to use the Share API" => "Apps toestaan de Share API te gebruiken", "Allow links" => "Toestaan links", "Allow users to share items to the public with links" => "Toestaan dat gebruikers objecten met links delen met anderen", "Allow public uploads" => "Sta publieke uploads toe", "Allow users to enable others to upload into their publicly shared folders" => "Sta gebruikers toe anderen in hun publiek gedeelde mappen bestanden te uploaden", "Allow resharing" => "Toestaan opnieuw delen", "Allow users to share items shared with them again" => "Toestaan dat gebruikers objecten die anderen met hun gedeeld hebben zelf ook weer delen met anderen", "Allow users to share with anyone" => "Toestaan dat gebruikers met iedereen delen", "Allow users to only share with users in their groups" => "Instellen dat gebruikers alleen met leden binnen hun groepen delen", "Security" => "Beveiliging", "Enforce HTTPS" => "Afdwingen HTTPS", "Forces the clients to connect to %s via an encrypted connection." => "Dwingt de clients om een versleutelde verbinding te maken met %s", "Please connect to your %s via HTTPS to enable or disable the SSL enforcement." => "Maak verbinding naar uw %s via HTTPS om een geforceerde versleutelde verbinding in- of uit te schakelen.", "Log" => "Log", "Log level" => "Log niveau", "More" => "Meer", "Less" => "Minder", "Version" => "Versie", "Developed by the <a href=\"http://ownCloud.org/contact\" target=\"_blank\">ownCloud community</a>, the <a href=\"https://github.com/owncloud\" target=\"_blank\">source code</a> is licensed under the <a href=\"http://www.gnu.org/licenses/agpl-3.0.html\" target=\"_blank\"><abbr title=\"Affero General Public License\">AGPL</abbr></a>." => "Ontwikkeld door de <a href=\"http://ownCloud.org/contact\" target=\"_blank\">ownCloud gemeenschap</a>, de <a href=\"https://github.com/owncloud\" target=\"_blank\">bron code</a> is gelicenseerd onder de <a href=\"http://www.gnu.org/licenses/agpl-3.0.html\" target=\"_blank\"><abbr title=\"Affero General Public License\">AGPL</abbr></a>.", "Add your App" => "App toevoegen", "More Apps" => "Meer apps", "Select an App" => "Selecteer een app", "See application page at apps.owncloud.com" => "Zie de applicatiepagina op apps.owncloud.com", "<span class=\"licence\"></span>-licensed by <span class=\"author\"></span>" => "<span class=\"licence\"></span>-Gelicenseerd door <span class=\"author\"></span>", "Update" => "Bijwerken", "User Documentation" => "Gebruikersdocumentatie", "Administrator Documentation" => "Beheerdersdocumentatie", "Online Documentation" => "Online documentatie", "Forum" => "Forum", "Bugtracker" => "Bugtracker", "Commercial Support" => "Commerciële ondersteuning", "Get the apps to sync your files" => "Download de apps om bestanden te syncen", "Show First Run Wizard again" => "Toon de Eerste start Wizard opnieuw", "You have used <strong>%s</strong> of the available <strong>%s</strong>" => "Je hebt <strong>%s</strong> gebruikt van de beschikbare <strong>%s<strong>", "Password" => "Wachtwoord", "Your password was changed" => "Je wachtwoord is veranderd", "Unable to change your password" => "Niet in staat om uw wachtwoord te wijzigen", "Current password" => "Huidig wachtwoord", "New password" => "Nieuw", "Change password" => "Wijzig wachtwoord", "Display Name" => "Weergavenaam", "Email" => "E-mailadres", "Your email address" => "Uw e-mailadres", "Fill in an email address to enable password recovery" => "Vul een mailadres in om je wachtwoord te kunnen herstellen", "Language" => "Taal", "Help translate" => "Help met vertalen", "WebDAV" => "WebDAV", "Use this address to <a href=\"%s/server/5.0/user_manual/files/files.html\" target=\"_blank\">access your Files via WebDAV</a>" => "Gebruik dit adres <a href=\"%s/server/5.0/user_manual/files/files.html\" target=\"_blank\">toegang tot uw bestanden via WebDAV</a>", "Encryption" => "Versleuteling", "The encryption app is no longer enabled, decrypt all your file" => "De encryptie-appplicatie is niet meer aanwezig, decodeer al uw bestanden", "Log-in password" => "Inlog-wachtwoord", "Decrypt all Files" => "Decodeer alle bestanden", "Login Name" => "Inlognaam", "Create" => "Creëer", "Admin Recovery Password" => "Beheer herstel wachtwoord", "Enter the recovery password in order to recover the users files during password change" => "Voer het herstel wachtwoord in om de gebruikersbestanden terug te halen bij wachtwoordwijziging", "Default Storage" => "Standaard Opslaglimiet", "Unlimited" => "Ongelimiteerd", "Other" => "Anders", "Username" => "Gebruikersnaam", "Storage" => "Opslaglimiet", "change display name" => "wijzig weergavenaam", "set new password" => "Instellen nieuw wachtwoord", "Default" => "Standaard" ); $PLURAL_FORMS = "nplurals=2; plural=(n != 1);";