mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-17 07:33:57 +00:00
doc(sogo-tool): Add doc to detailed sogo-tool
This commit is contained in:
@@ -3312,6 +3312,489 @@ any mobile devices that support Microsoft ActiveSync. Microsoft Outlook
|
|||||||
The Microsoft ActiveSync server URL is generally something
|
The Microsoft ActiveSync server URL is generally something
|
||||||
like: `http://127.0.0.1/Microsoft-Server-ActiveSync`.
|
like: `http://127.0.0.1/Microsoft-Server-ActiveSync`.
|
||||||
|
|
||||||
|
Using sogo-tool
|
||||||
|
---------------
|
||||||
|
The command _sogo-tool_ allows to do some operations on database and sieve filter. It is included with
|
||||||
|
the sogo package on Debian/Ubuntu but must be installed manually on RHEl/CentOS:
|
||||||
|
|
||||||
|
yum install sogo-tool
|
||||||
|
|
||||||
|
*_WARNING_: Use sogo-tool with full awareness of what you are doing. This is an admin tool that can cause loss of data
|
||||||
|
or completely make the webmail unusable by a user.*
|
||||||
|
|
||||||
|
sogo-tool backup/restore
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The backup tool saves the information of a user into a file. The information saved are its preferences, its events and its contacts.
|
||||||
|
|
||||||
|
sogo-tool backup directory ALL|user1 [user2] ...
|
||||||
|
|
||||||
|
* First argument, *directory* must be a path, only the last subdirectory will be created, the previous ones must exist.
|
||||||
|
* Second argument is *ALL* to backup all users or put each user space-separated.
|
||||||
|
* Each user's info will take one file, the filename will be the username.
|
||||||
|
* The files are readable but in a specific format for _sogo-tool restore_.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool backup /tmp/foo ALL # Will save all users into /tmp/foo
|
||||||
|
sogo-tool backup /etc/sogo_backup/foo user1 user2 # Will save only user1 and user2. The directory /etc/sogo_backup must exist.
|
||||||
|
----
|
||||||
|
|
||||||
|
Using the files produced by the backup, you can restore all or some information of a user with the _restore_ command:
|
||||||
|
|
||||||
|
sogo-tool restore [-l|-p|-f/-F folder/ALL] [-c credentialFile] directory user
|
||||||
|
|
||||||
|
* First argument must be one of the four mode options *-l*, *-p*, *-f* or *-F*.
|
||||||
|
* Second, optional, is *-c* with the credential file. Only useful with *-p* mode.
|
||||||
|
* *directory* is the path where the backup file is.
|
||||||
|
* *user* is the name of the backup file, which is the username if you use the _backup_ tool.
|
||||||
|
|
||||||
|
The differents mode are:
|
||||||
|
|
||||||
|
* *-l* will only list the folders (meaning calendar and address book) that would be restored with *-f* or *-F*.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool restore -l /tmp/backup user1
|
||||||
|
----
|
||||||
|
Result:
|
||||||
|
----
|
||||||
|
Calendar/60C8-65323D80-7-4D9F7D80 (new_calendar)
|
||||||
|
Calendar/personal (Personal Calendar)
|
||||||
|
Contacts/personal (Personal Address Book)
|
||||||
|
----
|
||||||
|
|
||||||
|
* *-p* will restore the user's preferences. If the user has an active sieve script (filter, vacation, forward...) you must provide a credential file with
|
||||||
|
the parameters *-c*. The credential file is a simple one-line file that contains the "username:password" of an admin account of your imap/sieve server.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool restore -p /tmp/backup user1
|
||||||
|
sogo-tool restore -p -c /var/sogo/cred /tmp/backup user1
|
||||||
|
----
|
||||||
|
|
||||||
|
* *-f* will restore the events/contacts of folders (calendar and address book) from the backup file that don't exist in the database. If the event/contact
|
||||||
|
was deleted but is in the backup file, it will be restored. If the event/contact exist, nothing will be done even if it has been modified compared to the
|
||||||
|
backup file. If a whole folder has been deleted but is in the backup file, it will be restored. *-f* expect a value that can be ALL to restore all folders,
|
||||||
|
or the name of the folder to restore. You can list them with the *-l* mode.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool restore -f ALL /tmp/backup user1
|
||||||
|
sogo-tool restore -f "Contacts/personal" tmp/backup user1
|
||||||
|
sogo-tool restore -f "Calendar/60C8-65323D80-7-4D9F7D80" tmp/backup user1
|
||||||
|
----
|
||||||
|
The commands will either prints out nothing or any events/contacts restored:
|
||||||
|
----
|
||||||
|
restoring record '60CA-65323D00-1-680B0A00.ics'
|
||||||
|
restoring record '60C8-65323D00-1-4D9F7D80.vcf'
|
||||||
|
restoring record '60C8-65323D80-9-4D9F7D80.ics'
|
||||||
|
----
|
||||||
|
|
||||||
|
* *-F* has the same behavior as *-f* but it will first delete all contacts and events in database before restoring the backup.
|
||||||
|
So if an event/contact is not in the backup file, it will be lost. If a folder exists but is not in the backup file, nothing will happen
|
||||||
|
to it and its content.
|
||||||
|
|
||||||
|
*-F* expect a value that can be ALL to restore all folders, or the name of the folder to restore. You can list them with the *-l* mode.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool restore -F ALL /tmp/backup user1
|
||||||
|
sogo-tool restore -F "Contacts/personal" tmp/backup user1
|
||||||
|
sogo-tool restore -F "Calendar/60C8-65323D80-7-4D9F7D80" tmp/backup user1
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool checkup
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Check the events and contacts data's integrity of a user
|
||||||
|
|
||||||
|
sogo-tool checkup [-d] user...
|
||||||
|
|
||||||
|
* *-d*, optional, will delete all corrupted data.
|
||||||
|
* *user* is a single username or multiple user each space-separated.
|
||||||
|
|
||||||
|
Will print out nothing if no corrupted data were found or one of the following messages:
|
||||||
|
|
||||||
|
* Corrupted calendar item (missing tags) in path <path> with c_name = <ics_id>
|
||||||
|
* Corrupted calendar item (unparsable) in path <path> with c_name = <ics_id>
|
||||||
|
* Missing start date of event in path <path> with c_name = <ics_id> (<event_name>)"
|
||||||
|
* Missing end date of event in path <path> with c_name = <ics_id> (<event_name>)"
|
||||||
|
* Start date (<start_date>) is not before end date (<end_date>) for event in path <path> with c_name = <ics_id> (<event_name>)
|
||||||
|
* Corrupted card item (unparsable) in path <path> with c_name = <vcard_id>
|
||||||
|
|
||||||
|
It can also print log from SOGo if it raise errors:
|
||||||
|
|
||||||
|
* <0x0x55e0956c4d00[VSCardSaxDriver]> serious inconsistency among begin/end tags
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool checkup user1
|
||||||
|
sogo-tool checkup -d user1
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool cleanup
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Will purge all user's deleted events and contacts which the deletion is older than a number of days.
|
||||||
|
|
||||||
|
sogo-tool cleanup [days] ALL|user1 [user2]
|
||||||
|
|
||||||
|
* *days* the age of deleted records to purge in days
|
||||||
|
* Second argument is *ALL* to purge events of all users or put each user space-separated.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool cleanup 30 ALL
|
||||||
|
sogo-tool cleanup 30 user1 user2
|
||||||
|
----
|
||||||
|
Outputs:
|
||||||
|
----
|
||||||
|
Purged 3 records from folder /Users/user1/Calendar/60C7-65325300-9-18F39AA0
|
||||||
|
Purged 1 records from folder /Users/user1/Calendar/60C8-65323D80-7-4D9F7D80
|
||||||
|
Purged 0 records from folder /Users/user1/Calendar/personal
|
||||||
|
Purged 0 records from folder /Users/user1/Contacts/personal
|
||||||
|
Purged 5 records from folder /Users/user2/Calendar/personal
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
sogo-tool create-folder
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Create a folder (Calendar or Address Book) for a user.
|
||||||
|
|
||||||
|
create-folder user type [displayname ...]
|
||||||
|
|
||||||
|
* *user* is the name of the user
|
||||||
|
* *type* is either "Calendar" or "Contacts"
|
||||||
|
* *displayname* is the folder's name. If the name is already taken, a new folder will still be made but with another uid.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool create-folder user1 Contacts Pro_Contacts Ext_Contacts
|
||||||
|
sogo-tool create-folder user2 Calendar Pro_Calendar
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
sogo-tool dump-defaults
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Output all current defaults value of GNUstep and SOGo (sogo.conf)
|
||||||
|
|
||||||
|
sogo-tool dump-defaults [-f <filename>|[all]]
|
||||||
|
|
||||||
|
* Can be used without arguments and will output the defaults for gnustep domain 'sogod'.
|
||||||
|
* *all* option will output all defaults value found from sogo and gnustep in property list format.
|
||||||
|
* *-f* expect a filepath of an .xml file and will output it in property list format (sogo.conf). May need some tweaks, though.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool dump-defaults
|
||||||
|
sogo-tool dump-defaults all
|
||||||
|
sogo-tool dump-defaults -f /tmp/foo/conf.xml
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool expire-sessions
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Expires user sessions from database without activity for specified number of minutes. When a user log in to sogo for
|
||||||
|
the first time, sogo will create a entry in database's table OCSSessionsFolderURL with the user's information. Sogo will
|
||||||
|
also set a cookie with an encoded value to fetch this database table. That way, each time the user make a request with that cookie,
|
||||||
|
sogo will know which connected user it is. This command will remove all user's session from database
|
||||||
|
without activity for specified number of minutes. Those users will have to log in again.
|
||||||
|
|
||||||
|
sogo-tool expire-sessions [nbMinutes]
|
||||||
|
|
||||||
|
* *nbMinutes* Integer, number of minutes. All session without activity in these last minutes will be removed.
|
||||||
|
* _*Warning*_ Putting anything other that a number will be count as 0 minutes...
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool expire-sessions #Will print usage.
|
||||||
|
sogo-tool expire-sessions 160 #Will remove session which last activity is older than 160 minutes.
|
||||||
|
sogo-tool expire-sessions --help #Will remove session which last activity is older than 0 minutes.
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool manage-acl
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Manage access-control list (ACL) of a user for folders (Calendar and Address Book).
|
||||||
|
|
||||||
|
sogo-tool manage-acl get|add|remove|subscribe|unsubscribe owner folder user|group <rights>
|
||||||
|
|
||||||
|
* First argument is the action among *get*, *add*, *remove*, *subscribe* and *unsubscribe*.
|
||||||
|
* Second argument is the username of the folder's owner.
|
||||||
|
* Third argument is the name of the folder.
|
||||||
|
* Fourth argument is the user whom to manage its acl. It also can be _ALL_, _anonymous_ and _<defaults>_.
|
||||||
|
* Fifth argument, only needed with *add* action, is the rights to set for the user.
|
||||||
|
|
||||||
|
The actions:
|
||||||
|
|
||||||
|
* *get* will print out the folder's rights of the user or nothing if the user is not found or doesn't have any rights.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool manage-acl get sogo-tests1 Calendar/5E1F-653FC400-1-38155940 sogo-tests2
|
||||||
|
----
|
||||||
|
Result:
|
||||||
|
----
|
||||||
|
Rights for sogo-tests2 ["PublicModifier", "ConfidentialModifier", "PrivateModifier", "ObjectCreator", "ObjectEraser"]
|
||||||
|
----
|
||||||
|
|
||||||
|
* *add* will add the given rights to the user for the given folder. the value right is a json string of an array with each right to set.
|
||||||
|
Be Careful as there is no check for the rights already set. It could be better to first remove them, then add them.
|
||||||
|
|
||||||
|
The rights for Address Book are:
|
||||||
|
|
||||||
|
* _ObjectCreator_: can create cards (contact of list)
|
||||||
|
* _ObjectEditor_: can modify cards
|
||||||
|
* _ObjectViewer_: can view cards
|
||||||
|
* _ObjectEraser_: can delete cards
|
||||||
|
|
||||||
|
The rights for Calendar are:
|
||||||
|
|
||||||
|
* _ObjectCreator_: can create event or task
|
||||||
|
* _ObjectEraser_: can delete event or task
|
||||||
|
* _<type>_ can be _Public_, _Confidential_ or _Private_
|
||||||
|
* _<type>Viewer_: can view the whole events (ex: PrivateViewer)
|
||||||
|
* _<type>DAndTViewer_: can only view the date and time of events
|
||||||
|
* _<type>Modifier_: can view and edit the events
|
||||||
|
* _<type>Responder_: can view the events
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool manage-acl add sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2 '["ObjectViewer", "ObjectEraser"]'
|
||||||
|
----
|
||||||
|
|
||||||
|
* *remove* all user's rights to the folder.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool manage-acl remove sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2
|
||||||
|
----
|
||||||
|
|
||||||
|
* *subscribe* the user to the folder.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool manage-acl subscribe sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2
|
||||||
|
----
|
||||||
|
|
||||||
|
* *unsubscribe* the user to the folder.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool manage-acl unsubscribe sogo-tests1 Contacts/5E1D-653FC400-1-1A330C40 sogo-tests2
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool manage-eas
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Manage EAS folders
|
||||||
|
|
||||||
|
sogo-tool manage-eas listdevices|listfolders|resetdevice|resetfolder|mergevcard|mergevevent user <deviceId | folderId> <YES | NO>
|
||||||
|
|
||||||
|
* First argument is the action among *listdevices*, *listfolders*, *resetdevice*, *resetfolder*, *mergevcard* and *mergevevent*.
|
||||||
|
* Second argument is the *user* whom to perform the action.
|
||||||
|
* Third argument is the *deviceId* of, if the action is *resetFolder*, the *folderId*. No need of this argument if the action is *listdevices*
|
||||||
|
* Fourth argument, only use with *mergevcard* and *mergevevent* is either YES to merge and *NO* to un-merge.
|
||||||
|
|
||||||
|
The actions:
|
||||||
|
|
||||||
|
* *listdevices*: list the devices belonging to user.
|
||||||
|
* *listfolders*: list all folders of deviceId for user.
|
||||||
|
* *resetdevice*: force deviceId of user to resync everything.
|
||||||
|
* *resetfolder*: force folderId of user to resync everything.
|
||||||
|
* *mergevcard*: merge/un-merge all addressbooks into one for deviceId of user.
|
||||||
|
* *mergevevent*: merge/un-merge all calendars into one for deviceId of user.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
----
|
||||||
|
sogo-tool manage-eas listdevices janedoe
|
||||||
|
sogo-tool manage-eas listfolders janedoe androidc316986417
|
||||||
|
sogo-tool manage-eas resetdevice janedoe androidc316986417
|
||||||
|
sogo-tool manage-eas resetfolder janedow androidc316986417 folderlala-dada-sasa_7a13_1a2386e0_e
|
||||||
|
sogo-tool manage-eas mergevcard janedow androidc316986417 YES
|
||||||
|
sogo-tool manage-eas mergevevent janedow androidc316986417 YES
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool remove
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Remove all folders (Calendar and Address Book) and Preference settings of a user. The personal Calendar and Address Book
|
||||||
|
will stay but be emptied of all entries. The Preferences will go back to defaults values as for a new user.
|
||||||
|
|
||||||
|
sogo-tool remove user1 [user2]
|
||||||
|
|
||||||
|
* Arguments one user or several space-separated.
|
||||||
|
* You can add -v after sogo*tool to list all removed folders
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool remove user1
|
||||||
|
sogo-tool -v remove user2
|
||||||
|
----
|
||||||
|
Output example with -v options:
|
||||||
|
----
|
||||||
|
Deleting /Users/user2/Calendar/B088-65363400-3-2B9DF0C0
|
||||||
|
Deleting /Users/user2/Calendar/personal
|
||||||
|
Deleting /Users/user2/Contacts/B087-65363380-5-374DA380
|
||||||
|
Deleting /Users/user2/Contacts/B087-65363400-7-374DA380
|
||||||
|
Deleting /Users/user2/Contacts/personal
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool remove-doubles
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
remove duplicate contacts from the specified user addressbook
|
||||||
|
|
||||||
|
sogo-tool remove-doubles USER FOLDER
|
||||||
|
|
||||||
|
* *USER* is the name of the user to perform the removal
|
||||||
|
* *FOLDER* is the name of the folder to clean from duplicates
|
||||||
|
|
||||||
|
Two contacts are considered duplicates when they have the same mails or display name. When two or more contacts
|
||||||
|
are duplicates, each one will get a score. The ones with the highest score will be kept and the others
|
||||||
|
will be discarded. If two records have the same score, the first one to have reach it will be kept.
|
||||||
|
The scores are distributed as such:
|
||||||
|
* Record which has been the last modified: +1
|
||||||
|
* Record has the most content: +2
|
||||||
|
* Record has the most quick field set: +3
|
||||||
|
* Record is in a contact list: +6
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool remove-doubles user1 personal
|
||||||
|
sogo-tool remove-doubles user2 489-65376E80-1-2D2BA000
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool rename-user
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Update records pertaining to a user after a change of user id. Will change all folders path, subscriptions from others users,
|
||||||
|
all mention of user id in database.
|
||||||
|
|
||||||
|
sogo-tool rename-user fromuserid touserid
|
||||||
|
|
||||||
|
* *fromuserid* Previous user id
|
||||||
|
* *touserid* New user id
|
||||||
|
|
||||||
|
If the new user id already exist, the command will output a message and do nothing.
|
||||||
|
**WARNING** This command only change database of sogo, if you use ldap or others databases they will still keep the old id.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool rename-user old_username new_username
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool truncate-calendar
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Remove old calendar entries from the specified user calendar.
|
||||||
|
|
||||||
|
sogo-tool truncate-calendar [-r] USER FOLDER DATE
|
||||||
|
|
||||||
|
* *-r* optional, to also delete recurrent events that have occurrence after *DATE*
|
||||||
|
* *USER* is the name of the owner of the calendar
|
||||||
|
* *FOLDER* is the name of the calendar folder to clean up
|
||||||
|
* *DATE* UTC datetime, event older than this date will be removed
|
||||||
|
|
||||||
|
Without *-r*, only reccurrent events which all occurrences are older than *DATE* will be removed.
|
||||||
|
With *-r*, event the if there are occurrences after *DATE*, the recurrent event will be removed.
|
||||||
|
*DATE* can take the value `date +%FT%T` which is the current time
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool truncate-calendar sogo-tests1 personal 2023-10-25T00:00:00
|
||||||
|
sogo-tool truncate-calendar -r sogo-tests2 5E38-6538BE80-5-AFF8310 2022-01-01T15:30:00
|
||||||
|
sogo-tool truncate-calendar -r sogo-tests2 personal `date +%FT%T` #will delete all event older than now
|
||||||
|
----
|
||||||
|
Result:
|
||||||
|
----
|
||||||
|
No record to remove. All records kept.
|
||||||
|
----
|
||||||
|
----
|
||||||
|
Removing 1 records...
|
||||||
|
Removed 1 records.
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool update-autoreply
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
_This command is only useful if your sieve server doesn't have the capabilities *date* or *relational*_
|
||||||
|
In that case this command will check vacation's setting of all the users. If vacation is enabled/disabled,
|
||||||
|
the starting and ending date, the body and others parameters. If needed, the command
|
||||||
|
will write the sieve script accordingly.
|
||||||
|
|
||||||
|
sogo-tool update-autoreply -p credentialFile
|
||||||
|
|
||||||
|
* *-p- The credential file, a simple one-line file that contains "username:password" of an admin account of your imap/sieve server.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
----
|
||||||
|
sogo-tool update-autoreply -p /var/sogo/cred
|
||||||
|
----
|
||||||
|
|
||||||
|
sogo-tool user-preferences
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Get, set or unset user defaults / settings in the database.
|
||||||
|
|
||||||
|
_Settings_ stores parameters for user's calendar, mail and contacts. It can be graphic
|
||||||
|
as month view or week view for calendar but it also stores subscription, delegation...
|
||||||
|
It also stores the private Salt for TOTP and the parameter ForceResetPassword to force the
|
||||||
|
user to change password.
|
||||||
|
|
||||||
|
_Defaults_ stores all parameters that can be found in Preferences panel. So it goes from default language to
|
||||||
|
autoreply to many others options.
|
||||||
|
|
||||||
|
|
||||||
|
user-preferences get|set|unset defaults|settings user key [value|-f filename] [-p credentialFile]
|
||||||
|
|
||||||
|
* First argument is the action *get*, *set* or *unset*
|
||||||
|
* Second argument is parameter type *settings* or *defaults*
|
||||||
|
* Third argument *user* is the name of the user where to make the action
|
||||||
|
* Fourth argument *key* is the name of the parameters where to make the action
|
||||||
|
* If the action is *set*, you should provide either *value* as fifth argument or a file with the value inside with *-f* filename/path
|
||||||
|
* If the action is *set* or *unset* and the *key* concerns a sieve script (filter, vacation, forward...), you
|
||||||
|
should provide a credential file, a one-line file that contains "username:password" of an admin account of your imap/sieve server.
|
||||||
|
|
||||||
|
As there is a lot of parameters, this documentation will not go into details for each one. To know what are
|
||||||
|
the key names and their value you should got to your database in table defined by _SOGoProfileURL_ in your sogo.conf.
|
||||||
|
Here, for each row (user) you will find c_defaults and c_settings which are json with the keys. However, if
|
||||||
|
a parameter has never been set, it won't appears in those json. The clean way to know the missing keys is to
|
||||||
|
set it up in one of your dummy/dev/test account then see the values in _SOGoProfileURL_. Be careful, a value
|
||||||
|
can be a json itself, only the primary key can be get/set/unset.
|
||||||
|
|
||||||
|
Example with forwarding, *get* action:
|
||||||
|
----
|
||||||
|
sogo-tool user-preferences get defaults user1 Forward
|
||||||
|
----
|
||||||
|
If forward has never been set the result will be:
|
||||||
|
----
|
||||||
|
Value for key "Forward" not found in defaults
|
||||||
|
----
|
||||||
|
Else it will be
|
||||||
|
----
|
||||||
|
{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":1}
|
||||||
|
----
|
||||||
|
|
||||||
|
*unset* action:
|
||||||
|
----
|
||||||
|
sogo-tool user-preferences unset defaults user1 Forward -p /etc/sogo/cred
|
||||||
|
----
|
||||||
|
|
||||||
|
*set* action:
|
||||||
|
----
|
||||||
|
sogo-tool user-preferences set defaults user1 Forward '{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":0}' -p cred
|
||||||
|
----
|
||||||
|
or create a json file which contains _{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":0}_ then:
|
||||||
|
----
|
||||||
|
sogo-tool user-preferences set defaults user1 Forward -f /path/filename -p cred
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Upgrading
|
Upgrading
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|||||||
@@ -221,95 +221,95 @@
|
|||||||
count = [objects count];
|
count = [objects count];
|
||||||
|
|
||||||
for (i = 0; i < count; i++)
|
for (i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
content = [[[objects objectAtIndex: i] objectForKey: @"c_content"] stringByTrimmingSpaces];
|
content = [[[objects objectAtIndex: i] objectForKey: @"c_content"] stringByTrimmingSpaces];
|
||||||
c_name = [[objects objectAtIndex: i] objectForKey: @"c_name"];
|
c_name = [[objects objectAtIndex: i] objectForKey: @"c_name"];
|
||||||
if (is_calendar)
|
if (is_calendar)
|
||||||
{
|
{
|
||||||
// We check for
|
// We check for
|
||||||
// BEGIN:VCALENDAR
|
// BEGIN:VCALENDAR
|
||||||
// ..
|
// ..
|
||||||
// END:VCALENDAR
|
// END:VCALENDAR
|
||||||
iCalCalendar *calendar;
|
iCalCalendar *calendar;
|
||||||
|
|
||||||
if ([content length] < 30 ||
|
if ([content length] < 30 ||
|
||||||
[[content substringToIndex: 15] caseInsensitiveCompare: @"BEGIN:VCALENDAR"] != NSOrderedSame ||
|
[[content substringToIndex: 15] caseInsensitiveCompare: @"BEGIN:VCALENDAR"] != NSOrderedSame ||
|
||||||
[[content substringFromIndex: [content length]-13] caseInsensitiveCompare: @"END:VCALENDAR"] != NSOrderedSame)
|
[[content substringFromIndex: [content length]-13] caseInsensitiveCompare: @"END:VCALENDAR"] != NSOrderedSame)
|
||||||
{
|
{
|
||||||
NSLog(@"Corrupted calendar item (missing tags) in path %@ with c_name = %@", folder, c_name);
|
NSLog(@"Corrupted calendar item (missing tags) in path %@ with c_name = %@", folder, c_name);
|
||||||
if (delete)
|
if (delete)
|
||||||
[gcsFolder deleteContentWithName: c_name];
|
[gcsFolder deleteContentWithName: c_name];
|
||||||
rc = NO;
|
rc = NO;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
calendar = [iCalCalendar parseSingleFromSource: content];
|
calendar = [iCalCalendar parseSingleFromSource: content];
|
||||||
if (!calendar)
|
if (!calendar)
|
||||||
{
|
{
|
||||||
NSLog(@"Corrupted calendar item (unparsable) in path %@ with c_name = %@", folder, c_name);
|
NSLog(@"Corrupted calendar item (unparsable) in path %@ with c_name = %@", folder, c_name);
|
||||||
if (delete)
|
if (delete)
|
||||||
[gcsFolder deleteContentWithName: c_name];
|
[gcsFolder deleteContentWithName: c_name];
|
||||||
rc = NO;
|
rc = NO;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
iCalEvent *event;
|
iCalEvent *event;
|
||||||
|
|
||||||
event = (iCalEvent *) [calendar firstChildWithTag: @"vevent"];
|
event = (iCalEvent *) [calendar firstChildWithTag: @"vevent"];
|
||||||
if (event)
|
if (event)
|
||||||
{
|
{
|
||||||
iCalDateTime *startDate, *endDate;
|
iCalDateTime *startDate, *endDate;
|
||||||
|
|
||||||
startDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtstart"];
|
startDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtstart"];
|
||||||
if (![startDate dateTime])
|
if (![startDate dateTime])
|
||||||
{
|
{
|
||||||
NSLog(@"Missing start date of event in path %@ with c_name = %@ (%@)", folder, c_name, [event summary]);
|
NSLog(@"Missing start date of event in path %@ with c_name = %@ (%@)", folder, c_name, [event summary]);
|
||||||
if (delete)
|
if (delete)
|
||||||
[gcsFolder deleteContentWithName: c_name];
|
[gcsFolder deleteContentWithName: c_name];
|
||||||
rc = NO;
|
rc = NO;
|
||||||
}
|
}
|
||||||
endDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtend"];
|
endDate = (iCalDateTime *) [event uniqueChildWithTag: @"dtend"];
|
||||||
if (![endDate dateTime] && ![event hasDuration])
|
if (![endDate dateTime] && ![event hasDuration])
|
||||||
{
|
{
|
||||||
NSLog(@"Missing end date of event in path %@ with c_name = %@ (%@)", folder, c_name, [event summary]);
|
NSLog(@"Missing end date of event in path %@ with c_name = %@ (%@)", folder, c_name, [event summary]);
|
||||||
if (delete)
|
if (delete)
|
||||||
[gcsFolder deleteContentWithName: c_name];
|
[gcsFolder deleteContentWithName: c_name];
|
||||||
rc = NO;
|
rc = NO;
|
||||||
}
|
}
|
||||||
if ([startDate dateTime] && [endDate dateTime])
|
if ([startDate dateTime] && [endDate dateTime])
|
||||||
{
|
{
|
||||||
NSComparisonResult comparison;
|
NSComparisonResult comparison;
|
||||||
|
|
||||||
comparison = [[startDate dateTime] compare: [endDate dateTime]];
|
comparison = [[startDate dateTime] compare: [endDate dateTime]];
|
||||||
if (([event isAllDay] && comparison == NSOrderedDescending) ||
|
if (([event isAllDay] && comparison == NSOrderedDescending) ||
|
||||||
(![event isAllDay] && comparison != NSOrderedAscending))
|
(![event isAllDay] && comparison != NSOrderedAscending))
|
||||||
{
|
{
|
||||||
NSLog(@"Start date (%@) is not before end date (%@) for event in path %@ with c_name = %@ (%@)",
|
NSLog(@"Start date (%@) is not before end date (%@) for event in path %@ with c_name = %@ (%@)",
|
||||||
[startDate dateTime], [endDate dateTime], folder, c_name, [event summary]);
|
[startDate dateTime], [endDate dateTime], folder, c_name, [event summary]);
|
||||||
if (delete)
|
if (delete)
|
||||||
[gcsFolder deleteContentWithName: c_name];
|
[gcsFolder deleteContentWithName: c_name];
|
||||||
rc = NO;
|
rc = NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NGVCard *card;
|
NGVCard *card;
|
||||||
|
|
||||||
card = [NGVCard parseSingleFromSource: content];
|
card = [NGVCard parseSingleFromSource: content];
|
||||||
|
|
||||||
if (!card)
|
if (!card)
|
||||||
{
|
{
|
||||||
NSLog(@"Corrupted card item (unparsable) in path %@ with c_name = %@", folder, c_name);
|
NSLog(@"Corrupted card item (unparsable) in path %@ with c_name = %@", folder, c_name);
|
||||||
if (delete)
|
if (delete)
|
||||||
[gcsFolder deleteContentWithName: c_name];
|
[gcsFolder deleteContentWithName: c_name];
|
||||||
rc = NO;
|
rc = NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,8 @@
|
|||||||
sessionExpireMinutes = [[arguments objectAtIndex: 0] intValue];
|
sessionExpireMinutes = [[arguments objectAtIndex: 0] intValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NSLog(@"Remove all sessions older than %d min", sessionExpireMinutes);
|
||||||
|
|
||||||
if (sessionExpireMinutes >= 0)
|
if (sessionExpireMinutes >= 0)
|
||||||
{
|
{
|
||||||
rc = [self expireUserSessionOlderThan: sessionExpireMinutes];
|
rc = [self expireUserSessionOlderThan: sessionExpireMinutes];
|
||||||
|
|||||||
@@ -113,13 +113,13 @@ typedef enum
|
|||||||
" remove remove all ACL information of folder for user\n"
|
" remove remove all ACL information of folder for user\n"
|
||||||
" subscribe subscribe user to owner's folder\n"
|
" subscribe subscribe user to owner's folder\n"
|
||||||
" unsubscribe unsubscribe user to owner's folder\n"
|
" unsubscribe unsubscribe user to owner's folder\n"
|
||||||
" owner the user owning the folder\n"
|
" owner the user owning the folder\n"
|
||||||
" folder the folder - Calendar/<ID> or Contacts/<ID>\n"
|
" folder the folder - Calendar/<ID> or Contacts/<ID>\n"
|
||||||
" user the user (or group without the @ prefix) to get/set rights for - 'ALL', '<default>', 'anonymous' are supported\n"
|
" user the user (or group without the @ prefix) to get/set rights for - 'ALL', '<default>', 'anonymous' are supported\n"
|
||||||
" rights rights to add\n\n"
|
" rights rights to add\n\n"
|
||||||
"Example: sogo-tool manage-acl get jdoe Calendar/personal ALL\n\n"
|
"Example: sogo-tool manage-acl get jdoe Calendar/personal ALL\n\n"
|
||||||
"Note: You can add only one access right at the time. To set them all at once,\n"
|
"Note: You can add only one access right at the time. To set them all at once,\n"
|
||||||
" invoke 'remove' first to remove them all.\n\n");
|
" invoke 'remove' first to remove them all.\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) parseArguments
|
- (BOOL) parseArguments
|
||||||
|
|||||||
@@ -86,15 +86,15 @@
|
|||||||
/* we want to match the field value case-insensitively */
|
/* we want to match the field value case-insensitively */
|
||||||
recordEmail = [[record objectForKey: field] uppercaseString];
|
recordEmail = [[record objectForKey: field] uppercaseString];
|
||||||
if ([recordEmail length])
|
if ([recordEmail length])
|
||||||
|
{
|
||||||
|
recordList = [doubleEmails objectForKey: recordEmail];
|
||||||
|
if (!recordList)
|
||||||
{
|
{
|
||||||
recordList = [doubleEmails objectForKey: recordEmail];
|
recordList = [NSMutableArray arrayWithCapacity: 5];
|
||||||
if (!recordList)
|
[doubleEmails setObject: recordList forKey: recordEmail];
|
||||||
{
|
|
||||||
recordList = [NSMutableArray arrayWithCapacity: 5];
|
|
||||||
[doubleEmails setObject: recordList forKey: recordEmail];
|
|
||||||
}
|
|
||||||
[recordList addObject: record];
|
|
||||||
}
|
}
|
||||||
|
[recordList addObject: record];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) cleanupSingleRecords: (NSMutableDictionary *) doubleEmails
|
- (void) cleanupSingleRecords: (NSMutableDictionary *) doubleEmails
|
||||||
@@ -220,21 +220,20 @@
|
|||||||
|
|
||||||
recordsToRemove = [NSMutableArray arrayWithCapacity: (max - 1)];
|
recordsToRemove = [NSMutableArray arrayWithCapacity: (max - 1)];
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
if (count != keptRecord)
|
||||||
{
|
{
|
||||||
if (count != keptRecord)
|
currentRecord = [records objectAtIndex: count];
|
||||||
{
|
[recordsToRemove addObject: [currentRecord objectForKey: @"c_name"]];
|
||||||
currentRecord = [records objectAtIndex: count];
|
|
||||||
[recordsToRemove
|
|
||||||
addObject: [currentRecord objectForKey: @"c_name"]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return recordsToRemove;
|
return recordsToRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *) records: (NSArray *) records
|
- (NSArray *) records: (NSArray *) records
|
||||||
withLowestScores: (unsigned int *) scores
|
withLowestScores: (unsigned int *) scores
|
||||||
count: (unsigned int) max
|
count: (unsigned int) max
|
||||||
{
|
{
|
||||||
unsigned int count, highestScore;
|
unsigned int count, highestScore;
|
||||||
int highestScoreRecord;
|
int highestScoreRecord;
|
||||||
@@ -242,13 +241,13 @@
|
|||||||
highestScore = 0;
|
highestScore = 0;
|
||||||
highestScoreRecord = -1;
|
highestScoreRecord = -1;
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
if (scores[count] > highestScore)
|
||||||
{
|
{
|
||||||
if (scores[count] > highestScore)
|
highestScore = scores[count];
|
||||||
{
|
highestScoreRecord = count;
|
||||||
highestScore = scores[count];
|
|
||||||
highestScoreRecord = count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (highestScoreRecord == -1)
|
if (highestScoreRecord == -1)
|
||||||
highestScoreRecord = 0;
|
highestScoreRecord = 0;
|
||||||
@@ -268,16 +267,15 @@
|
|||||||
|
|
||||||
highestVersion = 0;
|
highestVersion = 0;
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
currentVersion = [[records objectAtIndex: count] objectForKey: @"c_version"];
|
||||||
|
version = [currentVersion intValue];
|
||||||
|
if (version > highestVersion)
|
||||||
{
|
{
|
||||||
currentVersion
|
mostModified = count;
|
||||||
= [[records objectAtIndex: count] objectForKey: @"c_version"];
|
highestVersion = version;
|
||||||
version = [currentVersion intValue];
|
|
||||||
if (version > highestVersion)
|
|
||||||
{
|
|
||||||
mostModified = count;
|
|
||||||
highestVersion = version;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mostModified;
|
return mostModified;
|
||||||
}
|
}
|
||||||
@@ -291,25 +289,25 @@
|
|||||||
amount = 0;
|
amount = 0;
|
||||||
|
|
||||||
if (!quickFields)
|
if (!quickFields)
|
||||||
{
|
{
|
||||||
quickFields = [NSArray arrayWithObjects: @"c_givenname", @"c_cn",
|
quickFields = [NSArray arrayWithObjects: @"c_givenname", @"c_cn",
|
||||||
@"c_sn", @"c_screenname", @"c_l", @"c_mail",
|
@"c_sn", @"c_screenname", @"c_l", @"c_mail",
|
||||||
@"c_o", @"c_ou", @"c_telephonenumber", nil];
|
@"c_o", @"c_ou", @"c_telephonenumber", nil];
|
||||||
[quickFields retain];
|
[quickFields retain];
|
||||||
}
|
}
|
||||||
|
|
||||||
max = [quickFields count];
|
max = [quickFields count];
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
value = [record objectForKey: [quickFields objectAtIndex: count]];
|
||||||
|
if ([value isKindOfClass: [NSString class]])
|
||||||
{
|
{
|
||||||
value = [record objectForKey: [quickFields objectAtIndex: count]];
|
if ([value length])
|
||||||
if ([value isKindOfClass: [NSString class]])
|
amount++;
|
||||||
{
|
|
||||||
if ([value length])
|
|
||||||
amount++;
|
|
||||||
}
|
|
||||||
else if ([value isKindOfClass: [NSNumber class]])
|
|
||||||
amount++;
|
|
||||||
}
|
}
|
||||||
|
else if ([value isKindOfClass: [NSNumber class]])
|
||||||
|
amount++;
|
||||||
|
}
|
||||||
|
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
@@ -323,15 +321,14 @@
|
|||||||
|
|
||||||
highestQFields = 0;
|
highestQFields = 0;
|
||||||
for (count = 0; count < max; count++)
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
currentQFields = [self amountOfFilledQuickFields: [records objectAtIndex: count]];
|
||||||
|
if (currentQFields > highestQFields)
|
||||||
{
|
{
|
||||||
currentQFields
|
mostQuickFields = count;
|
||||||
= [self amountOfFilledQuickFields: [records objectAtIndex: count]];
|
highestQFields = currentQFields;
|
||||||
if (currentQFields > highestQFields)
|
|
||||||
{
|
|
||||||
mostQuickFields = count;
|
|
||||||
highestQFields = currentQFields;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mostQuickFields;
|
return mostQuickFields;
|
||||||
}
|
}
|
||||||
@@ -399,10 +396,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void) assignScores: (unsigned int *) scores
|
- (void) assignScores: (unsigned int *) scores
|
||||||
toRecords: (NSArray *) records
|
toRecords: (NSArray *) records
|
||||||
count: (unsigned int) max
|
count: (unsigned int) max
|
||||||
withCardsInLists: (NSArray *) cardsInLists
|
withCardsInLists: (NSArray *) cardsInLists
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
Records is an Array of record which are duplicates of each other.
|
||||||
|
The goal here is to know which one to keep and whoch ones to discard.
|
||||||
|
We will assign a score to each record, the one with the best scores is kept
|
||||||
|
Record which has been the last modified: +1
|
||||||
|
Record has the most content: +2
|
||||||
|
Record has the most quick field set: +3
|
||||||
|
Record is in a list: +6
|
||||||
|
|
||||||
|
If two record have the same, for exemple, content. It's the first one on the list
|
||||||
|
that will get the points.
|
||||||
|
If two recors have the same score. t's the first one on the list
|
||||||
|
that will get the points.
|
||||||
|
|
||||||
|
quick fiels are =("c_givenname": Firstname, @"c_cn": Display,
|
||||||
|
@"c_sn": LastName, @"c_screenname": Screen Name @"c_l": City, @"c_mail": mails,
|
||||||
|
@"c_o": organisation, @"c_ou": organisation unit, @"c_telephonenumber": telephone)
|
||||||
|
*/
|
||||||
int recordIndex;
|
int recordIndex;
|
||||||
|
|
||||||
recordIndex = [self mostModifiedRecord: records count: max];
|
recordIndex = [self mostModifiedRecord: records count: max];
|
||||||
|
|||||||
@@ -634,7 +634,7 @@
|
|||||||
|
|
||||||
if (authname == nil || authpwd == nil)
|
if (authname == nil || authpwd == nil)
|
||||||
{
|
{
|
||||||
NSLog(@"To update Sieve scripts, you must provide the \"-p credentialFile\" parameter");
|
NSLog(@"To update Sieve scripts, you must provide the \"-c credentialFile\" parameter");
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -210,8 +210,7 @@
|
|||||||
GCSFolder *folder;
|
GCSFolder *folder;
|
||||||
BOOL rc;
|
BOOL rc;
|
||||||
|
|
||||||
folderPath = [NSString stringWithFormat: @"/Users/%@/Calendar/%@",
|
folderPath = [NSString stringWithFormat: @"/Users/%@/Calendar/%@", username, folderId];
|
||||||
username, folderId];
|
|
||||||
folder = [fom folderAtPath: folderPath];
|
folder = [fom folderAtPath: folderPath];
|
||||||
if (folder)
|
if (folder)
|
||||||
rc = [self truncateEntriesFromFolder: folder usingDate: date];
|
rc = [self truncateEntriesFromFolder: folder usingDate: date];
|
||||||
@@ -238,12 +237,13 @@
|
|||||||
// in the default timezone.
|
// in the default timezone.
|
||||||
s = [NSString stringWithFormat: @"%@ GMT", date];
|
s = [NSString stringWithFormat: @"%@ GMT", date];
|
||||||
d = [NSCalendarDate dateWithString: s calendarFormat: @"%Y-%m-%dT%H:%M:%S %Z"];
|
d = [NSCalendarDate dateWithString: s calendarFormat: @"%Y-%m-%dT%H:%M:%S %Z"];
|
||||||
|
NSLog(@"Date is: %@", d);
|
||||||
fom = [GCSFolderManager defaultFolderManager];
|
fom = [GCSFolderManager defaultFolderManager];
|
||||||
|
|
||||||
if (d && fom)
|
if (d && fom)
|
||||||
rc = [self processFolder: folder
|
rc = [self processFolder: folder
|
||||||
ofUser: username
|
ofUser: username
|
||||||
date: d
|
date: d
|
||||||
withFoM: fom];
|
withFoM: fom];
|
||||||
else
|
else
|
||||||
rc = NO;
|
rc = NO;
|
||||||
|
|||||||
@@ -146,6 +146,9 @@
|
|||||||
[helpString appendFormat: @"\t%-20@-- %@\n",
|
[helpString appendFormat: @"\t%-20@-- %@\n",
|
||||||
command, [currentTool objectAtIndex: 1]];
|
command, [currentTool objectAtIndex: 1]];
|
||||||
}
|
}
|
||||||
|
[helpString appendString: @"\n Visit https://www.sogo.nu/files/docs/SOGoInstallationGuide.html#_using_sogo_tool to get more infos"];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
NSLog (@"%@", helpString);
|
NSLog (@"%@", helpString);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user